Exemple #1
0
def build_context_residual(offxml, molfile, dihedrals):
    """Builds context for the openmm calculation keeping the dihedral frozen and the
    specific energy contributions from the dihedral zeroed out
    Parameters
    ----------
    offxml: forcefield file
    molfile: molecule file in sdf format
    dihedrals: list of atom indices in the dihedral
    Returns
    -------
    """
    forcefield = ForceField(offxml, allow_cosmetic_attributes=True)
    molecule = Molecule.from_file(molfile, allow_undefined_stereo=True)
    system = forcefield.create_openmm_system(molecule.to_topology())

    # freeze the dihedral atoms so that the dihedral angle stays fixed
    for i in dihedrals:
        system.setParticleMass(i, 0.0)
    # zero out the energy contributions from the dihedral atoms
    zero_dihedral_contribution(system, (dihedrals[1], dihedrals[2]))
    integrator = openmm.LangevinIntegrator(
        300.0 * unit.kelvin, 1.0 / unit.picosecond, 2.0 * unit.femtosecond
    )
    platform = openmm.Platform.getPlatformByName("Reference")
    context = openmm.Context(system, integrator, platform)
    return context
Exemple #2
0
    def test_bond_potential_handler(self):
        top = OFFBioTop.from_molecules(Molecule.from_smiles("O=O"))

        bond_handler = BondHandler(version=0.3)
        bond_parameter = BondHandler.BondType(
            smirks="[*:1]~[*:2]",
            k=1.5 * simtk_unit.kilocalorie_per_mole / simtk_unit.angstrom**2,
            length=1.5 * simtk_unit.angstrom,
            id="b1000",
        )
        bond_handler.add_parameter(bond_parameter.to_dict())

        from openff.toolkit.typing.engines.smirnoff import ForceField

        forcefield = ForceField()
        forcefield.register_parameter_handler(bond_handler)
        bond_potentials = SMIRNOFFBondHandler._from_toolkit(
            parameter_handler=forcefield["Bonds"],
            topology=top,
        )

        top_key = TopologyKey(atom_indices=(0, 1))
        pot_key = bond_potentials.slot_map[top_key]
        assert pot_key.associated_handler == "Bonds"
        pot = bond_potentials.potentials[pot_key]

        kcal_mol_a2 = unit.Unit("kilocalorie / (angstrom ** 2 * mole)")
        assert pot.parameters["k"].to(kcal_mol_a2).magnitude == pytest.approx(
            1.5)
Exemple #3
0
    def test_angle_potential_handler(self):
        top = OFFBioTop.from_molecules(Molecule.from_smiles("CCC"))

        angle_handler = AngleHandler(version=0.3)
        angle_parameter = AngleHandler.AngleType(
            smirks="[*:1]~[*:2]~[*:3]",
            k=2.5 * simtk_unit.kilocalorie_per_mole / simtk_unit.radian**2,
            angle=100 * simtk_unit.degree,
            id="b1000",
        )
        angle_handler.add_parameter(angle_parameter.to_dict())

        forcefield = ForceField()
        forcefield.register_parameter_handler(angle_handler)
        angle_potentials = SMIRNOFFAngleHandler._from_toolkit(
            parameter_handler=forcefield["Angles"],
            topology=top,
        )

        top_key = TopologyKey(atom_indices=(0, 1, 2))
        pot_key = angle_potentials.slot_map[top_key]
        assert pot_key.associated_handler == "Angles"
        pot = angle_potentials.potentials[pot_key]

        kcal_mol_rad2 = unit.Unit("kilocalorie / (mole * radian ** 2)")
        assert pot.parameters["k"].to(
            kcal_mol_rad2).magnitude == pytest.approx(2.5)
