Skip to content

septop #

Automated BFE calculations using the seperated topology method

Classes:

Functions:

  • compute_ddg

    Computes the binding free energy from the complex and solution phase samples.

  • load_config

    Load a configuration from a YAML file.

  • equilibrate_states

    Equilibrate the system at each lambda window.

  • run_complex_phase

    Run the complex phase of the SepTop calculation.

  • run_solution_phase

    Run the solution phase of the SepTop calculation.

  • submit_network

    Submits a set of SepTop calculations to an HPC queueing manager.

  • run_hremd

    Perform replica exchange sampling for a system prepared for SepTop calculations.

  • setup_complex

    Prepares a system ready for running the SepTop method.

  • setup_solution

    Prepares a system ready for running the SepTop method.

  • create_state_dicts

    Map the lambda states specified in the configuration to a dictionary.

Attributes:

DEFAULT_BORESCH_K_DISTANCE module-attribute #

DEFAULT_BORESCH_K_DISTANCE = 20.0 * _KCAL_PER_ANG_SQR

The default force constant of the Boresch distance restraint.

DEFAULT_BORESCH_K_THETA module-attribute #

DEFAULT_BORESCH_K_THETA = 20.0 * _KCAL_PER_RAD_SQR

The default force constant of the Boresch angle restraint.

DEFAULT_EQUILIBRATE_INTEGRATOR module-attribute #

DEFAULT_EQUILIBRATE_INTEGRATOR = LangevinIntegrator(
    timestep=2.0 * femtosecond, friction=1.0 / picosecond
)

The default integrator to use during equilibration.

DEFAULT_EQUILIBRATE_RESTRAINTS module-attribute #

DEFAULT_EQUILIBRATE_RESTRAINTS = {
    DEFAULT_RESTRAINT_MASK: FlatBottomRestraint(
        k=25.0 * _KCAL_PER_ANG_SQR, radius=1.5 * _ANGSTROM
    )
}

The default position restraints to apply during equilibration.

DEFAULT_LAMBDA_BORESCH_LIGAND_1 module-attribute #

DEFAULT_LAMBDA_BORESCH_LIGAND_1 = (
    [0.0, 0.05, 0.1, 0.3, 0.5, 0.75, 1.0, 1.0]
    + [1.0] * 3
    + [1.0] * 8
)

The default lambda schedule of the Boresch restraint on the first ligand in the complex phase.

DEFAULT_LAMBDA_BORESCH_LIGAND_2 module-attribute #

DEFAULT_LAMBDA_BORESCH_LIGAND_2 = (
    [1.0] * 8
    + [1.0] * 3
    + [1.0, 0.95, 0.9, 0.7, 0.5, 0.25, 0.0, 0.0]
)

The default lambda schedule of the Boresch restraint on the second ligand in the complex phase.

DEFAULT_LAMBDA_CHARGES_1_COMPLEX module-attribute #

DEFAULT_LAMBDA_CHARGES_1_COMPLEX = (
    [0.0] * 8 + [0.25, 0.5, 0.75] + [1.0] * 8
)

The default charge lambda schedule of the first ligand in the complex phase.

DEFAULT_LAMBDA_CHARGES_1_SOLUTION module-attribute #

DEFAULT_LAMBDA_CHARGES_1_SOLUTION = [
    0.0,
    0.125,
    0.25,
    0.375,
    0.5,
    0.625,
    0.75,
    0.875,
    1.0,
    1.0,
    1.0,
    1.0,
    1.0,
    1.0,
    1.0,
    1.0,
    1.0,
    1.0,
]

The default charge lambda schedule of the first ligand in the solution phase.

DEFAULT_LAMBDA_CHARGES_2_COMPLEX module-attribute #

DEFAULT_LAMBDA_CHARGES_2_COMPLEX = (
    [1.0] * 8 + [0.75, 0.5, 0.25] + [0.0] * 8
)

The default charge lambda schedule of the second ligand in the complex phase.

DEFAULT_LAMBDA_CHARGES_2_SOLUTION module-attribute #

DEFAULT_LAMBDA_CHARGES_2_SOLUTION = [
    1.0,
    1.0,
    1.0,
    1.0,
    1.0,
    1.0,
    1.0,
    1.0,
    1.0,
    1.0,
    0.875,
    0.75,
    0.625,
    0.5,
    0.375,
    0.25,
    0.125,
    0.0,
]

The default charge lambda schedule of the second ligand in the solution phase.

DEFAULT_LAMBDA_VDW_1_COMPLEX module-attribute #

DEFAULT_LAMBDA_VDW_1_COMPLEX = (
    [0.0] * 8 + [0.0, 0.0, 0.0] + tolist()
)

The default vdW lambda schedule of the first ligand in the complex phase.

DEFAULT_LAMBDA_VDW_1_SOLUTION module-attribute #

DEFAULT_LAMBDA_VDW_1_SOLUTION = [
    0.0,
    0.0,
    0.0,
    0.0,
    0.0,
    0.0,
    0.0,
    0.0,
    0.0,
    0.12,
    0.24,
    0.36,
    0.48,
    0.6,
    0.7,
    0.77,
    0.85,
    1.0,
]

The default vdW lambda schedule of the first ligand in the solution phase.

DEFAULT_LAMBDA_VDW_2_COMPLEX module-attribute #

DEFAULT_LAMBDA_VDW_2_COMPLEX = (
    tolist() + [0.0, 0.0, 0.0] + [0.0] * 8
)

The default vdW lambda schedule of the second ligand in the complex phase.

DEFAULT_LAMBDA_VDW_2_SOLUTION module-attribute #

DEFAULT_LAMBDA_VDW_2_SOLUTION = [
    1.0,
    0.85,
    0.77,
    0.7,
    0.6,
    0.48,
    0.36,
    0.24,
    0.12,
    0.0,
    0.0,
    0.0,
    0.0,
    0.0,
    0.0,
    0.0,
    0.0,
    0.0,
]

The default vdW lambda schedule of the second ligand in the solution phase.

DEFAULT_RESTRAINT_MASK module-attribute #

DEFAULT_RESTRAINT_MASK = 'not (water or ion or elem H)'

The default Amber style selection mask to apply position restraints to.

LAMBDA_BORESCH_LIGAND_1 module-attribute #

LAMBDA_BORESCH_LIGAND_1 = 'lambda_boresch_lig_1'

The name of the context variable used to control the Boresch-style restraints on the first ligand.

LAMBDA_BORESCH_LIGAND_2 module-attribute #

LAMBDA_BORESCH_LIGAND_2 = 'lambda_boresch_lig_2'

The name of the context variable used to control the Boresch-style restraints on the second ligand.

SepTopComplexRestraints pydantic-model #

