Skip to main content

PyFraME: Python framework for Fragment-based Multiscale Embedding

Copyright (C) 2017-2026 PyFraME Contributors

This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.

pipeline status coverage report PyPI version Python versions License: MPL 2.0 DOI

Description

PyFraME is a Python package providing a framework for managing fragment-based multiscale embedding calculations. In such calculations, a molecular system is divided into two principal domains: a central core and its environment. The core part is treated at the highest level of theory while the effects from the environment, which can be further subdivided into several differently treated domains, are included effectively through an embedding potential. Using PyFraME the user can automatize the workflow starting from an initial structure to the final embedding potential. It can be used to build a multilayer description of the molecular environment. Each layer can be described either by a standard embedding potential, i.e., using a predefined set of parameters, or by deriving the embedding-potential parameters based on first-principles calculations. For the latter, a fragmentation method is used to subdivide large molecular structures into smaller computationally manageable fragments. The number of layers, as well as the composition and level of theory used for each layer, can be fully customized.

The basic workflow consists of three main steps. First, a molecular structure is given as an input. PyFraME reads structures, and the topology and trajectory files of a molecular dynamics simulation, in any of the formats MDAnalysis reads, and builds a potential for each of the frames taken from them, a single structure being one frame. The structure defines the basic units of the system, i.e., fragments, one per residue. Small molecules typically constitute a fragment on their own, but larger molecules need to be broken down into smaller fragments. For example, for proteins, a fragment would usually consist of an amino-acid residue. The molecular system to be used for the embedding calculation is then built by selecting fragments with MDAnalysis atom selections, e.g. by name, chain ID, distance, or a combination thereof, and placing them into separate regions. As mentioned above, any number of regions may be added, and each can be fully customized. Once the system has been built, the final step is the derivation of the embedding potential. Depending on the specifics, it may involve a large number of separate calculations on the individual fragments in order to compute the embedding-potential parameters. For large molecules, where the parameters cannot be computed directly, PyFraME uses a fragmentation method based on the molecular fractionation with conjugate caps (MFCC) approach to derive the parameters. The individual fragment calculations are typically performed by Dalton and the LoProp Python package but this can be customized. The fragmentation of the system, fragment calculations, and subsequent joining of parameters to build the embedding potential are fully automatized and can make full use of large-scale HPC resources.

For an example showing how PyFraME can be used, see Quick start and Usage example.

Contents

How to cite

To cite PyFraME please use a format similar to the following:

J. M. H. Olsen, P. Reinholdt, J. Vester, T. N. Heegaard, and G. Berger, PyFraME: Python framework for Fragment-based Multiscale Embedding (version 0.5.0), 2026. DOI: 10.5281/zenodo.22996201. See https://gitlab.com/pyframe-project/pyframe.

where the version and DOI should correspond to the actual version that was used. Note that the DOI 10.5281/zenodo.775113 represents all versions, and will always resolve to the latest one. A possible BibTeX entry can be found in the CITATION file, and the same entry is printed by pyframe.print_citation(). Alternatively, BibTeX and other formats can be generated by Zenodo.

Contributors

The list of past and current contributors is found here.

Requirements

To use PyFraME you need:

These are all available on PyPI and are installed along with the package, except daltools and LoProp, which pip install pyframe[dalton] adds. MDAnalysis publishes no wheels for Linux on aarch64, so installing PyFraME there builds MDAnalysis from source, which needs a C compiler. A parallel calculation needs an MPI library as well, which mpi4py loads when it is first given a communicator; a serial one needs none. Building PyFraME from source needs a C++ compiler with OpenMP support and the Eigen headers, which the compiled embedding engine is built against.

The headers usually need not be pointed at: the build asks pkg-config for them, and then looks where the distribution, Homebrew, and MacPorts packages (libeigen3-dev, eigen3-devel, eigen) put them, so installing Eigen through the package manager is all that is needed. Where it sits somewhere else, or where a particular copy of it is wanted, point EIGEN_INCLUDE_DIR at the directory that contains Eigen/Dense, which overrides the search:

EIGEN_INCLUDE_DIR=/path/to/eigen3 python -m pip install PyFraME

The fragment calculations themselves are run by a quantum chemistry program, which is not installed by the above:

  • Dalton, which is the default and is used together with LoProp
  • Molcas (not tested recently)
  • OpenMolcas (same)

To run the test suite you need the test dependency group of pyproject.toml, which is part of the dev group described under Development:

Installation

The PyFraME package can be installed from PyPI directly using pip

python -m pip install PyFraME

This will also install required dependencies (see above) that are available on PyPI, i.e., not Dalton, Molcas, etc.

Binary wheels are provided for Linux on x86_64 and aarch64 and for macOS 15 or newer on Apple silicon, for CPython 3.11 to 3.14. Anywhere else pip builds the package from source, which needs the compiler, OpenMP, and Eigen described under Requirements.

The entire source including history can be found at GitLab. All releases are also deposited at Zenodo.

Development

To work on PyFraME, clone the repository and install it in editable mode with the dev dependency group, which brings pytest, mypy, and pre-commit along with the rest. Dependency groups need pip 25.1 or newer:

git clone https://gitlab.com/pyframe-project/pyframe.git
cd pyframe
python -m pip install --editable . --group dev
pre-commit install

The editable install compiles the embedding engine, so the compiler, OpenMP and Eigen described under Requirements are needed here as well.

The hooks that pre-commit install sets up run ruff for linting, import sorting and formatting, mypy for type checking, clang-format over the C++ sources of the engine, and codespell over everything. They are what the pipeline gates on, so running them locally is the quickest way to find out whether a change will pass. To run them over the whole tree rather than over what is staged:

pre-commit run --all-files

Testing

The tests are not part of the wheel on PyPI. They come with the source, from GitLab or the sdist, and are run against an editable install as described under Development. To run the full test suite, i.e., the unit tests under pyframe and the integration tests under tests, type

python -m pytest

