Exemple #1
0
    def get_parameter_handler_from_forcefield(self, parameter_handler_name,
                                              forcefield):
        """
        It returns a parameter handler from the forcefield based on its
        name.

        Parameters
        ----------
        parameter_handler_name : str
            The name of the parameter handler that is requested
        forcefield : an openforcefield.typing.engines.smirnoff.ForceField
                     object
            The forcefield from which the parameter handler will be obtained

        Returns
        -------
        parameter_handler : an openforcefield.typing.engines.smirnoff.parameters.ParameterHandler
                            object
            The ParameterHandler that was requested
        """
        from openff.toolkit.typing.engines.smirnoff import ForceField

        if isinstance(forcefield, str):
            forcefield = ForceField(forcefield)
        elif isinstance(forcefield, ForceField):
            pass
        else:
            raise Exception('Invalid forcefield type')

        return forcefield.get_parameter_handler(parameter_handler_name)
Exemple #2
0
def test_force_field_custom_handler(mock_entry_point_plugins):
    """Tests a force field can make use of a custom parameter handler registered
    through the entrypoint plugin system.
    """

    # Construct a simple FF which only uses the custom handler.
    force_field_contents = "\n".join([
        "<?xml version='1.0' encoding='ASCII'?>",
        "<SMIRNOFF version='0.3' aromaticity_model='OEAroModel_MDL'>",
        "  <CustomHandler version='0.3'></CustomHandler>",
        "</SMIRNOFF>",
    ])

    # An exception should be raised when plugins aren't allowed.
    with pytest.raises(KeyError) as error_info:
        ForceField(force_field_contents)

    assert (
        "Cannot find a registered parameter handler class for tag 'CustomHandler'"
        in error_info.value.args[0])

    # Otherwise the FF should be created as expected.
    force_field = ForceField(force_field_contents, load_plugins=True)

    parameter_handler = force_field.get_parameter_handler("CustomHandler")
    assert parameter_handler is not None
    assert parameter_handler.__class__.__name__ == "CustomHandler"
def test_openmm_nonbonded_methods(inputs):
    """See test_nonbonded_method_resolution in openff/toolkit/tests/test_forcefield.py"""
    vdw_method = inputs["vdw_method"]
    electrostatics_method = inputs["electrostatics_method"]
    periodic = inputs["periodic"]
    result = inputs["result"]

    molecules = [create_ethanol()]
    forcefield = ForceField("test_forcefields/test_forcefield.offxml")

    pdbfile = app.PDBFile(get_data_file_path("systems/test_systems/1_ethanol.pdb"))
    topology = Topology.from_openmm(pdbfile.topology, unique_molecules=molecules)

    if not periodic:
        topology.box_vectors = None

    if type(result) == int:
        nonbonded_method = result
        # The method is validated and may raise an exception if it's not supported.
        forcefield.get_parameter_handler("vdW", {}).method = vdw_method
        forcefield.get_parameter_handler(
            "Electrostatics", {}
        ).method = electrostatics_method
        openff_interchange = Interchange.from_smirnoff(
            force_field=forcefield, topology=topology
        )
        openmm_system = openff_interchange.to_openmm(combine_nonbonded_forces=True)
        for force in openmm_system.getForces():
            if isinstance(force, openmm.NonbondedForce):
                assert force.getNonbondedMethod() == nonbonded_method
                break
        else:
            raise Exception
    elif issubclass(result, (BaseException, Exception)):
        exception = result
        forcefield.get_parameter_handler("vdW", {}).method = vdw_method
        forcefield.get_parameter_handler(
            "Electrostatics", {}
        ).method = electrostatics_method
        openff_interchange = Interchange.from_smirnoff(
            force_field=forcefield, topology=topology
        )
        with pytest.raises(exception):
            openff_interchange.to_openmm(combine_nonbonded_forces=True)
    else:
        raise Exception("uh oh")
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"
    def test_prepare_force_field(self, optimization):
        """Test that the correct cosmetic attributes are attached to the FF, especially
        in the special case of BCC handlers."""

        optimization.parameters_to_train.append(
            Parameter(
                handler_type="ChargeIncrementModel",
                smirks="[#6:1]-[#6:2]",
                attribute_name="charge_increment1",
            ))
        optimization.parameters_to_train.append(
            Parameter(
                handler_type="vdW",
                smirks=None,
                attribute_name="scale14",
            ))

        with temporary_cd():

            OptimizationInputFactory._prepare_force_field(optimization, None)

            assert os.path.isfile(
                os.path.join("forcefield", "force-field.offxml"))

            off_force_field = OFFForceField(
                os.path.join("forcefield", "force-field.offxml"),
                allow_cosmetic_attributes=True,
            )

        vdw_handler = off_force_field.get_parameter_handler("vdW")
        assert vdw_handler._parameterize == "scale14"

        assert len(vdw_handler.parameters) == 1
        parameter = vdw_handler.parameters["[#6:1]"]
        assert parameter._parameterize == "epsilon, sigma"

        bcc_handler = off_force_field.get_parameter_handler(
            "ChargeIncrementModel")
        assert len(bcc_handler.parameters) == 1
        parameter = bcc_handler.parameters["[#6:1]-[#6:2]"]
        assert len(parameter.charge_increment) == 1
        assert parameter._parameterize == "charge_increment1"
