def test_element_from_symbol(self, Sodium): na = element_from_symbol("Na") assert na == Sodium na = element_from_symbol("na") assert na == Sodium na = element_from_symbol("NA") assert na == Sodium
def test_invalid_element_from_symbol(self): with pytest.raises(ElementError): na = element_from_symbol("Na0") with pytest.raises(ElementError): na = element_from_symbol("sodium") with pytest.raises(TypeError): na = element_from_symbol(11)
def set_elements(self): """Set the ele.element of each particle in the compound.""" for p in self.particles(): try: p.element = element_from_symbol(p.name) except ElementError: # This is a hack for our typed mol2 files p.element = element_from_symbol(p.name.strip("0123456789"))
def _generate_compounds(self): if self.system.monomer_sequence is not None: sequence = self.system.monomer_sequence else: sequence = "random" random.seed(self.system.seed) mb_compounds = [] for length, n in zip( self.system.polymer_lengths, self.system.n_compounds ): for i in range(n): polymer, sequence = build_molecule( self.system.molecule, length, sequence, self.system.para_weight ) self.system.molecule_sequences.append(sequence) mb_compounds.append(polymer) self.system.para += sequence.count("P") self.system.meta += sequence.count("M") mass = n * sum( [ele.element_from_symbol(p.name).mass for p in polymer.particles()] ) self.system.system_mass += mass # amu return mb_compounds
def test_element_from_mass(self, Sodium, Magnesium): na = element_from_mass(22.98) assert na == Sodium with pytest.warns(UserWarning): mg = element_from_mass(24.0, exact=False) assert mg == Magnesium with pytest.warns(UserWarning): mg = element_from_mass(24, exact=False) assert mg == Magnesium elements = element_from_mass(289.0, duplicates="none") assert elements == None elements = element_from_mass(289.0, duplicates="all") fl = element_from_symbol("Fl") uup = element_from_symbol("Uup") assert elements == (fl, uup) elements = element_from_mass(288.5, duplicates="all", exact=False) assert elements == (fl, uup)
def custom(self, file_path): system = mb.load(file_path) mass = sum( [ele.element_from_symbol(p.name).mass for p in system.particles()] ) self.system.system_mass += mass self.L = self._calculate_L() system.box = system.get_boundingbox() return system
def _proto_to_mb(proto): """ Given compound_pb2.Compound, create mb.Compound Parameters ---------- proto: compound_pb2.Compound() """ if proto.element.symbol is '': elem = None else: elem = ele.element_from_symbol(proto.element.symbol) return mb.Compound(name=proto.name, pos=[proto.pos.x, proto.pos.y, proto.pos.z], charge=proto.charge, periodicity=[proto.periodicity.x, proto.periodicity.y, proto.periodicity.z], element=elem)
def _proto_to_mb(proto): """Given compound_pb2.Compound, create mb.Compound. Parameters ---------- proto: compound_pb2.Compound() """ if proto.element.symbol == "": elem = None else: elem = ele.element_from_symbol(proto.element.symbol) lengths = [proto.periodicity.x, proto.periodicity.y, proto.periodicity.z] if np.all(np.array(lengths) != 0): box = Box(lengths) else: box = None return Compound( name=proto.name, pos=[proto.pos.x, proto.pos.y, proto.pos.z], charge=proto.charge, box=box, element=elem, )
def md_initialization(self): molecules = self.molecules functional = self.functional box = self.box cutoff = self.cutoff scf_tolerance = self.scf_tolerance basis_set = self.basis_set basis_set_filename = self.basis_set_filename potential_filename = self.potential_filename fixed_list = self.fixed_list periodicity = self.periodicity simulation_time = self.simulation_time time_step = self.time_step ensemble = self.ensemble project_name = self.project_name temperature = self.temperature pressure = self.pressure n_molecules = self.n_molecules thermostat = self.thermostat traj_type = self.traj_type traj_freq = self.traj_freq seed = self.seed initial_coordinate_filename = self.initial_coordinate_filename use_atom_name_as_symbol = self.use_atom_name_as_symbol atom_list = [] mass_list = [] symbol_list = [] for i in range(len(molecules)): current_molecule = mb.clone(molecules[i]) for particle in current_molecule.particles(): atom_list.append(particle.name) if not (use_atom_name_as_symbol): symbol_list.append(particle.element) else: symbol_list.append(particle.name) if use_atom_name_as_symbol: mass_list.append( ele.element_from_symbol("{}".format( particle.name)).mass) else: mass_list.append( ele.element_from_symbol("{}".format( particle.element)).mass) for i in range(len(molecules)): current_molecule = mb.clone(molecules[i]) for particle in current_molecule.particles(): atom_list.append(particle.name) if not (use_atom_name_as_symbol): symbol_list.append(particle.element) else: symbol_list.append(particle.name) if use_atom_name_as_symbol: mass_list.append( ele.element_from_symbol("{}".format( particle.name)).mass) else: mass_list.append( ele.element_from_symbol("{}".format( particle.element)).mass) if project_name == None: self.project_name = 'sample_project' print('project_name not specified, set as sample_project') if cutoff == None: self.cutoff = 600 print('cutoff not specified, set as 600') if scf_tolerance == None: self.scf_tolerance = 1e-6 print('scf_tolerance not specified, set as 1e-6') if basis_set_filename == None: self.basis_set_filename = 'BASIS_MOLOPT' print('basis_set_filename not defined, set as BASIS_MOLOPT') if potential_filename == None: self.potential_filename = 'GTH_POTENTIALS' print('potential_filename not specified, set as GTH_POTENTIALS') if periodicity == None: self.periodicity = 'XYZ' print('periodicity not specified, set as XYZ') if simulation_time == None: self.simulation_time = 1 * u.ps #ps print('simulation_time not specified, set as 1 ps') if time_step == None: lightest = min(mass_list) if lightest < 1.5: self.time_step = 0.5 * u.fs print( 'time_step not specified, time_step set as 0.5 fs as the lighest element has mass {} au' .format(lightest)) elif (lightest >= 1.5) and (lightest < 40): self.time_step = 1 * u.fs print( 'time_step not specified, time_step set as 1 fs as the lighest element has mass {} au' .format(lightest)) if lightest >= 40: self.time_step = 1.5 * u.fs print( 'time_step not specified, time_step set as 1.5 fs as the lighest element has mass {} au' .format(lightest)) if ensemble == None: self.ensemble = 'NVE' print('ensemble not specified, set as NVE') if temperature_ensemble(ensemble): if thermostat == None: self.thermostat = 'NOSE' if traj_type == None: self.traj_type = 'XYZ' print('output trajectory format set as XYZ') if traj_freq == None: self.traj_freq = 10 if seed == None: self.seed = 0 if self.input_filename == None: self.input_filename = self.project_name + '_md_input.inp' print('input_filename not specified, set as {}'.format( self.input_filename)) if self.output_filename == None: self.output_filename = self.project_name + '_md_output.out' print('output_filename not specified, set as {}'.format( self.output_filename)) output_pos_filename = self.project_name + "-pos-1.xyz" print('Output position filename is {}'.format(output_pos_filename)) if self.temperature is not None: self.temperature = (temperature.to('K')).value if self.pressure is not None: self.pressure = (pressure.to('bar')).value self.simulation_time = (self.simulation_time.to('fs')).value self.time_step = (self.time_step.to('fs')).value print('You can change default settings in setter.md_files')
def write_qcc_pair_input(snap, chromo_i, chromo_j, j_shift, conversion_dict=None): """Write a quantum chemical input string for chromophore pairs. Pair input requires taking periodic images into account. Input string for pySCF containing elements and positions in Angstroms (e.g., "C 0.0 0.0 0.0; H 1.54 0.0 0.0") See https://pyscf.org/quickstart.html for more information. Parameters ---------- snap : gsd.hoomd.Snapshot Atomistic simulation snapshot from a GSD file. It is expected that the lengths in this file have been converted to Angstroms. chromo_i : Chromophore One of the chromophores to be written. chromo_j : Chromophore One of the chromophores to be written. j_shift : numpy.ndarray(3) Vector to shift chromo_j. (chromo_j minimum image center - unwrapped center) conversion_dict : dictionary, default None A dictionary that maps the atom type to its element. e.g., `{'c3': C}`. An instance that maps AMBER types to their element can be found in `amber_dict`. If None is given, assume the particles already have element names. Returns ------- str The input for the MINDO3 quantum chemical calculation run in pySCF. """ box = snap.configuration.box[:3] unwrapped_pos = snap.particles.position + snap.particles.image * box # chromophore i is shifted into 0,0,0 image positions = [ i + chromo_i.image * box for i in unwrapped_pos[chromo_i.atom_ids] ] # shift chromophore j's unwrapped positions positions += [i for i in unwrapped_pos[chromo_j.atom_ids] + j_shift] atom_ids = np.concatenate((chromo_i.atom_ids, chromo_j.atom_ids)) typeids = snap.particles.typeid[atom_ids] if conversion_dict is not None: atoms = [ conversion_dict[snap.particles.types[i]].symbol for i in typeids ] else: atoms = [ ele.element_from_symbol(snap.particles.types[i]) for i in typeids ] # To determine where to add hydrogens, check the bonds that go to # particles outside of the ids provided for i, j in snap.bonds.group: if i in atom_ids and j not in atom_ids: # If bond is to chromophore j, additional shifting might be needed if i in chromo_j.atom_ids: shift = j_shift else: shift = chromo_i.image * box if conversion_dict is not None: element = conversion_dict[snap.particles.types[ snap.particles.typeid[j]]] else: element = ele.element_from_symbol( snap.particles.types[snap.particles.typeid[j]]) # If it's already a Hydrogen, just add it if element.atomic_number == 1: atoms.append(element.symbol) positions.append(unwrapped_pos[j] + shift) # If it's not a hydrogen, use the existing bond vector to # determine the direction and scale it to a more reasonable # length for C-H bond else: # Average sp3 C-H bond is 1.094 Angstrom v = unwrapped_pos[j] - unwrapped_pos[i] unit_vec = v / np.linalg.norm(v) new_pos = unit_vec * 1.094 + unwrapped_pos[i] + shift atoms.append("H") positions.append(new_pos) # Same as above but j->i instead of i->j elif j in atom_ids and i not in atom_ids: if j in chromo_j.atom_ids: shift = j_shift else: shift = chromo_i.image * box if conversion_dict is not None: element = conversion_dict[snap.particles.types[ snap.particles.typeid[i]]] else: element = ele.element_from_symbol( snap.particles.types[snap.particles.typeid[i]]) if element.atomic_number == 1: atoms.append(element.symbol) positions.append(unwrapped_pos[i] + shift) else: v = unwrapped_pos[i] - unwrapped_pos[j] unit_vec = v / np.linalg.norm(v) new_pos = unit_vec * 1.094 + unwrapped_pos[j] + shift atoms.append("H") positions.append(new_pos) # Shift center to origin positions = np.stack(positions) positions -= np.mean(positions, axis=0) qcc_input = " ".join( [f"{atom} {x} {y} {z};" for atom, (x, y, z) in zip(atoms, positions)]) return qcc_input
def write_poscar(compound, filename, lattice_constant=1.0, coord_style="cartesian"): """Write a VASP POSCAR file from a Compound. See //https://www.vasp.at formore information. Parameters ---------- compound : mbuild.Compound The Compound to write to the POSCAR file filename : str Path of the output file lattice_constant : float Scaling constant for POSCAR file, used to scale all lattice vectors and atomic coordinates (default 1.0) coord_style : str Coordinate style of atom positions 'cartesian' or 'direct' (default 'cartesian') """ try: atoms = [p.element.symbol for p in compound.particles()] except AttributeError: atoms = [ element_from_symbol(p.name).symbol for p in compound.particles() ] # This automatically sorts element names alphabetically unique_atoms = np.unique(atoms) count_list = [str(atoms.count(i)) for i in unique_atoms] # This sorts the coordinates so they are in the same # order as the elements # mBuild (nm) --> (x10) --> VASP (angstroms) sorted_xyz = compound.xyz[np.argsort(atoms)] * 10 try: lattice = compound.box.vectors except AttributeError: lattice = compound.get_boundingbox().vectors if coord_style == "direct": warnings.warn( "'direct' coord_style specified, but compound has no box " "-- using 'cartesian' instead") coord_style = "cartesian" if coord_style == "cartesian": sorted_xyz /= lattice_constant elif coord_style == "direct": sorted_xyz = sorted_xyz.dot(inv(lattice)) / lattice_constant else: raise ValueError("coord_style must be either 'cartesian' or 'direct'") with open(filename, "w") as f: f.write(filename + " - created by mBuild\n") f.write(f"\t{lattice_constant:.15f}\n") f.write("\t{0:.15f} {1:.15f} {2:.15f}\n".format(*lattice[0])) f.write("\t{0:.15f} {1:.15f} {2:.15f}\n".format(*lattice[1])) f.write("\t{0:.15f} {1:.15f} {2:.15f}\n".format(*lattice[2])) f.write("{}\n".format("\t".join(unique_atoms))) f.write("{}\n".format("\t".join(count_list))) f.write(f"{coord_style}\n") for row in sorted_xyz: f.write(f"{row[0]:.15f} {row[1]:.15f} {row[2]:.15f}\n")
def get_chromo_ids_smiles(snap, smarts_str, conversion_dict=None): """Get the atom indices in a snapshot associated with a SMARTS string. This function can be used to determine the atom indices for each chromophore. SMARTS matching depends on the molecular structures making chemical sense (e.g., aromatic structures are planar, etc). Often snapshots from molecular simulations based on classical methods (e.g., MC, MD) may have distortions that are chemically unphysical, in which case this function may not find all chromophores. A solution is to use this function on a snapshot of the initial frame of the trajectory, and then apply these indices to a later frame. Parameters ---------- snap : gsd.hoomd.Snapshot Atomistic simulation snapshot from a GSD file. It is expected that the lengths in this file have been converted to Angstroms. smarts_str : str SMARTS string used to find the atom indices. conversion_dict : dictionary, default None A dictionary that maps the atom type to its element. e.g., `{'c3': C}`. An instance that maps AMBER types to their element can be found in `amber_dict`. If None is given, assume the particles already have element names. Returns ------- list of numpy.ndarray of int atom indices of each SMARTS match Note ---- If no matches are found, a warning is raised and the pybel.Molecule object is returned for debugging. """ box = snap.configuration.box[:3] unwrapped_positions = snap.particles.position + snap.particles.image * box mol = openbabel.OBMol() for i, typeid in enumerate(snap.particles.typeid): a = mol.NewAtom() if conversion_dict is not None: element = conversion_dict[snap.particles.types[typeid]] else: element = ele.element_from_symbol(snap.particles.types[typeid]) a.SetAtomicNum(element.atomic_number) a.SetVector(*[float(x) for x in unwrapped_positions[i]]) for i, j in snap.bonds.group: # openbabel indexes atoms from 1 # AddBond(i_index, j_index, bond_order) mol.AddBond(int(i + 1), int(j + 1), 1) # This will correctly set the bond order # (necessary for smarts matching) mol.PerceiveBondOrders() mol.SetAromaticPerceived() pybelmol = pybel.Molecule(mol) smarts = pybel.Smarts(smarts_str) # shift indices by 1 atom_ids = [np.array(i) - 1 for i in smarts.findall(pybelmol)] if not atom_ids: warn(f"No matches found for smarts string {smarts_str}. " + "Please check the returned pybel.Molecule for errors.\n") return pybelmol print(f"Found {len(atom_ids)} chromophores.") return atom_ids
def read_xyz(filename, compound=None): """Read an XYZ file. The expected format is as follows: The first line contains the number of atoms in the file. The second line contains a comment, which is not read. Remaining lines, one for each atom in the file, include an elemental symbol followed by X, Y, and Z coordinates in Angstroms. Columns are expected tbe separated by whitespace. See https://openbabel.org/wiki/XYZ_(format). Parameters ---------- filename : str Path of the input file Returns ------- compound : Compound Notes ----- The XYZ file format neglects many important details, including bonds, residues, and box information. There are some other flavors of the XYZ file format and not all are guaranteed to be compatible with this reader. For example, the TINKER XYZ format is not expected to be properly read. """ if compound is None: compound = mb.Compound() guessed_elements = set() with open(filename, "r") as xyz_file: n_atoms = int(xyz_file.readline()) xyz_file.readline() coords = np.zeros(shape=(n_atoms, 3), dtype=np.float64) for row, _ in enumerate(coords): line = xyz_file.readline().split() if not line: msg = ( "Incorrect number of lines in input file. Based on the " "number in the first line of the file, {} rows of atoms " "were expected, but at least one fewer was found.") raise MBuildError(msg.format(n_atoms)) coords[row] = line[1:4] coords[row] *= 0.1 name = line[0] try: element = name.capitalize() element = element_from_symbol(element) except ElementError: if name not in guessed_elements: warn("No matching element found for {}; the particle will " "be added to the compound without an element " "attribute.".format(name)) guessed_elements.add(name) element = None particle = mb.Compound(pos=coords[row], name=name, element=element) compound.add(particle) # Verify we have read the last line by ensuring the next line in blank line = xyz_file.readline().split() if line: msg = ("Incorrect number of lines in input file. Based on the " "number in the first line of the file, {} rows of atoms " "were expected, but at least one more was found.") raise MBuildError(msg.format(n_atoms)) return compound
def system_builder(seed, chainlength=17, backbone=Alkylsilane, terminal_group='methyl', num_chains=100): """ Define system variable""" chainlength = chainlength backbone = backbone seed = seed pattern_type = "random" terminal_group = terminal_group num_chains = num_chains """ ----------------------------------- Generate amorphous silica interface ----------------------------------- """ surface_a = SilicaInterface(thickness=1.2, seed=seed) surface_b = SilicaInterface(thickness=1.2, seed=seed) """ ------------------------------------------------------ Generate prototype of functionalized alkylsilane chain ------------------------------------------------------ """ chain_prototype_A = backbone(chain_length=chainlength, terminal_group=terminal_group) chain_prototype_B = backbone(chain_length=chainlength, terminal_group=terminal_group) """ ---------------------------------------------------------- Create monolayer on surface, backfilled with hydrogen caps ---------------------------------------------------------- """ # bottom monolayer is backfilled with the other terminal group # num_chains = num_chains * a_fraction monolayer_a = SurfaceMonolayer( surface=surface_a, chains=chain_prototype_A, n_chains=num_chains, seed=seed, backfill=H(), rotate=False, ) monolayer_a.name = "Bottom" monolayer_b = SurfaceMonolayer( surface=surface_b, chains=chain_prototype_B, n_chains=num_chains, seed=seed, backfill=H(), rotate=False, ) monolayer_b.name = "Top" """ ---------------------- Create dual monolayers ---------------------- """ dual_monolayer = DualSurface(bottom=monolayer_a, top=monolayer_b, separation=2.0) """ -------------------------------------------------------- Make sure box is elongated in z to be pseudo-2D periodic -------------------------------------------------------- """ box = mb.Box(lengths=[ dual_monolayer.box.lengths[0], dual_monolayer.box.lengths[1], dual_monolayer.box.lengths[2] * 5.0 ]) dual_monolayer.box = box #dual_monolayer.periodicity += np.array([0, 0, 5.0 * box.lengths[2]]) """ ------------------------------------------------------------------- - Save to .GRO, .TOP, and .LAMMPS formats - Atom-type the system using Foyer, with parameters from the OPLS force field obtained from GROMACS. Parameters are located in a Foyer XML file in "../util/forcefield/oplsaa.xml". ------------------------------------------------------------------- """ if os.path.isfile("../util/forcefield/oplsaa.xml"): forcefield_filepath = "../util/forcefield/oplsaa.xml" elif os.path.isfile("../../../util/forcefield/oplsaa.xml"): forcefield_filepath = "../../../util/forcefield/oplsaa.xml" else: raise Exception('Forcefield file is not found') dual_monolayer.save("init.gro", residues=["Top", "Bottom"], overwrite=True) for particle in list(dual_monolayer.particles()): if 'Si' in particle.name[:2]: particle.element = element_from_symbol('Si') elif particle.name[0] == 'O': particle.element = element_from_symbol('O') else: particle.element = element_from_symbol(particle.name) structure = dual_monolayer.to_parmed(box=None, residues=["Top", "Bottom"]) ff = Forcefield(forcefield_files=forcefield_filepath) structure = ff.apply(structure) structure.combining_rule = "geometric" structure.save("init.top", overwrite=True) write_lammpsdata(filename="init.lammps", structure=structure) """ -------------------------------------- Specify index groups and write to file -------------------------------------- """ index_groups = generate_index_groups( system=dual_monolayer, terminal_group=terminal_group, freeze_thickness=0.5, ) write_monolayer_ndx(rigid_groups=index_groups, filename="init.ndx") return dual_monolayer
def read_poscar(filename, conversion=0.1): """Read a VASP POSCAR or CONTCAR file into a Compound. Parameters ---------- filename : str Path to the POSCAR file conversion : float Conversion factor multiplied to coordinates when converting between VASP units (angstroms) and mbuild units (nm) (default = 0.1) Returns ------- mbuild.Compound """ comp = mb.Compound() with open(filename, "r") as f: data = f.readlines() title = data.pop(0) comp.name = title scale = float(data.pop(0).strip()) a = np.fromiter(data.pop(0).split(), dtype="float64") b = np.fromiter(data.pop(0).split(), dtype="float64") c = np.fromiter(data.pop(0).split(), dtype="float64") lattice_vectors = np.stack((a, b, c)) # POSCAR files do not require atom types to be specified # this block handles unspecified types line = data.pop(0).split() try: n_types = np.fromiter(line, dtype="int") types = ["_" + chr(i + 64) for i in range(1, len(n_types) + 1)] # if no types exist, assign placeholder types "_A", "_B", "_C", etc except ValueError: types = line n_types = np.fromiter(data.pop(0).split(), dtype="int") total_atoms = np.sum(n_types) all_types = list( chain.from_iterable([[itype] * n for itype, n in zip(types, n_types)])) # handle optional argument "Selective dynamics" # and required arguments "Cartesian" or "Direct" switch = data.pop(0)[0].upper() # If we ever want to do something with selective dynamics, # the following lines could be uncommented # selective_dynamics = False if switch == "S": # selective_dynamics = True switch = data.pop(0)[0].upper() if switch == "C": cartesian = True else: cartesian = False # Slice is necessary to handle files using selective dynamics coords = np.stack([ np.fromiter(line.split()[:3], dtype="float64") for line in data[:total_atoms] ]) if cartesian: coords = coords * scale else: coords = coords.dot(lattice_vectors) * scale comp.box = mb.Box.from_vectors(lattice_vectors) for i, xyz in enumerate(coords): comp.add( mb.Particle( name=all_types[i], element=element_from_symbol(all_types[i]), pos=xyz * conversion, )) return comp
j_shift = centers[imin] - chromo_j.unwrapped_center chromo_i.neighbors.append([j, rel_image]) chromo_i.neighbors_delta_e.append(None) chromo_i.neighbors_ti.append(None) chromo_j.neighbors.append([i, -rel_image]) chromo_j.neighbors_delta_e.append(None) chromo_j.neighbors_ti.append(None) neighbors.append((i, j)) qcc_input = eqcc.write_qcc_pair_input(snap, chromo_i, chromo_j, j_shift, conversion_dict) qcc_pairs.append(((i, j), qcc_input)) return qcc_pairs conversion_dict = { "S1": ele.element_from_symbol("S"), "H1": ele.element_from_symbol("H"), "C5": ele.element_from_symbol("C"), "C1": ele.element_from_symbol("C"), "C4": ele.element_from_symbol("C"), "C6": ele.element_from_symbol("C"), "C8": ele.element_from_symbol("C"), "C9": ele.element_from_symbol("C"), "C3": ele.element_from_symbol("C"), "C7": ele.element_from_symbol("C"), "C2": ele.element_from_symbol("C"), "C10": ele.element_from_symbol("C"), } amber_dict = { "H": ele.element_from_symbol("H"),
def md_files(instance): molecules = instance.molecules functional = instance.functional box = instance.box cutoff = instance.cutoff scf_tolerance = instance.scf_tolerance basis_set = instance.basis_set basis_set_filename = instance.basis_set_filename potential_filename = instance.potential_filename fixed_list = instance.fixed_list periodicity = instance.periodicity simulation_time = instance.simulation_time time_step = instance.time_step ensemble = instance.ensemble project_name = instance.project_name temperature = instance.temperature pressure = instance.pressure n_molecules = instance.n_molecules thermostat = instance.thermostat traj_type = instance.traj_type traj_freq = instance.traj_freq seed = instance.seed use_atom_name_as_symbol = self.use_atom_name_as_symbol input_filename = instance.input_filename output_filename = instance.output_filename initial_coordinate_filename = instance.initial_coordinate_filename if initial_coordinate_filename is None: filled_box = mb.packing.fill_box(compound=molecules, n_compounds=n_molecules, box=box) initial_coord_file = project_name + ".xyz" filled_box.save(initial_coord_file, overwrite="True") with open(initial_coord_file, "r") as fin: data = fin.read().splitlines(True) with open(initial_coord_file, "w") as fout: fout.writelines(data[2:]) # deleting first two lines else: filled_box = mb.load(initial_coordinate_filename) initial_coord_file = project_name + ".xyz" filled_box.save(initial_coord_file, overwrite="True") with open(initial_coord_file, "r") as fin: data = fin.read().splitlines(True) with open(initial_coord_file, "w") as fout: fout.writelines(data[2:]) # deleting first two lines print("MD initial structure saved as {}".format(initial_coord_file)) atom_list = [] mass_list = [] symbol_list = [] for i in range(len(molecules)): current_molecule = mb.clone(molecules[i]) for particle in current_molecule.particles(): atom_list.append(particle.name) if not (use_atom_name_as_symbol): symbol_list.append(particle.element) else: symbol_list.append(particle.name) if use_atom_name_as_symbol: mass_list.append( ele.element_from_symbol("{}".format(particle.name)).mass) else: mass_list.append( ele.element_from_symbol("{}".format( particle.element)).mass) unique_atom_list, indices = np.unique(atom_list, return_index=True) unique_atom_list = list(unique_atom_list) unique_symbol_list = [symbol_list[i] for i in indices] num_atoms = len(atom_list) num_unique_atoms = len(unique_atom_list) mySim = sim.SIM() # setting defaults n_steps = int(simulation_time / time_step) mySim.GLOBAL.RUN_TYPE = "MD" mySim.GLOBAL.PROJECT_NAME = project_name mySim.GLOBAL.PRINT_LEVEL = "LOW" mySim.GLOBAL.SEED = seed # FORCE EVAL SECTION mySim.FORCE_EVAL.METHOD = "QUICKSTEP" mySim.FORCE_EVAL.STRESS_TENSOR = "ANALYTICAL" mySim.FORCE_EVAL.DFT.BASIS_SET_FILE_NAME = basis_set_filename mySim.FORCE_EVAL.DFT.POTENTIAL_FILE_NAME = potential_filename mySim.FORCE_EVAL.DFT.CHARGE = 0 mySim.FORCE_EVAL.DFT.MULTIPLICITY = 1 mySim.FORCE_EVAL.DFT.MGRID.CUTOFF = cutoff mySim.FORCE_EVAL.DFT.MGRID.REL_CUTOFF = 50 mySim.FORCE_EVAL.DFT.MGRID.NGRIDS = 4 mySim.FORCE_EVAL.DFT.QS.METHOD = "GPW" mySim.FORCE_EVAL.DFT.QS.EPS_DEFAULT = 1e-8 mySim.FORCE_EVAL.DFT.QS.EXTRAPOLATION = "ASPC" mySim.FORCE_EVAL.DFT.POISSON.PERIODIC = periodicity mySim.FORCE_EVAL.DFT.PRINT.E_DENSITY_CUBE.SECTION_PARAMETERS = "OFF" mySim.FORCE_EVAL.DFT.SCF.SCF_GUESS = "ATOMIC" mySim.FORCE_EVAL.DFT.SCF.MAX_SCF = 30 mySim.FORCE_EVAL.DFT.SCF.EPS_SCF = scf_tolerance mySim.FORCE_EVAL.DFT.SCF.OT.SECTION_PARAMETERS = ".TRUE." mySim.FORCE_EVAL.DFT.SCF.OT.PRECONDITIONER = "FULL_SINGLE_INVERSE" mySim.FORCE_EVAL.DFT.SCF.OT.MINIMIZER = "DIIS" mySim.FORCE_EVAL.DFT.SCF.OUTER_SCF.SECTION_PARAMETERS = ".TRUE." mySim.FORCE_EVAL.DFT.SCF.OUTER_SCF.MAX_SCF = 10 mySim.FORCE_EVAL.DFT.SCF.OUTER_SCF.EPS_SCF = 1e-6 mySim.FORCE_EVAL.DFT.SCF.PRINT.RESTART.SECTION_PARAMETERS = "OFF" mySim.FORCE_EVAL.DFT.SCF.PRINT.DM_RESTART_WRITE = ".TRUE." mySim.FORCE_EVAL.DFT.XC.XC_FUNCTIONAL.SECTION_PARAMETERS = functional mySim.FORCE_EVAL.DFT.XC.VDW_POTENTIAL.POTENTIAL_TYPE = "PAIR_POTENTIAL" mySim.FORCE_EVAL.DFT.XC.VDW_POTENTIAL.PAIR_POTENTIAL.TYPE = "DFTD3" mySim.FORCE_EVAL.DFT.XC.VDW_POTENTIAL.PAIR_POTENTIAL.PARAMETER_FILE_NAME = ( "dftd3.dat") mySim.FORCE_EVAL.DFT.XC.VDW_POTENTIAL.PAIR_POTENTIAL.REFERENCE_FUNCTIONAL = ( functional) mySim.FORCE_EVAL.DFT.XC.VDW_POTENTIAL.PAIR_POTENTIAL.R_CUTOFF = 8 mySim.FORCE_EVAL.SUBSYS.COORD.DEFAULT_KEYWORD = project_name + ".xyz" mySim.FORCE_EVAL.SUBSYS.init_atoms(num_atoms) for i in range(num_unique_atoms): mySim.FORCE_EVAL.SUBSYS.KIND[ i + 1].SECTION_PARAMETERS = unique_atom_list[i] if basis_set == [None]: mySim.FORCE_EVAL.SUBSYS.KIND[i + 1].BASIS_SET = basis_set_setter( unique_atom_list[i]) else: mySim.FORCE_EVAL.SUBSYS.KIND[i + 1].BASIS_SET = basis_set[ unique_atom_list[i]] if not (use_atom_name_as_symbol): mySim.FORCE_EVAL.SUBSYS.KIND[i + 1].ELEMENT = unique_symbol_list[i] mySim.FORCE_EVAL.SUBSYS.KIND[i + 1].POTENTIAL = potential( unique_atom_list[i], functional) mySim.FORCE_EVAL.SUBSYS.CELL.ABC = "{a} {b} {c}".format( a=10 * box.lengths[0], b=10 * box.lengths[1], c=10 * box.lengths[2]) mySim.FORCE_EVAL.SUBSYS.CELL.ALPHA_BETA_GAMMA = "{a} {b} {c}".format( a=box.angles[0], b=box.angles[1], c=box.angles[2]) # MOTION SECTION mySim.MOTION.GEO_OPT.OPTIMIZER = "BFGS" mySim.MOTION.GEO_OPT.MAX_ITER = 100 mySim.MOTION.GEO_OPT.MAX_DR = 0.003 mySim.MOTION.MD.ENSEMBLE = ensemble mySim.MOTION.MD.STEPS = n_steps mySim.MOTION.MD.TIMESTEP = time_step if temperature_ensemble(ensemble): mySim.MOTION.MD.TEMPERATURE = temperature mySim.MOTION.MD.THERMOSTAT.TYPE = thermostat mySim.MOTION.MD.THERMOSTAT.REGION = "MASSIVE" mySim.MOTION.MD.THERMOSTAT.NOSE.LENGTH = 5 mySim.MOTION.MD.THERMOSTAT.NOSE.YOSHIDA = 3 mySim.MOTION.MD.THERMOSTAT.NOSE.TIMECON = 1000.0 mySim.MOTION.MD.THERMOSTAT.NOSE.MTS = 2 if pressure_ensemble(ensemble): mySim.MOTION.MD.BAROSTAT.PRESSURE = pressure if pressure == None: print("you need to define pressure") if fixed_list is not None: mySim.MOTION.CONSTRAINT.FIXED_ATOMS.LIST = fixed_list mySim.MOTION.MD.THERMOSTAT.REGION = "GLOBAL" if periodicity == "NONE": mySim.FORCE_EVAL.DFT.POISSON.PERIODIC = "NONE" mySim.FORCE_EVAL.DFT.POISSON.POISSON_SOLVER = "WAVELET" if not is_cubic(box) and periodicity == "NONE": print( "The box should be cubic for non-periodic calculations and the box must be around 15 times the size of the molecule" ) mySim.MOTION.PRINT.STRESS.SECTION_PARAMETERS = "OFF" mySim.MOTION.PRINT.TRAJECTORY.EACH.MD = traj_freq mySim.MOTION.PRINT.TRAJECTORY.FORMAT = traj_type mySim.MOTION.PRINT.VELOCITIES.SECTION_PARAMETERS = "OFF" mySim.MOTION.PRINT.FORCES.SECTION_PARAMETERS = "OFF" mySim.MOTION.PRINT.RESTART_HISTORY.SECTION_PARAMETERS = "ON" mySim.MOTION.PRINT.RESTART_HISTORY.EACH.MD = 500 mySim.MOTION.PRINT.RESTART.SECTION_PARAMETERS = "ON" mySim.MOTION.PRINT.RESTART.BACKUP_COPIES = 3 mySim.MOTION.PRINT.RESTART.EACH.MD = 1 mySim.write_changeLog(fn="md-changeLog.out") mySim.write_errorLog() mySim.write_inputFile(fn=input_filename) print("MD input file saved as {}".format(input_filename))
def calculate_charges(smiles, resp_type, overwrite=False): """Run the charge calculation for the molecule defined by smiles Parameters ---------- smiles: str the smiles string of the desired molecule resp_type: str the type of RESP to perform (RESP1 or RESP2) overwrite: boolean, optional, default=False overwrite the previous results if they exist Returns ------- Raises ------ InvalidMoleculeError if the smiles string is invalid """ smiles = canonicalize_smiles(smiles) iupac_name = smiles_to_iupac(smiles) mol_path = Path.joinpath(LIB_PATH, iupac_name) if not mol_path.is_dir(): raise RESPLibraryError( f"The directory for the molecule: '{smiles}' with name " f"{iupac_name} does not exist. Please run " "resp_library.prepare_charge_calculation() first.") # Initialize RESP stuff resp_type = resp_type.upper() resp_path = Path.joinpath(mol_path, resp_type) if not overwrite: if Path.joinpath(resp_path, "results").is_dir(): raise RESPLibraryError( "It appears that this partial charge calculation " "has already been attempted or performed. If you wish " "to re-run this calculation, please remove the 'results', " "'structures', and 'esp_grids' directories before proceeding.") inp, log = _initialize_resp(resp_path) # Molecule definition assert inp['smiles'] == smiles assert inp['resp']['type'].upper() == resp_type # Parse the YAML file n_conformers = inp['conformer_generation']['n_conformers'] rms_threshold = inp['conformer_generation']['rms_threshold'] energy_threshold = inp['conformer_generation'][ 'energy_threshold'] # kJ/mol conformer_seed = inp['conformer_generation']['random_seed'] charge_constraints = inp['resp']['charge_constraints'] equality_constraints = inp['resp']['equality_constraints'] point_density = inp['resp']['point_density'] vdw_scale_factors = inp['resp']['vdw_scale_factors'] if resp_type == "RESP1": esp_method = "hf" esp_basis_set = "6-31g*" elif resp_type == "RESP2": esp_method = "pw6b95" esp_basis_set = "aug-cc-pV(D+d)Z" else: raise RESPLibraryError( "Invalid RESP type. Only 'RESP1' and 'RESP2' are supported.") # Create the molecule, add H's rdmol = Chem.MolFromSmiles(smiles) rdmol = Chem.AddHs(rdmol) # Get the net charge net_charge = Chem.rdmolops.GetFormalCharge(rdmol) log.log(f"The net charge is {net_charge}.") # Get the elements elements = [a.GetSymbol() for a in rdmol.GetAtoms()] vdw_radii = { elem_sym: ele.element_from_symbol(elem_sym).radius_bondi for elem_sym in elements } # Generate conformers cids = AllChem.EmbedMultipleConfs(rdmol, numConfs=500, pruneRmsThresh=rms_threshold, randomSeed=conformer_seed) AllChem.AlignMolConformers(rdmol) if len(cids) < n_conformers: raise ValueError( "Not enough conformers found. Please reduce the " "'rms_threshold' or the 'n_conformers'. For molecules " "with < 5 atoms it may be difficult to generate more " "than 2 conformers.") # Select n_conformers at random np.random.seed(conformer_seed) conformer_ids = np.random.choice([i for i in range(len(cids))], size=n_conformers, replace=False) remove = [i for i in range(len(cids)) if i not in conformer_ids] for idx in remove: rdmol.RemoveConformer(idx) # Renumber conformers for idx, c in enumerate(rdmol.GetConformers()): c.SetId(idx) optimized_p4molecules = [] optimized_energies = [] # For each conformer, geometry optimize with psi4 for conformer in rdmol.GetConformers(): p4mol = build_p4mol(elements, conformer.GetPositions(), net_charge) p4mol, energy = _geometry_optimize(p4mol, resp_type) # Save optimized structure and energy optimized_p4molecules.append(p4mol) optimized_energies.append(energy) # Extract optimized coordinates; update coordinates RDKIT molecule coords = p4mol.geometry().to_array() * BOHR_TO_ANGSTROM for i in range(rdmol.GetNumAtoms()): x, y, z = coords[i] conformer.SetAtomPosition(i, Point3D(x, y, z)) # Check energies and remove high energy conformers _check_relative_conformer_energies(rdmol, optimized_p4molecules, optimized_energies, energy_threshold, log) # Align conformers for easier visual comparison _save_aligned_conformers(rdmol, log) # Save the conformers used for resp Path("structures/optimized_geometries.pdb").write_text( Chem.rdmolfiles.MolToPDBBlock(rdmol)) log.log( "Wrote the final optimized gemoetries to 'optimized_geometries.pdb'.\n\n" ) # Finally we do multi-conformer RESP pcm = False charges = _perform_resp( optimized_p4molecules, charge_constraints, equality_constraints, esp_method, esp_basis_set, pcm, point_density, vdw_radii, vdw_scale_factors, log, ) _write_results(elements, charges, equality_constraints, "vacuum", log) if resp_type == "RESP2": pcm = True charges = _perform_resp( optimized_p4molecules, charge_constraints, equality_constraints, esp_method, esp_basis_set, pcm, point_density, vdw_radii, vdw_scale_factors, log, ) _write_results(elements, charges, equality_constraints, "pcm", log) log.close()
def single_molecule_opt_files(instance): molecule = instance.molecule functional = instance.functional box = instance.box cutoff = instance.cutoff scf_tolerance = instance.scf_tolerance basis_set = instance.basis_set basis_set_filename = instance.basis_set_filename potential_filename = instance.potential_filename fixed_list = instance.fixed_list periodicity = instance.periodicity n_iter = instance.n_iter use_atom_name_as_symbol = self.use_atom_name_as_symbol input_filename = instance.input_filename output_filename = instance.output_filename name = molecule.name filled_box = mb.packing.fill_box(molecule, 1, box) mol_unopt_coord = name + "_unoptimized_coord.xyz" # opt_inp_file=name+'_optimization_input.inp' filled_box.save(mol_unopt_coord, overwrite="True") with open(mol_unopt_coord, "r") as fin: data = fin.read().splitlines(True) with open(mol_unopt_coord, "w") as fout: fout.writelines(data[2:]) # deleting first two lines print("Initial structure saved as {}".format(mol_unopt_coord)) molecules = [molecule] atom_list = [] mass_list = [] symbol_list = [] for i in range(len(molecules)): current_molecule = mb.clone(molecules[i]) for particle in current_molecule.particles(): atom_list.append(particle.name) if not (use_atom_name_as_symbol): symbol_list.append(particle.element) else: symbol_list.append(particle.name) if use_atom_name_as_symbol: mass_list.append( ele.element_from_symbol("{}".format(particle.name)).mass) else: mass_list.append( ele.element_from_symbol("{}".format( particle.element)).mass) unique_atom_list, indices = np.unique(atom_list, return_index=True) unique_atom_list = list(unique_atom_list) unique_symbol_list = [symbol_list[i] for i in indices] num_atoms = len(atom_list) num_unique_atoms = len(unique_atom_list) mySim = sim.SIM() # setting defaults mySim.GLOBAL.RUN_TYPE = "GEO_OPT" mySim.GLOBAL.PROJECT_NAME = name + "_opt" mySim.GLOBAL.PRINT_LEVEL = "LOW" # FORCE EVAL SECTION mySim.FORCE_EVAL.METHOD = "QUICKSTEP" mySim.FORCE_EVAL.SUBSYS.CELL.ABC = "{a} {b} {c}".format( a=10 * box.lengths[0], b=10 * box.lengths[1], c=10 * box.lengths[2]) mySim.FORCE_EVAL.SUBSYS.CELL.ALPHA_BETA_GAMMA = "{a} {b} {c}".format( a=box.angles[0], b=box.angles[1], c=box.angles[2]) mySim.FORCE_EVAL.SUBSYS.CELL.PERIODIC = periodicity mySim.FORCE_EVAL.SUBSYS.COORD.DEFAULT_KEYWORD = mol_unopt_coord mySim.FORCE_EVAL.SUBSYS.init_atoms(num_atoms) for i in range(num_unique_atoms): mySim.FORCE_EVAL.SUBSYS.KIND[ i + 1].SECTION_PARAMETERS = unique_atom_list[i] if basis_set == [None]: mySim.FORCE_EVAL.SUBSYS.KIND[i + 1].BASIS_SET = basis_set_setter( unique_atom_list[i]) else: mySim.FORCE_EVAL.SUBSYS.KIND[i + 1].BASIS_SET = basis_set[ unique_atom_list[i]] if not (use_atom_name_as_symbol): mySim.FORCE_EVAL.SUBSYS.KIND[i + 1].ELEMENT = unique_symbol_list[i] mySim.FORCE_EVAL.SUBSYS.KIND[i + 1].POTENTIAL = potential( unique_atom_list[i], functional) mySim.FORCE_EVAL.DFT.BASIS_SET_FILE_NAME = basis_set_filename mySim.FORCE_EVAL.DFT.POTENTIAL_FILE_NAME = potential_filename mySim.FORCE_EVAL.DFT.QS.EPS_DEFAULT = 1e-7 mySim.FORCE_EVAL.DFT.MGRID.CUTOFF = cutoff mySim.FORCE_EVAL.DFT.MGRID.REL_CUTOFF = 50 mySim.FORCE_EVAL.DFT.MGRID.NGRIDS = 4 mySim.FORCE_EVAL.DFT.XC.XC_FUNCTIONAL.SECTION_PARAMETERS = functional mySim.FORCE_EVAL.DFT.XC.VDW_POTENTIAL.POTENTIAL_TYPE = "PAIR_POTENTIAL" mySim.FORCE_EVAL.DFT.XC.VDW_POTENTIAL.PAIR_POTENTIAL.TYPE = "DFTD3" mySim.FORCE_EVAL.DFT.XC.VDW_POTENTIAL.PAIR_POTENTIAL.PARAMETER_FILE_NAME = ( "dftd3.dat") mySim.FORCE_EVAL.DFT.XC.VDW_POTENTIAL.PAIR_POTENTIAL.REFERENCE_FUNCTIONAL = ( functional) mySim.FORCE_EVAL.DFT.XC.VDW_POTENTIAL.PAIR_POTENTIAL.R_CUTOFF = 8 mySim.FORCE_EVAL.DFT.SCF.SCF_GUESS = "ATOMIC" mySim.FORCE_EVAL.DFT.SCF.MAX_SCF = 30 mySim.FORCE_EVAL.DFT.SCF.EPS_SCF = scf_tolerance if periodicity == "NONE": mySim.FORCE_EVAL.DFT.POISSON.PERIODIC = "NONE" mySim.FORCE_EVAL.DFT.POISSON.POISSON_SOLVER = "WAVELET" print( "The box should be cubic for non-periodic calculations and the box must be around 15 times the size of the molecule when periodicity is NONE" ) if not is_cubic(box) and periodicity == "NONE": print( "The box should be cubic for non-periodic calculations and the box must be around 15 times the size of the molecule" ) mySim.MOTION.GEO_OPT.TYPE = "MINIMIZATION" mySim.MOTION.GEO_OPT.OPTIMIZER = "BFGS" mySim.MOTION.GEO_OPT.MAX_ITER = n_iter mySim.MOTION.GEO_OPT.MAX_DR = 1e-3 mySim.MOTION.CONSTRAINT.FIXED_ATOMS.LIST = fixed_list mySim.write_changeLog(fn="mol_opt-changeLog.out") mySim.write_errorLog() mySim.write_inputFile(fn=input_filename) print("Molecule optimization file saved as {}".format(input_filename))
def write_qcc_inp(snap, atom_ids, conversion_dict=None): """Write a quantum chemical input string. Input string for pySCF containing elements and positions in Angstroms (e.g., "C 0.0 0.0 0.0; H 1.54 0.0 0.0") See https://pyscf.org/quickstart.html for more information. Parameters ---------- snap : gsd.hoomd.Snapshot Atomistic simulation snapshot from a GSD file. It is expected that the lengths in this file have been converted to Angstroms. atom_ids : numpy.ndarray of int Snapshot indices of the particles to include in the input string. conversion_dict : dictionary, default None A dictionary that maps the atom type to its element. e.g., `{'c3': C}`. An instance that maps AMBER types to their element can be found in `amber_dict`. If None is given, assume the particles already have element names. Returns ------- str The input for the MINDO3 quantum chemical calculation run in pySCF. """ atoms = [] positions = [] box = snap.configuration.box[:3] unwrapped_pos = snap.particles.position + snap.particles.image * box for i in atom_ids: if conversion_dict is not None: element = conversion_dict[snap.particles.types[ snap.particles.typeid[i]]] else: element = ele.element_from_symbol( snap.particles.types[snap.particles.typeid[i]]) atoms.append(element.symbol) positions.append(unwrapped_pos[i]) # To determine where to add hydrogens, check the bonds that go to # particles outside of the ids provided for i, j in snap.bonds.group: if i in atom_ids and j not in atom_ids: if conversion_dict is not None: element = conversion_dict[snap.particles.types[ snap.particles.typeid[j]]] else: element = ele.element_from_symbol( snap.particles.types[snap.particles.typeid[j]]) # If it's already a Hydrogen, just add it if element.atomic_number == 1: atoms.append(element.symbol) positions.append(unwrapped_pos[j]) # If it's not a hydrogen, use the existing bond vector to # determine the direction and scale it to a more reasonable # length for C-H bond else: # Average sp3 C-H bond is 1.094 Angstrom v = unwrapped_pos[j] - unwrapped_pos[i] unit_vec = v / np.linalg.norm(v) new_pos = unit_vec * 1.094 + unwrapped_pos[i] atoms.append("H") positions.append(new_pos) # Same as above but j->i instead of i->j elif j in atom_ids and i not in atom_ids: if conversion_dict is not None: element = conversion_dict[snap.particles.types[ snap.particles.typeid[i]]] else: element = ele.element_from_symbol( snap.particles.types[snap.particles.typeid[i]]) if element.atomic_number == 1: atoms.append(element.symbol) positions.append(unwrapped_pos[i]) else: v = unwrapped_pos[i] - unwrapped_pos[j] unit_vec = v / np.linalg.norm(v) new_pos = unit_vec * 1.094 + unwrapped_pos[j] atoms.append("H") positions.append(new_pos) # Shift center to origin positions = np.stack(positions) positions -= np.mean(positions, axis=0) qcc_input = " ".join( [f"{atom} {x} {y} {z};" for atom, (x, y, z) in zip(atoms, positions)]) return qcc_input
def find_atomtypes(structure, forcefield, max_iter=10): """Determine atomtypes for all atoms. Parameters ---------- structure : parmed.Structure, or gmso.Topology, or TopologyGraph The topology that we are trying to atomtype. If a parmed.Structure or gmso.Topology is provided, it will be convert to a TopologyGraph before atomtyping. forcefield : AtomTypingRulesProvider, foyer.ForceField, foyer.general_forcefield.Forcefield The atomtyping rules provider object/foyer forcefield. max_iter : int, optional, default=10 The maximum number of iterations. """ # ToDo: This function eventually should be refactored into chunks # for a less painful conversion process from foyer.forcefield import Forcefield from foyer.general_forcefield import Forcefield as GeneralForcefield topology_graph = structure if isinstance(structure, Structure): topology_graph = TopologyGraph.from_parmed(structure) elif isinstance(structure, Topology): topology_graph = TopologyGraph.from_gmso_topology(structure) if isinstance(forcefield, (Forcefield, GeneralForcefield)): forcefield = AtomTypingRulesProvider.from_foyer_forcefield(forcefield) typemap = { atom_index: { "whitelist": set(), "blacklist": set(), "atomtype": None } for atom_index in topology_graph.atoms(data=False) } rules = _load_rules(forcefield, typemap) # Only consider rules for elements found in topology subrules = dict() system_elements = set() for _, atom_data in topology_graph.atoms(data=True): # First add non-element types, which are strings, then elements name = atom_data.name if name.startswith("_"): if name in forcefield.non_element_types: system_elements.add(name) else: atomic_number = atom_data.atomic_number atomic_symbol = atom_data.element try: element_from_num = ele.element_from_atomic_number( atomic_number).symbol element_from_sym = ele.element_from_symbol( atomic_symbol).symbol assert element_from_num == element_from_sym system_elements.add(element_from_num) except ElementError: raise FoyerError("Parsed atom {} as having neither an element " "nor non-element type.".format(name)) except AssertionError: raise FoyerError( f"Parsed atom {name} has mismatching atom number ({atomic_number}) " f"and atom symbol ({atomic_symbol}).") for key, val in rules.items(): atom = val.nodes[0]["atom"] if len(list(atom.find_data("atom_symbol"))) == 1 and not list( atom.find_data("not_expression")): try: element = next(atom.find_data("atom_symbol")).children[0] except IndexError: try: atomic_num = next(atom.find_data("atomic_num")).children[0] element = ele.element_from_atomic_number(atomic_num).symbol except IndexError: element = None else: element = None if element is None or element in system_elements: subrules[key] = val rules = subrules _iterate_rules(rules, topology_graph, typemap, max_iter=max_iter) _resolve_atomtypes(topology_graph, typemap) return typemap