in the PyFraME root directory, with no path: python -m pytest pyframe leaves the integration tests out.

The tests of the task farming are marked cluster. They start workers over SSH and therefore need a host that can connect to itself. Where it cannot, deselect them, as the pipeline does:

python -m pytest -m 'not cluster'

The tests that need an MPI library skip themselves where mpi4py cannot load one, and those that launch several ranks skip themselves where there is no mpirun or mpiexec on the PATH, so the suite also runs without an MPI. The pipeline uses MPICH. Open MPI works as well, provided the machine has at least five cores, since it refuses to start more ranks than there are cores and the parallel tests start five. On a machine that keeps its MPI in an environment module, load the module in the same shell before running the tests, or they skip.

Documentation

The API is documented in the docstrings of the package, which python -m pydoc pyframe and help() read, and the sections below cover the workflow from a structure or a trajectory to a potential.

Three notebooks go further than the sections below. docs/embedding.ipynb runs the whole workflow on a small system from the test suite, from the structure to the potential and on into an embedding calculation, and needs no quantum chemistry program to do it. docs/electrostatic_embedding.ipynb walks through the embedding library described under Embedding calculations, starting from a UML diagram of it. docs/host_program.ipynb embeds a host program in full, with PySCF as the host: an integral driver of three methods, an embedding model, and the few lines that attach the model to an SCF. It needs PySCF to run.

What has changed between versions, including the changes that are not backwards compatible, is recorded in the changelog.

For the background of the method and for examples of how PyFraME is used in computational studies, see our tutorial review article.

Issues

Please report issues here.

Contributing

Please take a look at the contribution guide.

Quick start

The shortest way from a structure to an embedding potential is to treat one molecule quantum mechanically and to take the parameters of everything around it from a parameter set, so that no fragment calculation has to be run at all:

import pyframe

# Read the structure. A single structure is a trajectory of one frame, read
# through MDAnalysis, and a residue of the file is a fragment.
trajectory = pyframe.Trajectory('solvated_solute.pdb')

# Put the solute in the core region, i.e. the part treated quantum
# mechanically, and give it the basis set to be used for it. A region is given
# as an MDAnalysis atom selection.
trajectory.set_core_region('resname LIG', basis='pcseg-2')

# Describe the water within 8 angstrom of it by the TIP3P parameter set.
trajectory.add_region('solvent', 'resname WAT',
                      distance=8.0, reference='resname LIG',
                      use_parameter_set=True,
                      parameter_set='tip3p')

# Assemble the potential and write the two files it takes to run the embedding
# calculation: the core region as a molecule file and the environment as a
# potential file. The structure is the one snapshot of the trajectory.
snapshot = trajectory.snapshots[0]
project = pyframe.Project(work_dir='/path/to/work')
project.create_embedding_potential(snapshot)
project.write_core(snapshot)
project.write_potential(snapshot)

Regions whose parameters are computed rather than tabulated are set up the same way, with the level of theory of the fragment calculations given to add_region instead; the usage example shows both together.

The same, for every frame of a molecular dynamics trajectory rather than for a single structure:

import pyframe

# Read the frames through MDAnalysis, with the solute unwrapped and centered in
# the box in every frame.
trajectory = pyframe.Trajectory(topology_file='topology.tpr',
                                trajectory_file='trajectory.xtc',
                                center_selection='resname LIG')

# The selections are resolved again in every frame, so the solvent region
# follows what is within 8 angstrom of the solute as the frames go by.
trajectory.set_core_region('resname LIG', basis='pcseg-2')
trajectory.add_region('solvent', 'resname SOL',
                      distance=8.0, reference='resname LIG',
                      use_parameter_set=True,
                      parameter_set='tip3p')

# One potential per frame, in files named after the frame they came from.
trajectory.create_potential(pyframe.Project(work_dir='/path/to/work'))
trajectory.write_core()
trajectory.write_potential()

The next section covers this in full.

Trajectories

A Trajectory reads a topology and a trajectory file through MDAnalysis, so any pair of formats it supports can be used, or a single file carrying both, e.g. a PDB, which is a trajectory of one frame per model. A single structure is a trajectory of one frame. Every selected frame becomes a Snapshot, carrying the fragments, the regions, and the simulation box of that frame, and a residue of the topology is a fragment.

A topology that carries no elements or partial charges, e.g. a plain PDB, is read as well. The element is guessed from the atom name, which is warned about once, and the charge is taken from the charge column of a PDB, so that an ion keeps its charge. A PQR is read with its fields separated by whitespace, or in the columns of the format where they run together. The coordinates and box are read at the precision of the file for the common formats, and a warning says so where they are not.

import pyframe

# Take every tenth of the first thousand frames. The core region is unwrapped
# and centered in the box, and everything else is wrapped around it, so that a
# molecule split across the periodic boundary is whole in the snapshots.
trajectory = pyframe.Trajectory(topology_file='/path/to/topology.tpr',
                                trajectory_file='/path/to/trajectory.xtc',
                                start=0, stop=1000, step=10,
                                center_selection='resname LIG')

# Regions are given as MDAnalysis atom selections. A fragment belongs to the
# first region that selects it, so a later selection takes only what is left,
# and 'all' takes the rest.
trajectory.set_core_region('resname LIG', basis='pcseg-2')
trajectory.add_region('solvent', 'resname SOL',
                      distance=8.0, reference='resname LIG',
                      use_parameter_set=True,
                      parameter_set='tip3p')

# check_selection reports which fragments a selection covers, which is worth
# checking before anything is run.
print(trajectory.check_selection('resname LIG'))

# The settings of the fragment calculations are those of a Project, which every
# snapshot is handed to in turn. The files are named after the frame they came
# from, e.g. "trajectory_frame-10.pot".
project = pyframe.Project(work_dir='/path/to/work')
trajectory.create_potential(project)
trajectory.write_core()
trajectory.write_potential()