Bases: BoreschRestraint

Configure the restraints to apply in the complex phase.

Fields:

k_distance pydantic-field #

k_distance: OpenMMQuantity[_KCAL_PER_ANG_SQR]

Force constant [kcal/mol/Å^2] of the harmonic distance restraint between r3 and l1.

k_angle_a pydantic-field #

k_angle_a: OpenMMQuantity[_KCAL_PER_RAD_SQR]

Force constant [kcal/mol/rad^2] of the harmonic angle restraint on the angle formed by r2, r3, and l1.

k_angle_b pydantic-field #

k_angle_b: OpenMMQuantity[_KCAL_PER_RAD_SQR]

Force constant [kcal/mol/rad^2] of the harmonic angle restraint on the angle formed by r3, l1, and l2.

k_dihedral_a pydantic-field #

k_dihedral_a: OpenMMQuantity[_KCAL_PER_RAD_SQR]

Force constant [kcal/mol/rad^2] of the harmonic dihedral restraint on the dihedral angle formed by r1, r2, r3, and l1.

k_dihedral_b pydantic-field #

k_dihedral_b: OpenMMQuantity[_KCAL_PER_RAD_SQR]

Force constant [kcal/mol/rad^2] of the harmonic dihedral restraint on the dihedral angle formed by r2, r3, l1, and l2.

k_dihedral_c pydantic-field #

k_dihedral_c: OpenMMQuantity[_KCAL_PER_RAD_SQR]

Force constant [kcal/mol/rad^2] of the harmonic dihedral restraint on the dihedral angle formed by r3, l1, l2, and l3.

scale_k_angle_a pydantic-field #

scale_k_angle_a: bool = True

Whether to scale the force constant for the P2, P1, and L1 angle based on the initial distance between P1 and L1.

scale_k_angle_b pydantic-field #

scale_k_angle_b: bool = True

Whether to scale the force constant for the P1, L1, and L2 angle based on the initial distance between P1 and L1.

model_dump_yaml #

model_dump_yaml(
    output_path: Path | None = None, **kwargs
) -> str

Dump the model to a YAML representation.

Parameters:

  • output_path (Path | None, default: None ) –

    The (optional) path to save the YAML representation to.

Returns:

  • str

    The YAML representation.

Source code in femto/md/utils/models.py
def model_dump_yaml(self, output_path: pathlib.Path | None = None, **kwargs) -> str:
    """Dump the model to a YAML representation.

    Args:
        output_path: The (optional) path to save the YAML representation to.

    Returns:
        The YAML representation.
    """

    model_yaml = yaml.safe_dump(self.model_dump(), **kwargs)

    if output_path is not None:
        output_path.parent.mkdir(exist_ok=True, parents=True)
        output_path.write_text(model_yaml)

    return model_yaml

SepTopConfig pydantic-model #

Bases: BaseModel

Configuration a separated topology FE calculation.

Fields:

complex pydantic-field #

complex: SepTopPhaseConfig = SepTopPhaseConfig(
    setup=SepTopSetupStage(
        restraints=DEFAULT_COMPLEX_RESTRAINTS
    ),
    states=SepTopStates(
        lambda_vdw_ligand_1=DEFAULT_LAMBDA_VDW_1_COMPLEX,
        lambda_charges_ligand_1=DEFAULT_LAMBDA_CHARGES_1_COMPLEX,
        lambda_boresch_ligand_1=DEFAULT_LAMBDA_BORESCH_LIGAND_1,
        lambda_vdw_ligand_2=DEFAULT_LAMBDA_VDW_2_COMPLEX,
        lambda_charges_ligand_2=DEFAULT_LAMBDA_CHARGES_2_COMPLEX,
        lambda_boresch_ligand_2=DEFAULT_LAMBDA_BORESCH_LIGAND_2,
    ),
)

Configure the complex phase calculations.

solution pydantic-field #

solution: SepTopPhaseConfig = SepTopPhaseConfig(
    setup=SepTopSetupStage(
        box_shape="cube",
        restraints=DEFAULT_SOLUTION_RESTRAINTS,
    ),
    states=SepTopStates(
        lambda_vdw_ligand_1=DEFAULT_LAMBDA_VDW_1_SOLUTION,
        lambda_charges_ligand_1=DEFAULT_LAMBDA_CHARGES_1_SOLUTION,
        lambda_vdw_ligand_2=DEFAULT_LAMBDA_VDW_2_SOLUTION,
        lambda_charges_ligand_2=DEFAULT_LAMBDA_CHARGES_2_SOLUTION,
        lambda_boresch_ligand_1=None,
        lambda_boresch_ligand_2=None,
    ),
)

Configure the solution phase calculations.

model_dump_yaml #

model_dump_yaml(
    output_path: Path | None = None, **kwargs
) -> str

Dump the model to a YAML representation.

Parameters:

  • output_path (Path | None, default: None ) –

    The (optional) path to save the YAML representation to.

Returns:

  • str

    The YAML representation.

Source code in femto/md/utils/models.py
def model_dump_yaml(self, output_path: pathlib.Path | None = None, **kwargs) -> str:
    """Dump the model to a YAML representation.

    Args:
        output_path: The (optional) path to save the YAML representation to.

    Returns:
        The YAML representation.
    """

    model_yaml = yaml.safe_dump(self.model_dump(), **kwargs)

    if output_path is not None:
        output_path.parent.mkdir(exist_ok=True, parents=True)
        output_path.write_text(model_yaml)

    return model_yaml

SepTopEquilibrateStage pydantic-model #

Bases: BaseModel

Configure how the system will be equilibrated prior to replica exchange.

Fields:

report_interval pydantic-field #

report_interval: int = 5000

The number of steps to report energy, volume, etc after.

model_dump_yaml #

model_dump_yaml(
    output_path: Path | None = None, **kwargs
) -> str

Dump the model to a YAML representation.

Parameters:

  • output_path (Path | None, default: None ) –

    The (optional) path to save the YAML representation to.

Returns:

  • str

    The YAML representation.

Source code in femto/md/utils/models.py
def model_dump_yaml(self, output_path: pathlib.Path | None = None, **kwargs) -> str:
    """Dump the model to a YAML representation.

    Args:
        output_path: The (optional) path to save the YAML representation to.

    Returns:
        The YAML representation.
    """

    model_yaml = yaml.safe_dump(self.model_dump(), **kwargs)

    if output_path is not None:
        output_path.parent.mkdir(exist_ok=True, parents=True)
        output_path.write_text(model_yaml)

    return model_yaml

SepTopPhaseConfig pydantic-model #

Bases: BaseModel

Configure one phase (i.e. complex or solution) of a separated topology FE calculation.

Fields:

setup pydantic-field #

Prepare the system for equilibration.

