Ejemplo n.º 1
0
    def test_from_ff_and_topologies(self):
        mass = OrderedDict()
        mass["H"] = 1.0079401
        mass["O"] = 15.999400
        nonbond_coeffs = [[0.00774378, 0.98], [0.1502629, 3.1169]]
        topo_coeffs = {
            "Bond Coeffs": [{"coeffs": [176.864, 0.9611], "types": [("H", "O")]}],
            "Angle Coeffs": [
                {"coeffs": [42.1845, 109.4712], "types": [("H", "O", "H")]}
            ],
        }
        ff = ForceField(mass.items(), nonbond_coeffs, topo_coeffs)
        with gzip.open(os.path.join(test_dir, "topologies_ice.json.gz")) as f:
            topo_dicts = json.load(f)
        topologies = [Topology.from_dict(d) for d in topo_dicts]
        box = LammpsBox(
            [[-0.75694412, 44.165558], [0.38127473, 47.066074], [0.17900842, 44.193867]]
        )
        ice = LammpsData.from_ff_and_topologies(box=box, ff=ff, topologies=topologies)
        atoms = ice.atoms
        bonds = ice.topology["Bonds"]
        angles = ice.topology["Angles"]
        np.testing.assert_array_equal(atoms.index.values, np.arange(1, len(atoms) + 1))
        np.testing.assert_array_equal(bonds.index.values, np.arange(1, len(bonds) + 1))
        np.testing.assert_array_equal(
            angles.index.values, np.arange(1, len(angles) + 1)
        )

        i = random.randint(0, len(topologies) - 1)
        sample = topologies[i]
        in_atoms = ice.atoms[ice.atoms["molecule-ID"] == i + 1]
        np.testing.assert_array_equal(
            in_atoms.index.values, np.arange(3 * i + 1, 3 * i + 4)
        )
        np.testing.assert_array_equal(in_atoms["type"].values, [2, 1, 1])
        np.testing.assert_array_equal(in_atoms["q"].values, sample.charges)
        np.testing.assert_array_equal(
            in_atoms[["x", "y", "z"]].values, sample.sites.cart_coords
        )
        broken_topo_coeffs = {
            "Bond Coeffs": [{"coeffs": [176.864, 0.9611], "types": [("H", "O")]}],
            "Angle Coeffs": [
                {"coeffs": [42.1845, 109.4712], "types": [("H", "H", "H")]}
            ],
        }
        broken_ff = ForceField(mass.items(), nonbond_coeffs, broken_topo_coeffs)
        ld_woangles = LammpsData.from_ff_and_topologies(
            box=box, ff=broken_ff, topologies=[sample]
        )
        self.assertNotIn("Angles", ld_woangles.topology)
Ejemplo n.º 2
0
    def run_task(self, fw_spec):

        molecules = self["constituent_molecules"]
        mols_number = self["mols_number"]
        input_filename = self["input_filename"]
        forcefield = self["forcefield"]
        topologies = self["topologies"]

        user_settings = self.get("user_settings", {})
        data_filename = self.get("data_filename",
                                 user_settings.get("data_file", "lammps.data"))
        final_molecule = self["final_molecule"]

        # if the final molecule was generated using packmol
        if fw_spec.get("packed_mol", None):
            final_molecule = fw_spec["packed_mol"]
        elif isinstance(final_molecule, six.string_types):
            final_molecule = Molecule.from_file(final_molecule)

        #molecules, mols_number, final_molecule
        lammps_ff_data = LammpsData.from_ff_and_topologies(
            forcefield, topologies, self["box_size"])

        lammps_input_set = LammpsInputSet.from_file(
            "ff-inputset",
            self["input_file"],
            user_settings=user_settings,
            lammps_data=lammps_ff_data,
            data_filename=data_filename,
            is_forcefield=True)

        lammps_input_set.write_input(input_filename, data_filename)