A selection is resolved again in every frame by default, so that a region defined by a distance follows what is within that distance as the frames go by. With updating=False it is resolved once, in the first frame taken, and the region holds the same fragments throughout. A region that would hold no fragment in any frame is refused, and the frames in which one holds none are reported in the log.

The distance of a region is measured to the fragments of its reference selection, which may be in a region already, between centers of mass or, with use_center_of_mass=False, between nearest atoms. With protect_molecules, which is the default, every fragment bonded to one within the distance is taken as well, so that no molecule is cut. The distance is measured as the coordinates lie, which suits frames centered with center_selection, and to the nearest periodic image with periodic=True. A distance written into the selection itself, e.g. 'resname SOL and around 8.0 resname LIG', is MDAnalysis's: always between atoms, and to the nearest image where the frame has a box.

A fragment can be split into named parts, e.g. a lipid into its head group and tails, before any region is set. An atom goes to the first part with a pattern that its name matches, the patterns being regular expressions, and the parts are residues of their own afterwards, which a selection can name:

trajectory.split_fragments('resname POPC',
                           {'HEAD': ['N', 'C1[1-5]', 'H1[1-5][A-C]', 'P', 'O1[1-4]'],
                            'TAIL': ['.*']})
trajectory.add_region('lipid', 'resname HEAD TAIL', use_mfcc=True,
                      use_multipoles=True, use_polarizabilities=True)

reset() drops every region, potential, and split and starts the setup over. A single structure is read the same way, and its snapshot and files are named after it alone, e.g. structure.pot:

trajectory = pyframe.Trajectory('/path/to/structure.pdb')
snapshot = trajectory.snapshots[0]

A universe that is already there, e.g. one read with options of MDAnalysis's own or built in memory, is taken with Trajectory.from_universe(universe, name='run'), which works on a copy of it. The frame each snapshot was built from is in Trajectory.frames, and the MDAnalysis universe is in Trajectory.universe. The frames are selected once, when the trajectory is built, so a run over a different set of frames is a new Trajectory.

Usage example

The following commented example is based on a molecular system consisting of a channelrhodopsin protein dimer embedded in a lipid membrane.

import pyframe

# Read the structure. Any format MDAnalysis reads will do, and a residue of the
# file is a fragment.
trajectory = pyframe.Trajectory('/path/to/input/file.pdb')

# By default fragments are defined by the input but fragments can be split into
# parts as shown here, which affects every residue the selection touches.
trajectory.split_fragments(
        'resname RETK',
        {'LYSB': ['N', 'H', 'CA', 'HA', 'C', 'O'],
         'LYSS': ['CB', 'HB1', 'HB2', 'CG', 'HG1', 'HG2', 'CD',
                  'HD1', 'HD2', 'CE', 'HE1', 'HE2'],
         'RET': ['.*']})
trajectory.split_fragments(
        'resname POPE',
        {'POP1': ['N', 'HN1', 'HN2', 'HN3', 'C12', 'H12A', 'H12B', 'C11', 'H11A',
                  'H11B', 'P', 'O13', 'O14', 'O11', 'O12', 'C1', 'HA', 'HB', 'C2',
                  'HS', 'O21', 'C3', 'HX', 'HY', 'O31'],
         'POP2': ['C21', 'O22', 'C22', 'H2R', 'H2S', 'C23', 'H3R', 'H3S', 'C24',
                  'H4R', 'H4S', 'C25', 'H5R', 'H5S', 'C26', 'H6R', 'H6S', 'C27',
                  'H7R', 'H7S', 'C28', 'H8R', 'H8S', 'C29', 'H91'],
         'POP3': ['0C21', '1H10', '1C21', 'H11R', 'H11S', '2C21', 'H12R', 'H12S',
                  '3C21', 'H13R', 'H13S', '4C21', 'H14R', 'H14S', '5C21', 'H15R',
                  'H15S', '6C21', 'H16R', 'H16S', '7C21', 'H17R', 'H17S', '8C21',
                  'H18R', 'H18S', 'H18T'],
         'POP4': ['C31', 'O32', 'C32', 'H2X', 'H2Y', 'C33', 'H3X', 'H3Y', 'C34',
                  'H4X', 'H4Y', 'C35', 'H5X', 'H5Y', 'C36', 'H6X', 'H6Y', 'C37',
                  'H7X', 'H7Y', 'C38', 'H8X', 'H8Y', 'C39', 'H9X', 'H9Y'],
         'POP5': ['0C31', 'H10X', 'H10Y', '1C31', 'H11X', 'H11Y', '2C31', 'H12X',
                  'H12Y', '3C31', 'H13X', 'H13Y', '4C31', 'H14X', 'H14Y', '5C31',
                  'H15X', 'H15Y', '6C31', 'H16X', 'H16Y', 'H16Z']})

# The retinal and whatever is within 3 angstrom of it, by the nearest atoms
# and without taking whole molecules along, make up the core region.
trajectory.set_core_region('all', distance=3.0,
                           reference='resid 248 and chainID A and resname RET',
                           use_center_of_mass=False, protect_molecules=False,
                           basis='pcseg-2')

# Add a region containing the protein (here chain A holds all of it). Note that
# each of these settings have defaults and that there are more than those shown
# here.
trajectory.add_region('protein', 'chainID A', use_mfcc=True,
                      mfcc_order=2, use_multipoles=True, multipole_order=2,
                      use_polarizabilities=True, basis='loprop-6-31+G*')

# Here we repeat for lipids, ions, and solvent within 8 angstrom of the protein,
# which may be in a region already to be measured to.
trajectory.add_region('lipid', 'resname POP1 POP2 POP3 POP4 POP5',
                      distance=8.0, reference='chainID A',
                      use_mfcc=True, mfcc_order=2,
                      use_multipoles=True, multipole_order=2,
                      use_polarizabilities=True, basis='loprop-6-31+G*')
trajectory.add_region('ion', 'resname NA CL', distance=8.0, reference='chainID A',
                      use_multipoles=True, multipole_order=0,
                      use_polarizabilities=True, basis='6-31+G*')
