Exemple #1
0
def test_combine_molecules_deepdiff_offxml(
    acetone, openff, coumarin, tmpdir, rfree_data
):
    """When not optimising anything make sure we can round trip openff parameters"""

    with tmpdir.as_cwd():
        openff.run(acetone)
        acetone_ref_system = xmltodict.parse(open("serialised.xml").read())
        openff.run(coumarin)
        coumarin_ref_system = xmltodict.parse(open("serialised.xml").read())

        _combine_molecules_offxml(
            molecules=[acetone, coumarin],
            parameters=[],
            rfree_data=rfree_data,
            filename="combined.offxml",
        )

        # load up new systems and compare
        combinded_ff = ForceField("combined.offxml")
        assert combinded_ff.author == f"QUBEKit_version_{qubekit.__version__}"

        acetone_combine_system = xmltodict.parse(
            XmlSerializer.serialize(
                combinded_ff.create_openmm_system(
                    topology=Molecule.from_rdkit(acetone.to_rdkit()).to_topology()
                )
            )
        )
        acetone_diff = DeepDiff(
            acetone_ref_system,
            acetone_combine_system,
            ignore_order=True,
            significant_digits=6,
        )
        # should only be a difference in torsions with zero k values as openff drops these
        assert len(acetone_diff) == 1
        for item in acetone_diff["iterable_item_added"].values():
            assert item["@k"] == "0"

        coumarin_combine_system = xmltodict.parse(
            XmlSerializer.serialize(
                combinded_ff.create_openmm_system(
                    topology=Molecule.from_rdkit(coumarin.to_rdkit()).to_topology()
                )
            )
        )
        coumarin_diff = DeepDiff(
            coumarin_ref_system,
            coumarin_combine_system,
            ignore_order=True,
            significant_digits=6,
        )
        assert len(coumarin_diff) == 1
        for item in coumarin_diff["iterable_item_added"].values():
            assert item["@k"] == "0"
Exemple #2
0
def test_scaled_de_energy():
    """For a molecule with 1-4 interactions make sure the scaling is correctly applied.
    Note that only nonbonded parameters are non zero.
    """

    ff = ForceField(load_plugins=True)
    ff.get_parameter_handler("Electrostatics")

    ff.get_parameter_handler(
        "ChargeIncrementModel",
        {
            "version": "0.3",
            "partial_charge_method": "formal_charge"
        },
    )
    vdw_handler = ff.get_parameter_handler("vdW")
    vdw_handler.add_parameter({
        "smirks": "[*:1]",
        "epsilon": 0.0 * unit.kilojoule_per_mole,
        "sigma": 1.0 * unit.angstrom,
    })
    double_exp = ff.get_parameter_handler("DoubleExponential")
    double_exp.alpha = 18.7
    double_exp.beta = 3.3
    double_exp.scale14 = 1
    double_exp.add_parameter({
        "smirks": "[#6X4:1]",
        "r_min": 3.816 * unit.angstrom,
        "epsilon": 0.1094 * unit.kilocalorie_per_mole,
    })
    double_exp.add_parameter({
        "smirks": "[#1:1]-[#6X4]",
        "r_min": 2.974 * unit.angstrom,
        "epsilon": 0.0157 * unit.kilocalorie_per_mole,
    })

    ethane = Molecule.from_smiles("CC")
    ethane.generate_conformers(n_conformers=1)
    off_top = ethane.to_topology()
    omm_top = off_top.to_openmm()
    system_no_scale = ff.create_openmm_system(topology=off_top)
    energy_no_scale = evaluate_energy(system=system_no_scale,
                                      topology=omm_top,
                                      positions=ethane.conformers[0])
    # now scale 1-4 by half
    double_exp.scale14 = 0.5
    system_scaled = ff.create_openmm_system(topology=off_top)
    energy_scaled = evaluate_energy(system=system_scaled,
                                    topology=omm_top,
                                    positions=ethane.conformers[0])
    assert double_exp.scale14 * energy_no_scale == pytest.approx(energy_scaled,
                                                                 abs=1e-6)
Exemple #3
0
def _build_system(mol, constrained):
    if constrained:
        parsley = ForceField("openff-1.0.0.offxml")
    else:
        parsley = ForceField("openff_unconstrained-1.0.0.offxml")

    mol = Molecule.from_file(get_data_file_path("molecules/" + mol),
                             file_format="sdf")

    if type(mol) == Molecule:
        off_top = mol.to_topology()
        positions = mol.conformers[0]
    elif type(mol) == list:
        # methane_multiconformer case is a list of two mols
        off_top = Topology()
        for mol_i in mol:
            off_top.add_molecule(mol_i)
        positions = (np.vstack([mol[0].conformers[0], mol[1].conformers[0]]) *
                     unit.angstrom)

    from openff.toolkit.utils.toolkits import (
        AmberToolsToolkitWrapper,
        RDKitToolkitWrapper,
        ToolkitRegistry,
    )

    toolkit_registry = ToolkitRegistry(
        toolkit_precedence=[RDKitToolkitWrapper, AmberToolsToolkitWrapper])

    omm_sys = parsley.create_openmm_system(off_top,
                                           toolkit_registry=toolkit_registry)

    return omm_sys, positions, off_top
Exemple #4
0
def test_combine_molecules_offxml_plugin_deepdiff(tmpdir, coumarin, rfree_data):
    """Make sure that systems made from molecules using the xml method match offxmls with plugins"""

    # we need to recalculate the Nonbonded terms using the fake Rfree
    coumarin_copy = coumarin.copy(deep=True)
    # apply symmetry to make sure systems match
    MBISCharges.apply_symmetrisation(coumarin_copy)
    with tmpdir.as_cwd():
        # make the offxml using the plugin interface
        _combine_molecules_offxml(
            molecules=[coumarin_copy],
            parameters=elements,
            rfree_data=rfree_data,
            filename="openff.offxml",
            water_model="tip3p",
        )
        offxml = ForceField(
            "openff.offxml", load_plugins=True, allow_cosmetic_attributes=True
        )
        # check the plugin is being used
        vdw = offxml.get_parameter_handler("QUBEKitvdWTS")
        # make sure we have the parameterize tags
        assert len(vdw._cosmetic_attribs) == 1
        assert len(vdw.parameters) == 28

        alpha = rfree_data.pop("alpha")
        beta = rfree_data.pop("beta")
        lj = LennardJones612(free_parameters=rfree_data, alpha=alpha, beta=beta)
        # get new Rfree data
        lj.run(coumarin_copy)
        coumarin_copy.write_parameters("coumarin.xml")
        coumarin_ref_system = xmltodict.parse(
            XmlSerializer.serialize(
                app.ForceField("coumarin.xml").createSystem(
                    topology=coumarin_copy.to_openmm_topology(),
                    nonbondedCutoff=9 * unit.angstroms,
                    removeCMMotion=False,
                )
            )
        )
        coumarin_off_system = xmltodict.parse(
            XmlSerializer.serialize(
                offxml.create_openmm_system(
                    Molecule.from_rdkit(coumarin_copy.to_rdkit()).to_topology()
                )
            )
        )

        coumarin_diff = DeepDiff(
            coumarin_ref_system,
            coumarin_off_system,
            ignore_order=True,
            significant_digits=6,
            exclude_regex_paths="mass",
        )
        assert len(coumarin_diff) == 1
        for item in coumarin_diff["iterable_item_added"].values():
            assert item["@k"] == "0"
Exemple #5
0
def _create_impropers_only_system(
    smiles: str = "CC1=C(C(=O)C2=C(C1=O)N3CC4C(C3(C2COC(=O)N)OC)N4)N",
) -> mm.System:
    """Create a simulation that contains only improper torsion terms,
    by parameterizing with openff-1.2.0 and deleting  all terms but impropers
    """

    molecule = Molecule.from_smiles(smiles, allow_undefined_stereo=True)
    g = esp.Graph(molecule)

    topology = Topology.from_molecules(molecule)
    forcefield = ForceField("openff-1.2.0.offxml")
    openmm_system = forcefield.create_openmm_system(topology)

    # delete all forces except PeriodicTorsionForce
    is_torsion = (
        lambda force: "PeriodicTorsionForce" in force.__class__.__name__
    )
    for i in range(openmm_system.getNumForces())[::-1]:
        if not is_torsion(openmm_system.getForce(i)):
            openmm_system.removeForce(i)
    assert openmm_system.getNumForces() == 1
    torsion_force = openmm_system.getForce(0)
    assert is_torsion(torsion_force)

    # set k = 0 for any torsion that's not an improper
    indices = set(
        map(
            tuple,
            esp.graphs.utils.offmol_indices.improper_torsion_indices(
                molecule
            ),
        )
    )
    num_impropers_retained = 0
    for i in range(torsion_force.getNumTorsions()):
        (
            p1,
            p2,
            p3,
            p4,
            periodicity,
            phase,
            k,
        ) = torsion_force.getTorsionParameters(i)

        if (p1, p2, p3, p4) in indices:
            num_impropers_retained += 1
        else:
            torsion_force.setTorsionParameters(
                i, p1, p2, p3, p4, periodicity, phase, 0.0
            )

    assert (
        num_impropers_retained > 0
    )  # otherwise this molecule is not a useful test case!

    return openmm_system, topology, g