Ejemplo n.º 3
0
    def test_from_ff_and_topologies(self):
        mass = OrderedDict()
        mass["H"] = 1.0079401
        mass["O"] = 15.999400
        nonbond_coeffs = [[0.00774378, 0.98], [0.1502629, 3.1169]]
        topo_coeffs = {"Bond Coeffs": [{"coeffs": [176.864, 0.9611],
                                        "types": [("H", "O")]}],
                       "Angle Coeffs": [{"coeffs": [42.1845, 109.4712],
                                         "types": [("H", "O", "H")]}]}
        ff = ForceField(mass.items(), nonbond_coeffs, topo_coeffs)
        with gzip.open(os.path.join(test_dir, "topologies_ice.json.gz")) as f:
            topo_dicts = json.load(f)
        topologies = [Topology.from_dict(d) for d in topo_dicts]
        box_bounds = [[-0.75694412, 44.165558],
                      [0.38127473, 47.066074],
                      [0.17900842, 44.193867]]
        ice = LammpsData.from_ff_and_topologies(ff=ff, topologies=topologies,
                                                box_bounds=box_bounds)
        atoms = ice.atoms
        bonds = ice.topology["Bonds"]
        angles = ice.topology["Angles"]
        np.testing.assert_array_equal(atoms.index.values,
                                      np.arange(1, len(atoms) + 1))
        np.testing.assert_array_equal(bonds.index.values,
                                      np.arange(1, len(bonds) + 1))
        np.testing.assert_array_equal(angles.index.values,
                                      np.arange(1, len(angles) + 1))

        i = random.randint(0, len(topologies) - 1)
        sample = topologies[i]
        in_atoms = ice.atoms[ice.atoms["molecule-ID"] == i + 1]
        np.testing.assert_array_equal(in_atoms.index.values,
                                      np.arange(3 * i + 1, 3 * i + 4))
        np.testing.assert_array_equal(in_atoms["type"].values, [2, 1, 1])
        np.testing.assert_array_equal(in_atoms["q"].values, sample.charges)
        np.testing.assert_array_equal(in_atoms[["x", "y", "z"]].values,
                                      sample.sites.cart_coords)
        broken_topo_coeffs = {"Bond Coeffs": [{"coeffs": [176.864, 0.9611],
                                               "types": [("H", "O")]}],
                              "Angle Coeffs": [{"coeffs": [42.1845, 109.4712],
                                                "types": [("H", "H", "H")]}]}
        broken_ff = ForceField(mass.items(), nonbond_coeffs,
                               broken_topo_coeffs)
        ld_woangles = LammpsData.from_ff_and_topologies(ff=broken_ff,
                                                        topologies=[sample],
                                                        box_bounds=box_bounds)
        self.assertNotIn("Angles", ld_woangles.topology)
Ejemplo n.º 4
0
    def write_data_file(self, organism, job_dir_path, composition_space):
        """
        Writes the file (called in.data) containing the structure that LAMMPS
        reads.

        Args:
            organism: the Organism whose structure to write

            job_dir_path: the path the job directory (as a string) where the
                file will be written

            composition_space: the CompositionSpace of the search
        """

        # get xhi, yhi and zhi from the lattice vectors
        lattice_coords = organism.cell.lattice.matrix
        xhi = lattice_coords[0][0]
        yhi = lattice_coords[1][1]
        zhi = lattice_coords[2][2]
        box_bounds = [[0.0, xhi], [0.0, yhi], [0.0, zhi]]

        # get xy, xz and yz from the lattice vectors
        xy = lattice_coords[1][0]
        xz = lattice_coords[2][0]
        yz = lattice_coords[2][1]
        box_tilts = [xy, xz, yz]

        # parse the element symbols and atom_style from the lammps input
        # script, preserving the order in which the element symbols appear
        # TODO: not all formats give the element symbols at the end of the line
        #       containing the pair_coeff keyword. Find a better way.
        elements_dict = collections.OrderedDict()
        num_elements = len(composition_space.get_all_elements())
        if num_elements == 1:
            single_element = composition_space.get_all_elements()
            elements_dict[single_element[0].symbol] = single_element[0]
        else:
            with open(self.input_script, 'r') as f:
                lines = f.readlines()
                for line in lines:
                    if 'pair_coeff' in line:
                        element_symbols = line.split()[-1 * num_elements:]
                    elif 'atom_style' in line:
                        atom_style_in_script = line.split()[1]
                for symbol in element_symbols:
                    elements_dict[symbol] = Element(symbol)

        # make a LammpsData object and use it write the in.data file
        force_field = ForceField(elements_dict.items())
        topology = Topology(organism.cell.sites)
        lammps_data = LammpsData.from_ff_and_topologies(
            force_field, [topology],
            box_bounds,
            box_tilts,
            atom_style=atom_style_in_script)
        lammps_data.write_file(job_dir_path + '/in.data')