Exemple #4
0
def build_restrained_context_for_residual(offxml, molfile, traj, dihedrals):
    """
    Builds openmm context for a full MM calculation keeping the dihedral frozen
    and applying a restraint of 1 kcal/mol on atoms not involved in torsion
    Parameters
    ----------
    offxml: forcefield file
    molfile: molecule file in sdf format
    positions: list of positions of all conformers
    dihedrals: list of atom indices in the dihedral

    Returns
    -------
    list of openmm contexts
    """
    contexts = []
    forcefield = ForceField(offxml, allow_cosmetic_attributes=True)
    molecule = Molecule.from_file(molfile, allow_undefined_stereo=True)

    for pos in traj:
        system = forcefield.create_openmm_system(molecule.to_topology())
        add_restraints_to_system(system, pos * unit.angstrom, dihedrals)
        for force in system.getForces():
            if force.__class__.__name__ == "CustomExternalForce":
                force.setForceGroup(11)
        zero_dihedral_contribution(system, (dihedrals[1], dihedrals[2]))
        for i in dihedrals:
            system.setParticleMass(i, 0.0)
        integrator = openmm.LangevinIntegrator(
            300.0 * unit.kelvin, 1.0 / unit.picosecond, 2.0 * unit.femtosecond
        )
        platform = openmm.Platform.getPlatformByName("Reference")
        contexts.append(openmm.Context(system, integrator, platform))

    return contexts
    def build_residue_forcefield(self,
                                 forcefield: ForceField,
                                 all_handler_kwargs: Dict[str, dict],
                                 compressed: bool = True) -> ForceField:

        for handler_name, handler_kwargs in all_handler_kwargs.items():
            handler = forcefield.get_parameter_handler(handler_name)
            if handler._INFOTYPE is None:
                warnings.warn(
                    f"{handler_name} has no INFOTYPE so I don't know how to add params"
                )
                continue

            # TODO: hierarchical smarts
            smirker = Smirker(handler_kwargs)
            if handler_name in ("LibraryCharges", ):
                for monomer in self.monomers:
                    param = smirker.get_combined_smirks_parameter(
                        monomer, compressed=compressed)
                    try:
                        handler.add_parameter(param)
                    except:  # TODO: figure out the error
                        pass
                continue

            unified_smirks = smirker.get_unified_smirks_parameters(
                context="residue", compressed=compressed)
            for smirks, group_parameter in unified_smirks.items():
                param = dict(smirks=smirks,
                             **group_parameter.mean_parameter.fields)
                handler.add_parameter(param)

        return forcefield
Exemple #6
0
def convert_frcmod_to_ffxml(infile, inxml, outxml):
    """Convert a modified AMBER frcmod (with SMIRKS replacing atom types) to SMIRNOFF ffxml format by inserting parameters into a template ffxml file.

    Parameters
    ----------
    infile : str
        File name of input SMIRKS-ified frcmod file containing parameters
    inxml : str
        File name of template SMIRNOFF FFXML file into which to insert these parameters.
    outxml : str
        File name of resulting output SMIRNOFF FFXML

    Notes:
    -------
    Input XML file will normally be the template of a SMIRNOFF XML file without any parameters present (but with requisite force types already specified).
    """
    from openff.toolkit.typing.engines.smirnoff import XMLParameterIOHandler

    io_handler = XMLParameterIOHandler()
    smirnoff_data = io_handler.parse_file(inxml)
    parameter_lists, author, date = parse_frcmod(infile)
    for section_tag in parameter_lists:
        # Nest the actual parameter lists one level deeper, by their parameter_list_tag (eg Bonds > Bond > List)
        parameter_list_tag = list(parameter_lists[section_tag].keys())[0]
        smirnoff_data['SMIRNOFF'][section_tag][
            parameter_list_tag] = parameter_lists[section_tag][
                parameter_list_tag]
    ff = ForceField()
    ff._load_smirnoff_data(smirnoff_data)
    print(ff.to_string())

    # TODO: Add author and date

    ff.to_file(outxml, io_format='XML')
Exemple #7
0
    def from_openff(
        cls, force_field: Union["OpenForceField",
                                "ForceFieldSource"]) -> "ForceField":

        from openff.evaluator.forcefield import ForceFieldSource
        from openff.toolkit.typing.engines.smirnoff.forcefield import (
            ForceField as OpenForceField, )

        if isinstance(force_field, OpenForceField):

            return ForceField(inner_content=force_field.to_string(
                discard_cosmetic_attributes=True))

        elif isinstance(force_field, ForceFieldSource):

            return ForceField(inner_content=force_field.json())

        else:
            raise NotImplementedError()