trajectory.add_region('solvent', 'resname SOL', distance=8.0,
                      reference='chainID A', use_multipoles=True,
                      multipole_order=2, use_polarizabilities=True,
                      basis='loprop-6-31+G*')

# Create Project() object that is used to create embedding potentials and write
# input files. Every setting shown below has a default, and there are more of
# them than are shown here.
project = pyframe.Project()

# Set path to scratch directory. This will be used by the auxiliary programs,
# e.g. Dalton or Molcas.
project.scratch_dir = '/path/to/scratch'

# Set path to working directory (it will be created if it does not exist).
# This directory will contain the final output files from PyFraME (e.g. Dalton
# mol and pot files), and the output from the auxiliary program. In addition,
# during execution it will contain temporary directories for each fragment.
project.work_dir = '/path/to/work'

# Specifies the number of jobs that will be run on each node. A fragment may
# require one or more calculations run by an auxiliary program. Each of these
# counts as a job.
project.jobs_per_node = 2

# Specifies memory per job. Note that this amount will be shared by MPI processes.
project.memory_per_job = 2048 * 12

# Number of MPI processes per job.
project.mpi_procs_per_job = 12

# You can manually specify the name of nodes that should be used to run jobs.
# PyFraME will attempt to autodetect nodes from SLURM and PBS/Torque queuing
# systems. For example, with os imported:
# project.node_list = [os.environ['HOSTNAME']]

# This will start the fragment calculations using the auxiliary
# programs and settings defined when creating the regions. The structure is the
# one snapshot of the trajectory.
snapshot = trajectory.snapshots[0]
project.create_embedding_potential(snapshot)

# Write potential file containing all parameters of the embedding potential.
project.write_potential(snapshot)

# Write molecule file containing the core quantum region.
project.write_core(snapshot)

The calculations of a snapshot are run in a directory of its own in the work directory, and are named after the index of the residue of their fragment, e.g. fragment-9_dalton_multipoles.out, or of the two fragments a concap caps, e.g. concap-8-9_dalton_multipoles.out. fragments.txt beside them says which fragment each is of, as messages about a fragment do, e.g. ILE 10, chain A, resindex 9, atoms index 120:138, where resindex 9 selects it in the universe of the trajectory. A calculation whose output is there already is not run again, so one that failed is run again by deleting its output and creating the potential again.

The fragment calculations are run by workers that PyFraME starts on each node, over SSH on any node other than the one it runs on, and each worker and each job runs in a login shell, i.e. bash -l. Each job is given the PATH and LD_LIBRARY_PATH of the process that started the run. Where that is not enough for the programs to be found, e.g. where Dalton is kept in an environment module, two environment variables give commands to run first: PyFraME_WORKER_COMMANDS before each worker is started, and PyFraME_JOB_COMMANDS before each job:

export PyFraME_JOB_COMMANDS='module load dalton'

A job runs only if its commands succeed. A third variable, PyFraME_MPI_COMMAND, sets the command that a fragment calculation is launched with where the program is run through MPI, mpirun -map-by core -np by default. It is read when the Project is created, an mpi_command given to the Project takes precedence over it, and it has to end with the option that takes the number of processes, which PyFraME adds:

export PyFraME_MPI_COMMAND='srun -n'

The uppercase spellings, PYFRAME_WORKER_COMMANDS, PYFRAME_JOB_COMMANDS, and PYFRAME_MPI_COMMAND, are accepted as well. All three are for setting up the environment, and never for a password, a token, or any other secret. The worker and job commands become part of a command line that other users on a node can read in a process listing, and of the message logged when a worker fails to start, and the MPI command is written into the run script of every calculation that uses it and logged.

Moving from MolecularSystem

MolecularSystem was removed in 0.5.0. A structure is read as a Trajectory of one frame, and the regions are given as MDAnalysis atom selections rather than as fragments taken from the system one call at a time. What a script of 0.4 did is done as follows:

0.4 0.5
system = pyframe.MolecularSystem(input_file='file.pdb') trajectory = pyframe.Trajectory('file.pdb'), whose one snapshot is trajectory.snapshots[0]
MolecularSystem(..., input_reader=reader) read the file into an MDAnalysis universe, and pyframe.Trajectory.from_universe(universe, name='system')
get_fragments_by_name(['WAT']) 'resname WAT'
get_fragments_by_number([1, *range(3, 11)]) 'resid 1 3:10'
get_fragments_by_chain_id(['A']) 'chainID A'
get_fragments_by_identifier(['248_A_RET']) 'resid 248 and chainID A and resname RET'
get_fragments_by_charge([1, -1]) no counterpart; select the charged fragments by name, e.g. 'resname NA CL'
get_fragments_by_distance(8.0, reference=core) distance=8.0, reference='resname LIG' given to add_region or set_core_region, with the selection it narrows down, e.g. 'all'
get_fragments_by_distance_and_name(8.0, ['WAT'], reference=core) add_region('solvent', 'resname WAT', distance=8.0, reference='resname LIG')
get_remaining_fragments() 'all', which takes what no earlier region took
system.add_region(name, fragments, ...) trajectory.add_region(name, selection, ...)
system.set_core_region(fragments, ...) trajectory.set_core_region(selection, ...)
split_fragment_by_name('POPC', names, definitions) trajectory.split_fragments('resname POPC', dict(zip(names, definitions)))
split_fragment_by_identifier('1_A_VAL', names, definitions) trajectory.split_fragments('resid 1 and chainID A', dict(zip(names, definitions)))
system.reset() trajectory.reset()
project.create_embedding_potential(system) project.create_embedding_potential(snapshot), or trajectory.create_potential(project) for every snapshot
project.write_core(system), project.write_potential(system), system.to_subsystems() the same, given the snapshot

The options of a selection by distance keep their names and defaults: use_center_of_mass=True and protect_molecules=True. A selection is an atom selection, so a fragment is taken if any of its atoms is selected.