Exemple #6
0
def test_smirnoff_hack():
    """Test basic behavior of smirnoff_hack.py, in particular
    the compatibility with Molecule API"""
    from openff.toolkit.topology import Molecule, Topology
    from openff.toolkit.typing.engines.smirnoff import ForceField
    from openff.toolkit.utils.toolkits import (AmberToolsToolkitWrapper,
                                               RDKitToolkitWrapper,
                                               ToolkitRegistry)

    from forcebalance import smirnoff_hack

    top = Topology.from_molecules(Molecule.from_smiles("CCO"))
    parsley = ForceField("openff-1.0.0.offxml")

    registry = ToolkitRegistry()
    registry.register_toolkit(RDKitToolkitWrapper)
    registry.register_toolkit(AmberToolsToolkitWrapper)
    parsley.create_openmm_system(top, toolkit_registry=registry)
def test_offxml_round_trip(tmpdir, openff, molecule):
    """
    Test round tripping offxml parameters through qubekit
    """

    with tmpdir.as_cwd():
        mol = Ligand.from_file(get_data(molecule))
        offmol = Molecule.from_file(get_data(molecule))
        openff.run(mol)
        mol.to_offxml("test.offxml")
        # build another openmm system and serialise to compare with deepdiff
        offxml = ForceField("test.offxml")
        assert offxml.author == f"QUBEKit_version_{qubekit.__version__}"
        qubekit_system = offxml.create_openmm_system(
            topology=offmol.to_topology())
        qubekit_xml = xmltodict.parse(
            openmm.XmlSerializer.serialize(qubekit_system))
        with open("qubekit_xml", "w") as output:
            output.write(openmm.XmlSerializer.serialize(qubekit_system))
        openff_system = xmltodict.parse(open("serialised.xml").read())

        offxml_diff = DeepDiff(
            qubekit_xml,
            openff_system,
            ignore_order=True,
            significant_digits=6,
        )
        # the only difference should be in torsions with a 0 barrier height which are excluded from an offxml
        for item in offxml_diff["iterable_item_removed"].values():
            assert item["@k"] == "0"

        # load both systems and compute the energy
        qubekit_top = load_topology(
            mol.to_openmm_topology(),
            system=qubekit_system,
            xyz=mol.openmm_coordinates(),
        )
        qubekit_energy = energy_decomposition_system(qubekit_top,
                                                     qubekit_system,
                                                     platform="Reference")

        ref_system = XmlSerializer.deserializeSystem(
            open("serialised.xml").read())
        parm_top = load_topology(mol.to_openmm_topology(),
                                 system=ref_system,
                                 xyz=mol.openmm_coordinates())
        ref_energy = energy_decomposition_system(parm_top,
                                                 ref_system,
                                                 platform="Reference")
        # compare the decomposed energies of the groups
        for force_group, energy in ref_energy:
            for qube_force, qube_e in qubekit_energy:
                if force_group == qube_force:
                    assert energy == pytest.approx(qube_e, abs=2e-3)
def test_from_openmm_single_mols(mol, n_mols):
    """
    Test that ForceField.create_openmm_system and Interchange.to_openmm produce
    objects with similar energies

    TODO: Tighten tolerances
    TODO: Test periodic and non-periodic
    """

    parsley = ForceField(get_test_file_path("parsley.offxml"))

    mol = Molecule.from_smiles(mol)
    mol.generate_conformers(n_conformers=1)
    top = Topology.from_molecules(n_mols * [mol])
    mol.conformers[0] -= np.min(mol.conformers) * simtk_unit.angstrom

    top.box_vectors = np.eye(3) * np.asarray([15, 15, 15]) * simtk_unit.nanometer

    if n_mols == 1:
        positions = mol.conformers[0]
    elif n_mols == 2:
        positions = np.vstack(
            [mol.conformers[0], mol.conformers[0] + 3 * simtk_unit.nanometer]
        )
        positions = positions * simtk_unit.angstrom

    toolkit_system = parsley.create_openmm_system(top)

    native_system = Interchange.from_smirnoff(
        force_field=parsley, topology=top
    ).to_openmm()

    toolkit_energy = _get_openmm_energies(
        omm_sys=toolkit_system,
        box_vectors=toolkit_system.getDefaultPeriodicBoxVectors(),
        positions=positions,
    )
    native_energy = _get_openmm_energies(
        omm_sys=native_system,
        box_vectors=native_system.getDefaultPeriodicBoxVectors(),
        positions=positions,
    )

    toolkit_energy.compare(native_energy)
def single_molecule_coverage(molecule: Molecule, forcefield: ForceField):
    #-> Dict[str, Dict[str, int]], List[Molecule], List[Molecule, Exception]
    """
    For a single molecule generate a coverage report and try to build an openmm system this will also highlight any missing parameters and dificulties with charging the molecule.

    Parameters
    ----------
    molecule: The openff-toolkit molecule object for which the report should be generated.
    ff: The openff-toolkit typing engine that should be used to check coverage and build an openmm system.

    Returns
    -------
    report: dict
        A dictionary of the coverage report 
    e: Exception or None. 
        The exception raised in this step, if any. 
        If not None, it should be assumed that coverage is invalid.
    """

    coverage = {
        "Angles": {},
        "Bonds": {},
        "ProperTorsions": {},
        "ImproperTorsions": {},
        "vdW": {}
    }
    coverage["molecule"] = molecule
    try:
        labels = forcefield.label_molecules(molecule.to_topology())[0]
        for param_type, params in labels.items():
            for param in params.values():
                if param.id not in coverage[param_type]:
                    coverage[param_type][param.id] = 1
                else:
                    coverage[param_type][param.id] += 1
        # now generate a system this will catch any missing parameters
        # and molecules that can not be charged
        _ = forcefield.create_openmm_system(molecule.to_topology())
        return coverage, None
    except Exception as e:
        return coverage, e
Exemple #10
0
    def test_from_toolkit_packmol_boxes(self, pdb_path, unique_molecules):
        """
        Test loading some pre-prepared PACKMOL-generated systems.

        These use PDB files already prepared in the toolkit because PDB files are a pain.
        """
        ff = ForceField("openff-1.0.0.offxml")

        pdb_file_path = get_data_file_path("systems/packmol_boxes/" + pdb_path)
        pdbfile = openmm.app.PDBFile(pdb_file_path)
        top = OFFBioTop.from_openmm(
            pdbfile.topology,
            unique_molecules=unique_molecules,
        )
        top.mdtop = md.Topology.from_openmm(top.to_openmm())
        box = pdbfile.topology.getPeriodicBoxVectors()
        box = box.value_in_unit(nm) * unit.nanometer

        out = Interchange.from_smirnoff(ff, top)
        out.box = box
        out.positions = pdbfile.getPositions()

        assert np.allclose(
            out.positions.to(unit.nanometer).magnitude,
            pdbfile.getPositions().value_in_unit(nm),
        )

        get_openmm_energies(
            out,
            hard_cutoff=True,
            combine_nonbonded_forces=True,
        ).compare(
            _get_openmm_energies(
                omm_sys=ff.create_openmm_system(top),
                box_vectors=pdbfile.topology.getPeriodicBoxVectors(),
                positions=pdbfile.getPositions(),
                hard_cutoff=True,
            )
        )
def test_water_dimer():
    tip3p = ForceField(get_test_file_path("tip3p.offxml"))
    water = Molecule.from_smiles("O")
    top = Topology.from_molecules(2 * [water])
    top.mdtop = md.Topology.from_openmm(top.to_openmm())

    pdbfile = openmm.app.PDBFile(get_test_file_path("water-dimer.pdb"))

    positions = pdbfile.positions

    openff_sys = Interchange.from_smirnoff(tip3p, top)
    openff_sys.positions = positions
    openff_sys.box = [10, 10, 10] * unit.nanometer

    omm_energies = get_openmm_energies(
        openff_sys,
        hard_cutoff=True,
        electrostatics=False,
    )

    toolkit_energies = _get_openmm_energies(
        tip3p.create_openmm_system(top),
        openff_sys.box,
        openff_sys.positions,
        hard_cutoff=True,
        electrostatics=False,
    )

    omm_energies.compare(toolkit_energies)

    # TODO: Fix GROMACS energies by handling SETTLE constraints
    # gmx_energies, _ = get_gromacs_energies(openff_sys)
    # compare_gromacs_openmm(omm_energies=omm_energies, gmx_energies=gmx_energies)

    openff_sys["Electrostatics"].method = "cutoff"
    omm_energies_cutoff = get_gromacs_energies(openff_sys)
    lmp_energies = get_lammps_energies(openff_sys)

    lmp_energies.compare(omm_energies_cutoff)
Exemple #12
0
def openff_openmm_pmd_gmx(
    topology: Topology,
    forcefield: ForceField,
    box: ArrayQuantity,
    prefix: str,
) -> None:
    """Pipeline to write GROMACS files from and OpenMM interchange through ParmEd"""
    topology.box_vectors = box.to(
        unit.nanometer).magnitude * omm_unit.nanometer  # type: ignore
    omm_sys = forcefield.create_openmm_system(topology)

    struct = pmd.openmm.load_topology(
        system=omm_sys,
        topology=topology.to_openmm(),
        xyz=topology.topology_molecules[0].reference_molecule.conformers[0],
    )

    # Assign dummy residue names, GROMACS will not accept empty strings
    # TODO: Patch upstream?
    for res in struct.residues:
        res.name = "FOO"

    struct.save(prefix + ".gro")
    struct.save(prefix + ".top")
from openff.toolkit.utils.toolkits import (
    GLOBAL_TOOLKIT_REGISTRY,
    AmberToolsToolkitWrapper,
    RDKitToolkitWrapper,
)

assert RDKitToolkitWrapper().is_available()
assert AmberToolsToolkitWrapper().is_available()