states pydantic-field #

states: SepTopStates

Configure the lambda schedules.

equilibrate pydantic-field #

Equilibrate the system.

sample pydantic-field #

Sample the system across lambda windows using HREMD.

model_dump_yaml #

model_dump_yaml(
    output_path: Path | None = None, **kwargs
) -> str

Dump the model to a YAML representation.

Parameters:

  • output_path (Path | None, default: None ) –

    The (optional) path to save the YAML representation to.

Returns:

  • str

    The YAML representation.

Source code in femto/md/utils/models.py
def model_dump_yaml(self, output_path: pathlib.Path | None = None, **kwargs) -> str:
    """Dump the model to a YAML representation.

    Args:
        output_path: The (optional) path to save the YAML representation to.

    Returns:
        The YAML representation.
    """

    model_yaml = yaml.safe_dump(self.model_dump(), **kwargs)

    if output_path is not None:
        output_path.parent.mkdir(exist_ok=True, parents=True)
        output_path.write_text(model_yaml)

    return model_yaml

SepTopSamplingStage pydantic-model #

Bases: HREMD

Configure how the system will be sampled using Hamiltonian replica exchange.

Fields:

temperature pydantic-field #

temperature: OpenMMQuantity[kelvin] = DEFAULT_TEMPERATURE

The temperature to sample at.

n_warmup_steps pydantic-field #

n_warmup_steps: int = 150000

The number of steps to run each replica for before starting hremd trials. All energies gathered during this period will be discarded.

n_steps_per_cycle pydantic-field #

n_steps_per_cycle: int = 1000

The number of steps to propagate the system by before attempting an exchange.

n_cycles pydantic-field #

n_cycles: int = 2500

The number of cycles of 'propagate the system' -> 'exchange replicas' to run.

max_step_retries pydantic-field #

max_step_retries: int = 5

The maximum number of times to attempt to step if a NaN is encountered before raising an exception

swap_mode pydantic-field #

swap_mode: HREMDSwapModeLiteral | None = ALL.value

The mode in which to propose state swaps between replicas. This can either be: 'neighbours', only try and swap adjacent states or ii. 'all', try and swap all states stochastically. If None, no replica exchanges will be attempted.

max_swaps pydantic-field #

max_swaps: int | None = None

The maximum number of swap proposals to make if running in 'all' mode. This variable does nothing when running in 'neighbours' mode.

trajectory_interval pydantic-field #

trajectory_interval: int | None = None

The number of cycles to run before saving the current replica states to DCD trajectory files. If None, no trajectories will be saved.

trajectory_enforce_pbc pydantic-field #

trajectory_enforce_pbc: bool = False

Whether to apply periodic boundary conditions when retrieving coordinates for writing to trajectory files.

checkpoint_interval pydantic-field #

checkpoint_interval: int | None = None

The number of cycles to run before saving the current replica states to checkpoint files. If None, no checkpoints will be saved.

integrator pydantic-field #

integrator: LangevinIntegrator = LangevinIntegrator(
    timestep=4.0 * femtosecond, friction=1.0 / picosecond
)

The MD integrator to use.

pressure pydantic-field #

pressure: OpenMMQuantity[atmosphere] | None = (
    DEFAULT_PRESSURE
)

The pressure to simulate at, or None to run in NVT.

barostat_frequency pydantic-field #

barostat_frequency: int = 25

The frequency at which to apply the barostat. This is ignored if pressure is None.

analysis_interval pydantic-field #

analysis_interval: int | None = None

The interval (in number of cycles) between estimating and reporting the free energy. If None, no analysis will be performed.

model_dump_yaml #

model_dump_yaml(
    output_path: Path | None = None, **kwargs
) -> str

Dump the model to a YAML representation.

Parameters:

  • output_path (Path | None, default: None ) –

    The (optional) path to save the YAML representation to.

Returns:

  • str

    The YAML representation.

Source code in femto/md/utils/models.py
def model_dump_yaml(self, output_path: pathlib.Path | None = None, **kwargs) -> str:
    """Dump the model to a YAML representation.

    Args:
        output_path: The (optional) path to save the YAML representation to.

    Returns:
        The YAML representation.
    """

    model_yaml = yaml.safe_dump(self.model_dump(), **kwargs)

    if output_path is not None:
        output_path.parent.mkdir(exist_ok=True, parents=True)
        output_path.write_text(model_yaml)

    return model_yaml

SepTopSetupStage pydantic-model #

Bases: Prepare

Configure how the complex will be solvated and restrained prior to equilibration

Fields:

Validators:

ionic_strength pydantic-field #

ionic_strength: OpenMMQuantity[molar] = 0.0 * molar

The total concentration of ions pairs (anion and cation) to add to approximate an ionic strength. This does not include ions that are added to neutralize the system.

neutralize pydantic-field #

neutralize: bool = True

Whether to add counter ions to neutralize the system.

cation pydantic-field #

cation: Literal['Na+', 'K+'] = 'K+'

The cation to use when neutralizing the system.

anion pydantic-field #

anion: Literal['Cl-'] = 'Cl-'

The anion to use when neutralizing the system.

water_model pydantic-field #

water_model: Literal['tip3p'] = 'tip3p'

The water model to use when generating solvent coordinates. The actual force field parameters used for the solvent are determined by the default_protein_ff or any extra parameters provided while preparing the system.

default_protein_ff pydantic-field #

default_protein_ff: list[str] = [*DEFAULT_OPENMM_FF_SOURCES]

The default parameters to use when parameterizing the protein, solvent, and ions.

default_ligand_ff pydantic-field #

default_ligand_ff: str | None = 'openff-2.0.0.offxml'

The default parameters to apply when parameterizing ligands, or None otherwise. Currently, only the path to an OpenFF offxml file can be specified.

box_padding pydantic-field #

box_padding: OpenMMQuantity[_ANGSTROM] | None = (
    10.0 * _ANGSTROM
)

The minimum distance between any complex atom (including any offset ligands) and the box wall. This option is mutually exclusive with n_waters.

box_shape pydantic-field #

box_shape: Literal['cube', 'cubeoid'] = 'cubeoid'

The shape of the box to use when solvating the complex, when box_padding is specified.

n_waters pydantic-field #

n_waters: int | None = None

The number of extra waters to solvate the complex using. This option is mutually exclusive with box_padding.

restraints pydantic-field #

Control how the system should be restrained.

apply_hmr pydantic-field #

apply_hmr: bool = True

Whether to aply hydrogen mass repartitioning to the system.

hydrogen_mass pydantic-field #

hydrogen_mass: OpenMMQuantity[amu] = 1.5 * amu

The mass to assign to hydrogen atoms when applying HMR.

apply_rest pydantic-field #

