예제 #1
0
    def _get_nonbonded(
        self,
        mmff: forcefield.ForceField,
        empty_atom: parmed.topologyobjects.Atom,
    ) -> Tuple["numpy.ndarray", "numpy.ndarray"]:

        assert (mmff.nonbonded.form == "LennardJones"
                ), "Only LJ potential supported for now"

        lj_units = mmff.nonbonded.units
        scaling_factor = 2**(1.0 / 6.0)  # rmin = 2^(1/6) sigma

        rmin = mmff.nonbonded.params.sigma * scaling_factor
        rmin = convert(rmin, lj_units["sigma_units"],
                       empty_atom.urmin.unit.get_name())
        # atom.rmin_14 * rmin_14_factor * scaling_factor,
        epsilon = convert(
            mmff.nonbonded.params.epsilon,
            lj_units["epsilon_units"],
            empty_atom.uepsilon.unit.get_name(),
        )
        # atom.epsilon_14 * epsilon_14_factor,
        return rmin, epsilon
예제 #2
0
    def execute(
        self,
        inputs: InputTrans,
        extra_outfiles: Optional[List[str]] = None,
        extra_commands: Optional[List[str]] = None,
        scratch_name: Optional[str] = None,
        timeout: Optional[int] = None,
    ) -> Tuple[bool, OutputTrans]:
        """
        TODO: Routine is also very slow. Try to vectorize.
        Too many for loops. Can easily reduce these esp in the ParmedToFFComponent.
        """
        if isinstance(inputs, dict):
            inputs = self.input(**inputs)

        empty_atom = parmed.topologyobjects.Atom()
        mmff = inputs.schema_object
        pff = parmed.structure.Structure()

        masses = convert(mmff.masses, mmff.masses_units,
                         empty_atom.umass.unit.get_symbol())

        charges = getattr(mmff, "charges", None)
        charges = convert(charges, mmff.charges_units,
                          empty_atom.ucharge.unit.get_symbol())

        atomic_numbers = getattr(mmff, "atomic_numbers", None)
        atom_types = getattr(mmff, "defs", None)

        rmin, epsilon = self._get_nonbonded(mmff, empty_atom)

        for index, symb in enumerate(mmff.symbols):

            # Will likely lose FF-related info ... but then Molecule is not supposed to store any params specific to FFs
            if atomic_numbers is not None:
                atomic_number = atomic_numbers[index]
            else:
                atomic_number = None

            if atom_types is not None:
                atom_type = atom_types[index]
            else:
                atom_type = None

            if masses is not None:
                mass = masses[index]
            else:
                mass = None

            if charges is not None:
                charge = charges[index]
            else:
                charge = None

            atom = parmed.topologyobjects.Atom(
                list=None,
                atomic_number=atomic_number,
                name=symb,
                type=atom_type,
                mass=mass,
                charge=charge,
                nb_idx=0,
                solvent_radius=0.0,
                screen=0.0,
                tree="BLA",
                join=0.0,
                irotat=0.0,
                occupancy=1.0,
                bfactor=0.0,
                altloc="",
                number=-1,
                rmin=rmin[index],
                epsilon=epsilon[index],
                rmin14=None,
                epsilon14=None,
                # bonds=..., faster than connecting atoms one by one as done below?
                # angles=...,
                # dihedrals=...,
                # impropers=...,
                # polarizable=...,
            )

            residues = getattr(mmff, "substructs", None)

            if residues:
                resname, resnum = residues[index]
            else:
                raise NotImplementedError(
                    "Residues must be supplied for forcefields based on atom typing."
                )

            pff.add_atom(atom, resname, resnum, chain="", inscode="", segid="")

        # Bonds
        bonds = getattr(mmff, "bonds", None)
        if bonds is not None:
            assert (mmff.bonds.form == "Harmonic"
                    ), "Only Harmonic potential supported for now"

            spring = convert(bonds.params.spring, bonds.params.spring_units,
                             "kcal/mol/angstroms**2")
            req = convert(bonds.lengths, bonds.lengths_units, "angstroms")

            for (
                    bi,
                (
                    i,
                    j,
                    order,
                ),
            ) in enumerate(mmff.bonds.connectivity):
                btype = parmed.topologyobjects.BondType(k=spring[bi],
                                                        req=req[bi],
                                                        list=pff.bond_types)
                pff.bonds.append(
                    parmed.topologyobjects.Bond(pff.atoms[i],
                                                pff.atoms[j],
                                                order=order,
                                                type=btype))
                pff.bond_types.append(btype)
                # both implementations seem to perform almost the same:
                # pff.atoms[i].bond_to(pff.atoms[j])

        # Angles
        angles = getattr(mmff, "angles", None)
        if angles is not None:
            assert (mmff.angles.form == "Harmonic"
                    ), "Only Harmonic potential supported for now"

            spring = convert(angles.params.spring, angles.params.spring_units,
                             "kcal/mol/radians^2")
            angles_eq = convert(angles.angles, angles.angles_units, "degrees")

            for ai, (i, j, k) in enumerate(mmff.angles.connectivity):
                atype = parmed.topologyobjects.AngleType(k=spring[ai],
                                                         theteq=angles_eq[ai],
                                                         list=pff.angle_types)
                pff.angles.append(
                    parmed.topologyobjects.Angle(pff.atoms[i],
                                                 pff.atoms[j],
                                                 pff.atoms[k],
                                                 type=atype))
                pff.angle_types.append(atype)

        # Dihedrals
        dihedrals = getattr(mmff, "dihedrals", None)
        if dihedrals is not None:
            dihedrals = (
                dihedrals.pop() if isinstance(dihedrals, list) else dihedrals
            )  # For now, keep track of only a single type
            # Need to change this ASAP! Must take multiple types into account!
            assert (dihedrals.form == "Charmm"
                    or dihedrals.form == "CharmmMulti"
                    ), "Only Charmm-style potentials supported for now"

            energy = convert(dihedrals.params.energy,
                             dihedrals.params.energy_units, "kcal/mol")
            phase = convert(dihedrals.params.phase,
                            dihedrals.params.phase_units, "degrees")
            periodicity = dihedrals.params.periodicity

            for di, (i, j, k, l) in enumerate(dihedrals.connectivity):
                if isinstance(energy[di], Iterable):
                    dtype = [
                        parmed.topologyobjects.DihedralType(
                            phi_k=energy[di][dj],
                            per=periodicity[di][dj],
                            phase=phase[di][dj],
                            # scee,
                            # scnb,
                            list=pff.dihedral_types,
                        ) for dj in range(len(energy[di]))
                    ]
                else:
                    dtype = parmed.topologyobjects.DihedralType(
                        phi_k=energy[di],
                        per=periodicity[di],
                        phase=phase[di],
                        # scee
                        # scnb
                        list=pff.dihedral_types,
                    )
                # assert:
                # dtype.funct = (
                #    9  # hackish: assume all dihedrals are proper and charmm-style
                # )
                pff.dihedrals.append(
                    parmed.topologyobjects.Dihedral(
                        pff.atoms[i],
                        pff.atoms[j],
                        pff.atoms[k],
                        pff.atoms[l],
                        improper=False,
                        type=dtype,
                    ))
                pff.dihedral_types.append(dtype)

        return True, OutputTrans(
            schema_version=1,
            schema_name=inputs.schema_name,
            proc_input=inputs,
            data_object=pff,
            success=True,
            provenance=provenance_stamp,
        )