Standard potentials are called parameter sets, and the settings of a region that take its parameters from one are renamed: use_standard_potentials, standard_potential_model, and standard_potential_exclusion_type are use_parameter_set, parameter_set, and parameter_set_exclusion_type. An old name is refused with an error that gives the new one. A region that names no parameter set takes pyframe-2026 rather than sep.

A fragment is keyed by the index of its residue rather than by a name such as 10_A_ILE, and the files of its calculations are named after that index, e.g. fragment-9_..., so the calculations of a work directory of 0.4 are run again. Where a structure gives no elements, they are guessed by MDAnalysis, which reads some atom names better, e.g. those of a protonated histidine, HIP.

Parameter sets

A region can take its parameters from a parameter set, i.e., a table of parameters per fragment type and atom name, rather than having them computed:

trajectory.add_region('solvent', 'resname SOL',
                      use_parameter_set=True,
                      parameter_set='tip3p')

The parameter sets shipped with PyFraME are alep, cp3, ff03, ff94, lipid14, lipid17, pyframe-2026, sep, and tip3p. A region that names none uses pyframe-2026, which merges three of them, each the parameters of one publication: cp3 for the amino acids, with their terminal variants, sep for water, common organic solvents, and ions, and alep for the lipids DMPC, POPC, and POPS. A protein in a membrane, with its solvent and ions, can therefore be a single region. The phenol of sep is named PHN, so that PHE is phenylalanine. A parameter set merged from several tables takes each fragment whole from the first table that has it.

alep gives the parameters of the head group and the tails of a lipid, which carry fractional charges that only add up to that of the whole lipid. A lipid is selected and given as a whole, e.g. 'resname POPC', and the parameter set splits it into its parts itself. The sites of a lipid are excluded as the parameters were made to be used, i.e. each part with caps of three atoms into the parts it is bonded to, whatever the region says, so the far ends of a lipid polarize each other. A lipid split by hand with split_fragments is treated the same way, and its parts are corrected to the charge of the lipid as a whole.

A program that offers a choice of them, e.g. a host program, can list the parameter sets and ask what each provides, i.e. its description, its tables, the fragments it covers, and its parameters. The description names the publication the parameters are from, with its DOI, for alep, cp3, sep, and pyframe-2026:

for name in pyframe.available_parameter_sets():
    info = pyframe.describe_parameter_set(name)
    print(name, info.multipole_order, info.polarizabilities, info.lennard_jones, info.description)

A file named after a parameter set with a .csv extension in the working directory takes precedence over the one shipped with the package, so a parameter set of your own is used by placing it there and naming it in parameter_set. It is not listed by available_parameter_sets, but it is described. A pyframe-2026.csv there replaces all of pyframe-2026, and a sep.csv replaces sep, both by itself and as the part of pyframe-2026 that it is.

A parameter set is a CSV file whose first two columns hold the fragment name and the atom name, followed by one column per parameter component. Every column states the unit of its values in brackets after its label. Lines above the header that begin with # are ignored, except that one beginning with # description: gives the description that describe_parameter_set reports. For example, a parameter set of charges and isotropic dipole-dipole polarizabilities:

name,atom,M_000[e],P1_iso[bohr^3]
WAT,O,-0.67444,5.73935
WAT,H,0.33722,2.30839

Two more lines above the header say how the fragments of a parameter set are used. # exclusion: mfcc 3 gives the exclusion type and cap order of the sites of its fragments, which then apply in place of the parameter_set_exclusion_type and mfcc_order of the region. # parts: POPC = POCH POCO POCP makes a fragment of the parts listed, each atom taking the parameters of the part that lists its name, so that a structure can name the whole fragment. These are the lines alep begins with:

# exclusion: mfcc 3
# parts: DMPC = DMCH DMCM
# parts: POPC = POCH POCO POCP
# parts: POPS = POSH POSO POSP

A component of a multipole is labeled M_ followed by its multi-index, i.e., by how many times x, y, and z occur among its Cartesian indices. M_000 is the charge, M_100, M_010, and M_001 are the x, y, and z components of the dipole, and M_200, M_110, M_101, M_020, M_011, and M_002 are the six components of the quadrupole. Multipoles up to order six are supported.

A component of a polarizability carries one multi-index per operator, since the two operators need not be the same: P_100_010 is the xy component of the dipole-dipole polarizability and P_000_100 the x component of the charge-dipole one. An isotropic polarizability, which is defined only where the two orders are equal, is labeled by its order instead, so that P1_iso is an isotropic dipole-dipole polarizability and P2_iso an isotropic quadrupole-quadrupole one.

The Lennard-Jones parameters of a site are labeled sigma and epsilon. For example, the TIP3P oxygen:

name,atom,M_000[e],epsilon[kJ/mol],sigma[nm]
HOH,O,-0.834,0.636386,0.315061

Every value is converted to the atomic units a site holds it in. The units each column may be given in are:

Column Units
charge, M_000 e
dipole, M_100 etc. e*bohr, e*angstrom, debye
quadrupole, M_200 etc. e*bohr^2, e*angstrom^2, debye*angstrom
multipole of order k e*bohr^k, e*angstrom^k
polarizability of orders k and l, P_100_010 or P1_iso etc. bohr^n, angstrom^n, with n = k + l + 1
sigma bohr, angstrom, nm
epsilon hartree, kJ/mol, kcal/mol, eV, K

and any column may be given in au. A polarizability is given as a polarizability volume, i.e., the atomic unit of polarizability divided by 4 pi epsilon_0, so that a dipole-dipole polarizability is in bohr^3 or angstrom^3 and the value in bohr^3 is the value in atomic units. An epsilon in K is the well depth divided by the Boltzmann constant.

A column without a unit, or in a unit its parameter cannot be given in, is refused, since the same number is a different potential in each unit.