apply_rest: bool = False

Whether to prepare the system for REST sampling.

rest_config pydantic-field #

rest_config: REST | None = REST(
    scale_nonbonded=True,
    scale_torsions=True,
    scale_angles=False,
    scale_bonds=False,
)

The REST configuration to use if apply_rest is True.

fep_config pydantic-field #

fep_config: FEP = FEP(ligands_can_interact=False)

Configure how to alchemically couple the ligands.

model_dump_yaml #

model_dump_yaml(
    output_path: Path | None = None, **kwargs
) -> str

Dump the model to a YAML representation.

Parameters:

  • output_path (Path | None, default: None ) –

    The (optional) path to save the YAML representation to.

Returns:

  • str

    The YAML representation.

Source code in femto/md/utils/models.py
def model_dump_yaml(self, output_path: pathlib.Path | None = None, **kwargs) -> str:
    """Dump the model to a YAML representation.

    Args:
        output_path: The (optional) path to save the YAML representation to.

    Returns:
        The YAML representation.
    """

    model_yaml = yaml.safe_dump(self.model_dump(), **kwargs)

    if output_path is not None:
        output_path.parent.mkdir(exist_ok=True, parents=True)
        output_path.write_text(model_yaml)

    return model_yaml

SepTopSolutionRestraints pydantic-model #

Bases: BaseModel

Configure the restraints to apply in the solution phase.

Fields:

  • type (Literal['harmonic'])
  • k_distance (OpenMMQuantity[_KCAL_PER_ANG_SQR])

k_distance pydantic-field #

k_distance: OpenMMQuantity[_KCAL_PER_ANG_SQR] = (
    2.4 * _KCAL_PER_ANG_SQR
)

Force constant [kcal/mol/Å^2] of the distance restraint that will separate the ligands during an RBFE calculation.

model_dump_yaml #

model_dump_yaml(
    output_path: Path | None = None, **kwargs
) -> str

Dump the model to a YAML representation.

Parameters:

  • output_path (Path | None, default: None ) –

    The (optional) path to save the YAML representation to.

Returns:

  • str

    The YAML representation.

Source code in femto/md/utils/models.py
def model_dump_yaml(self, output_path: pathlib.Path | None = None, **kwargs) -> str:
    """Dump the model to a YAML representation.

    Args:
        output_path: The (optional) path to save the YAML representation to.

    Returns:
        The YAML representation.
    """

    model_yaml = yaml.safe_dump(self.model_dump(), **kwargs)

    if output_path is not None:
        output_path.parent.mkdir(exist_ok=True, parents=True)
        output_path.write_text(model_yaml)

    return model_yaml

SepTopStates pydantic-model #

Bases: BaseModel

Configure the lambda schedules.

Fields:

Validators:

  • _validate_lambda_lengths

lambda_vdw_ligand_1 pydantic-field #

lambda_vdw_ligand_1: list[float]

The vdW lambda schedule of the first ligand.

lambda_vdw_ligand_2 pydantic-field #

lambda_vdw_ligand_2: list[float] | None

The vdW lambda schedule of the second ligand.

lambda_charges_ligand_1 pydantic-field #

lambda_charges_ligand_1: list[float]

The charge lambda schedule of the first ligand.

lambda_charges_ligand_2 pydantic-field #

lambda_charges_ligand_2: list[float] | None

The charge lambda schedule of the second ligand.

lambda_boresch_ligand_1 pydantic-field #

lambda_boresch_ligand_1: list[float] | None

The lambda schedule of the boresch restraint on the first ligand.

lambda_boresch_ligand_2 pydantic-field #

lambda_boresch_ligand_2: list[float] | None

The lambda schedule of the boresch restraint on the second ligand.

bm_b0 pydantic-field #

bm_b0: list[float] | None = None

The REST2 beta scaling factors (beta_m / beta_0) to use. Set this to None to disable REST2 scaling.

model_dump_yaml #

model_dump_yaml(
    output_path: Path | None = None, **kwargs
) -> str

Dump the model to a YAML representation.

Parameters:

  • output_path (Path | None, default: None ) –

    The (optional) path to save the YAML representation to.

Returns:

  • str

    The YAML representation.

Source code in femto/md/utils/models.py
def model_dump_yaml(self, output_path: pathlib.Path | None = None, **kwargs) -> str:
    """Dump the model to a YAML representation.

    Args:
        output_path: The (optional) path to save the YAML representation to.

    Returns:
        The YAML representation.
    """

    model_yaml = yaml.safe_dump(self.model_dump(), **kwargs)

    if output_path is not None:
        output_path.parent.mkdir(exist_ok=True, parents=True)
        output_path.write_text(model_yaml)

    return model_yaml

compute_ddg #

compute_ddg(
    config: SepTopConfig,
    complex_u_kn: ndarray,
    complex_n_k: ndarray,
    complex_system: System,
    solution_u_kn: ndarray,
    solution_n_k: ndarray,
    solution_system: System,
) -> DataFrame

Computes the binding free energy from the complex and solution phase samples.

Parameters:

  • config (SepTopConfig) –

    The configuration.

  • complex_u_kn (ndarray) –

    The complex phase samples.

  • complex_n_k (ndarray) –

    The complex phase sample counts.

  • complex_system (System) –

    The complex phase system.

  • solution_u_kn (ndarray) –

    The solution phase samples.

  • solution_n_k (ndarray) –

    The solution phase sample counts.

  • solution_system (System) –

    The solution phase system.

Returns:

  • DataFrame

    A pandas DataFrame containing the total binding free energy and its components.

Source code in femto/fe/septop/_analyze.py
def compute_ddg(
    config: "femto.fe.septop.SepTopConfig",
    complex_u_kn: numpy.ndarray,
    complex_n_k: numpy.ndarray,
    complex_system: openmm.System,
    solution_u_kn: numpy.ndarray,
    solution_n_k: numpy.ndarray,
    solution_system: openmm.System,
) -> "pandas.DataFrame":
    """Computes the binding free energy from the complex and solution phase samples.

    Args:
        config: The configuration.
        complex_u_kn: The complex phase samples.
        complex_n_k: The complex phase sample counts.
        complex_system: The complex phase system.
        solution_u_kn: The solution phase samples.
        solution_n_k: The solution phase sample counts.
        solution_system: The solution phase system.

    Returns:
        A pandas DataFrame containing the total binding free energy and its components.
    """
    import pandas

    import femto.fe.septop

    samples = {
        "complex": (complex_u_kn, complex_n_k),
        "solution": (solution_u_kn, solution_n_k),
    }
    results = {}

    for phase in "complex", "solution":
        phase_config: femto.fe.septop.SepTopPhaseConfig = getattr(config, phase)
        phase_u_kn, phase_n_k = samples[phase]

        estimated, _ = femto.fe.ddg.estimate_ddg(
            phase_u_kn, phase_n_k, phase_config.sample.temperature
        )
        del estimated["ddG_0_kcal_mol"]
        del estimated["ddG_0_error_kcal_mol"]

        results.update({f"{phase}_{k}": v for k, v in estimated.items()})

    results.update(compute_complex_correction(config.complex, complex_system))
    results.update(compute_solution_correction(config.solution, solution_system))

    results["ddG_kcal_mol"] = (
        results["complex_ddG_kcal_mol"] + results["complex_ddG_correction_kcal_mol"]
    ) - (results["solution_ddG_kcal_mol"] + results["solution_ddG_correction_kcal_mol"])

    results["ddG_error_kcal_mol"] = numpy.sqrt(
        results["complex_ddG_error_kcal_mol"] ** 2
        + results["solution_ddG_error_kcal_mol"] ** 2
    )

    return pandas.DataFrame([results])