print(GLOBAL_TOOLKIT_REGISTRY.registered_toolkit_versions)

from openff.toolkit.topology import Molecule, Topology
from openff.toolkit.typing.engines.smirnoff import ForceField

offmol = Molecule.from_smiles('CCO')
ff = ForceField('openff-1.0.0.offxml')
ff.create_openmm_system(offmol.to_topology())
Exemple #14
0
def compute_conformer_energies_from_file(filename):
    # Load in the molecule and its conformers.
    # Note that all conformers of the same molecule are loaded as separate Molecule objects
    # If using a OFF Toolkit version before 0.7.0, loading SDFs through RDKit and OpenEye may provide
    # different behavior in some cases. So, here we force loading through RDKit to ensure the correct behavior
    rdktkw = RDKitToolkitWrapper()
    loaded_molecules = Molecule.from_file(filename, toolkit_registry=rdktkw)
    # The logic below only works for lists of molecules, so if a
    # single molecule was loaded, cast it to list
    if type(loaded_molecules) is not list:
        loaded_molecules = [loaded_molecules]
    # Collatate all conformers of the same molecule
    # NOTE: This isn't necessary if you have already loaded or created multi-conformer molecules;
    # it is just needed because our SDF reader does not automatically collapse conformers.
    molecules = [loaded_molecules[0]]
    for molecule in loaded_molecules[1:]:
        if molecule == molecules[-1]:
            for conformer in molecule.conformers:
                molecules[-1].add_conformer(conformer)
        else:
            molecules.append(molecule)

    n_molecules = len(molecules)
    n_conformers = sum([mol.n_conformers for mol in molecules])
    print(
        f'{n_molecules} unique molecule(s) loaded, with {n_conformers} total conformers'
    )

    # Load the openff-1.1.0 force field appropriate for vacuum calculations (without constraints)
    from openff.toolkit.typing.engines.smirnoff import ForceField
    forcefield = ForceField('openff_unconstrained-1.1.0.offxml')
    # Loop over molecules and minimize each conformer
    for molecule in molecules:
        # If the molecule doesn't have a name, set mol.name to be the hill formula
        if molecule.name == '':
            molecule.name = Topology._networkx_to_hill_formula(
                molecule.to_networkx())
            print('%s : %d conformers' %
                  (molecule.name, molecule.n_conformers))
            # Make a temporary copy of the molecule that we can update for each minimization
        mol_copy = Molecule(molecule)
        # Make an OpenFF Topology so we can parameterize the system
        off_top = molecule.to_topology()
        print(
            f"Parametrizing {molecule.name} (may take a moment to calculate charges)"
        )
        system = forcefield.create_openmm_system(off_top)
        # Use OpenMM to compute initial and minimized energy for all conformers
        integrator = openmm.VerletIntegrator(1 * unit.femtoseconds)
        platform = openmm.Platform.getPlatformByName('Reference')
        omm_top = off_top.to_openmm()
        simulation = openmm.app.Simulation(omm_top, system, integrator,
                                           platform)

        # Print text header
        print(
            'Conformer         Initial PE         Minimized PE       RMS between initial and minimized conformer'
        )
        output = [[
            'Conformer', 'Initial PE (kcal/mol)', 'Minimized PE (kcal/mol)',
            'RMS between initial and minimized conformer (Angstrom)'
        ]]
        for conformer_index, conformer in enumerate(molecule.conformers):
            simulation.context.setPositions(conformer)
            orig_potential = simulation.context.getState(
                getEnergy=True).getPotentialEnergy()
            simulation.minimizeEnergy()
            min_state = simulation.context.getState(getEnergy=True,
                                                    getPositions=True)
            min_potential = min_state.getPotentialEnergy()

            # Calculate the RMSD between the initial and minimized conformer
            min_coords = min_state.getPositions()
            min_coords = np.array([[atom.x, atom.y, atom.z]
                                   for atom in min_coords]) * unit.nanometer
            mol_copy._conformers = None
            mol_copy.add_conformer(conformer)
            mol_copy.add_conformer(min_coords)
            rdmol = mol_copy.to_rdkit()
            rmslist = []
            rdMolAlign.AlignMolConformers(rdmol, RMSlist=rmslist)
            minimization_rms = rmslist[0]

            # Save the minimized conformer to file
            mol_copy._conformers = None
            mol_copy.add_conformer(min_coords)
            mol_copy.to_file(
                f'{molecule.name}_conf{conformer_index+1}_minimized.sdf',
                file_format='sdf')
            print(
                '%5d / %5d : %8.3f kcal/mol %8.3f kcal/mol  %8.3f Angstroms' %
                (conformer_index + 1, molecule.n_conformers,
                 orig_potential / unit.kilocalories_per_mole,
                 min_potential / unit.kilocalories_per_mole, minimization_rms))
            output.append([
                str(conformer_index + 1),
                f'{orig_potential/unit.kilocalories_per_mole:.3f}',
                f'{min_potential/unit.kilocalories_per_mole:.3f}',
                f'{minimization_rms:.3f}'
            ])
            # Write the results out to CSV
        with open(f'{molecule.name}.csv', 'w') as of:
            for line in output:
                of.write(','.join(line) + '\n')
                # Clean up OpenMM Simulation
        del simulation, integrator
def test_energies_single_mol(constrained, mol_smi):
    import mbuild as mb

    mol = Molecule.from_smiles(mol_smi)
    mol.generate_conformers(n_conformers=1)
    mol.name = "FOO"
    top = mol.to_topology()
    top.box_vectors = None  # [10, 10, 10] * simtk_unit.nanometer

    if constrained:
        parsley = ForceField("openff-1.0.0.offxml")
    else:
        parsley = ForceField("openff_unconstrained-1.0.0.offxml")

    off_sys = Interchange.from_smirnoff(parsley, top)

    off_sys.handlers["Electrostatics"].method = "cutoff"

    mol.to_file("out.xyz", file_format="xyz")
    compound: mb.Compound = mb.load("out.xyz")
    packed_box: mb.Compound = mb.fill_box(compound=compound,
                                          n_compounds=1,
                                          box=mb.Box(lengths=[10, 10, 10]))

    positions = packed_box.xyz * unit.nanometer
    off_sys.positions = positions

    # Compare directly to toolkit's reference implementation
    omm_energies = get_openmm_energies(off_sys, round_positions=8)
    omm_reference = parsley.create_openmm_system(top)
    reference_energies = _get_openmm_energies(
        omm_sys=omm_reference,
        box_vectors=off_sys.box,
        positions=off_sys.positions,
        round_positions=8,
    )

    omm_energies.compare(reference_energies)

    mdp = "cutoff_hbonds" if constrained else "auto"
    # Compare GROMACS writer and OpenMM export
    gmx_energies = get_gromacs_energies(off_sys, mdp=mdp)

    custom_tolerances = {
        "Bond": 2e-5 * simtk_unit.kilojoule_per_mole,
        "Electrostatics": 2 * simtk_unit.kilojoule_per_mole,
        "vdW": 2 * simtk_unit.kilojoule_per_mole,
        "Nonbonded": 2 * simtk_unit.kilojoule_per_mole,
        "Angle": 1e-4 * simtk_unit.kilojoule_per_mole,
    }

    gmx_energies.compare(
        omm_energies,
        custom_tolerances=custom_tolerances,
    )

    if not constrained:
        other_energies = get_openmm_energies(
            off_sys,
            round_positions=8,
            hard_cutoff=True,
            electrostatics=True,
        )
        lmp_energies = get_lammps_energies(off_sys)
        custom_tolerances = {
            "vdW": 5.0 * simtk_unit.kilojoule_per_mole,
            "Electrostatics": 5.0 * simtk_unit.kilojoule_per_mole,
        }
        lmp_energies.compare(other_energies,
                             custom_tolerances=custom_tolerances)
Exemple #16
0
def simulate(
    force_field: ForceField,
    topology: Topology,
    positions: unit.Quantity,
    box_vectors: Optional[unit.Quantity],
    n_steps: int,
    temperature: unit.Quantity,
    pressure: Optional[unit.Quantity],
    platform: Literal["Reference", "OpenCL", "CUDA", "CPU"] = "Reference",
    output_directory: Optional[str] = None,
):
    """A helper function for simulating a system parameterised with a specific OpenFF
    force field using OpenMM.

    Parameters
    ----------
    force_field
        The force field to apply.
    topology
        The topology detailing the system to simulate.
    positions
        The starting coordinates of the molecules in the system.
    box_vectors
        The box vectors to use. These will overwrite the topology box vectors.
    n_steps
        The number of steps to simulate for.
    temperature
        The temperature to simulate at.
    pressure
        The pressure to simulate at.
    platform
        The platform to simulate using.
    output_directory
        The optional directory to store the simulation outputs in.
    """

    assert pressure is None or (
        pressure is not None and box_vectors is not None
    ), "box vectors must be provided when the pressure is specified."

    topology.box_vectors = box_vectors

    # Create an OpenMM system by applying the parameters to the topology.
    omm_system, topology = force_field.create_openmm_system(
        topology, return_topology=True)

    if output_directory is not None:
        os.makedirs(output_directory, exist_ok=True)

    # Add the virtual sites to the OpenMM topology and positions.
    omm_topology = topology.to_openmm()

    omm_chain = [*omm_topology.chains()][0]
    omm_residue = omm_topology.addResidue("", chain=omm_chain)

    for particle in topology.topology_particles:

        if isinstance(particle, TopologyAtom):
            continue

        omm_topology.addAtom(particle.virtual_site.name,
                             app.Element.getByMass(0), omm_residue)

    positions = numpy.vstack(
        [positions,
         numpy.zeros((topology.n_topology_virtual_sites, 3))])

    with temporary_cd(output_directory):

        __simulate(
            positions=positions * unit.angstrom,
            box_vectors=box_vectors,
            omm_topology=omm_topology,
            omm_system=omm_system,
            n_steps=n_steps,
            temperature=temperature,
            pressure=pressure,
            platform=platform,
        )