Exemple #8
0
    def to_openff(self) -> Union["OpenForceField", "ForceFieldSource"]:

        from openff.evaluator.forcefield import ForceFieldSource
        from openff.toolkit.typing.engines.smirnoff.forcefield import ForceField

        try:
            force_field = ForceField(self.inner_content)
        except (TypeError, IOError):
            force_field = ForceFieldSource.parse_json(self.inner_content)

        return force_field
 def _build_forcefield(self, forcefield_parameters, residue_based=True):
     forcefield_parameters = dict(**forcefield_parameters)
     new_ff = ForceField()
     if residue_based:
         if residue_based is True:
             residue_based = list(forcefield_parameters.keys())
         specific_kwargs = {}
         for k in residue_based:
             specific_kwargs[k] = forcefield_parameters.pop(k, {})
         self.build_residue_forcefield(new_ff, specific_kwargs)
     if forcefield_parameters:
         self.build_combination_forcefield(new_ff, forcefield_parameters)
     return new_ff
Exemple #10
0
    def test_num_constraints(self, mol, n_constraints):
        force_field = ForceField("openff-1.0.0.offxml")

        bond_handler = force_field["Bonds"]
        constraint_handler = force_field["Constraints"]

        topology = Molecule.from_smiles(mol).to_topology()

        constraints = SMIRNOFFConstraintHandler._from_toolkit(
            parameter_handler=[
                val for val in [bond_handler, constraint_handler]
                if val is not None
            ],
            topology=topology,
        )

        assert len(constraints.slot_map) == n_constraints
Exemple #11
0
    def store_charges(
        self,
        forcefield: ForceField,
        topology: Topology,
    ) -> None:
        """
        Populate self.slot_map with key-val pairs of slots
        and unique potential identifiers

        """
        self.method = forcefield["Electrostatics"].method

        partial_charges = get_partial_charges_from_openmm_system(
            forcefield.create_openmm_system(topology=topology))

        for i, charge in enumerate(partial_charges):
            self.charge_map[str((i, ))] = charge * unit.elementary_charge
    def build_combination_forcefield(self,
                                     forcefield: ForceField,
                                     all_handler_kwargs: Dict[str, dict],
                                     compressed: bool = True) -> ForceField:
        combinations = []
        for monomer in self.monomers:
            built = monomer.build_all_combinations(self.caps_for_r_groups)
            combinations.append(built)

        for handler_name, handler_kwargs in all_handler_kwargs.items():
            handler = forcefield.get_parameter_handler(handler_name)
            if handler._INFOTYPE is None:
                warnings.warn(
                    f"{handler_name} has no INFOTYPE so I don't know how to add params"
                )
                continue

            smirker = Smirker(handler_kwargs)

            if handler_name in ("LibraryCharges", ):
                for oligomer in combinations:
                    param = smirker.get_combined_smirks_parameter(
                        oligomer, compressed=compressed)
                    try:
                        handler.add_parameter(param)
                    except:  # TODO: figure out the error
                        pass
                continue
            for oligomer in combinations:
                unified_smirks = smirker.get_smirks_for_oligomer(
                    oligomer, compressed=compressed)
                for smirks, group_parameter in unified_smirks.items():
                    param = dict(smirks=smirks,
                                 **group_parameter.mean_parameter.fields)
                    try:
                        handler.add_parameter(param)
                    except:  # TODO: figure out error
                        pass
        return forcefield