The header is checked before any parameter is read. A multipole or a polarizability must have a column for each of its components, none of them twice, and a P(k,l) polarizability with k different from l must have the columns of its P(l,k) polarizability as well, the two being transposes of each other only where the two operators are the same. A polarizability given both as an isotropic value and component by component is refused, as is a label that is neither of the above, e.g. the M0 and P11 of the format that took all components of a parameter in a single column, or a column holding anything other than a multipole, a polarizability, sigma, or epsilon.

JSON potentials

A finished potential is written either as a PElib potential file or as a JSON file, i.e. snapshot.write_potential(filetype='json'), and the JSON file is the one that pyframe.embedding reads back through read_input.reader. It carries what a potential file has no place for: the simulation box, the core region and the Lennard-Jones parameters of every site.

A complete file, for a hydrogen fluoride molecule described quantum mechanically with a sodium and a chloride ion around it:

{
  "generated_by": "Generated by PyFraME (version 0.5.0)",
  "units": {
    "length": "bohr",
    "energy": "hartree",
    "angle": "degree",
    "multipoles": "au",
    "polarizabilities": "au"
  },
  "quantum_subsystems": [
    {
      "name": "core region",
      "charge": 0,
      "basis": [
        "6-31+G*",
        "6-31+G*"
      ],
      "quantum_fragments": [
        {
          "index": 0,
          "name": "HF 20, resindex 0, atoms index 0:1",
          "nuclei": [
            {
              "index": 0,
              "element": "F",
              "charge": 9,
              "coordinate": [
                0.0,
                0.0,
                0.0
              ]
            },
            {
              "index": 1,
              "element": "H",
              "charge": 1,
              "coordinate": [
                1.7385480346557087,
                0.0,
                0.0
              ]
            }
          ]
        }
      ]
    }
  ],
  "classical_subsystems": [
    {
      "classical_fragments": [
        {
          "name": "NA 10, resindex 1, atom index 2",
          "index": 0,
          "atoms": [
            {
              "index": 0,
              "element": "Na",
              "coordinate": [
                5.669178373877311,
                0.0,
                0.0
              ],
              "exclusions": [
                0
              ],
              "multipoles": {
                "elements": [
                  1.0
                ],
                "order": 0
              }
            }
          ]
        },
        {
          "name": "CL 11, resindex 2, atom index 3",
          "index": 1,
          "atoms": [
            {
              "repulsion": {
                "method": "LJ",
                "parameters": {
                  "lj_sigma": 4.4,
                  "lj_epsilon": 0.1
                }
              },
              "dispersion": {
                "method": "LJ",
                "parameters": {
                  "lj_sigma": 4.4,
                  "lj_epsilon": 0.1
                }
              },
              "index": 1,
              "element": "Cl",
              "coordinate": [
                -5.669178373877311,
                0.0,
                0.0
              ],
              "exclusions": [
                1
              ],
              "multipoles": {
                "elements": [
                  -1.0
                ],
                "order": 0
              }
            }
          ]
        }
      ]
    }
  ]
}

The file states the units of what it holds in its units block, and one without the block is refused when it is read. The block gives one unit for each kind of quantity:

Key Gives the unit of Units
length coordinates, box lengths, Lennard-Jones sigma bohr, angstrom, nm
energy Lennard-Jones epsilon hartree, kJ/mol, kcal/mol, eV, K
angle box angles degree, radian
multipoles multipoles, a unit for every order au, e*bohr^k, e*angstrom^k
polarizabilities polarizabilities, a unit for every pair of orders au, bohr^(k+l+1), angstrom^(k+l+1)

and every value is converted to bohr, hartree, degrees, and atomic units as it is read. A file PyFraME writes is in those already: a system carries its coordinates in angstrom everywhere else in PyFraME, and the writer converts them on the way out, while the parameters of a site are held in atomic units. A multipole or polarizability in other than atomic units is converted order by order, so its components have to be every component up to the order the multipoles are of, and the order of polarizabilities has to give the two orders of every block, as the writer gives them.

Everything is numbered from zero: the nuclei, the fragments of either subsystem, and the atoms of a classical subsystem. An index is an identity rather than a position, i.e. it is matched by value where an exclusion list names it, so a file numbered from one is read as well, provided it is numbered that way throughout. The potential file numbers its sites from one instead, because its exclusion lists are padded to a common width and a zero is what marks the padding; a JSON file has no such need, its exclusion lists being arrays of their own length.

generated_by records the version that wrote the file. It is ignored when the file is read, as is any other key the reader does not know at the top level.

A system read from a structure that carries a simulation box states it under system, in the units of the block, and one that carries none leaves the key out altogether:

"system": [{"simulation_box": [{"lengths": [56.69178373877310,
                                            56.69178373877310,
                                            56.69178373877310],
                                "angles": [90.0, 90.0, 90.0]}]}]

The quantum subsystem is the core region: its formal charge under charge, its basis set under basis, and its fragments under quantum_fragments. The basis is one label per nucleus, in the order the nuclei come in, whichever of the three forms the region was given it in, i.e. one label for the whole region, one per element or one per atom, so that a program reading the file needs neither the element table nor the atom order to resolve a label; a list that does not cover the nuclei one for one is refused rather than read. The nuclei live in the fragments and nowhere else, and are numbered across the whole subsystem rather than restarting in each fragment, since a nucleus index and a basis label are positional in the subsystem. Each nucleus states its kind: atom for an atom of the core region, link for a hydrogen link atom and cap for a capping atom, where a file that gives none is read as atom. A file may give them in a flat nuclei list beside the fragments instead, which is how they were written before and is still read; a file that gives both is read from the flat list.

The classical subsystem is the environment, as one classical fragment per fragment of the system, each named by the description of the fragment it is, which is how a person finds it, and under region the name of the region it belongs to. Its atoms are the sites of the potential, numbered across the whole subsystem rather than within a fragment, i.e. the second fragment of the file above carries atom 1 rather than a second atom 0, since that is the numbering exclusions refers to. The first entry of an exclusion list is the atom itself. A water and a sodium ion, as two fragments of one subsystem, therefore exclude as follows, with the rest of each atom left out:

{"name": "WAT 1, resindex 0, atoms index 0:2", "index": 0,
 "atoms": [{"index": 0, "exclusions": [0, 1, 2]},
           {"index": 1, "exclusions": [1, 0, 2]},
           {"index": 2, "exclusions": [2, 0, 1]}]}
{"name": "NA 2, resindex 1, atom index 3", "index": 1,
 "atoms": [{"index": 3, "exclusions": [3]}]}

An exclusion that names no atom of the subsystem is refused when the file is read, since it excludes nothing and is what an input whose atoms and exclusion lists are numbered differently comes to. A potential that was not built from the regions of the system, e.g. one read back from a potential file, does not line up with them and is written as a single fragment named all sites.

The parameters of a site are its multipoles, its polarizabilities, and its Lennard-Jones parameters. Multipoles and polarizabilities are written as one flat array of components per site, padded with zeros to the highest orders found anywhere in the system so that every site has an array of the same shape, with order naming what the array holds: the highest multipole order, and the two orders of every polarizability block in the order the blocks come in. A site carrying a charge and an isotropic dipole-dipole polarizability, in a system whose highest polarizability is that one, is therefore written as:

"multipoles": {"elements": [-0.834], "order": 0},
"polarizabilities": {"elements": [0.0, 0.0, 0.0, 0.0,
                                  9.5, 0.0, 0.0, 9.5, 0.0, 9.5],
                     "order": [[0, 0], [0, 1], [1, 1]]}

where the four leading zeros are the (0,0) and (0,1) blocks that no site of that system carries. Where nothing in the system is polarizable, no site carries the key at all.

Repulsion and dispersion are kept apart, under repulsion and dispersion, because they need not agree: a file written by hand may give a site different parameters for the two, although a site of a PyFraME potential holds one sigma and one epsilon and so gives both the same. Either may carry an lj_exponent beside lj_sigma and lj_epsilon, which defaults to 12 for repulsion and 6 for dispersion. A site that has no Lennard-Jones parameters carries neither key.

Embedding calculations

Where the top-level package builds an embedding potential, the pyframe.embedding subpackage consumes one. It evaluates the interactions between the quantum subsystem and the classical surroundings it is embedded in, i.e. their energies, the contributions they make to the Fock matrix, and their geometric derivatives.

The evaluation is split into a module per interaction type, i.e. electrostatic_interactions, induction_interactions, repulsion_interactions and dispersion_interactions, which operate on the subsystem and particle objects that read_input builds out of a potential. The numerically intensive parts are delegated to a compiled engine, which is the C++ extension module that is built when the package is installed.

A snapshot whose embedding potential has been created gives its subsystems directly, without a JSON file in between:

project.create_embedding_potential(snapshot)
simulation_box, (quantum_subsystem,), (classical_subsystem,) = snapshot.to_subsystems()

The subsystems are those read_input.reader builds from the JSON file of the snapshot, and a communicator is passed on as snapshot.to_subsystems(comm=comm). The embedding module is imported only then, so import pyframe does not load the compiled engine.

The notebook docs/electrostatic_embedding.ipynb walks through the library.

License

PyFraME is licensed under the Mozilla Public License, v. 2.0. The full text is in the LICENSE file, and pyframe.print_license() shows it as well.

Metadata

Release files for PyFraME 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for PyFraME 0.5.0
File Size Uploaded Provenance
pyframe-0.5.0.tar.gz 808.0 kB Without provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for PyFraME 0.5.0
File
pyframe-0.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl CPython 3.14 CPython 3.14 Linux glibc 2.28+ x86-64, Linux glibc 2.24+ x86-64 Details
pyframe-0.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl CPython 3.14 CPython 3.14 Linux glibc 2.28+ ARM64, Linux glibc 2.24+ ARM64 Details
pyframe-0.5.0-cp314-cp314-macosx_15_0_arm64.whl CPython 3.14 CPython 3.14 macOS 15.0+ ARM64 Details
pyframe-0.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl CPython 3.13 CPython 3.13 Linux glibc 2.24+ x86-64, Linux glibc 2.28+ x86-64 Details
pyframe-0.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl CPython 3.13 CPython 3.13 Linux glibc 2.28+ ARM64, Linux glibc 2.24+ ARM64 Details
pyframe-0.5.0-cp313-cp313-macosx_15_0_arm64.whl CPython 3.13 CPython 3.13 macOS 15.0+ ARM64 Details
pyframe-0.5.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl CPython 3.12 CPython 3.12 Linux glibc 2.28+ x86-64, Linux glibc 2.24+ x86-64 Details
pyframe-0.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl CPython 3.12 CPython 3.12 Linux glibc 2.28+ ARM64, Linux glibc 2.24+ ARM64 Details
pyframe-0.5.0-cp312-cp312-macosx_15_0_arm64.whl CPython 3.12 CPython 3.12 macOS 15.0+ ARM64 Details
pyframe-0.5.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl CPython 3.11 CPython 3.11 Linux glibc 2.24+ x86-64, Linux glibc 2.28+ x86-64 Details
pyframe-0.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl CPython 3.11 CPython 3.11 Linux glibc 2.28+ ARM64, Linux glibc 2.24+ ARM64 Details
pyframe-0.5.0-cp311-cp311-macosx_15_0_arm64.whl CPython 3.11 CPython 3.11 macOS 15.0+ ARM64 Details

Total release size: 35.6 MB

Release files / pyframe-0.5.0.tar.gz

Download URL pyframe-0.5.0.tar.gz
Size 808.0 kB
Tags Source
SHA-256 checksum
How to use checksums
d675b818296046b428ab9f184449fa64fa1dad26b4ba83d2c87ae2b1fca5d818
BLAKE2b-256 checksum
How to use checksums
66113d4bb3d178c577d570864022a6449bf59253ce7494825705bc9ae1cfe210
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / pyframe-0.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