load_config #

load_config(path: Path) -> SepTopConfig

Load a configuration from a YAML file.

Parameters:

  • path (Path) –

    The path to the YAML configuration.

Returns:

Source code in femto/fe/septop/_config.py
def load_config(path: pathlib.Path) -> SepTopConfig:
    """Load a configuration from a YAML file.

    Args:
        path: The path to the YAML configuration.

    Returns:
        The loaded configuration.
    """
    return SepTopConfig(**yaml.safe_load(path.read_text()))

equilibrate_states #

equilibrate_states(
    system: System,
    topology: Topology,
    states: SepTopStates,
    config: SepTopEquilibrateStage,
    platform: OpenMMPlatform,
    reporter: Reporter | None = None,
) -> list[State]

Equilibrate the system at each lambda window.

Parameters:

  • system (System) –

    The system to simulate.

  • topology (Topology) –

    The topology of the system to simulate.

  • states (SepTopStates) –

    The states of the system to simulate.

  • config (SepTopEquilibrateStage) –

    Configuration settings.

  • platform (OpenMMPlatform) –

    The accelerator to use.

  • reporter (Reporter | None, default: None ) –

    The (optional) reporter to use to record system statistics such as volume and energy.

Returns:

  • list[State]

    The final equilibrated state.

Source code in femto/fe/septop/_equilibrate.py
def equilibrate_states(
    system: openmm.System,
    topology: mdtop.Topology,
    states: "femto.fe.septop.SepTopStates",
    config: "femto.fe.septop.SepTopEquilibrateStage",
    platform: femto.md.constants.OpenMMPlatform,
    reporter: femto.md.reporting.Reporter | None = None,
) -> list[openmm.State]:
    """Equilibrate the system at each lambda window.

    Args:
        system: The system to simulate.
        topology: The topology of the system to simulate.
        states: The states of the system to simulate.
        config: Configuration settings.
        platform: The accelerator to use.
        reporter: The (optional) reporter to use to record system statistics such as
            volume and energy.

    Returns:
        The final equilibrated state.
    """
    import femto.fe.septop

    reporter = femto.md.reporting.NullReporter() if reporter is None else reporter

    openmm_reporter = femto.md.reporting.openmm.OpenMMStateReporter(
        reporter, "equilibration", config.report_interval
    )

    state_dicts = femto.fe.septop.create_state_dicts(states, system)

    equilibrated_coords = femto.md.simulate.simulate_states(
        system, topology, state_dicts, config.stages, platform, openmm_reporter
    )
    return equilibrated_coords

run_complex_phase #

run_complex_phase(
    config: SepTopConfig,
    ligand_1_path: Path,
    ligand_2_path: Path | None,
    receptor_path: Path,
    cofactor_paths: list[Path] | None,
    output_dir: Path,
    report_dir: Path | None = None,
    ligand_1_ref_atoms: tuple[str, str, str] | None = None,
    ligand_2_ref_atoms: tuple[str, str, str] | None = None,
    receptor_ref_atoms: tuple[str, str, str] | None = None,
    extra_params: list[Path] | None = None,
)

Run the complex phase of the SepTop calculation.

Parameters:

  • config (SepTopConfig) –

    The configuration.

  • ligand_1_path (Path) –

    The path to the first ligand.

  • ligand_2_path (Path | None) –

    The path to the second ligand, if present.

  • receptor_path (Path) –

    The path to the receptor.

  • cofactor_paths (list[Path] | None) –

    The paths to any cofactors.

  • output_dir (Path) –

    The directory to store all outputs in.

  • report_dir (Path | None, default: None ) –

    The directory to store the logs / reports in.

  • ligand_1_ref_atoms (tuple[str, str, str] | None, default: None ) –

    The AMBER style query masks that select the first ligands reference atoms.

  • ligand_2_ref_atoms (tuple[str, str, str] | None, default: None ) –

    The AMBER style query masks that select the second ligands reference atoms.

  • receptor_ref_atoms (tuple[str, str, str] | None, default: None ) –

    The AMBER style query mask that selects the receptor atoms used to align the ligand.

  • extra_params (list[Path] | None, default: None ) –

    The paths to any extra parameter files (.xml, .parm) to use when parameterizing the system.

Source code in femto/fe/septop/_runner.py
def run_complex_phase(
    config: "femto.fe.septop.SepTopConfig",
    ligand_1_path: pathlib.Path,
    ligand_2_path: pathlib.Path | None,
    receptor_path: pathlib.Path,
    cofactor_paths: list[pathlib.Path] | None,
    output_dir: pathlib.Path,
    report_dir: pathlib.Path | None = None,
    ligand_1_ref_atoms: tuple[str, str, str] | None = None,
    ligand_2_ref_atoms: tuple[str, str, str] | None = None,
    receptor_ref_atoms: tuple[str, str, str] | None = None,
    extra_params: list[pathlib.Path] | None = None,
):
    """Run the complex phase of the SepTop calculation.

    Args:
        config: The configuration.
        ligand_1_path: The path to the first ligand.
        ligand_2_path: The path to the second ligand, if present.
        receptor_path: The path to the receptor.
        cofactor_paths: The paths to any cofactors.
        output_dir: The directory to store all outputs in.
        report_dir: The directory to store the logs / reports in.
        ligand_1_ref_atoms: The AMBER style query masks that select the first ligands
            reference atoms.
        ligand_2_ref_atoms: The AMBER style query masks that select the second ligands
            reference atoms.
        receptor_ref_atoms: The AMBER style query mask that selects the receptor atoms
            used to align the ligand.
        extra_params: The paths to any extra parameter files (.xml, .parm) to use
            when parameterizing the system.
    """

    prepare_fn = functools.partial(
        _prepare_complex_phase,
        config.complex,
        receptor_path,
        ligand_1_path,
        ligand_2_path,
        cofactor_paths,
        ligand_1_ref_atoms,
        ligand_2_ref_atoms,
        receptor_ref_atoms,
        extra_params,
    )
    _run_phase(config.complex, prepare_fn, output_dir, report_dir)