def convert_frcmod_to_ffxml( infile, inxml, outxml ):
    """Convert a modified AMBER frcmod (with SMIRKS replacing atom types) to SMIRNOFF ffxml format by inserting parameters into a template ffxml file.

    Parameters
    ----------
    infile : str
        File name of input SMIRKS-ified frcmod file containing parameters
    inxml : str
        File name of template SMIRNOFF FFXML file into which to insert these parameters.
    outxml : str
        File name of resulting output SMIRNOFF FFXML

    Notes:
    -------
    Input XML file will normally be the template of a SMIRNOFF XML file without any parameters present (but with requisite force types already specified).
    """

    # Obtain sections from target file
    file = open(infile, 'r')
    text = file.readlines()
    file.close()
    sections = {}
    # Section names from frcmod which we will parse
    secnames = ['NONBON', 'BOND', 'ANGL', 'IMPR', 'DIHE']
    # Tags that will be used in the FFXML for these (same order)
    tag = ['Atom', 'Bond', 'Angle', 'Improper', 'Proper']
    # Force names in the FFXML (same order)
    force_section = ['NonbondedForce', 'HarmonicBondForce', 'HarmonicAngleForce', 'PeriodicTorsionForce', 'PeriodicTorsionForce']
    ct = 0
    thissec = None
    # Why is this a while loop and not a for line in text loop?
    while ct < len(text):
        line = text[ct]
        tmp = line.split()

        # Skip lines starting with comment or which are blank
        if line[0]=='#' or len(tmp) < 1:
            ct+=1
            continue

        # Check first entry to see if it's a section name, if so initialize storage
        if tmp[0] in secnames:
            thissec = tmp[0]
            sections[thissec] = []

        elif tmp[0] in ['DATE','AUTHOR']:
            thissec = tmp[0]
        # Otherwise store
        else:
            if thissec == 'DATE':
                date = line.strip()
            elif thissec == 'AUTHOR':
                author = line.strip()
            else:
                sections[thissec].append(line)

        ct+=1

    # fix date and author in inxml file:
    add_date_and_author(inxml, date, author)
    # Read template force field file
    ff = ForceField(inxml)
    # Use functions to parse sections from target file and add parameters to force field
    param_id_by_section={}
    param_prefix_by_sec = {'NONBON':'n' , 'BOND':'b', 'ANGL':'a', 'DIHE':'t', 'IMPR':'i'}
    env_method_by_sec = {'NONBON': environment.AtomChemicalEnvironment,
            'BOND': environment.BondChemicalEnvironment,
            'ANGL': environment.AngleChemicalEnvironment,
            'DIHE': environment.TorsionChemicalEnvironment,
            'IMPR': environment.ImproperChemicalEnvironment}
    for (idx, name) in enumerate(secnames):
        param_id_by_section[name] = 1
        env_method = env_method_by_sec[name]
        for line in sections[name]:
            # Parse line for parameters
            if name=='NONBON':
                params = _parse_nonbon_line(line)
            elif name=='BOND':
                params = _parse_bond_line(line)
            elif name=='DIHE':
                params = _parse_dihe_line(line)
            elif name=='IMPR':
                params = _parse_impr_line(line)
            elif name=='ANGL':
                params = _parse_angl_line(line)

            # Add parameter ID
            params['id'] = param_prefix_by_sec[name]+str( param_id_by_section[name] )

            smirks = params['smirks']
            #Check smirks is valid for chemical enviroment parsing:
            env = env_method(smirks)

            # If it's not a torsion, just store in straightforward way
            if not (name=='IMPR' or name=='DIHE'):
                # Check for duplicates first
                if ff.getParameter( smirks, force_type = force_section[idx] ):
                    raise ValueError(f"Error: parameter for {smirks} is already present in force field.")
                else:
                    ff.addParameter( params, smirks, force_section[idx], tag[idx] )

                # Increment parameter id
                param_id_by_section[name] +=1
            # If it's a torsion, check to see if there are already parameters and
            # if so, add a new term to this torsion
            else:
                # If we have parameters already
                oldparams = ff.getParameter(smirks, force_type=force_section[idx])
                if oldparams:
                    # Find what number to use
                    idnr = 1
                    paramtag = f"k{idnr}"
                    # This was "while paramtag in params" so it was overwriting k2 etc.
                    while paramtag in oldparams:
                        idnr+=1
                        paramtag = f"k{idnr}"
                    # Construct new param object with updated numbers
                    for paramtag in ('periodicity1', 'phase1', 'idivf1', 'k1'):
                        if paramtag in params:
                            val = params.pop(paramtag)
                            oldparams[paramtag[:-1]+str(idnr) ] = val
                    # Store
                    ff.setParameter( oldparams, smirks=smirks, force_type=force_section[idx])
                else:
                    # Otherwise, just store new parameters
                    ff.addParameter( params, smirks, force_section[idx], tag[idx])
                    # Increment parameter ID
                    param_id_by_section[name] += 1


    # Write SMIRNOFF XML file
    ff.writeFile(outxml)

    # Roundtrip to fix formatting (for some reason etree won't format it properly on first write after modification)
    tmp = ForceField(outxml)
    tmp.writeFile(outxml)