def ideal_water_force_field() -> ForceField:
    """Returns a force field that will assign constraints, a vdW handler and
    a library charge handler to a three site water molecule with all LJ
    ``epsilon=0.0`` and all ``q=0.0``.
    """
    ff = ForceField(load_plugins=True)

    constraint_handler = ff.get_parameter_handler("Constraints")
    constraint_handler.add_parameter({
        "smirks": "[#1:1]-[#8X2H2+0:2]-[#1]",
        "distance": 0.9572 * unit.angstrom
    })
    constraint_handler.add_parameter({
        "smirks": "[#1:1]-[#8X2H2+0]-[#1:2]",
        "distance": 1.5139 * unit.angstrom
    })
    # add a dummy vdW term
    vdw_handler = ff.get_parameter_handler("vdW")
    vdw_handler.add_parameter({
        "smirks": "[#1:1]-[#8X2H2+0]-[#1]",
        "epsilon": 0.0 * unit.kilojoule_per_mole,
        "sigma": 1.0 * unit.angstrom,
    })
    vdw_handler.add_parameter({
        "smirks": "[#1]-[#8X2H2+0:1]-[#1]",
        "epsilon": 0.0 * unit.kilojoules_per_mole,
        "sigma": 0.0 * unit.nanometers,
    })
    # add the library charges
    library_charge = ff.get_parameter_handler("LibraryCharges")
    library_charge.add_parameter({
        "smirks": "[#1]-[#8X2H2+0:1]-[#1]",
        "charge1": 0 * unit.elementary_charge
    })
    library_charge.add_parameter({
        "smirks": "[#1:1]-[#8X2H2+0]-[#1]",
        "charge1": 0 * unit.elementary_charge
    })

    return ff
Exemple #7
0
def test_combine_cli_all_offxml(
    run_cli, tmpdir, acetone, coumarin, openff, rdkit_workflow, parameters, expected
):
    """
    Test combining offxmls via the cli with different parameters.
    """
    workflow = rdkit_workflow.copy(deep=True)
    workflow.non_bonded = get_protocol(protocol_name="5b")
    with tmpdir.as_cwd():
        openff.run(coumarin)
        openff.run(acetone)
        # make some dummy dirs
        os.mkdir("QUBEKit_acetone")
        os.mkdir("QUBEKit_coumarin")
        result = workflow._build_initial_results(molecule=acetone)
        result.to_file(os.path.join("QUBEKit_acetone", "workflow_result.json"))
        result = workflow._build_initial_results(molecule=coumarin)
        result.to_file(os.path.join("QUBEKit_coumarin", "workflow_result.json"))
        output = run_cli.invoke(
            combine, args=["combined.offxml", "-offxml", *parameters]
        )
        assert output.exit_code == 0
        assert "2 molecules found, combining..." in output.output

        if expected != 0:
            ff = ForceField(
                "combined.offxml", allow_cosmetic_attributes=True, load_plugins=True
            )
            # make sure we have used the plugin method
            vdw_handler = ff.get_parameter_handler("QUBEKitvdWTS")
            assert len(vdw_handler.parameters) == 32
            assert "parameterize" in vdw_handler._cosmetic_attribs
            assert len(getattr(vdw_handler, "_parameterize").split(",")) == expected
        else:
            # we are using the normal format so make sure it complies
            ff = ForceField("combined.offxml")
            vdw_handler = ff.get_parameter_handler("vdW")
            assert len(vdw_handler.parameters) == 34