예제 #3
0
    def _get_dihedrals(self, funct, ff):

        assert dihedral_types.get(
            funct), f"Functional form {funct} not supported in mmic_parmed"

        dihedrals_units = dihedral_types[
            funct].default_units  # model param units
        dihedrals_units.update(
            forcefield.bonded.Dihedrals.default_units)  # global param units

        dihedral_phi_factor = convert(
            1.0,
            "kcal/mol",  # dihedrals_type.uphi_k.unit.get_name(),
            dihedrals_units["energy_units"],
        )
        dihedral_phase_factor = convert(
            1.0,
            "degrees",  # dihedrals_type.uphase.unit.get_name(),
            dihedrals_units["phase_units"],
        )
        dihedrals_indices = [(
            dihedral.atom1.idx,
            dihedral.atom2.idx,
            dihedral.atom3.idx,
            dihedral.atom4.idx,
        ) for dihedral in ff.dihedrals]

        # Need to look into per, scee, and scnb ... their meaning, units, etc.
        if funct == 9:  # multi-dihedrals ... special case
            phase, energy, per = [], [], []
            for dihedral in ff.dihedrals:
                if dihedral.funct == funct:
                    phase.append([
                        item.phase * dihedral_phase_factor
                        for item in dihedral.type
                    ])
                    energy.append([
                        item.phi_k * dihedral_phi_factor
                        for item in dihedral.type
                    ])
                    per.append([item.per for item in dihedral.type])
        else:
            dihedrals_params = [[(
                dihedral.type.phase * dihedral_phase_factor,
                dihedral.type.phi_k * dihedral_phi_factor,
                dihedral.type.per,
                dihedral.type.scee,
                dihedral.type.scnb,
            )] for dihedral in ff.dihedrals if dihedral.funct == funct]
            phase, energy, per, _, _ = zip(*dihedrals_params)

        params = dihedral_types.get(funct)(
            phase=phase,
            energy=energy,
            periodicity=per,
        )

        return forcefield.bonded.Dihedrals(
            params=params,
            connectivity=dihedrals_indices,
            form=dihedral_types.get(funct).__name__,
        )