Exemple #14
0
def create_energy_dataframe(subdirs, offxml_list, molfile_format, qdata_filename):
    """
    Creates a dataframe with the entries listed under cols below

    """
    cols = [
        "Torsion ID",
        "assigned params",
        "wbo",
        "Max - min for (QM - [MM_SF3, MM_1.3.0]) kcal/mol",
        "Chemical Structure",
        "QM-MM_intrinsic relative energies",
        "QM Vs MM_full",
        "rmsd",
    ]
    df = pd.DataFrame(columns=cols)
    ff_names = [os.path.splitext(os.path.basename(f))[0] for f in offxml_list]
    ff_dict = {}
    for key, ff_loc in zip(ff_names, offxml_list):
        ff_dict[key] = ForceField(ff_loc, allow_cosmetic_attributes=True)
    count = 0
    for mol_folder in subdirs:
        count += 1
        #     if(count > 10):
        #         break
        mol_folder = mol_folder.rstrip()
        if count % 10 == 0:
            print("processed ", count)
        count += 1
        mol_file = mol_folder + "/" + molfile_format
        mol = Molecule.from_file(
            mol_folder + "/" + molfile_format, allow_undefined_stereo=True
        )
        # build openmm contexts for each force field file

        # find scan trajectories
        traj_files = [
            f for f in os.listdir(mol_folder) if os.path.splitext(f)[-1] == ".xyz"
        ]
        # create output subfolder
        mol_name = os.path.basename(mol_folder)
        with open(mol_folder + "/metadata.json") as json_data:
            metadata = json.load(json_data)
        dihedrals = metadata["dihedrals"][0]
        assigned_torsion_params = dict(
            (
                os.path.splitext(os.path.basename(key))[0],
                get_assigned_torsion_param(mol, ff_dict[key], dihedrals),
            )
            for key in ff_dict.keys()
        )
        #     {key: value for (key, value) in iterable}[ for x in offxml_list]

        #   zero out the assigned torsion parameter for this particular dihedral to get the intrinsic torsion potential
        #   "(full QM energy) - (full MM energy, locally optimized, with that specific torsion zeroed out)"
        for i in range(len(offxml_list)):
            key = os.path.splitext(os.path.basename(offxml_list[i]))[0]

        #         contexts_full = [build_context_full(offxml, mol_file, dihedrals) for offxml in offxml_list]

        wbo = get_wbo(mol, dihedrals)
        for f in traj_files:
            #         print(f"- {f}")
            # hold energy data for this trajectory
            energies_data_dict = {}
            energies_full_dict = {}
            tb_dict = {}
            rmsd_dict = {}
            # use ForceBalance.molecule to read the xyz file
            fb_mol = fb_molecule(os.path.join(mol_folder, f))
            # read torsion angles
            with open(mol_folder + "/metadata.json") as json_data:
                metadata = json.load(json_data)
            energies_data_dict["td_angles"] = metadata["torsion_grid_ids"]
            rmsd_dict["td_angles"] = metadata["torsion_grid_ids"]
            # read QM energies
            qdata = np.genfromtxt(
                str(mol_folder) + "/" + str(qdata_filename),
                delimiter="ENERGY ",
                dtype=None,
            )
            # print(qdata.shape)
            eqm = qdata[:, 1]
            # record the index of the minimum energy structure
            ground_idx = np.argmin(eqm)
            eqm = [627.509 * (x - eqm[ground_idx]) for x in eqm]
            energies_data_dict["QM"] = eqm
            energies_full_dict["QM"] = eqm
            tb_dict["QM"] = get_torsion_barrier(
                energies_data_dict["QM"], energies_data_dict["td_angles"]
            )
            #             print("WBO of conformers")

            for i in range(len(fb_mol.xyzs)):
                g_frame = fb_mol.xyzs[i]
                pos = np.array(g_frame) * unit.angstrom
                mol.assign_fractional_bond_orders(
                    bond_order_model="am1-wiberg-elf10"
                )  # , use_conformers=[pos])
                bond = mol.get_bond_between(dihedrals[1], dihedrals[2])
            #                 print(metadata["torsion_grid_ids"][i], bond.fractional_bond_order)

            ### FULL
            # evalute full mm energies for each force field
            energies_full_dict = energies_data_dict.copy()
            for offxml, ffname in zip(offxml_list, ff_names):
                contexts_full = build_restrained_context(
                    offxml, mol_file, fb_mol.xyzs, dihedrals
                )
                mm_energies, minimized_pos, rmsd = evaluate_restrained_energies(
                    contexts_full, fb_mol, dihedrals
                )
                # mm_energies -= mm_energies[ground_idx]
                mm_energies -= mm_energies.min()
                key_name = ffname + "_full"
                energies_full_dict[ffname] = mm_energies
                rmsd_dict[ffname] = rmsd
                tb_dict[key_name] = get_torsion_barrier(
                    energies_full_dict[ffname], energies_full_dict["td_angles"]
                )

            ### INTRINSIC
            # evalute intrinsic mm energies for each force field
            for offxml, ffname in zip(offxml_list, ff_names):
                contexts_res = build_restrained_context_for_residual(
                    offxml, mol_file, fb_mol.xyzs, dihedrals
                )
                mm_energies, _, _ = evaluate_restrained_energies(
                    contexts_res, fb_mol, dihedrals
                )
                # mm_energies -= mm_energies[ground_idx]
                mm_energies -= mm_energies.min()
                energies_data_dict[ffname] = mm_energies
                energies_data_dict[ffname] = (
                    energies_data_dict["QM"] - energies_data_dict[ffname]
                )
                key_name = ffname + "_residual"
                tb_dict[key_name] = get_torsion_barrier(
                    energies_data_dict[ffname], energies_data_dict["td_angles"]
                )

            plot_iobytes_full = plot_energies_data(energies_full_dict, mol_name, "rel")
            figdata_png_full = base64.b64encode(plot_iobytes_full.getvalue()).decode()
            plot_str_full = '<img src="data:image/png;base64,{}" />'.format(
                figdata_png_full
            )

            plot_iobytes = plot_energies_data(energies_data_dict, mol_name, "res")
            figdata_png = base64.b64encode(plot_iobytes.getvalue()).decode()
            plot_str = '<img src="data:image/png;base64,{}" />'.format(figdata_png)

            rmsd_iobytes = plot_rmsd_data(rmsd_dict, mol_name)
            rmsddata_png = base64.b64encode(rmsd_iobytes.getvalue()).decode()
            plot_rmsd = '<img src="data:image/png;base64,{}" />'.format(rmsddata_png)

            oemol = mol.to_openeye()
            dihedral_indices = metadata["dihedrals"][0]
            struc_str = fig2inlinehtml(
                show_oemol_struc(oemol, torsions=True, atom_indices=dihedral_indices)
            )
            info_dict = {}
            tid = mol_name[4:]
            info_dict["assigned_params"] = assigned_torsion_params

            df = df.append(
                {
                    cols[0]: tid,
                    cols[1]: info_dict["assigned_params"].values(),
                    cols[2]: wbo,
                    cols[3]: tb_dict,
                    cols[4]: struc_str,
                    cols[5]: plot_str,
                    cols[6]: plot_str_full,
                    cols[7]: plot_rmsd,
                },
                ignore_index=True,
            )
    return df