run_solution_phase #

run_solution_phase(
    config: SepTopConfig,
    ligand_1_path: Path,
    ligand_2_path: Path | None,
    output_dir: Path,
    report_dir: Path | None = None,
    ligand_1_ref_atoms: tuple[str, str, str] | None = None,
    ligand_2_ref_atoms: tuple[str, str, str] | None = None,
    extra_params: list[Path] | None = None,
)

Run the solution phase of the SepTop calculation.

Parameters:

  • config (SepTopConfig) –

    The configuration.

  • ligand_1_path (Path) –

    The path to the first ligand.

  • ligand_2_path (Path | None) –

    The path to the second ligand, if present.

  • output_dir (Path) –

    The directory to store all outputs in.

  • report_dir (Path | None, default: None ) –

    The directory to store the report in.

  • ligand_1_ref_atoms (tuple[str, str, str] | None, default: None ) –

    The AMBER style query masks that select the first ligands reference atoms.

  • ligand_2_ref_atoms (tuple[str, str, str] | None, default: None ) –

    The AMBER style query masks that select the second ligands reference atoms.

  • extra_params (list[Path] | None, default: None ) –

    The paths to any extra parameter files (.xml, .parm) to use when parameterizing the system.

Source code in femto/fe/septop/_runner.py
def run_solution_phase(
    config: "femto.fe.septop.SepTopConfig",
    ligand_1_path: pathlib.Path,
    ligand_2_path: pathlib.Path | None,
    output_dir: pathlib.Path,
    report_dir: pathlib.Path | None = None,
    ligand_1_ref_atoms: tuple[str, str, str] | None = None,
    ligand_2_ref_atoms: tuple[str, str, str] | None = None,
    extra_params: list[pathlib.Path] | None = None,
):
    """Run the solution phase of the SepTop calculation.

    Args:
        config: The configuration.
        ligand_1_path: The path to the first ligand.
        ligand_2_path: The path to the second ligand, if present.
        output_dir: The directory to store all outputs in.
        report_dir: The directory to store the report in.
        ligand_1_ref_atoms: The AMBER style query masks that select the first ligands
            reference atoms.
        ligand_2_ref_atoms: The AMBER style query masks that select the second ligands
            reference atoms.
        extra_params: The paths to any extra parameter files (.xml, .parm) to use
            when parameterizing the system.
    """

    prepare_fn = functools.partial(
        _prepare_solution_phase,
        config.solution,
        ligand_1_path,
        ligand_2_path,
        ligand_1_ref_atoms,
        ligand_2_ref_atoms,
        extra_params,
    )
    _run_phase(config.solution, prepare_fn, output_dir, report_dir)

submit_network #

submit_network(
    config: SepTopConfig,
    network: Network,
    output_dir: Path,
    queue_options: SLURMOptions,
    mpi_command: list[str] | None = None,
) -> list[tuple[str, str, str]]

Submits a set of SepTop calculations to an HPC queueing manager.

Parameters:

  • config (SepTopConfig) –

    The configuration.

  • network (Network) –

    The network of edges to run.

  • output_dir (Path) –

    The directory to store any outputs in.

  • queue_options (SLURMOptions) –

    The options to use when submitting the jobs.

  • mpi_command (list[str] | None, default: None ) –

    The mpi runner command to use. The default is "srun --mpi=pmix".

Returns:

  • list[tuple[str, str, str]]

    The ids of the submitted jobs.

Source code in femto/fe/septop/_runner.py
def submit_network(
    config: "femto.fe.septop.SepTopConfig",
    network: femto.fe.inputs.Network,
    output_dir: pathlib.Path,
    queue_options: femto.fe.utils.queue.SLURMOptions,
    mpi_command: list[str] | None = None,
) -> list[tuple[str, str, str]]:
    """Submits a set of SepTop calculations to an HPC queueing manager.

    Args:
        config: The configuration.
        network: The network of edges to run.
        output_dir: The directory to store any outputs in.
        queue_options: The options to use when submitting the jobs.
        mpi_command: The mpi runner command to use. The default is
            ``"srun --mpi=pmix"``.

    Returns:
        The ids of the submitted jobs.
    """

    mpi_command = mpi_command if mpi_command is not None else ["srun", "--mpi=pmix"]

    output_dir.mkdir(exist_ok=True, parents=True)

    date_str = datetime.datetime.now().strftime("%Y-%m-%d--%H-%M-%S")
    config_path = output_dir / f"config-{date_str}.yaml"
    config_path.write_text(config.model_dump_yaml(sort_keys=False))

    femto_command = ["femto", "septop", "--config", config_path]

    slurm_job_ids = []

    for edge in network.edges:
        edge_dir = output_dir / f"{edge.ligand_1.name}~{edge.ligand_2.name}"

        complex_output_dir = edge_dir / "complex"
        solution_output_dir = edge_dir / "solution"

        ligand_args = [
            *_create_run_flags(edge.ligand_1, "ligand-1"),
            *_create_run_flags(edge.ligand_2, "ligand-2"),
        ]

        run_solution_id = femto.fe.utils.queue.submit_slurm_job(
            [
                *mpi_command,
                *femto_command,
                "run-solution",
                *ligand_args,
                f"--output-dir={solution_output_dir}",
                f"--report-dir={solution_output_dir}",
            ],
            queue_options,
            edge_dir / f"run-solution-{date_str}.out",
        )
        run_complex_id = femto.fe.utils.queue.submit_slurm_job(
            [
                *mpi_command,
                *femto_command,
                "run-complex",
                *_create_run_flags(network.receptor, "receptor"),
                *ligand_args,
                f"--output-dir={complex_output_dir}",
                f"--report-dir={complex_output_dir}",
            ],
            queue_options,
            edge_dir / f"run-complex-{date_str}.out",
        )

        analyze_id = femto.fe.utils.queue.submit_slurm_job(
            [
                *femto_command,
                "analyze",
                "--complex-samples",
                complex_output_dir / "_sample/samples.arrow",
                "--complex-system",
                complex_output_dir / "_setup/system.xml",
                "--solution-samples",
                solution_output_dir / "_sample/samples.arrow",
                "--solution-system",
                solution_output_dir / "_setup/system.xml",
                "--output",
                edge_dir / "ddg.csv",
            ],
            queue_options,
            edge_dir / f"analyze-{date_str}.out",
            [run_solution_id, run_complex_id],
        )

        slurm_job_ids.append((run_solution_id, run_complex_id, analyze_id))

    return slurm_job_ids