예제 #4
0
    def execute(
        self,
        inputs: InputTrans,
        extra_outfiles: Optional[List[str]] = None,
        extra_commands: Optional[List[str]] = None,
        scratch_name: Optional[str] = None,
        timeout: Optional[int] = None,
    ) -> Tuple[bool, OutputTrans]:

        if isinstance(inputs, dict):
            inputs = self.input(**inputs)

        ff = inputs.data_object
        mm_units = forcefield.ForceField.default_units

        # Need to map these to potential types
        lj_units = forcefield.nonbonded.potentials.lenjones.LennardJones.default_units

        atom = ff.atoms[0]
        bond = ff.bonds[0] if len(ff.bonds) else None
        angle = ff.angles[0] if len(ff.angles) else None
        dihedral = ff.dihedrals[0] if len(ff.dihedrals) else None

        data = [(
            atom.name,
            atom.type,
            atom.charge,
            atom.mass,
            atom.atomic_number,
            atom.element_name,
        ) for atom in ff.atoms]
        names, types, charges, masses, atomic_numbers, elements = zip(*data)
        charges = convert(charges, atom.ucharge.unit.get_symbol(),
                          mm_units["charges_units"])
        masses = convert(masses, atom.umass.unit.get_symbol(),
                         mm_units["masses_units"])

        rmin_factor = convert(1.0, atom.urmin.unit.get_name(),
                              lj_units["sigma_units"])
        rmin_14_factor = convert(1.0, atom.urmin_14.unit.get_name(),
                                 lj_units["sigma_units"])
        uepsilon_factor = convert(1.0, atom.uepsilon.unit.get_name(),
                                  lj_units["epsilon_units"])
        epsilon_14_factor = convert(1.0, atom.uepsilon_14.unit.get_name(),
                                    lj_units["epsilon_units"])
        scaling_factor = 1.0 / 2**(1.0 / 6.0)  # rmin = 2^(1/6) sigma
        params = [(
            atom.rmin * rmin_factor * scaling_factor,
            atom.rmin_14 * rmin_14_factor * scaling_factor,
            atom.epsilon * uepsilon_factor,
            atom.epsilon_14 * epsilon_14_factor,
        ) for atom in ff.atoms]
        sigma, sigma_14, epsilon, epsilon_14 = zip(*params)

        lj = forcefield.nonbonded.potentials.LennardJones(sigma=sigma,
                                                          epsilon=epsilon)
        # Need to include sigma_14 and epsilon_14
        nonbonded = forcefield.nonbonded.NonBonded(params=lj,
                                                   form="LennardJones")

        if bond:
            bonds_units = (forcefield.bonded.bonds.potentials.harmonic.
                           Harmonic.default_units)
            bonds_units.update(forcefield.bonded.Bonds.default_units)

            bond_req_factor = convert(1.0, bond.type.ureq.unit.get_name(),
                                      bonds_units["lengths_units"])
            bond_k_factor = convert(1.0, bond.type.uk.unit.get_name(),
                                    bonds_units["spring_units"])
            bonds_lengths = [
                bond.type.req * bond_req_factor for bond in ff.bonds
            ]

            bonds_type = [bond.funct for bond in ff.bonds]
            bonds_k = [bond.type.k * bond_k_factor for bond in ff.bonds]
            connectivity = [(bond.atom1.idx, bond.atom2.idx, bond.order or 1)
                            for bond in ff.bonds]

            unique_bonds_type = set(bonds_type)

            if len(unique_bonds_type) > 1:
                raise NotImplementedError(
                    "Multiple bond types not yet supported.")
                # params = [
                #    bond_types.get(btype)(spring=bonds_k[bonds_type == btype])
                #    for btype in unique_bonds_type
                #    if bond_types.get(btype)
                # ]
            else:
                bond_funct = unique_bonds_type.pop()
                assert (
                    bond_funct == 1
                ), "Only Harmonic bond potentials supported in mmic_parmed."
                params = bond_types.get(bond_funct)(spring=bonds_k)

            bonds = forcefield.bonded.Bonds(
                params=params,
                lengths=bonds_lengths,
                connectivity=connectivity,
                form="Harmonic",
            )
        else:
            bonds = None

        if angle:
            angles_units = (forcefield.bonded.angles.potentials.harmonic.
                            Harmonic.default_units)
            angles_units.update(forcefield.bonded.Angles.default_units)

            angle_theta_factor = convert(1.0,
                                         angle.type.utheteq.unit.get_name(),
                                         angles_units["angles_units"])
            angle_k_factor = convert(1.0, angle.type.uk.unit.get_name(),
                                     angles_units["spring_units"])
            angles_ = [
                angle.type.theteq * angle_theta_factor for angle in ff.angles
            ]
            angles_k = [angle.type.k * angle_k_factor for angle in ff.angles]
            angles_type = [angle.funct for angle in ff.angles]

            angles_indices = [(angle.atom1.idx, angle.atom2.idx,
                               angle.atom3.idx) for angle in ff.angles]

            unique_angles_type = set(angles_type)
            if len(unique_angles_type) > 1:
                raise NotImplementedError(
                    "Multiple angle types not yet supported.")
                # params = [
                #    angleTypes.get(btype)(spring=angles_k[angles_type == btype])
                #    for btype in unique_angles_type
                #    if angleTypes.get(btype)
                # ]
            else:
                angle_funct = unique_angles_type.pop()
                assert (
                    angle_funct == 1
                ), "Only Harmonic angle potentials supported in mmic_parmed."
                params = angle_types.get(angle_funct)(spring=angles_k)

            angles = forcefield.bonded.Angles(
                params=params,
                angles=angles_,
                connectivity=angles_indices,
                form="Harmonic",
            )
        else:
            angles = None

        if dihedral:
            dihedrals_funct = set(
                [di.funct for di in ff.dihedrals if di.funct != 4])
            # funct == 4: periodic improper dihedrals see https://manual.gromacs.org/documentation/2019/reference-manual/topologies/topology-file-formats.html#tab-topfile2
            dihedrals = [
                self._get_dihedrals(funct, ff) for funct in dihedrals_funct
            ]

            if len(dihedrals
                   ) == 1:  # no point in keeping a list for single-type
                dihedrals = dihedrals.pop()
        else:
            dihedrals = None

        if hasattr(ff, "residues"):
            residues = [(atom.residue.name, atom.residue.idx)
                        for atom in ff.atoms]

        # charge_groups = None ... should support charge_groups?
        exclusions = ff.nrexcl
        inclusions = None

        input_dict = {
            "masses": masses,
            "charges": charges,
            "bonds": bonds,
            "angles": angles,
            "dihedrals": dihedrals,
            "nonbonded": nonbonded,
            "exclusions": exclusions,
            "inclusions": inclusions,
            "defs": types,  # or names?
            "symbols": elements,
            "substructs": residues,
            "atomic_numbers": atomic_numbers,
        }

        ff = forcefield.ForceField(**input_dict)
        return True, OutputTrans(
            schema_version=inputs.schema_version,
            schema_name=inputs.schema_name,
            proc_input=inputs,
            schema_object=ff,
            success=True,
            provenance=provenance_stamp,
        )