Ejemplo n.º 5
0
    def get_ion(
        ion: Union[Ion, str],
        parameter_set: str = "auto",
        water_model: str = "auto",
        mixing_rule: Optional[str] = None,
    ) -> LammpsData:
        """
        Retrieve force field parameters for an ion in water.

        Args:
            ion: Formula of the ion (e.g., "Li+"). Not case sensitive. May be
                passed as either a string or an Ion object.
            parameter_set: Force field parameters to use for ions.
                Valid choices are:
                    1. "jj" for the Jensen and Jorgensen parameters (2006)"
                    2. "jc" for Joung-Cheatham parameters (2008)
                    3. "lm" for the Li and Merz group parameters (2020-2021)"
                The default parameter set is "auto", which assigns a recommended
                parameter set that is compatible with the chosen water model.
            water_model: Water model to use. Models must be given as a string
                (not case sensitive). "-" and "/" are ignored. Hence "tip3pfb"
                and "TIP3P-FB" are both valid inputs for the TIP3P-FB water model.
                Available water models are:
                    1. SPC
                    2. SPC/E
                    3. TIP3P-EW
                    4. TIP3P-FB
                    5. OPC3
                    6. TIP4P-EW
                    7. TIP4P-2005
                    8. TIP4P-FB
                    9. OPC
                The default water model is "auto", which assigns a recommended
                water model that is compatible with the chosen ion parameters. Other
                combinations are possible at your own risk. See documentation.

            When both the parameter_set and water_model are set to "auto", the function returns the
            Joung-Cheatham parameters for the SPC/E water model.

                For a systematic comparison of the performance of different water models, refer to

                    Sachini et al., Systematic Comparison of the Structural and Dynamic Properties of
                    Commonly Used Water Models for Molecular Dynamics Simulations. J. Chem. Inf. Model.
                    2021, 61, 9, 4521–4536. https://doi.org/10.1021/acs.jcim.1c00794

            mixing_rule: The mixing rule to use for the ion parameter. Default to None, which does not
                change the original mixing rule of the parameter set. Available choices are 'LB'
                (Lorentz-Berthelot or arithmetic) and 'geometric'. If the specified mixing rule does not
                match the default mixing rule of the parameter set, the output parameter will be converted
                accordingly.


        Returns:
            Force field parameters for the chosen water model.
        """
        alias = {
            "aq": "aqvist",
            "jj": "jensen_jorgensen",
            "jc": "joung_cheatham",
            "lm": "li_merz"
        }
        default_sets = {
            "spc": "N/A",
            "spce": "jc",
            "tip3p": "jc",
            "tip3pew": "N/A",
            "tip3pfb": "lm",
            "opc3": "lm",
            "tip4p2005": "N/A",
            "tip4p": "jj",
            "tip4pew": "jc",
            "tip4pfb": "lm",
            "opc": "lm",
            "jj": "tip4p",
            "jc": "spce",
            "lm": "tip4pfb",
        }
        water_model = water_model.replace("-", "").replace("/", "").lower()
        parameter_set = parameter_set.lower()

        if water_model == "auto" and parameter_set == "auto":
            water_model = "spce"
            parameter_set = "jc"
        elif parameter_set == "auto":
            parameter_set = default_sets.get(water_model, parameter_set)
            if parameter_set == "N/A":
                raise ValueError(
                    f"The {water_model} water model has no specifically parameterized ion parameter sets"
                    "Please try a different water model.")
        elif water_model == "auto":
            water_model = default_sets.get(parameter_set, water_model)

        parameter_set = alias.get(parameter_set, parameter_set)

        # Make the Ion object to get mass and charge
        if isinstance(ion, Ion):
            ion_obj = ion
        else:
            ion_obj = Ion.from_formula(ion.capitalize())

        # load ion data as a list of IonLJData objects
        ion_data = loadfn(os.path.join(DATA_DIR, "ion_lj_params.json"))

        # make sure the ion is in the DataFrame
        key = ion_obj.reduced_formula
        filtered_data = [d for d in ion_data if d.formula == key]
        if len(filtered_data) == 0:
            raise ValueError(
                f"Ion {key} not found in database. Please try a different ion."
            )

        # make sure the parameter set is in the DataFrame
        filtered_data = [
            d for d in filtered_data
            if d.name == parameter_set and d.water_model == water_model
        ]
        if len(filtered_data) == 0:
            raise ValueError(
                f"No {parameter_set} parameters for water model {water_model} for ion {key}. "
                "See documentation and try a different combination.")

        if len(filtered_data) != 1:
            raise ValueError(
                f"Something is wrong: multiple ion data entries for {key}, {parameter_set}, and {water_model}"
            )

        # we only consider monatomic ions at present
        # construct a cubic LammpsBox from a lattice
        lat = Lattice([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
        box = lattice_2_lmpbox(lat)[0]
        # put it in the center of a cubic Structure
        struct = Structure(lat, ion_obj, [[0.5, 0.5, 0.5]])
        # construct Topology with the ion centered in the box
        topo = Topology(struct, charges=[ion_obj.charge])

        # retrieve Lennard-Jones parameters
        # construct ForceField object
        sigma = filtered_data[0].sigma
        epsilon = filtered_data[0].epsilon
        if mixing_rule is None:
            pass
        else:
            default_mixing = filtered_data[0].combining_rule
            water_sigma = WATER_SIGMA.get(filtered_data[0].water_model)
            if mixing_rule.lower() in [
                    "lb", "arithmetic", "lorentz-berthelot",
                    "lorentz berthelot"
            ]:
                mixing_rule = "LB"
            elif mixing_rule.lower() == "geometric":
                mixing_rule = "geometric"
            else:
                raise ValueError(
                    "Invalid mixing rule. Supported mixing rules are 'LB'(arithmetic) and 'geometric'. "
                )
            if default_mixing == mixing_rule:
                pass
            elif default_mixing == "LB" and mixing_rule == "geometric":
                sigma = ((water_sigma + sigma) / 2)**2 / water_sigma
                print(
                    "The parameter mixing rule has been converted from the original 'LB' to 'geometric'.\n"
                    "Please use the parameter set with caution!")
            else:
                sigma = 2 * ((water_sigma * sigma)**(1 / 2)) - water_sigma
                print(
                    "The parameter mixing rule has been converted from the original 'geometric' to 'LB'.\n"
                    "Please use the parameter set with caution!")
        ff = ForceField([(str(e), e) for e in ion_obj.elements],
                        nonbond_coeffs=[[epsilon, sigma]])

        return LammpsData.from_ff_and_topologies(box,
                                                 ff, [topo],
                                                 atom_style="full")