Exemple #17
0
def evaluate_water_energy_at_distances(force_field: ForceField,
                                       distances: List[float]) -> List[float]:
    """
    Evaluate the energy of a system of two water molecules at the requested distances using the provided force field.

    Parameters
    ----------
    force_field:
        The openff.toolkit force field object that should be used to parameterise the system.
    distances:
        The list of absolute distances between the oxygen atoms in angstroms.

    Returns
    -------
        A list of energies evaluated at the given distances in kj/mol
    """

    # build the topology
    water = Molecule.from_smiles("O")
    water.generate_conformers(n_conformers=1)
    topology = Topology.from_molecules([water, water])

    # make the openmm system
    omm_system, topology = force_field.create_openmm_system(
        topology, return_topology=True)
    # generate positions at the requested distance
    positions = [
        numpy.vstack([
            water.conformers[0].value_in_unit(unit.angstrom),
            water.conformers[0].value_in_unit(unit.angstrom) +
            numpy.array([x, 0, 0]),
        ]) * unit.angstrom for x in distances
    ]
    # Add the virtual sites to the OpenMM topology and positions.
    omm_topology = topology.to_openmm()
    omm_chain = [*omm_topology.chains()][-1]
    omm_residue = omm_topology.addResidue("", chain=omm_chain)

    for particle in topology.topology_particles:

        if isinstance(particle, TopologyAtom):
            continue

        omm_topology.addAtom(particle.virtual_site.name,
                             app.Element.getByMass(0), omm_residue)

    positions = [
        numpy.vstack(
            [p, numpy.zeros(
                (topology.n_topology_virtual_sites, 3))]) * unit.angstrom
        for p in positions
    ]

    integrator = openmm.LangevinIntegrator(
        300 * unit.kelvin,  # simulation temperature,
        1.0 / unit.picosecond,  # friction
        2.0 * unit.femtoseconds,  # simulation timestep
    )

    platform = openmm.Platform.getPlatformByName("CPU")

    simulation = app.Simulation(omm_topology, omm_system, integrator, platform)

    energies = []
    for i, p in enumerate(positions):
        simulation.context.setPositions(p)
        simulation.context.computeVirtualSites()
        state = simulation.context.getState(getEnergy=True)
        energies.append(state.getPotentialEnergy().value_in_unit(
            unit.kilojoule_per_mole))

    return energies
Exemple #18
0
def system_subset(
    parameter_key: ParameterGradientKey,
    force_field: "ForceField",
    topology: "Topology",
    scale_amount: Optional[float] = None,
) -> Tuple["openmm.System", "simtk_unit.Quantity"]:
    """Produces an OpenMM system containing the minimum number of forces while
    still containing a specified force field parameter, and those other parameters
    which may interact with it (e.g. in the case of vdW parameters).

    The value of the parameter of interest may optionally be perturbed by an amount
    specified by ``scale_amount``.

    Parameters
    ----------
    parameter_key
        The parameter of interest.
    force_field
        The force field to create the system from (and optionally perturb).
    topology
        The topology of the system to apply the force field to.
    scale_amount: float, optional
        The optional amount to perturb the ``parameter`` by such that
        ``parameter = (1.0 + scale_amount) * parameter``.

    Returns
    -------
        The created system as well as the value of the specified ``parameter``.
    """

    # As this method deals mainly with the toolkit, we stick to
    # simtk units here.
    from openff.toolkit.typing.engines.smirnoff import ForceField

    # Create the force field subset.
    force_field_subset = ForceField()

    handlers_to_register = {parameter_key.tag}

    if parameter_key.tag in {"ChargeIncrementModel", "LibraryCharges"}:
        # Make sure to retain all of the electrostatic handlers when dealing with
        # charges as the applied charges will depend on which charges have been applied
        # by previous handlers.
        handlers_to_register.update(
            {"Electrostatics", "ChargeIncrementModel", "LibraryCharges"})

    registered_handlers = force_field.registered_parameter_handlers

    for handler_to_register in handlers_to_register:

        if handler_to_register not in registered_handlers:
            continue

        force_field_subset.register_parameter_handler(
            copy.deepcopy(
                force_field.get_parameter_handler(handler_to_register)))

    handler = force_field_subset.get_parameter_handler(parameter_key.tag)

    parameter = handler.parameters[parameter_key.smirks]
    parameter_value = getattr(parameter, parameter_key.attribute)

    # Optionally perturb the parameter of interest.
    if scale_amount is not None:

        if numpy.isclose(parameter_value.value_in_unit(parameter_value.unit),
                         0.0):
            # Careful thought needs to be given to this. Consider cases such as
            # epsilon or sigma where negative values are not allowed.
            parameter_value = (scale_amount if scale_amount > 0.0 else
                               0.0) * parameter_value.unit
        else:
            parameter_value *= 1.0 + scale_amount

    setattr(parameter, parameter_key.attribute, parameter_value)

    # Create the parameterized sub-system.
    system = force_field_subset.create_openmm_system(topology)
    return system, parameter_value
Exemple #19
0
def test_molecule_and_water_offxml(coumarin, water, tmpdir, rfree_data, parameters):
    """Test that an offxml can parameterize a molecule and water mixture."""

    coumarin_copy = coumarin.copy(deep=True)
    MBISCharges.apply_symmetrisation(coumarin_copy)

    with tmpdir.as_cwd():

        # run the lj method to make sure the parameters match
        alpha = rfree_data.pop("alpha")
        beta = rfree_data.pop("beta")
        lj = LennardJones612(free_parameters=rfree_data, alpha=alpha, beta=beta)
        # get new Rfree data
        lj.run(coumarin_copy)

        # remake rfree
        rfree_data["alpha"] = alpha
        rfree_data["beta"] = beta

        _combine_molecules_offxml(
            molecules=[coumarin_copy],
            parameters=parameters,
            rfree_data=rfree_data,
            filename="combined.offxml",
            water_model="tip3p",
        )

        combinded_ff = ForceField(
            "combined.offxml", load_plugins=True, allow_cosmetic_attributes=True
        )
        mixed_top = Topology.from_molecules(
            molecules=[
                Molecule.from_rdkit(water.to_rdkit()),
                Molecule.from_rdkit(coumarin_copy.to_rdkit()),
            ]
        )
        system = combinded_ff.create_openmm_system(topology=mixed_top)
        # make sure we have 3 constraints
        assert system.getNumConstraints() == 3
        # check each constraint
        reference_constraints = [
            [0, 1, unit.Quantity(0.9572, unit=unit.angstroms)],
            [0, 2, unit.Quantity(0.9572, unit=unit.angstroms)],
            [1, 2, unit.Quantity(1.5139006545247014, unit=unit.angstroms)],
        ]
        for i in range(3):
            a, b, constraint = system.getConstraintParameters(i)
            assert a == reference_constraints[i][0]
            assert b == reference_constraints[i][1]
            assert constraint == reference_constraints[i][2].in_units_of(
                unit.nanometers
            )
        # now loop over the forces and make sure water was correctly parameterised
        forces = dict((force.__class__.__name__, force) for force in system.getForces())
        nonbonded_force: openmm.NonbondedForce = forces["NonbondedForce"]
        # first check water has the correct parameters
        water_reference = [
            [
                unit.Quantity(-0.834, unit.elementary_charge),
                unit.Quantity(3.1507, unit=unit.angstroms),
                unit.Quantity(0.1521, unit=unit.kilocalorie_per_mole),
            ],
            [
                unit.Quantity(0.417, unit.elementary_charge),
                unit.Quantity(1, unit=unit.angstroms),
                unit.Quantity(0, unit=unit.kilocalorie_per_mole),
            ],
            [
                unit.Quantity(0.417, unit.elementary_charge),
                unit.Quantity(1, unit=unit.angstroms),
                unit.Quantity(0, unit=unit.kilocalorie_per_mole),
            ],
        ]
        for i in range(3):
            charge, sigma, epsilon = nonbonded_force.getParticleParameters(i)
            assert charge == water_reference[i][0]
            assert sigma.in_units_of(unit.angstroms) == water_reference[i][1]
            assert (
                epsilon.in_units_of(unit.kilocalorie_per_mole) == water_reference[i][2]
            )

        # now check coumarin
        for i in range(coumarin_copy.n_atoms):
            ref_params = coumarin_copy.NonbondedForce[(i,)]
            charge, sigma, epsilon = nonbonded_force.getParticleParameters(i + 3)
            assert charge.value_in_unit(unit.elementary_charge) == float(
                ref_params.charge
            )
            assert sigma.value_in_unit(unit.nanometers) == ref_params.sigma
            assert epsilon.value_in_unit(unit.kilojoule_per_mole) == ref_params.epsilon