run_hremd #

run_hremd(
    system: System,
    topology: Topology,
    coords: list[State],
    states: SepTopStates,
    config: SepTopSamplingStage,
    platform: OpenMMPlatform,
    output_dir: Path,
    reporter: Reporter | None = None,
)

Perform replica exchange sampling for a system prepared for SepTop calculations.

Parameters:

  • system (System) –

    The system.

  • topology (Topology) –

    The topology associated with the system.

  • coords (list[State]) –

    The starting coordinates for each state.

  • states (SepTopStates) –

    The lambda states to sample.

  • config (SepTopSamplingStage) –

    Configuration settings.

  • platform (OpenMMPlatform) –

    The platform to run on.

  • output_dir (Path) –

    The directory to store the sampled energies and statistics to, and any trajectory files if requested.

  • reporter (Reporter | None, default: None ) –

    The reporter to log statistics such as online estimates of the free energy to.

Source code in femto/fe/septop/_sample.py
def run_hremd(
    system: openmm.System,
    topology: mdtop.Topology,
    coords: list[openmm.State],
    states: "femto.fe.septop.SepTopStates",
    config: "femto.fe.septop.SepTopSamplingStage",
    platform: femto.md.constants.OpenMMPlatform,
    output_dir: pathlib.Path,
    reporter: femto.md.reporting.Reporter | None = None,
):
    """Perform replica exchange sampling for a system prepared for SepTop calculations.

    Args:
        system: The system.
        topology: The topology associated with the system.
        coords: The starting coordinates for each state.
        states: The lambda states to sample.
        config: Configuration settings.
        platform: The platform to run on.
        output_dir: The directory to store the sampled energies and statistics to, and
            any trajectory files if requested.
        reporter: The reporter to log statistics such as online estimates of the
            free energy to.
    """
    import femto.fe.septop

    system = copy.deepcopy(system)

    n_barostats = sum(
        1
        for force in system.getForces()
        if isinstance(force, openmm.MonteCarloBarostat)
    )
    if n_barostats > 0:
        raise RuntimeError("the system should not contain a barostat already")

    if config.pressure is not None:
        barostat = openmm.MonteCarloBarostat(
            config.pressure, config.temperature, config.barostat_frequency
        )
        system.addForce(barostat)

    femto.md.utils.openmm.assign_force_groups(system)

    state_dicts = femto.fe.septop.create_state_dicts(states, system)

    integrator = femto.md.utils.openmm.create_integrator(
        config.integrator, config.temperature
    )
    simulation = femto.md.utils.openmm.create_simulation(
        system, topology, coords[0], integrator, state_dicts[0], platform
    )

    analysis_fn = None

    if reporter is not None and config.analysis_interval is not None:
        analysis_fn = functools.partial(_analyze, config=config, reporter=reporter)

    return femto.md.hremd.run_hremd(
        simulation,
        state_dicts,
        config,
        output_dir,
        initial_coords=coords,
        analysis_fn=analysis_fn,
        analysis_interval=config.analysis_interval,
    )

setup_complex #

setup_complex(
    config: SepTopSetupStage,
    receptor: Topology,
    ligand_1: Topology,
    ligand_2: Topology | None,
    cofactors: list[Topology] | None,
    receptor_ref_query: tuple[str, str, str] | None = None,
    ligand_1_ref_query: tuple[str, str, str] | None = None,
    ligand_2_ref_query: tuple[str, str, str] | None = None,
    extra_params: list[Path] | None = None,
) -> tuple[Topology, System]

Prepares a system ready for running the SepTop method.

Parameters:

  • config (SepTopSetupStage) –

    The configuration for setting up the system.

  • receptor (Topology) –

    The receptor.

  • ligand_1 (Topology) –

    The first ligand.

  • ligand_2 (Topology | None) –

    The second ligand if one is present.

  • cofactors (list[Topology] | None) –

    Any cofactors.

  • receptor_ref_query (tuple[str, str, str] | None, default: None ) –

    The query to select the reference atoms of the receptor.

  • ligand_1_ref_query (tuple[str, str, str] | None, default: None ) –

    The query to select the reference atoms of the first ligand.

  • ligand_2_ref_query (tuple[str, str, str] | None, default: None ) –

    The query to select the reference atoms of the second ligand

  • extra_params (list[Path] | None, default: None ) –

    The paths to any extra parameter files (.xml, .parm) to use when parameterizing the system.

Returns:

  • tuple[Topology, System]

    The prepared topology and OpenMM system object.

Source code in femto/fe/septop/_setup.py
def setup_complex(
    config: "femto.fe.septop.SepTopSetupStage",
    receptor: mdtop.Topology,
    ligand_1: mdtop.Topology,
    ligand_2: mdtop.Topology | None,
    cofactors: list[mdtop.Topology] | None,
    receptor_ref_query: tuple[str, str, str] | None = None,
    ligand_1_ref_query: tuple[str, str, str] | None = None,
    ligand_2_ref_query: tuple[str, str, str] | None = None,
    extra_params: list[pathlib.Path] | None = None,
) -> tuple[mdtop.Topology, openmm.System]:
    """Prepares a system ready for running the SepTop method.

    Args:
        config: The configuration for setting up the system.
        receptor: The receptor.
        ligand_1: The first ligand.
        ligand_2: The second ligand if one is present.
        cofactors: Any cofactors.
        receptor_ref_query: The query to select the reference atoms of the receptor.
        ligand_1_ref_query: The query to select the reference atoms of the first ligand.
        ligand_2_ref_query: The query to select the reference atoms of the second ligand
        extra_params: The paths to any extra parameter files (.xml, .parm) to use
            when parameterizing the system.

    Returns:
        The prepared topology and OpenMM system object.
    """
    import femto.fe.septop

    if not isinstance(config.restraints, femto.fe.septop.SepTopComplexRestraints):
        raise ValueError("invalid restraint config")

    system, topology, ligand_1_ref_idxs, ligand_2_ref_idxs = _setup_system(
        config,
        ligand_1,
        ligand_2,
        receptor,
        cofactors,
        ligand_1_ref_query,
        ligand_2_ref_query,
        None,
        extra_params,
    )

    _LOGGER.info("applying restraints.")

    if receptor_ref_query is None:
        _LOGGER.info("selecting receptor reference atoms")

        receptor_ref_idxs_1 = femto.fe.reference.select_receptor_idxs(
            topology, ligand_1_ref_idxs
        )
    else:
        receptor_ref_idxs_1 = femto.fe.reference.queries_to_idxs(
            receptor, receptor_ref_query
        )

        ligand_idxs = topology.select(
            f"resn {femto.md.constants.LIGAND_1_RESIDUE_NAME} | "
            f"resn {femto.md.constants.LIGAND_2_RESIDUE_NAME}"
        )
        receptor_start_idx = 0 if len(ligand_idxs) == 0 else (max(ligand_idxs) + 1)
        receptor_ref_idxs_1 = tuple(v + receptor_start_idx for v in receptor_ref_idxs_1)

    _LOGGER.info(f"receptor ref idxs for ligand 1={receptor_ref_idxs_1}")

    _apply_complex_restraints(
        topology,
        receptor_ref_idxs_1,
        ligand_1_ref_idxs,
        config.restraints,
        system,
        LAMBDA_BORESCH_LIGAND_1,
    )

    if ligand_2 is None:
        return topology, system

    receptor_ref_idxs_2 = receptor_ref_idxs_1

    if receptor_ref_query is None and femto.fe.reference.check_receptor_idxs(
        topology, receptor_ref_idxs_2, ligand_2_ref_idxs
    ):
        _LOGGER.info("selecting alternate receptor reference atoms for ligand 2")

        receptor_ref_idxs_2 = femto.fe.reference.select_receptor_idxs(
            topology, ligand_2_ref_idxs
        )

    _LOGGER.info(f"receptor ref idxs for ligand 2={receptor_ref_idxs_2}")

    _apply_complex_restraints(
        topology,
        receptor_ref_idxs_2,
        ligand_2_ref_idxs,
        config.restraints,
        system,
        LAMBDA_BORESCH_LIGAND_2,
    )
    return topology, system