Exemple #8
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 #9
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 #10
0
ff = ForceField()
ff.aromaticity_model = 'OEAroModel_MDL'

# Loop over the parameters to convert and their corresponding SMIRNOFF header tags
for smirnoff_tag, param_dicts in {
        "Bonds": bond_dicts,
        "Angles": angle_dicts,
        "ProperTorsions": proper_dicts,
        "AmberImproperTorsions": improper_dicts,
        #'LibraryCharges': charge_dicts,
        #'vdW': nonbond_dicts
}.items():
    # Get the parameter handler for the given tag. The
    # "get_parameter_handler" method will initialize an
    # empty handler if none exists yet (as is the case here).
    handler = ff.get_parameter_handler(smirnoff_tag)

    # Loop over the list of parameter dictionaries, using each one as an input to
    # handler.add_parameter.  This mimics deserializing an OFFXML into a ForceField
    # object, and will do any sanitization that we might otherwise miss
    from openff.toolkit.typing.engines.smirnoff.parameters import DuplicateParameterError
    for param_dict in param_dicts:
        try:
            handler.add_parameter(param_dict)
        except DuplicateParameterError:
            continue

# Add the ElectrostaticsHandler, with the proper 1-4 scaling factors
handler = ff.get_parameter_handler('Electrostatics')
# Write the now-populated forcefield out to OFFXML
ff.to_file('result_backbone.offxml')
Exemple #11
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
def generate_coverage_report(input_molecules: List[Molecule],
                             forcefield_name: str,
                             processors: Optional[int] = None):
    """
    For the given set of molecules produce a coverage report of the smirks parameters used in typing the molecule.
    Also try and produce charges for the molecules as some may have missing bccs.

    Parameters
    ----------
    input_molecules: The List of 3d sdf files to run the coverage on or the name of the directory containing the files.
    forcefield_name: The name of the openFF forcefield to run the coverage for.
    processors: The number of processors we can use to build the coverage report

    Returns
    -------
    coverage_report: A dictionary split into parameter types which lists the number of occurrences of each parameter.
    success_mols: A list of openff.toolkit.topology.Molecule objects that were successful in this step
    error_mols: A list of tuples (Molecule, Exception) of molecules that failed this step
    """
    if isinstance(input_molecules, Molecule):
        input_molecules = [
            input_molecules,
        ]
    coverage = {
        "Angles": {},
        "Bonds": {},
        "ProperTorsions": {},
        "ImproperTorsions": {},
        "vdW": {}
    }

    # make the forcefield
    ff = ForceField(forcefield_name)
    # For speed, don't test charge assignment for now
    ff.deregister_parameter_handler('ToolkitAM1BCC')
    # ff.deregister_parameter_handler("Electrostatics")
    ff.get_parameter_handler('ChargeIncrementModel', {
        'partial_charge_method': 'formal_charge',
        'version': '0.3'
    })
    # now run coverage on each molecule
    success_mols = []
    error_mols = []
    if processors is None or processors > 1:
        from multiprocessing import Pool

        with Pool(processes=processors) as pool:
            # generate the work list
            work_list = [
                pool.apply_async(single_molecule_coverage, (molecule, ff))
                for molecule in input_molecules
            ]
            for work in tqdm.tqdm(
                    work_list,
                    total=len(work_list),
                    ncols=80,
                    desc="{:30s}".format("Generating Coverage"),
            ):
                report, e = work.get()
                molecule = report["molecule"]
                if e is None:
                    _update_coverage(coverage, report)
                    success_mols.append(molecule)
                #except Exception as e:
                else:
                    error_mols.append((molecule, e))

    else:
        for molecule in tqdm.tqdm(input_molecules,
                                  total=len(input_molecules),
                                  ncols=80,
                                  desc="{:30s}".format("Generating Coverage")):
            #try:
            report, e = single_molecule_coverage(molecule, ff)
            # update the master coverage dict
            mol: Molecule = report["molecule"]
            if e is None:
                _update_coverage(coverage, report)
                success_mols.append(mol)
            #except Exception as e:
            else:
                error_mols.append((molecule, e))

    # Sort the keys of the coverage dict, so that it doesn't show which parameters were found first
    for parameter_type in coverage.keys():
        sorted_sub_dict = dict(sorted(coverage[parameter_type].items()))
        coverage[parameter_type] = sorted_sub_dict

    # record how many molecules we processed
    coverage["passed_unique_molecules"] = len(success_mols)
    coverage["total_unique_molecules"] = len(success_mols) + len(error_mols)
    coverage["forcefield_name"] = forcefield_name

    return coverage, success_mols, error_mols