Download URL pyframe-0.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Size 4.0 MB
Tags CPython 3.14 Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
ca8a492b86a96238cd3048636b387094feacfbe3bb896730c38a5374d931a21e
BLAKE2b-256 checksum
How to use checksums
df2e405af6b6ae8137f0c0058b11544ada02728b56b916168fece4acb74f6fb1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / pyframe-0.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl

Download URL pyframe-0.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
Size 4.0 MB
Tags CPython 3.14 Linux glibc 2.24+ ARM64 Linux glibc 2.28+ ARM64
SHA-256 checksum
How to use checksums
65bc99b26a32ad4cadf7160274fa2b60150f727a7a7e0e13aa29f050c6c28a2a
BLAKE2b-256 checksum
How to use checksums
96d88f3120be57026057a5e4fbbc6b3f14353828f4d1c76e81cd07c16ccc9c28
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / pyframe-0.5.0-cp314-cp314-macosx_15_0_arm64.whl

Download URL pyframe-0.5.0-cp314-cp314-macosx_15_0_arm64.whl
Size 723.6 kB
Tags CPython 3.14 macOS 15.0+ ARM64
SHA-256 checksum
How to use checksums
26424d1143d036801f701aded17da117d1b3467e59555a4077ff802aa69edb76
BLAKE2b-256 checksum
How to use checksums
da79e7d517484db82ce4f60ef5529aa50cbff8cda8f71f013fabfbe243e2b00b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / pyframe-0.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

Download URL pyframe-0.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Size 4.0 MB
Tags CPython 3.13 Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
afc8cb9a06cdbcfa71a3f3336978cb490d1a8028f704ce81910d4b1f29fa4268
BLAKE2b-256 checksum
How to use checksums
27c4e3cb0ceb0d73569472f121fe97f91d50b3e125ab8d771735832d20f74b18
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / pyframe-0.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl

Download URL pyframe-0.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
Size 4.0 MB
Tags CPython 3.13 Linux glibc 2.24+ ARM64 Linux glibc 2.28+ ARM64
SHA-256 checksum
How to use checksums
9562fd079c16d620ef49e40f35ec08a9ef37619ac215dd40e204434c485faa09
BLAKE2b-256 checksum
How to use checksums
d9528da393f782f6267f746914b421b7611dd1a1e20f735cbc825dc103025f0d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / pyframe-0.5.0-cp313-cp313-macosx_15_0_arm64.whl

Download URL pyframe-0.5.0-cp313-cp313-macosx_15_0_arm64.whl
Size 723.6 kB
Tags CPython 3.13 macOS 15.0+ ARM64
SHA-256 checksum
How to use checksums
150aeab6b2233ae4956e918a9835a0c0d158098ef78dfeaff24e101be0a44aa7
BLAKE2b-256 checksum
How to use checksums
77e7eaea2d7965057dd9e845d76b9d9511c1dfd6717ce7147053c1ac4b8cb354
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / pyframe-0.5.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

Download URL pyframe-0.5.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Size 4.0 MB
Tags CPython 3.12 Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
19ddd7415845946ae848f1d2cb31baa686d93d2b8b029d66d5055f9e0ce6f56b
BLAKE2b-256 checksum
How to use checksums
cf539becc3f3caf6aab09c44f8ecaf0479e75c513e5e0e2880c84cf84350218e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / pyframe-0.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl

Download URL pyframe-0.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
Size 4.0 MB
Tags CPython 3.12 Linux glibc 2.24+ ARM64 Linux glibc 2.28+ ARM64
SHA-256 checksum
How to use checksums
21b6fdcb350f0744067171ea40f1e3927e99d2c5951e88a74ab54927dbd5fce2
BLAKE2b-256 checksum
How to use checksums
2dbc853a73e431a8add1446018c7699bbbd57fc13e4eae986b26de452631d077
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / pyframe-0.5.0-cp312-cp312-macosx_15_0_arm64.whl

Download URL pyframe-0.5.0-cp312-cp312-macosx_15_0_arm64.whl
Size 723.6 kB
Tags CPython 3.12 macOS 15.0+ ARM64
SHA-256 checksum
How to use checksums
6f5b020a10bcab65e3b44401173a10b6a28c236551b8ce3e67248f5494f9b72d
BLAKE2b-256 checksum
How to use checksums
0b47598e44af51e3c023f1a85e795045565609eb7f44d1136b0d3bb459935433
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / pyframe-0.5.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl

Download URL pyframe-0.5.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Size 4.0 MB
Tags CPython 3.11 Linux glibc 2.24+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
a4cb4e87055c7daf0500290f726e451cb842477af13398cb075c19618b703ee9
BLAKE2b-256 checksum
How to use checksums
8c0194cc3a84a718d1e874508bfec89b1b445dc21b433c586bbe877db71a9f1b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / pyframe-0.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl

Download URL pyframe-0.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
Size 4.0 MB
Tags CPython 3.11 Linux glibc 2.24+ ARM64 Linux glibc 2.28+ ARM64
SHA-256 checksum
How to use checksums
3e0100bc137fe6ff580659bfc75c42c800aae85cacb296db3e6186e26463054a
BLAKE2b-256 checksum
How to use checksums
b162cecc98d187fdfbceb021acb93b52205a030bdc140b8f6f8b5104cdb2b5b9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / pyframe-0.5.0-cp311-cp311-macosx_15_0_arm64.whl

Download URL pyframe-0.5.0-cp311-cp311-macosx_15_0_arm64.whl
Size 723.5 kB
Tags CPython 3.11 macOS 15.0+ ARM64
SHA-256 checksum
How to use checksums
85aa467071e73d5c980946b17616e4ab3c8e69e233bfab0fbf13c8e0b609d300
BLAKE2b-256 checksum
How to use checksums
2efc7bdbf2cc2431a9bf268794718c429366268e889a980d1de893046454ed7f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Security for PyFraME 0.5.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page