예제 #5
0
    def execute(
        self,
        inputs: InputTrans,
        extra_outfiles: Optional[List[str]] = None,
        extra_commands: Optional[List[str]] = None,
        scratch_name: Optional[str] = None,
        timeout: Optional[int] = None,
    ) -> Tuple[bool, OutputTrans]:
        """
        Works for writing PDB files e.g. pmol.save("file.pdb") but fails for gro files
        TODO: need to investigate this more. Routine is also very slow. Try to vectorize.
        """
        if isinstance(inputs, dict):
            inputs = self.input(**inputs)

        mmol = inputs.schema_object
        ndim = mmol.ndim

        if ndim != 3:
            raise NotImplementedError("mmic_parmed supports only 3D molecules.")

        pmol = parmed.structure.Structure()
        natoms = len(mmol.symbols)
        atom_empty = parmed.topologyobjects.Atom()

        if mmol.masses is not None:
            masses = convert(
                mmol.masses, mmol.masses_units, atom_empty.umass.unit.get_name()
            )
        else:
            masses = None

        if mmol.partial_charges is not None:
            charges = convert(
                mmol.partial_charges,
                mmol.partial_charges_units,
                atom_empty.ucharge.unit.get_symbol(),
            )
        else:
            charges = None

        if mmol.atom_labels is not None:
            atom_labels = mmol.atom_labels
        else:
            atom_labels = None

        if mmol.atomic_numbers is not None:
            atomic_numbers = mmol.atomic_numbers
        else:
            raise NotImplementedError(
                "mmic_parmed is supported only for atomic/molecular systems. Molecule.atomic_numbers must be defined."
            )

        for index, symb in enumerate(mmol.symbols):

            label = atom_labels[index] if atom_labels is not None else None
            # name = ToolkitMolecule.check_name(name)
            atomic_number = (
                atomic_numbers[index] if atomic_numbers is not None else None
            )
            mass = None if masses is None else masses[index]
            charge = None if charges is None else charges[index]

            # Will likely lose FF-related info ... but then Molecule is not supposed to store any params specific to FFs
            atom = parmed.topologyobjects.Atom(
                list=None,
                atomic_number=atomic_number,
                name=label or "",  # do not use symb?,
                type=label,
                mass=mass,
                charge=charge,
                nb_idx=0,
                solvent_radius=0.0,
                screen=0.0,
                tree="BLA",
                join=0.0,
                irotat=0.0,
                occupancy=1.0,
                bfactor=0.0,
                altloc="",
                number=-1,
                rmin=None,
                epsilon=None,
                rmin14=None,
                epsilon14=None,
            )

            if mmol.substructs is not None:
                resname, resnum = mmol.substructs[index]
            else:
                resname, resnum = "UNK", 0
            # classparmed.Residue(name, number=- 1, chain='', insertion_code='', segid='', list=None)[source]
            pmol.add_atom(atom, resname, resnum, chain="", inscode="", segid="")

        if mmol.geometry is not None:
            pmol.coordinates = mmol.geometry.reshape(natoms, ndim)
            pmol.coordinates = convert(
                pmol.coordinates, mmol.geometry_units, pmol.positions.unit.get_name()
            )

        if mmol.velocities is not None:
            units_speed = "angstrom/picosecond"  # hard-coded in parmed 3.4.0
            pmol.velocities = mmol.velocities.reshape(natoms, ndim)
            pmol.velocities = convert(
                pmol.velocities, mmol.velocities_units, units_speed
            )

        if mmol.connectivity is not None:
            for (
                i,
                j,
                order,
            ) in mmol.connectivity:
                # pmol.atoms[i].bond_to(pmol.atoms[j])
                pmol.bonds.append(
                    parmed.topologyobjects.Bond(pmol.atoms[i], pmol.atoms[j], order)
                )
                # both implementations seem to perform almost the same

        # if mmol.angles:
        #    for i, j, k in mmol.angles:
        #        pmol.angles.append(
        #            parmed.topologyobjects.Angle(
        #                pmol.atoms[i], pmol.atoms[j], pmol.atoms[k]
        #            )
        #        )

        # if mmol.dihedrals:
        #    for i, j, k, l in mmol.dihedrals:
        #        pmol.dihedrals.append(
        #            parmed.topologyobjects.Dihedral(
        #                pmol.atoms[i], pmol.atoms[j], pmol.atoms[k], pmol.atoms[l]
        #            )
        #        )

        return True, OutputTrans(
            schema_version=inputs.schema_version,
            schema_name=inputs.schema_name,
            proc_input=inputs,
            data_object=pmol,
            success=True,
            provenance=provenance_stamp,
        )