Exemple #20
0
        amber_energy = calc_energy(amber_system, amber_top,
                                   amber_struct.positions)
        print(amber_energy)

        # prepare openff system
        mol = Molecule.from_file(f'{prefix}.mol2')
        #mol = Molecule.from_file('%s.mol2' %(prefix))
        #print(mol.to_smiles())
        from utils import fix_carboxylate_bond_orders
        fix_carboxylate_bond_orders(mol)
        print(mol.to_smiles())
        off_top = mol.to_topology()
        #off_top.box_vectors = [[48, 0, 0], [0, 48, 0], [0, 0, 48]] * unit.angstrom
        #print('off_box', off_top.box_vectors)
        #print('amb_box', pmd_struct.box)
        off_sys = ff.create_openmm_system(mol.to_topology(),
                                          )  #allow_nonintegral_charges=True)
        #nonbonded_force = [force for force in off_sys.getForces() if isinstance(force, openmm.NonbondedForce)][0]
        #nonbonded_force.createExceptionsFromBonds([(bond.atom1.molecule_atom_index,
        #                                            bond.atom2.molecule_atom_index) for bond in mol.bonds],
        #                                          )
        #nonbonded_force.set
        with open('off_sys.xml', 'w') as of:
            of.write(XmlSerializer.serialize(off_sys))
        off_energy = calc_energy(
            off_sys,
            off_top,
            amber_struct.positions,
            #mol.conformers[0]
        )
        print(off_energy)
        off_struct = ParmEd.openmm.load_topology(
Exemple #21
0
class SMIRNOFF(OpenMM):
    """ Derived from Engine object for carrying out OpenMM calculations that use the SMIRNOFF force field. """
    def __init__(self, name="openmm", **kwargs):
        self.valkwd = [
            'ffxml', 'pdb', 'mol2', 'platname', 'precision', 'mmopts',
            'vsite_bonds', 'implicit_solvent', 'restrain_k', 'freeze_atoms'
        ]
        if not toolkit_import_success:
            warn_once(
                "Note: Failed to import the OpenFF Toolkit - SMIRNOFF Engine will not work. "
            )
        super(SMIRNOFF, self).__init__(name=name, **kwargs)

    def readsrc(self, **kwargs):
        """
        SMIRNOFF simulations always require the following passed in via kwargs:

        Parameters
        ----------
        pdb : string
            Name of a .pdb file containing the topology of the system
        mol2 : list
            A list of .mol2 file names containing the molecule/residue templates of the system

        Also provide 1 of the following, containing the coordinates to be used:
        mol : Molecule
            forcebalance.Molecule object
        coords : string
            Name of a file (readable by forcebalance.Molecule)
            This could be the same as the pdb argument from above.
        """

        pdbfnm = None
        # Determine the PDB file name.
        if 'pdb' in kwargs and os.path.exists(kwargs['pdb']):
            # Case 1. The PDB file name is provided explicitly
            pdbfnm = kwargs['pdb']
            if not os.path.exists(pdbfnm):
                logger.error("%s specified but doesn't exist\n" % pdbfnm)
                raise RuntimeError

        if 'mol' in kwargs:
            self.mol = kwargs['mol']
        elif 'coords' in kwargs:
            if not os.path.exists(kwargs['coords']):
                logger.error("%s specified but doesn't exist\n" %
                             kwargs['coords'])
                raise RuntimeError
            self.mol = Molecule(kwargs['coords'])
        else:
            logger.error(
                'Must provide either a molecule object or coordinate file.\n')
            raise RuntimeError

        # Here we cannot distinguish the .mol2 files linked by the target
        # vs. the .mol2 files to be provided by the force field.
        # But we can assume that these files should exist when this function is called.

        self.mol2 = kwargs.get('mol2')
        if self.mol2:
            for fnm in self.mol2:
                if not os.path.exists(fnm):
                    if hasattr(self, 'FF') and fnm in self.FF.fnms: continue
                    logger.error("%s doesn't exist" % fnm)
                    raise RuntimeError
        else:
            logger.error("Must provide a list of .mol2 files.\n")

        if pdbfnm is not None:
            self.abspdb = os.path.abspath(pdbfnm)
            mpdb = Molecule(pdbfnm)
            for i in ["chain", "atomname", "resid", "resname", "elem"]:
                self.mol.Data[i] = mpdb.Data[i]

        # Store a separate copy of the molecule for reference restraint positions.
        self.ref_mol = deepcopy(self.mol)

    @staticmethod
    def _openff_to_openmm_topology(openff_topology):
        """Convert an OpenFF topology to an OpenMM topology. Currently this requires
        manually adding the v-sites as OpenFF currently does not."""

        from openff.toolkit.topology import TopologyAtom

        openmm_topology = openff_topology.to_openmm()

        # Return the topology if the number of OpenMM particles matches the number
        # expected by the OpenFF topology. This may happen if there are no virtual sites
        # in the system OR if a new version of the the OpenFF toolkit includes virtual
        # sites in the OpenMM topology it returns.
        if openmm_topology.getNumAtoms(
        ) == openff_topology.n_topology_particles:
            return openmm_topology

        openmm_chain = openmm_topology.addChain()
        openmm_residue = openmm_topology.addResidue("", chain=openmm_chain)

        for particle in openff_topology.topology_particles:

            if isinstance(particle, TopologyAtom):
                continue

            openmm_topology.addAtom(particle.virtual_site.name,
                                    app.Element.getByMass(0), openmm_residue)

        return openmm_topology

    def prepare(self, pbc=False, mmopts={}, **kwargs):
        """
        Prepare the calculation.  Note that we don't create the
        Simulation object yet, because that may depend on MD
        integrator parameters, thermostat, barostat etc.

        This is mostly copied and modified from openmmio.py's OpenMM.prepare(),
        but we are calling ForceField() from the OpenFF toolkit and ignoring
        AMOEBA stuff.
        """

        if hasattr(self, 'abspdb'):
            self.pdb = PDBFile(self.abspdb)
        else:
            pdb1 = "%s-1.pdb" % os.path.splitext(os.path.basename(
                self.mol.fnm))[0]
            self.mol[0].write(pdb1)
            self.pdb = PDBFile(pdb1)
            os.unlink(pdb1)

        # Create the OpenFF ForceField object.
        if hasattr(self, 'FF'):
            self.offxml = [self.FF.offxml]
            self.forcefield = self.FF.openff_forcefield
        else:
            self.offxml = listfiles(kwargs.get('offxml'), 'offxml', err=True)
            self.forcefield = OpenFF_ForceField(*self.offxml,
                                                load_plugins=True)

        ## Load mol2 files for smirnoff topology
        openff_mols = []
        for fnm in self.mol2:
            try:
                mol = OffMolecule.from_file(fnm)
            except Exception as e:
                logger.error("Error when loading %s" % fnm)
                raise e
            openff_mols.append(mol)
        self.off_topology = OffTopology.from_openmm(
            self.pdb.topology, unique_molecules=openff_mols)

        ## OpenMM options for setting up the System.
        self.mmopts = dict(mmopts)

        ## Specify frozen atoms and restraint force constant
        if 'restrain_k' in kwargs:
            self.restrain_k = kwargs['restrain_k']
        if 'freeze_atoms' in kwargs:
            self.freeze_atoms = kwargs['freeze_atoms'][:]

        ## Set system options from ForceBalance force field options.
        fftmp = False
        if hasattr(self, 'FF'):
            self.mmopts['rigidWater'] = self.FF.rigid_water
            if not all([os.path.exists(f) for f in self.FF.fnms]):
                # If the parameter files don't already exist, create them for the purpose of
                # preparing the engine, but then delete them afterward.
                fftmp = True
                self.FF.make(np.zeros(self.FF.np))

        ## Set system options from periodic boundary conditions.
        self.pbc = pbc
        ## print warning for 'nonbonded_cutoff' keywords
        if 'nonbonded_cutoff' in kwargs:
            logger.warning(
                "nonbonded_cutoff keyword ignored because it's set in the offxml file\n"
            )

        # Apply the FF parameters to the system. Currently this is the only way to
        # determine if the FF will apply virtual sites to the system.
        _, openff_topology = self.forcefield.create_openmm_system(
            self.off_topology, return_topology=True)

        ## Generate OpenMM-compatible positions
        self.xyz_omms = []

        for I in range(len(self.mol)):
            xyz = self.mol.xyzs[I]
            xyz_omm = ([Vec3(i[0], i[1], i[2]) for i in xyz]
                       # Add placeholder positions for an v-sites.
                       + [Vec3(0.0, 0.0, 0.0)] *
                       openff_topology.n_topology_virtual_sites) * angstrom

            if self.pbc:
                # Obtain the periodic box
                if self.mol.boxes[I].alpha != 90.0 or self.mol.boxes[
                        I].beta != 90.0 or self.mol.boxes[I].gamma != 90.0:
                    logger.error('OpenMM cannot handle nonorthogonal boxes.\n')
                    raise RuntimeError
                box_omm = np.diag([
                    self.mol.boxes[I].a, self.mol.boxes[I].b,
                    self.mol.boxes[I].c
                ]) * angstrom
            else:
                box_omm = None
            # Finally append it to list.
            self.xyz_omms.append((xyz_omm, box_omm))

        # used in create_simulation()
        openmm_topology = SMIRNOFF._openff_to_openmm_topology(openff_topology)
        openmm_positions = (
            self.pdb.positions.value_in_unit(angstrom) +
            # Add placeholder positions for an v-sites.
            [Vec3(0.0, 0.0, 0.0)] *
            openff_topology.n_topology_virtual_sites) * angstrom

        self.mod = Modeller(openmm_topology, openmm_positions)

        ## Build a topology and atom lists.
        Top = self.mod.getTopology()
        Atoms = list(Top.atoms())

        # vss = [(i, [system.getVirtualSite(i).getParticle(j) for j in range(system.getVirtualSite(i).getNumParticles())]) \
        #            for i in range(system.getNumParticles()) if system.isVirtualSite(i)]
        self.AtomLists = defaultdict(list)
        self.AtomLists['Mass'] = [
            a.element.mass.value_in_unit(dalton)
            if a.element is not None else 0 for a in Atoms
        ]
        self.AtomLists['ParticleType'] = [
            'A' if m >= 1.0 else 'D' for m in self.AtomLists['Mass']
        ]
        self.AtomLists['ResidueNumber'] = [a.residue.index for a in Atoms]
        self.AtomMask = [a == 'A' for a in self.AtomLists['ParticleType']]
        self.realAtomIdxs = [
            i for i, a in enumerate(self.AtomMask) if a is True
        ]
        if hasattr(self, 'FF') and fftmp:
            for f in self.FF.fnms:
                os.unlink(f)

    def update_simulation(self, **kwargs):
        """
        Create the simulation object, or update the force field
        parameters in the existing simulation object.  This should be
        run when we write a new force field XML file.
        """
        if len(kwargs) > 0:
            self.simkwargs = kwargs

        # Because self.forcefield is being updated in forcebalance.forcefield.FF.make()
        # there is no longer a need to create a new force field object here.
        try:
            self.system, openff_topology = self.forcefield.create_openmm_system(
                self.off_topology, return_topology=True)
        except Exception as error:
            logger.error("Error when creating system for %s" % self.mol2)
            raise error
        # Commenting out all virtual site stuff for now.
        # self.vsinfo = PrepareVirtualSites(self.system)
        self.nbcharges = np.zeros(self.system.getNumParticles())

        #----
        # If the virtual site parameters have changed,
        # the simulation object must be remade.
        #----
        # vsprm = GetVirtualSiteParameters(self.system)
        # if hasattr(self,'vsprm') and len(self.vsprm) > 0 and np.max(np.abs(vsprm - self.vsprm)) != 0.0:
        #     if hasattr(self, 'simulation'):
        #         delattr(self, 'simulation')
        # self.vsprm = vsprm.copy()

        if openff_topology.n_topology_virtual_sites > 0:
            # For now always assume that the v-sites have changed. This is currently
            # needed as the FB checks don't support the ``LocalCoordinatesSite`` based
            # virtual sites that OpenFF uses.
            if hasattr(self, 'simulation'):
                delattr(self, 'simulation')

        if hasattr(self, 'simulation'):
            UpdateSimulationParameters(self.system, self.simulation)
        else:
            self.create_simulation(**self.simkwargs)

    def _update_positions(self, X1, disable_vsite):
        # X1 is a numpy ndarray not vec3

        if disable_vsite:
            super(SMIRNOFF, self)._update_positions(X1, disable_vsite)
            return

        n_v_sites = (self.mod.getTopology().getNumAtoms() -
                     self.pdb.topology.getNumAtoms())

        # Add placeholder positions for an v-sites.
        if isinstance(X1, np.ndarray):
            X1 = numpy.vstack([X1, np.zeros((n_v_sites, 3))]) * angstrom
        else:
            X1 = (X1 + [Vec3(0.0, 0.0, 0.0)] * n_v_sites) * angstrom

        self.simulation.context.setPositions(X1)
        self.simulation.context.computeVirtualSites()

    def interaction_energy(self, fraga, fragb):
        """
        Calculate the interaction energy for two fragments.
        Because this creates two new objects and requires passing in the mol2 argument,
        the codes are copied and modified from the OpenMM class.
        """

        self.update_simulation()

        if self.name == 'A' or self.name == 'B':
            logger.error("Don't name the engine A or B!\n")
            raise RuntimeError

        # Create two subengines.
        if hasattr(self, 'target'):
            if not hasattr(self, 'A'):
                self.A = SMIRNOFF(name="A",
                                  mol=self.mol.atom_select(fraga),
                                  mol2=self.mol2,
                                  target=self.target)
            if not hasattr(self, 'B'):
                self.B = SMIRNOFF(name="B",
                                  mol=self.mol.atom_select(fragb),
                                  mol2=self.mol2,
                                  target=self.target)
        else:
            if not hasattr(self, 'A'):
                self.A = SMIRNOFF(name="A", mol=self.mol.atom_select(fraga), mol2=self.mol2, platname=self.platname, \
                                  precision=self.precision, offxml=self.offxml, mmopts=self.mmopts)
            if not hasattr(self, 'B'):
                self.B = SMIRNOFF(name="B", mol=self.mol.atom_select(fragb), mol2=self.mol2, platname=self.platname, \
                                  precision=self.precision, offxml=self.offxml, mmopts=self.mmopts)

        # Interaction energy needs to be in kcal/mol.
        D = self.energy()
        A = self.A.energy()
        B = self.B.energy()

        return (D - A - B) / 4.184

    def get_smirks_counter(self):
        """Get a counter for the time of appreance of each SMIRKS"""
        smirks_counter = Counter()
        molecule_force_list = self.forcefield.label_molecules(
            self.off_topology)
        for mol_idx, mol_forces in enumerate(molecule_force_list):
            for force_tag, force_dict in mol_forces.items():
                # e.g. force_tag = 'Bonds'
                for parameters in force_dict.values():

                    if not isinstance(parameters, list):
                        parameters = [parameters]

                    for parameter in parameters:
                        smirks_counter[parameter.smirks] += 1

        return smirks_counter
Exemple #22
0
def reparm(ligands, base):
    print(
        '**Running reparameterization of ligand(s) using open force fields\'s SMIRNOFF with openff 2.0.0**'
    )
    # Load already parm'd system
    in_prmtop = base + '.prmtop'
    in_crd = base + '.inpcrd'

    # Create parmed strucuture
    orig_structure = parmed.amber.AmberParm(in_prmtop, in_crd)

    # Split orig_stucuture into unique structure instances e.g. protein, water, ligand, etc.
    pieces = orig_structure.split()
    for piece in pieces:
        # TODO: Figure out how to know which piece is which
        print(f"There are {len(piece[1])} instance(s) of {piece[0]}")

    # Generate an openff topology for the ligand
    # Openff Molecule does not support mol2 so conversion is needed
    ligs_w_sdf = []
    for ligand in ligands:
        obabel[ligand[0], '-O', util.get_base(ligand[0]) + '.sdf']()
        ligs_w_sdf.append(
            (ligand[0], ligand[1], util.get_base(ligand[0]) + '.sdf'))

    # Keep track of ligands that were successfully reparmed so we know to skip them when putting the pieces back together
    reparmed_pieces = []
    complex_structure = parmed.Structure()
    force_field = ForceField("openff_unconstrained-2.0.0.offxml")

    for lig in ligs_w_sdf:
        # Set up openff topology
        ligand_off_molecule = Molecule(lig[2])
        ligand_pdbfile = PDBFile(lig[0])
        ligand_off_topology = Topology.from_openmm(
            ligand_pdbfile.topology,
            unique_molecules=[ligand_off_molecule],
        )
        # Parameterizing the ligand
        # Find ligand "piece", reparm, add to the new structure
        for piece in pieces:
            new_ligand_structure = None
            # TODO: Figure out how to know which piece is which
            if (ligand_off_molecule.n_atoms == len(piece[0].atoms)):
                if (ligand_off_molecule.n_bonds == len(piece[0].bonds)):
                    if ([
                            atom.atomic_number
                            for atom in ligand_off_molecule.atoms
                    ] == [atom.element for atom in piece[0].atoms]):
                        print('Found ligand piece', piece)
                        try:
                            # Since the method of matching the piece to ligand is imperfect, ligands that are isomers could mess things up.
                            # So try any piece that matches and see if we get an error
                            print('Reparameterizing ligand using SMIRNOFF')
                            ligand_system = force_field.create_openmm_system(
                                ligand_off_topology)
                            new_ligand_structure = parmed.openmm.load_topology(
                                ligand_off_topology.to_openmm(),
                                ligand_system,
                                xyz=piece[0].positions,
                            )
                            # A quick check to make sure things were not messed up during param
                            if check_discrepencies(new_ligand_structure,
                                                   piece):
                                # Add the newly parameterized ligand the complex structure
                                reparmed_pieces.append(piece)
                                new_ligand_structure *= len(piece[1])
                                complex_structure += parmed.amber.AmberParm.from_structure(
                                    new_ligand_structure)
                            break
                        except:
                            pass

    # Stick all the pieces back together
    for piece in pieces:
        if (piece not in reparmed_pieces):
            curr_structure = parmed.Structure()
            curr_structure += piece[0]
            curr_structure *= len(piece[1])
            complex_structure += parmed.amber.AmberParm.from_structure(
                curr_structure)

    # print("Unique atom names:",sorted(list({atom.atom_type.name for atom in complex_structure})),)
    # print("Number of unique atom types:", len({atom.atom_type for atom in complex_structure}))
    # print("Number of unique epsilons:", len({atom.epsilon for atom in complex_structure}))
    # print("Number of unique sigmas:", len({atom.sigma for atom in complex_structure}))

    # # Copy over the original coordinates and box vectors
    complex_structure.coordinates = orig_structure.coordinates
    complex_structure.box_vectors = orig_structure.box_vectors

    # Save the newly parameterized system
    complex_structure.save(base + ".prmtop", overwrite=True)
    complex_structure.save(base + ".inpcrd", overwrite=True)
def test_packmol_boxes(toolkit_file_path):
    # TODO: Isolate a set of systems here instead of using toolkit data
    # TODO: Fix nonbonded energy differences
    from openff.toolkit.utils import get_data_file_path

    pdb_file_path = get_data_file_path(toolkit_file_path)
    pdbfile = openmm.app.PDBFile(pdb_file_path)

    ethanol = Molecule.from_smiles("CCO")
    cyclohexane = Molecule.from_smiles("C1CCCCC1")
    omm_topology = pdbfile.topology
    off_topology = OFFBioTop.from_openmm(
        omm_topology, unique_molecules=[ethanol, cyclohexane])
    off_topology.mdtop = md.Topology.from_openmm(omm_topology)

    parsley = ForceField("openff_unconstrained-1.0.0.offxml")

    off_sys = Interchange.from_smirnoff(parsley, off_topology)

    off_sys.box = np.asarray(
        pdbfile.topology.getPeriodicBoxVectors().value_in_unit(
            simtk_unit.nanometer))
    off_sys.positions = pdbfile.positions

    sys_from_toolkit = parsley.create_openmm_system(off_topology)

    omm_energies = get_openmm_energies(off_sys,
                                       hard_cutoff=True,
                                       electrostatics=False)
    reference = _get_openmm_energies(
        sys_from_toolkit,
        off_sys.box,
        off_sys.positions,
        hard_cutoff=True,
        electrostatics=False,
    )

    omm_energies.compare(
        reference,
        custom_tolerances={
            "Electrostatics": 2e-2 * simtk_unit.kilojoule_per_mole,
        },
    )

    # custom_tolerances={"HarmonicBondForce": 1.0}

    # Compare GROMACS writer and OpenMM export
    gmx_energies = get_gromacs_energies(off_sys, electrostatics=False)

    omm_energies_rounded = get_openmm_energies(
        off_sys,
        round_positions=8,
        hard_cutoff=True,
        electrostatics=False,
    )

    omm_energies_rounded.compare(
        other=gmx_energies,
        custom_tolerances={
            "Angle": 1e-2 * simtk_unit.kilojoule_per_mole,
            "Torsion": 1e-2 * simtk_unit.kilojoule_per_mole,
            "Electrostatics": 3200 * simtk_unit.kilojoule_per_mole,
        },
    )
Exemple #24
0
    def serialise_system(self):
        """Create the OpenMM system; parametrise using frost; serialise the system."""

        # Create an openFF molecule from the rdkit molecule
        off_molecule = Molecule.from_rdkit(self.molecule.to_rdkit(),
                                           allow_undefined_stereo=True)

        # Make the OpenMM system
        off_topology = off_molecule.to_topology()

        forcefield = ForceField("openff_unconstrained-1.3.0.offxml")

        try:
            # Parametrise the topology and create an OpenMM System.
            system = forcefield.create_openmm_system(off_topology)
        except (
                UnassignedValenceParameterException,
                UnassignedBondParameterException,
                UnassignedProperTorsionParameterException,
                UnassignedAngleParameterException,
                UnassignedMoleculeChargeException,
                TypeError,
        ):
            # If this does not work then we have a molecule that is not in SMIRNOFF so we must add generics
            # and remove the charge handler to get some basic parameters for the moleucle
            new_bond = BondHandler.BondType(
                smirks="[*:1]~[*:2]",
                length="0 * angstrom",
                k="0.0 * angstrom**-2 * mole**-1 * kilocalorie",
            )
            new_angle = AngleHandler.AngleType(
                smirks="[*:1]~[*:2]~[*:3]",
                angle="0.0 * degree",
                k="0.0 * mole**-1 * radian**-2 * kilocalorie",
            )
            new_torsion = ProperTorsionHandler.ProperTorsionType(
                smirks="[*:1]~[*:2]~[*:3]~[*:4]",
                periodicity1="1",
                phase1="0.0 * degree",
                k1="0.0 * mole**-1 * kilocalorie",
                periodicity2="2",
                phase2="180.0 * degree",
                k2="0.0 * mole**-1 * kilocalorie",
                periodicity3="3",
                phase3="0.0 * degree",
                k3="0.0 * mole**-1 * kilocalorie",
                periodicity4="4",
                phase4="180.0 * degree",
                k4="0.0 * mole**-1 * kilocalorie",
                idivf1="1.0",
                idivf2="1.0",
                idivf3="1.0",
                idivf4="1.0",
            )
            new_vdw = vdWHandler.vdWType(
                smirks="[*:1]",
                epsilon=0 * unit.kilocalories_per_mole,
                sigma=0 * unit.angstroms,
            )
            new_generics = {
                "Bonds": new_bond,
                "Angles": new_angle,
                "ProperTorsions": new_torsion,
                "vdW": new_vdw,
            }
            for key, val in new_generics.items():
                forcefield.get_parameter_handler(key).parameters.insert(0, val)
            # This has to be removed as sqm will fail with unknown elements
            del forcefield._parameter_handlers["ToolkitAM1BCC"]
            del forcefield._parameter_handlers["Electrostatics"]
            # Parametrize the topology and create an OpenMM System.
            system = forcefield.create_openmm_system(off_topology)
            # This will tag the molecule so run.py knows that generics have been used.
            self.fftype = "generics"
        # Serialise the OpenMM system into the xml file
        with open("serialised.xml", "w+") as out:
            out.write(XmlSerializer.serializeSystem(system))
Exemple #25
0
class SMIRNOFF(OpenMM):
    """ Derived from Engine object for carrying out OpenMM calculations that use the SMIRNOFF force field. """
    def __init__(self, name="openmm", **kwargs):
        self.valkwd = [
            'ffxml', 'pdb', 'mol2', 'platname', 'precision', 'mmopts',
            'vsite_bonds', 'implicit_solvent', 'restrain_k', 'freeze_atoms'
        ]
        if not toolkit_import_success:
            warn_once(
                "Note: Failed to import the OpenFF Toolkit - SMIRNOFF Engine will not work. "
            )
        super(SMIRNOFF, self).__init__(name=name, **kwargs)

    def readsrc(self, **kwargs):
        """
        SMIRNOFF simulations always require the following passed in via kwargs:

        Parameters
        ----------
        pdb : string
            Name of a .pdb file containing the topology of the system
        mol2 : list
            A list of .mol2 file names containing the molecule/residue templates of the system

        Also provide 1 of the following, containing the coordinates to be used:
        mol : Molecule
            forcebalance.Molecule object
        coords : string
            Name of a file (readable by forcebalance.Molecule)
            This could be the same as the pdb argument from above.
        """

        pdbfnm = kwargs.get('pdb')
        # Determine the PDB file name.
        if not pdbfnm:
            raise RuntimeError('Name of PDB file not provided.')
        elif not os.path.exists(pdbfnm):
            logger.error("%s specified but doesn't exist\n" % pdbfnm)
            raise RuntimeError

        if 'mol' in kwargs:
            self.mol = kwargs['mol']
        elif 'coords' in kwargs:
            if not os.path.exists(kwargs['coords']):
                logger.error("%s specified but doesn't exist\n" %
                             kwargs['coords'])
                raise RuntimeError
            self.mol = Molecule(kwargs['coords'])
        else:
            logger.error(
                'Must provide either a molecule object or coordinate file.\n')
            raise RuntimeError

        # Here we cannot distinguish the .mol2 files linked by the target
        # vs. the .mol2 files to be provided by the force field.
        # But we can assume that these files should exist when this function is called.

        self.mol2 = kwargs.get('mol2')
        if self.mol2:
            for fnm in self.mol2:
                if not os.path.exists(fnm):
                    if hasattr(self, 'FF') and fnm in self.FF.fnms: continue
                    logger.error("%s doesn't exist" % fnm)
                    raise RuntimeError
        else:
            logger.error("Must provide a list of .mol2 files.\n")

        self.abspdb = os.path.abspath(pdbfnm)
        mpdb = Molecule(pdbfnm)
        for i in ["chain", "atomname", "resid", "resname", "elem"]:
            self.mol.Data[i] = mpdb.Data[i]

        # Store a separate copy of the molecule for reference restraint positions.
        self.ref_mol = deepcopy(self.mol)

    def prepare(self, pbc=False, mmopts={}, **kwargs):
        """
        Prepare the calculation.  Note that we don't create the
        Simulation object yet, because that may depend on MD
        integrator parameters, thermostat, barostat etc.

        This is mostly copied and modified from openmmio.py's OpenMM.prepare(),
        but we are calling ForceField() from the OpenFF toolkit and ignoring
        AMOEBA stuff.
        """
        self.pdb = PDBFile(self.abspdb)

        # Create the OpenFF ForceField object.
        if hasattr(self, 'FF'):
            self.offxml = [self.FF.offxml]
            self.forcefield = self.FF.openff_forcefield
        else:
            self.offxml = listfiles(kwargs.get('offxml'), 'offxml', err=True)
            self.forcefield = OpenFF_ForceField(*self.offxml)

        ## Load mol2 files for smirnoff topology
        openff_mols = []
        for fnm in self.mol2:
            try:
                mol = OffMolecule.from_file(fnm)
            except Exception as e:
                logger.error("Error when loading %s" % fnm)
                raise e
            openff_mols.append(mol)
        self.off_topology = OffTopology.from_openmm(
            self.pdb.topology, unique_molecules=openff_mols)

        # used in create_simulation()
        self.mod = Modeller(self.pdb.topology, self.pdb.positions)

        ## OpenMM options for setting up the System.
        self.mmopts = dict(mmopts)

        ## Specify frozen atoms and restraint force constant
        if 'restrain_k' in kwargs:
            self.restrain_k = kwargs['restrain_k']
        if 'freeze_atoms' in kwargs:
            self.freeze_atoms = kwargs['freeze_atoms'][:]

        ## Set system options from ForceBalance force field options.
        fftmp = False
        if hasattr(self, 'FF'):
            self.mmopts['rigidWater'] = self.FF.rigid_water
            if not all([os.path.exists(f) for f in self.FF.fnms]):
                # If the parameter files don't already exist, create them for the purpose of
                # preparing the engine, but then delete them afterward.
                fftmp = True
                self.FF.make(np.zeros(self.FF.np))

        ## Set system options from periodic boundary conditions.
        self.pbc = pbc
        ## print warning for 'nonbonded_cutoff' keywords
        if 'nonbonded_cutoff' in kwargs:
            logger.warning(
                "nonbonded_cutoff keyword ignored because it's set in the offxml file\n"
            )

        ## Generate OpenMM-compatible positions
        self.xyz_omms = []
        for I in range(len(self.mol)):
            position = self.mol.xyzs[I] * angstrom
            # xyz_omm = [Vec3(i[0],i[1],i[2]) for i in xyz]*angstrom
            # An extra step with adding virtual particles
            # mod = Modeller(self.pdb.topology, xyz_omm)
            # LPW commenting out because we don't have virtual sites yet.
            # mod.addExtraParticles(self.forcefield)
            if self.pbc:
                # Obtain the periodic box
                if self.mol.boxes[I].alpha != 90.0 or self.mol.boxes[
                        I].beta != 90.0 or self.mol.boxes[I].gamma != 90.0:
                    logger.error('OpenMM cannot handle nonorthogonal boxes.\n')
                    raise RuntimeError
                box_omm = np.diag([
                    self.mol.boxes[I].a, self.mol.boxes[I].b,
                    self.mol.boxes[I].c
                ]) * angstrom
            else:
                box_omm = None
            # Finally append it to list.
            self.xyz_omms.append((position, box_omm))

        ## Build a topology and atom lists.
        Top = self.pdb.topology
        Atoms = list(Top.atoms())
        Bonds = [(a.index, b.index) for a, b in list(Top.bonds())]

        # vss = [(i, [system.getVirtualSite(i).getParticle(j) for j in range(system.getVirtualSite(i).getNumParticles())]) \
        #            for i in range(system.getNumParticles()) if system.isVirtualSite(i)]
        self.AtomLists = defaultdict(list)
        self.AtomLists['Mass'] = [
            a.element.mass.value_in_unit(dalton)
            if a.element is not None else 0 for a in Atoms
        ]
        self.AtomLists['ParticleType'] = [
            'A' if m >= 1.0 else 'D' for m in self.AtomLists['Mass']
        ]
        self.AtomLists['ResidueNumber'] = [a.residue.index for a in Atoms]
        self.AtomMask = [a == 'A' for a in self.AtomLists['ParticleType']]
        self.realAtomIdxs = [
            i for i, a in enumerate(self.AtomMask) if a is True
        ]
        if hasattr(self, 'FF') and fftmp:
            for f in self.FF.fnms:
                os.unlink(f)

    def update_simulation(self, **kwargs):
        """
        Create the simulation object, or update the force field
        parameters in the existing simulation object.  This should be
        run when we write a new force field XML file.
        """
        if len(kwargs) > 0:
            self.simkwargs = kwargs

        # Because self.forcefield is being updated in forcebalance.forcefield.FF.make()
        # there is no longer a need to create a new force field object here.
        try:
            self.system = self.forcefield.create_openmm_system(
                self.off_topology)
        except Exception as error:
            logger.error("Error when creating system for %s" % self.mol2)
            raise error
        # Commenting out all virtual site stuff for now.
        # self.vsinfo = PrepareVirtualSites(self.system)
        self.nbcharges = np.zeros(self.system.getNumParticles())

        #----
        # If the virtual site parameters have changed,
        # the simulation object must be remade.
        #----
        # vsprm = GetVirtualSiteParameters(self.system)
        # if hasattr(self,'vsprm') and len(self.vsprm) > 0 and np.max(np.abs(vsprm - self.vsprm)) != 0.0:
        #     if hasattr(self, 'simulation'):
        #         delattr(self, 'simulation')
        # self.vsprm = vsprm.copy()

        if hasattr(self, 'simulation'):
            UpdateSimulationParameters(self.system, self.simulation)
        else:
            self.create_simulation(**self.simkwargs)

    def optimize(self, shot=0, align=True, crit=1e-4):
        return super(SMIRNOFF, self).optimize(shot=shot,
                                              align=align,
                                              crit=crit,
                                              disable_vsite=True)

    def interaction_energy(self, fraga, fragb):
        """
        Calculate the interaction energy for two fragments.
        Because this creates two new objects and requires passing in the mol2 argument,
        the codes are copied and modified from the OpenMM class.
        """

        self.update_simulation()

        if self.name == 'A' or self.name == 'B':
            logger.error("Don't name the engine A or B!\n")
            raise RuntimeError

        # Create two subengines.
        if hasattr(self, 'target'):
            if not hasattr(self, 'A'):
                self.A = SMIRNOFF(name="A",
                                  mol=self.mol.atom_select(fraga),
                                  mol2=self.mol2,
                                  target=self.target)
            if not hasattr(self, 'B'):
                self.B = SMIRNOFF(name="B",
                                  mol=self.mol.atom_select(fragb),
                                  mol2=self.mol2,
                                  target=self.target)
        else:
            if not hasattr(self, 'A'):
                self.A = SMIRNOFF(name="A", mol=self.mol.atom_select(fraga), mol2=self.mol2, platname=self.platname, \
                                  precision=self.precision, offxml=self.offxml, mmopts=self.mmopts)
            if not hasattr(self, 'B'):
                self.B = SMIRNOFF(name="B", mol=self.mol.atom_select(fragb), mol2=self.mol2, platname=self.platname, \
                                  precision=self.precision, offxml=self.offxml, mmopts=self.mmopts)

        # Interaction energy needs to be in kcal/mol.
        D = self.energy()
        A = self.A.energy()
        B = self.B.energy()

        return (D - A - B) / 4.184

    def get_smirks_counter(self):
        """Get a counter for the time of appreance of each SMIRKS"""
        smirks_counter = Counter()
        molecule_force_list = self.forcefield.label_molecules(
            self.off_topology)
        for mol_idx, mol_forces in enumerate(molecule_force_list):
            for force_tag, force_dict in mol_forces.items():
                # e.g. force_tag = 'Bonds'
                for parameter in force_dict.values():
                    smirks_counter[parameter.smirks] += 1
        return smirks_counter
Exemple #26
0
    def _build_system(self,
                      molecule: "Ligand",
                      input_files: Optional[List[str]] = None) -> System:
        """Create the OpenMM system; parametrise using frost; serialise the system."""

        # Create an openFF molecule from the rdkit molecule, we always have hydrogen by this point
        off_molecule = Molecule.from_rdkit(
            molecule.to_rdkit(),
            allow_undefined_stereo=True,
            hydrogens_are_explicit=True,
        )

        # Make the OpenMM system
        off_topology = off_molecule.to_topology()

        forcefield = ForceField(self.force_field)
        # we need to remove the constraints
        if "Constraints" in forcefield._parameter_handlers:
            del forcefield._parameter_handlers["Constraints"]

        try:
            # Parametrise the topology and create an OpenMM System.
            system = forcefield.create_openmm_system(off_topology, )
        except (
                UnassignedValenceParameterException,
                UnassignedBondParameterException,
                UnassignedProperTorsionParameterException,
                UnassignedAngleParameterException,
                UnassignedMoleculeChargeException,
                TypeError,
        ):
            # If this does not work then we have a molecule that is not in SMIRNOFF so we must add generics
            # and remove the charge handler to get some basic parameters for the moleucle
            new_bond = BondHandler.BondType(
                smirks="[*:1]~[*:2]",
                length="0 * angstrom",
                k="0.0 * angstrom**-2 * mole**-1 * kilocalorie",
            )
            new_angle = AngleHandler.AngleType(
                smirks="[*:1]~[*:2]~[*:3]",
                angle="0.0 * degree",
                k="0.0 * mole**-1 * radian**-2 * kilocalorie",
            )
            new_torsion = ProperTorsionHandler.ProperTorsionType(
                smirks="[*:1]~[*:2]~[*:3]~[*:4]",
                periodicity1="1",
                phase1="0.0 * degree",
                k1="0.0 * mole**-1 * kilocalorie",
                periodicity2="2",
                phase2="180.0 * degree",
                k2="0.0 * mole**-1 * kilocalorie",
                periodicity3="3",
                phase3="0.0 * degree",
                k3="0.0 * mole**-1 * kilocalorie",
                periodicity4="4",
                phase4="180.0 * degree",
                k4="0.0 * mole**-1 * kilocalorie",
                idivf1="1.0",
                idivf2="1.0",
                idivf3="1.0",
                idivf4="1.0",
            )
            new_vdw = vdWHandler.vdWType(
                smirks="[*:1]",
                epsilon=0 * unit.kilocalories_per_mole,
                sigma=0 * unit.angstroms,
            )
            new_generics = {
                "Bonds": new_bond,
                "Angles": new_angle,
                "ProperTorsions": new_torsion,
                "vdW": new_vdw,
            }
            for key, val in new_generics.items():
                forcefield.get_parameter_handler(key).parameters.insert(0, val)
            # This has to be removed as sqm will fail with unknown elements
            del forcefield._parameter_handlers["ToolkitAM1BCC"]
            del forcefield._parameter_handlers["Electrostatics"]
            # Parametrize the topology and create an OpenMM System.
            system = forcefield.create_openmm_system(off_topology)
        return system