def buckingham_water_force_field() -> ForceField:
    """Create a buckingham water model Forcefield object."""

    force_field = ForceField(load_plugins=True)

    # Add in a constraint handler to ensure the correct H-O-H geometry.
    constraint_handler = force_field.get_parameter_handler("Constraints")
    # Keep the H-O bond length fixed at 0.9572 angstroms.
    constraint_handler.add_parameter({
        "smirks": "[#1:1]-[#8X2H2+0:2]-[#1]",
        "distance": 0.9572 * unit.angstrom
    })
    # Keep the H-O-H angle fixed at 104.52 degrees.
    constraint_handler.add_parameter({
        "smirks": "[#1:1]-[#8X2H2+0]-[#1:2]",
        "distance": 1.5139 * unit.angstrom
    })

    # Add a default vdW handler which is currently required by the OFF TK.
    vdw_handler = force_field.get_parameter_handler("vdW")
    vdw_handler.add_parameter({
        "smirks": "[#1:1]-[#8X2H2+0]-[#1]",
        "epsilon": 0.0 * unit.kilojoule_per_mole,
        "sigma": 1.0 * unit.angstrom,
    })
    vdw_handler.add_parameter({
        "smirks": "[#1]-[#8X2H2+0:1]-[#1]",
        "epsilon": 0.0 * unit.kilojoules_per_mole,
        "sigma": 0.0 * unit.nanometers,
        # "epsilon": 0.680946 * unit.kilojoules_per_mole,
        # "sigma": 0.316435 * unit.nanometers,
    })

    # Add a charge handler to zero the charges on water. The charges will be
    # applied by the virtual site handler instead.
    force_field.get_parameter_handler("Electrostatics")

    force_field.get_parameter_handler(
        "ChargeIncrementModel",
        {
            "version": "0.3",
            "partial_charge_method": "formal_charge"
        },
    )

    # Add a virtual site handler to add the virtual charge site.
    virtual_site_handler = force_field.get_parameter_handler("VirtualSites")
    virtual_site_handler.add_parameter({
        "smirks":
        "[#1:2]-[#8X2H2+0:1]-[#1:3]",
        "type":
        "DivalentLonePair",
        "distance":
        -0.0106 * unit.nanometers,
        "outOfPlaneAngle":
        0.0 * unit.degrees,
        "match":
        "once",
        "charge_increment1":
        1.0552 * 0.5 * unit.elementary_charge,
        "charge_increment2":
        0.0 * unit.elementary_charge,
        "charge_increment3":
        1.0552 * 0.5 * unit.elementary_charge,
    })
    virtual_site_handler._parameters = ParameterList(
        virtual_site_handler._parameters)

    # Finally add the custom buckingham charge handler.
    buckingham_handler = force_field.get_parameter_handler(
        "DampedBuckingham68")
    buckingham_handler.add_parameter({
        "smirks":
        "[#1:1]-[#8X2H2+0]-[#1]",
        "a":
        0.0 * unit.kilojoule_per_mole,
        "b":
        0.0 / unit.nanometer,
        "c6":
        0.0 * unit.kilojoule_per_mole * unit.nanometer**6,
        "c8":
        0.0 * unit.kilojoule_per_mole * unit.nanometer**8,
    })
    buckingham_handler.add_parameter({
        "smirks":
        "[#1]-[#8X2H2+0:1]-[#1]",
        "a":
        1600000.0 * unit.kilojoule_per_mole,
        "b":
        42.00 / unit.nanometer,
        "c6":
        0.003 * unit.kilojoule_per_mole * unit.nanometer**6,
        "c8":
        0.00003 * unit.kilojoule_per_mole * unit.nanometer**8,
    })
    return force_field