setup_solution #

setup_solution(
    config: SepTopSetupStage,
    ligand_1: Topology,
    ligand_2: Topology | None,
    ligand_1_ref_query: tuple[str, str, str] | None = None,
    ligand_2_ref_query: tuple[str, str, str] | None = None,
    extra_params: list[Path] | None = None,
) -> tuple[Topology, System]

Prepares a system ready for running the SepTop method.

Parameters:

  • config (SepTopSetupStage) –

    The configuration for setting up the system.

  • ligand_1 (Topology) –

    The first ligand.

  • ligand_2 (Topology | None) –

    The second ligand if one is present.

  • ligand_1_ref_query (tuple[str, str, str] | None, default: None ) –

    The query to select the reference atoms of the first ligand.

  • ligand_2_ref_query (tuple[str, str, str] | None, default: None ) –

    The query to select the reference atoms of the second ligand

  • extra_params (list[Path] | None, default: None ) –

    The paths to any extra parameter files (.xml, .parm) to use when parameterizing the system.

Returns:

  • tuple[Topology, System]

    The prepared topology and OpenMM system object.

Source code in femto/fe/septop/_setup.py
def setup_solution(
    config: "femto.fe.septop.SepTopSetupStage",
    ligand_1: mdtop.Topology,
    ligand_2: mdtop.Topology | None,
    ligand_1_ref_query: tuple[str, str, str] | None = None,
    ligand_2_ref_query: tuple[str, str, str] | None = None,
    extra_params: list[pathlib.Path] | None = None,
) -> tuple[mdtop.Topology, openmm.System]:
    """Prepares a system ready for running the SepTop method.

    Args:
        config: The configuration for setting up the system.
        ligand_1: The first ligand.
        ligand_2: The second ligand if one is present.
        ligand_1_ref_query: The query to select the reference atoms of the first ligand.
        ligand_2_ref_query: The query to select the reference atoms of the second ligand
        extra_params: The paths to any extra parameter files (.xml, .parm) to use
            when parameterizing the system.

    Returns:
        The prepared topology and OpenMM system object.
    """
    import femto.fe.septop

    config = copy.deepcopy(config)

    if config.box_padding is None:
        raise NotImplementedError("box padding must be set for solution phase")

    restraint_config = config.restraints

    if not isinstance(restraint_config, femto.fe.septop.SepTopSolutionRestraints):
        raise ValueError("invalid restraint config")

    if ligand_2 is not None:
        ligand_2.xyz += _compute_ligand_offset(ligand_1, ligand_2)

    system, topology, ligand_1_ref_idxs, ligand_2_ref_idxs = _setup_system(
        config,
        ligand_1,
        ligand_2,
        None,
        None,
        ligand_1_ref_query,
        ligand_2_ref_query,
        None,
        extra_params,
    )

    if ligand_2 is not None:
        _apply_solution_restraints(
            topology,
            ligand_1_ref_idxs[1],
            ligand_2_ref_idxs[1],
            config.restraints,
            system,
        )
    femto.md.utils.openmm.assign_force_groups(system)

    return topology, system

create_state_dicts #

create_state_dicts(
    config: SepTopStates, system: System
) -> list[dict[str, float]]

Map the lambda states specified in the configuration to a dictionary.

Parameters:

  • config (SepTopStates) –

    The configuration.

  • system (System) –

    The system being simulated.

Returns:

  • list[dict[str, float]]

    The dictionary of lambda states.

Source code in femto/fe/septop/_utils.py
def create_state_dicts(
    config: "femto.fe.septop.SepTopStates", system: openmm.System
) -> list[dict[str, float]]:
    """Map the lambda states specified in the configuration to a dictionary.

    Args:
        config: The configuration.
        system: The system being simulated.

    Returns:
        The dictionary of lambda states.
    """
    from femto.fe.septop import LAMBDA_BORESCH_LIGAND_1, LAMBDA_BORESCH_LIGAND_2

    states = [
        {
            LAMBDA_VDW_LIGAND_1: config.lambda_vdw_ligand_1[i],
            LAMBDA_CHARGES_LIGAND_1: config.lambda_charges_ligand_1[i],
            **(
                {LAMBDA_VDW_LIGAND_2: config.lambda_vdw_ligand_2[i]}
                if config.lambda_vdw_ligand_2 is not None
                else {}
            ),
            **(
                {LAMBDA_CHARGES_LIGAND_2: config.lambda_charges_ligand_2[i]}
                if config.lambda_charges_ligand_2 is not None
                else {}
            ),
            **(
                {LAMBDA_BORESCH_LIGAND_1: config.lambda_boresch_ligand_1[i]}
                if config.lambda_boresch_ligand_1 is not None
                else {}
            ),
            **(
                {LAMBDA_BORESCH_LIGAND_2: config.lambda_boresch_ligand_2[i]}
                if config.lambda_boresch_ligand_2 is not None
                else {}
            ),
            **(
                {femto.md.rest.REST_CTX_PARAM: config.bm_b0[i]}
                if config.bm_b0 is not None
                else {}
            ),
        }
        for i in range(len(config.lambda_vdw_ligand_1))
    ]

    return [
        femto.md.utils.openmm.evaluate_ctx_parameters(state, system) for state in states
    ]