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)
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)
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)
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')
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")