Exemple #14
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
Exemple #15
0
def _combine_molecules_offxml(
    molecules: List["Ligand"],
    parameters: List[str],
    rfree_data: Dict[str, Dict[str, Union[str, float]]],
    filename: str,
    water_model: Literal["tip3p"] = "tip3p",
):
    """
    Main worker function to build the combined offxmls.
    """

    if sum([molecule.extra_sites.n_sites for molecule in molecules]) > 0:
        raise NotImplementedError(
            "Virtual sites can not be safely converted into offxml format yet."
        )

    if sum([molecule.RBTorsionForce.n_parameters for molecule in molecules]) > 0:
        raise NotImplementedError(
            "RBTorsions can not yet be safely converted into offxml format yet."
        )

    try:
        from chemper.graphs.cluster_graph import ClusterGraph
    except ModuleNotFoundError:
        raise ModuleNotFoundError(
            "chemper is required to make an offxml, please install with `conda install chemper -c conda-forge`."
        )

    fit_ab = False
    # if alpha and beta should be fit
    if "AB" in parameters:
        fit_ab = True

    rfree_codes = set()  # keep track of all rfree codes used by these molecules
    # create the master ff
    offxml = ForceField(allow_cosmetic_attributes=True, load_plugins=True)
    offxml.author = f"QUBEKit_version_{qubekit.__version__}"
    offxml.date = datetime.now().strftime("%Y_%m_%d")
    # get all of the handlers
    _ = offxml.get_parameter_handler("Constraints")
    bond_handler = offxml.get_parameter_handler("Bonds")
    angle_handler = offxml.get_parameter_handler("Angles")
    proper_torsions = offxml.get_parameter_handler("ProperTorsions")
    improper_torsions = offxml.get_parameter_handler("ImproperTorsions")
    _ = offxml.get_parameter_handler(
        "Electrostatics", handler_kwargs={"scale14": 0.8333333333, "version": 0.3}
    )
    using_plugin = False
    if parameters:
        # if we want to optimise the Rfree we need our custom handler
        vdw_handler = offxml.get_parameter_handler(
            "QUBEKitvdWTS", allow_cosmetic_attributes=True
        )
        using_plugin = True
    else:
        vdw_handler = offxml.get_parameter_handler(
            "vdW", allow_cosmetic_attributes=True
        )
    library_charges = offxml.get_parameter_handler("LibraryCharges")

    for molecule in molecules:
        rdkit_mol = molecule.to_rdkit()
        bond_types = molecule.bond_types
        # for each bond type collection create a single smirks pattern
        for bonds in bond_types.values():
            graph = ClusterGraph(
                mols=[rdkit_mol], smirks_atoms_lists=[bonds], layers="all"
            )
            qube_bond = molecule.BondForce[bonds[0]]
            bond_handler.add_parameter(
                parameter_kwargs={
                    "smirks": graph.as_smirks(),
                    "length": qube_bond.length * unit.nanometers,
                    "k": qube_bond.k * unit.kilojoule_per_mole / unit.nanometers**2,
                }
            )

        angle_types = molecule.angle_types
        for angles in angle_types.values():
            graph = ClusterGraph(
                mols=[rdkit_mol],
                smirks_atoms_lists=[angles],
                layers="all",
            )
            qube_angle = molecule.AngleForce[angles[0]]
            angle_handler.add_parameter(
                parameter_kwargs={
                    "smirks": graph.as_smirks(),
                    "angle": qube_angle.angle * unit.radian,
                    "k": qube_angle.k * unit.kilojoule_per_mole / unit.radians**2,
                }
            )

        torsion_types = molecule.dihedral_types
        for dihedrals in torsion_types.values():
            graph = ClusterGraph(
                mols=[rdkit_mol],
                smirks_atoms_lists=[dihedrals],
                layers="all",
            )
            qube_dihedral = molecule.TorsionForce[dihedrals[0]]
            proper_torsions.add_parameter(
                parameter_kwargs={
                    "smirks": graph.as_smirks(),
                    "k1": qube_dihedral.k1 * unit.kilojoule_per_mole,
                    "k2": qube_dihedral.k2 * unit.kilojoule_per_mole,
                    "k3": qube_dihedral.k3 * unit.kilojoule_per_mole,
                    "k4": qube_dihedral.k4 * unit.kilojoule_per_mole,
                    "periodicity1": qube_dihedral.periodicity1,
                    "periodicity2": qube_dihedral.periodicity2,
                    "periodicity3": qube_dihedral.periodicity3,
                    "periodicity4": qube_dihedral.periodicity4,
                    "phase1": qube_dihedral.phase1 * unit.radians,
                    "phase2": qube_dihedral.phase2 * unit.radians,
                    "phase3": qube_dihedral.phase3 * unit.radians,
                    "phase4": qube_dihedral.phase4 * unit.radians,
                    "idivf1": 1,
                    "idivf2": 1,
                    "idivf3": 1,
                    "idivf4": 1,
                }
            )

        improper_types = molecule.improper_types
        for torsions in improper_types.values():
            impropers = [
                (improper[1], improper[0], *improper[2:]) for improper in torsions
            ]
            graph = ClusterGraph(
                mols=[rdkit_mol], smirks_atoms_lists=[impropers], layers="all"
            )
            qube_improper = molecule.ImproperTorsionForce[torsions[0]]
            # we need to multiply each k value by as they will be applied as trefoil see
            # <https://openforcefield.github.io/standards/standards/smirnoff/#impropertorsions> for more details
            # we assume we only have a k2 term for improper torsions via a periodic term
            improper_torsions.add_parameter(
                parameter_kwargs={
                    "smirks": graph.as_smirks(),
                    "k1": qube_improper.k2 * 3 * unit.kilojoule_per_mole,
                    "periodicity1": qube_improper.periodicity2,
                    "phase1": qube_improper.phase2 * unit.radians,
                }
            )

        atom_types = {}
        for atom_index, cip_type in molecule.atom_types.items():
            atom_types.setdefault(cip_type, []).append((atom_index,))
        for sym_set in atom_types.values():
            graph = ClusterGraph(
                mols=[rdkit_mol], smirks_atoms_lists=[sym_set], layers="all"
            )
            qube_non_bond = molecule.NonbondedForce[sym_set[0]]
            rfree_code = _get_parameter_code(
                molecule=molecule, atom_index=sym_set[0][0]
            )
            atom_data = {
                "smirks": graph.as_smirks(),
            }

            if rfree_code in parameters or fit_ab:
                # keep track of present codes to optimise
                rfree_codes.add(rfree_code)
            if using_plugin:
                # this is to be refit
                atom = molecule.atoms[qube_non_bond.atoms[0]]
                atom_data["volume"] = atom.aim.volume * unit.angstroms**3
            else:
                atom_data["epsilon"] = qube_non_bond.epsilon * unit.kilojoule_per_mole
                atom_data["sigma"] = qube_non_bond.sigma * unit.nanometers

            vdw_handler.add_parameter(parameter_kwargs=atom_data)

        charge_data = dict(
            (f"charge{param.atoms[0] + 1}", param.charge * unit.elementary_charge)
            for param in molecule.NonbondedForce
        )
        charge_data["smirks"] = molecule.to_smiles(mapped=True)
        library_charges.add_parameter(parameter_kwargs=charge_data)

    # now loop over all the parameters to be fit and add them as cosmetic attributes
    to_parameterize = []
    for parameter_to_fit in parameters:
        if parameter_to_fit != "AB" and parameter_to_fit in rfree_codes:
            setattr(
                vdw_handler,
                f"{parameter_to_fit.lower()}free",
                unit.Quantity(
                    rfree_data[parameter_to_fit]["r_free"], unit=unit.angstroms
                ),
            )
            to_parameterize.append(f"{parameter_to_fit.lower()}free")
    if fit_ab:
        vdw_handler.alpha = rfree_data["alpha"]
        vdw_handler.beta = rfree_data["beta"]
        to_parameterize.extend(["alpha", "beta"])
    if to_parameterize:
        vdw_handler.add_cosmetic_attribute("parameterize", ", ".join(to_parameterize))

    # now add a water model to the force field
    _add_water_model(
        force_field=offxml, water_model=water_model, using_plugin=using_plugin
    )
    offxml.to_file(filename=filename)
Exemple #16
0
def _add_water_model(
    force_field: ForceField, using_plugin: bool, water_model: Literal["tip3p"] = "tip3p"
):
    """Add a water model to an offxml force field"""
    # add generic bond and angle smirks which will only hit water
    bond_handler = force_field.get_parameter_handler("Bonds")
    bond_handler.add_parameter(
        parameter_kwargs={
            "smirks": "[#1:1]-[#8X2H2+0:2]-[#1]",
            "k": 1087.053566377 * unit.kilocalorie_per_mole / unit.angstroms**2,
            "length": 0.9572 * unit.angstroms,
        }
    )
    angle_handler = force_field.get_parameter_handler("Angles")
    angle_handler.add_parameter(
        parameter_kwargs={
            "smirks": "[#1:1]-[#8X2H2+0:2]-[#1:3]",
            "k": 130.181232192 * unit.kilocalorie_per_mole / unit.radian**2,
            "angle": 110.3538806181 * unit.degree,
        }
    )

    if water_model == "tip3p":
        constrains = force_field.get_parameter_handler("Constraints")
        constrains.add_parameter(
            parameter_kwargs={
                "smirks": "[#1:1]-[#8X2H2+0:2]-[#1]",
                "id": "c-tip3p-H-O",
                "distance": 0.9572 * unit.angstroms,
            }
        )
        constrains.add_parameter(
            parameter_kwargs={
                "smirks": "[#1:1]-[#8X2H2+0]-[#1:2]",
                "id": "c-tip3p-H-O-H",
                "distance": 1.5139006545247014 * unit.angstroms,
            }
        )
        if using_plugin:
            # if we are using the plugin to optimise the molecule we need a special vdw handler
            vdw_handler = force_field.get_parameter_handler("QUBEKitvdW")
        else:
            vdw_handler = force_field.get_parameter_handler("vdW")

        vdw_handler.add_parameter(
            parameter_kwargs={
                "smirks": "[#1]-[#8X2H2+0:1]-[#1]",
                "epsilon": 0.1521 * unit.kilocalorie_per_mole,
                "id": "n-tip3p-O",
                "sigma": 3.1507 * unit.angstroms,
            }
        )
        vdw_handler.add_parameter(
            parameter_kwargs={
                "smirks": "[#1:1]-[#8X2H2+0]-[#1]",
                "epsilon": 0 * unit.kilocalorie_per_mole,
                "id": "n-tip3p-H",
                "sigma": 1 * unit.angstroms,
            }
        )
        library_charges = force_field.get_parameter_handler("LibraryCharges")
        library_charges.add_parameter(
            parameter_kwargs={
                "smirks": "[#1]-[#8X2H2+0:1]-[#1]",
                "charge1": -0.834 * unit.elementary_charge,
                "id": "q-tip3p-O",
            }
        )
        library_charges.add_parameter(
            parameter_kwargs={
                "smirks": "[#1:1]-[#8X2H2+0]-[#1]",
                "charge1": 0.417 * unit.elementary_charge,
                "id": "q-tip3p-H",
            }
        )
    else:
        raise NotImplementedError(
            "Only the tip3p water model is support for offxmls so far."
        )