def test_generate(self): autots_input = TSInput([self.reactant_1, self.reactant_2], [self.product], self.autots_variables, self.gen_variables) self.assertEqual( metal_edge_extender( MoleculeGraph.with_local_env_strategy( self.reactant_1, OpenBabelNN() ) ), autots_input.reactants[0]) self.assertEqual( metal_edge_extender( MoleculeGraph.with_local_env_strategy( self.reactant_2, OpenBabelNN() ) ), autots_input.reactants[1]) self.assertEqual( metal_edge_extender( MoleculeGraph.with_local_env_strategy( self.product, OpenBabelNN() ) ), autots_input.products[0]) self.assertDictEqual(self.autots_variables, autots_input.autots_variables) self.assertDictEqual(self.gen_variables, autots_input.gen_variables)
def setUpClass(cls): if ob: EC_mg = MoleculeGraph.with_local_env_strategy( Molecule.from_file(os.path.join(test_dir, "EC.xyz")), OpenBabelNN()) cls.EC_mg = metal_edge_extender(EC_mg) LiEC_mg = MoleculeGraph.with_local_env_strategy( Molecule.from_file(os.path.join(test_dir, "LiEC.xyz")), OpenBabelNN()) cls.LiEC_mg = metal_edge_extender(LiEC_mg) LEDC_mg = MoleculeGraph.with_local_env_strategy( Molecule.from_file(os.path.join(test_dir, "LEDC.xyz")), OpenBabelNN()) cls.LEDC_mg = metal_edge_extender(LEDC_mg) LEMC_mg = MoleculeGraph.with_local_env_strategy( Molecule.from_file(os.path.join(test_dir, "LEMC.xyz")), OpenBabelNN()) cls.LEMC_mg = metal_edge_extender(LEMC_mg) cls.LiEC_reextended_entries = [] entries = loadfn( os.path.join(test_dir, "LiEC_reextended_entries.json")) for entry in entries: if "optimized_molecule" in entry["output"]: mol = entry["output"]["optimized_molecule"] else: mol = entry["output"]["initial_molecule"] E = float(entry["output"]["final_energy"]) H = float(entry["output"]["enthalpy"]) S = float(entry["output"]["entropy"]) mol_entry = MoleculeEntry( molecule=mol, energy=E, enthalpy=H, entropy=S, entry_id=entry["task_id"], ) if mol_entry.formula == "Li1": if mol_entry.charge == 1: cls.LiEC_reextended_entries.append(mol_entry) else: cls.LiEC_reextended_entries.append(mol_entry) # dumpfn(cls.LiEC_reextended_entries, "unittest_input_molentries.json") with open(os.path.join(test_dir, "unittest_RN_build.pkl"), "rb") as input: cls.RN_build = pickle.load(input) with open(os.path.join(test_dir, "unittest_RN_pr_solved.pkl"), "rb") as input: cls.RN_pr_solved = pickle.load(input)
def __init__( self, molecule: Molecule, energy: float, correction: float = 0.0, enthalpy: Optional[float] = None, entropy: Optional[float] = None, parameters: Optional[Dict] = None, entry_id: Optional[Any] = None, attribute=None, mol_graph: Optional[MoleculeGraph] = None, ): self.uncorrected_energy = energy self.correction = correction self.enthalpy = enthalpy self.entropy = entropy self.parameters = parameters if parameters else {} self.entry_id = entry_id self.attribute = attribute if not mol_graph: mol_graph = MoleculeGraph.with_local_env_strategy( molecule, OpenBabelNN()) self.mol_graph = metal_edge_extender(mol_graph) else: self.mol_graph = mol_graph
def setUpClass(cls): EC_mg = MoleculeGraph.with_local_env_strategy( Molecule.from_file(os.path.join(test_dir, "EC.xyz")), OpenBabelNN()) EC_mg = metal_edge_extender(EC_mg) LiEC_mg = MoleculeGraph.with_local_env_strategy( Molecule.from_file(os.path.join(test_dir, "LiEC.xyz")), OpenBabelNN()) LiEC_mg = metal_edge_extender(LiEC_mg) LEDC_mg = MoleculeGraph.with_local_env_strategy( Molecule.from_file(os.path.join(test_dir, "LEDC.xyz")), OpenBabelNN()) LEDC_mg = metal_edge_extender(LEDC_mg) LEMC_mg = MoleculeGraph.with_local_env_strategy( Molecule.from_file(os.path.join(test_dir, "LEMC.xyz")), OpenBabelNN()) LEMC_mg = metal_edge_extender(LEMC_mg) cls.LiEC_reextended_entries = [] entries = loadfn(os.path.join(test_dir, "LiEC_reextended_entries.json")) for entry in entries: if "optimized_molecule" in entry["output"]: mol = entry["output"]["optimized_molecule"] else: mol = entry["output"]["initial_molecule"] E = float(entry["output"]["final_energy"]) H = float(entry["output"]["enthalpy"]) S = float(entry["output"]["entropy"]) mol_entry = MoleculeEntry( molecule=mol, energy=E, enthalpy=H, entropy=S, entry_id=entry["task_id"], ) if mol_entry.formula == "Li1": if mol_entry.charge == 1: cls.LiEC_reextended_entries.append(mol_entry) else: cls.LiEC_reextended_entries.append(mol_entry) cls.entries_box = EntriesBox(cls.LiEC_reextended_entries, remove_complexes=False) cls.RI = ReactionIterator(cls.entries_box) cls.RN = ReactionNetwork(cls.RI, add_concerteds=False) # set up input variables cls.LEDC_ind = None cls.LiEC_ind = None cls.EC_ind = None for entry in cls.entries_box.entries_dict["C3 H4 O3"][10][0]: if EC_mg.isomorphic_to(entry.mol_graph): cls.EC_ind = entry.parameters["ind"] break for entry in cls.entries_box.entries_dict["C4 H4 Li2 O6"][17][0]: if LEDC_mg.isomorphic_to(entry.mol_graph): cls.LEDC_ind = entry.parameters["ind"] break for entry in cls.entries_box.entries_dict["C3 H4 Li1 O3"][12][1]: if LiEC_mg.isomorphic_to(entry.mol_graph): cls.LiEC_ind = entry.parameters["ind"] break cls.Li1_ind = cls.entries_box.entries_dict["Li1"][0][1][0].parameters[ "ind"] print("LEDC_ind:", cls.LEDC_ind) print("LiEC_ind:", cls.LiEC_ind) print("EC_ind:", cls.EC_ind) print("Li1_ind:", cls.Li1_ind) cls.RN_solved = copy.deepcopy(cls.RN) cls.RN_solved.solve_prerequisites([cls.EC_ind, cls.Li1_ind], weight="softplus")
def __init__(self, molecule=None, bonds_formed=None, bonds_broken=None, angles=None, torsions=None, out_of_planes=None, use_graph=False): self.molecule = molecule self.bonds_formed = bonds_formed or list() self.bonds_broken = bonds_broken or list() self.angles = angles or list() self.torsions = torsions or list() self.out_of_planes = out_of_planes or list() self.use_graph = use_graph # First, check that there are not too many coordinates given # We do not allow more than 4 driving coordinates num_coords = 0 all_coords = list() if self.bonds_formed is not None: num_coords += len(self.bonds_formed) all_coords += self.bonds_formed if self.bonds_broken is not None: num_coords += len(self.bonds_broken) all_coords += self.bonds_broken if self.angles is not None: num_coords += len(self.angles) all_coords += self.angles if self.torsions is not None: num_coords += len(self.torsions) all_coords += self.torsions if self.out_of_planes is not None: num_coords += len(self.out_of_planes) all_coords += self.out_of_planes if num_coords > 4: raise ValueError("Too many driving coordinates given! At most 4 " "driving coordinates may be used with " "GSMIsomerInput.") else: self.num_coords = num_coords self.all_coords = all_coords # Verify that all coordinates are tuples including only valid indices for coord in all_coords: for index in coord: if isinstance(index, int): if self.molecule is not None: if index >= len(self.molecule): raise ValueError( "Invalid index given for coordinate {}!". format(coord)) else: raise ValueError( "Non-integer index given for coordinate {}!".format( coord)) # Verify that coordinates are of appropriate length for their type for coord in self.bonds_broken + self.bonds_formed: if len(set(coord)) != 2: raise ValueError( "All bond coordinates should involve 2 indices!") for coord in self.angles: if len(set(coord)) != 3: raise ValueError( "All angle coordinates should involve 3 indices!") for coord in self.torsions + self.out_of_planes: if len(set(coord)) != 4: raise ValueError( "All angle coordinates should involve 4 indices!") # If allowed, use graph methods to verify the coordinates if self.use_graph and self.molecule is not None: self.molecule_graph = MoleculeGraph.with_local_env_strategy( self.molecule, OpenBabelNN()) self.molecule_graph = metal_edge_extender(self.molecule_graph) self._verify_two_atom_coords() self._verify_three_atom_coords() self._verify_four_atom_coords() else: self.molecule_graph = None
def get_entries(): if ob: LiEC_reextended_entries = [] entries = loadfn(os.path.join(test_dir, "LiEC_reextended_entries.json")) for entry in entries: if "optimized_molecule" in entry["output"]: mol = entry["output"]["optimized_molecule"] else: mol = entry["output"]["initial_molecule"] E = float(entry["output"]["final_energy"]) H = float(entry["output"]["enthalpy"]) S = float(entry["output"]["entropy"]) mol_entry = MoleculeEntry( molecule=mol, energy=E, enthalpy=H, entropy=S, entry_id=entry["task_id"], ) if mol_entry.formula == "Li1": if mol_entry.charge == 1: LiEC_reextended_entries.append(mol_entry) else: LiEC_reextended_entries.append(mol_entry) RN = ReactionNetwork.from_input_entries(LiEC_reextended_entries) EC_mg = MoleculeGraph.with_local_env_strategy( Molecule.from_file(os.path.join(test_dir, "EC.xyz")), OpenBabelNN()) EC_mg = metal_edge_extender(EC_mg) LiEC_mg = MoleculeGraph.with_local_env_strategy( Molecule.from_file(os.path.join(test_dir, "LiEC.xyz")), OpenBabelNN()) LiEC_mg = metal_edge_extender(LiEC_mg) LiEC_RO_mg = MoleculeGraph.with_local_env_strategy( Molecule.from_file(os.path.join(test_dir, "LiEC_RO.xyz")), OpenBabelNN()) LiEC_RO_mg = metal_edge_extender(LiEC_RO_mg) C2H4_mg = MoleculeGraph.with_local_env_strategy( Molecule.from_file(os.path.join(test_dir, "C2H4.xyz")), OpenBabelNN()) C2H4_mg = metal_edge_extender(C2H4_mg) C1Li1O3_mg = MoleculeGraph.with_local_env_strategy( Molecule.from_file(os.path.join(test_dir, "C1Li1O3.xyz")), OpenBabelNN()) C1Li1O3_mg = metal_edge_extender(C1Li1O3_mg) LiEC_entry = None LiEC_plus_entry = None EC_minus_entry = None EC_0_entry = None EC_1_entry = None LiEC_RO_entry = None C2H4_entry = None C1Li1O3_entry = None Li_entry = None for entry in RN.entries_list: if (entry.formula == "C3 H4 O3" and entry.num_bonds == 10 and EC_mg.isomorphic_to(entry.mol_graph)): if entry.charge == -1: if EC_minus_entry is not None: if EC_minus_entry.get_free_energy( ) >= entry.get_free_energy(): EC_minus_entry = entry else: EC_minus_entry = entry elif entry.charge == 0: if EC_0_entry is not None: if EC_0_entry.get_free_energy( ) >= entry.get_free_energy(): EC_0_entry = entry else: EC_0_entry = entry elif entry.charge == 1: if EC_1_entry is not None: if EC_1_entry.get_free_energy( ) >= entry.get_free_energy(): EC_1_entry = entry else: EC_1_entry = entry elif (entry.formula == "C3 H4 Li1 O3" and entry.num_bonds == 12 and LiEC_mg.isomorphic_to(entry.mol_graph)): if entry.charge == 0: if LiEC_entry is not None: if LiEC_entry.get_free_energy( ) >= entry.get_free_energy(): LiEC_entry = entry else: LiEC_entry = entry elif entry.charge == 1: if LiEC_plus_entry is not None: if LiEC_plus_entry.get_free_energy( ) >= entry.get_free_energy(): LiEC_plus_entry = entry else: LiEC_plus_entry = entry elif (entry.formula == "C3 H4 Li1 O3" and entry.charge == 0 and entry.num_bonds == 11 and LiEC_RO_mg.isomorphic_to(entry.mol_graph)): if LiEC_RO_entry is not None: if LiEC_RO_entry.get_free_energy( ) >= entry.get_free_energy(): LiEC_RO_entry = entry else: LiEC_RO_entry = entry elif (entry.formula == "C2 H4" and entry.charge == 0 and entry.num_bonds == 5 and C2H4_mg.isomorphic_to(entry.mol_graph)): if C2H4_entry is not None: if C2H4_entry.get_free_energy() >= entry.get_free_energy(): C2H4_entry = entry else: C2H4_entry = entry elif (entry.formula == "C1 Li1 O3" and entry.charge == 0 and entry.num_bonds == 5 and C1Li1O3_mg.isomorphic_to(entry.mol_graph)): if C1Li1O3_entry is not None: if C1Li1O3_entry.get_free_energy( ) >= entry.get_free_energy(): C1Li1O3_entry = entry else: C1Li1O3_entry = entry elif entry.formula == "Li1" and entry.charge == 1 and entry.num_bonds == 0: if Li_entry is not None: if Li_entry.get_free_energy() >= entry.get_free_energy(): Li_entry = entry else: Li_entry = entry return { "entries": LiEC_reextended_entries, "RN": RN, "LiEC": LiEC_entry, "LiEC_plus": LiEC_plus_entry, "EC_1": EC_1_entry, "EC_0": EC_0_entry, "EC_-1": EC_minus_entry, "LiEC_RO": LiEC_RO_entry, "C2H4": C2H4_entry, "C1Li1O3": C1Li1O3_entry, "Li": Li_entry, } else: return None
def __init__( self, molecule, edges=None, depth=1, open_rings=False, use_metal_edge_extender=False, opt_steps=10000, prev_unique_frag_dict=None, assume_previous_thoroughness=True, ): """ Standard constructor for molecule fragmentation Args: molecule (Molecule): The molecule to fragment. edges (list): List of index pairs that define graph edges, aka molecule bonds. If not set, edges will be determined with OpenBabel. Defaults to None. depth (int): The number of levels of iterative fragmentation to perform, where each level will include fragments obtained by breaking one bond of a fragment one level up. Defaults to 1. However, if set to 0, instead all possible fragments are generated using an alternative, non-iterative scheme. open_rings (bool): Whether or not to open any rings encountered during fragmentation. Defaults to False. If true, any bond that fails to yield disconnected graphs when broken is instead removed and the entire structure is optimized with OpenBabel in order to obtain a good initial guess for an opened geometry that can then be put back into QChem to be optimized without the ring just reforming. use_metal_edge_extender (bool): Whether or not to attempt to add additional edges from O, N, F, or Cl to any Li or Mg atoms present that OpenBabel may have missed. Defaults to False. Most important for ionic bonding. Note that additional metal edges may yield new "rings" (e.g. -C-O-Li-O- in LiEC) that will not play nicely with ring opening. opt_steps (int): Number of optimization steps when opening rings. Defaults to 10000. prev_unique_frag_dict (dict): A dictionary of previously identified unique fragments. Defaults to None. Typically only used when trying to find the set of unique fragments that come from multiple molecules. assume_previous_thoroughness (bool): Whether or not to assume that a molecule / fragment provided in prev_unique_frag_dict has all of its unique subfragments also provided in prev_unique_frag_dict. Defaults to True. This is an essential optimization when trying to find the set of unique fragments that come from multiple molecules if all of those molecules are being fully iteratively fragmented. However, if you're passing a prev_unique_frag_dict which includes a molecule and its fragments that were generated at insufficient depth to find all possible subfragments to a fragmentation calculation of a different molecule that you aim to find all possible subfragments of and which has common subfragments with the previous molecule, this optimization will cause you to miss some unique subfragments. """ self.assume_previous_thoroughness = assume_previous_thoroughness self.open_rings = open_rings self.opt_steps = opt_steps if edges is None: self.mol_graph = MoleculeGraph.with_local_env_strategy(molecule, OpenBabelNN()) else: edges = {(e[0], e[1]): None for e in edges} self.mol_graph = MoleculeGraph.with_edges(molecule, edges) if ("Li" in molecule.composition or "Mg" in molecule.composition) and use_metal_edge_extender: self.mol_graph = metal_edge_extender(self.mol_graph) self.prev_unique_frag_dict = prev_unique_frag_dict or {} self.new_unique_frag_dict = {} # new fragments from the given molecule not contained in prev_unique_frag_dict self.all_unique_frag_dict = {} # all fragments from just the given molecule self.unique_frag_dict = {} # all fragments from both the given molecule and prev_unique_frag_dict if depth == 0: # Non-iterative, find all possible fragments: # Find all unique fragments besides those involving ring opening self.all_unique_frag_dict = self.mol_graph.build_unique_fragments() # Then, if self.open_rings is True, open all rings present in self.unique_fragments # in order to capture all unique fragments that require ring opening. if self.open_rings: self._open_all_rings() else: # Iterative fragment generation: self.fragments_by_level = {} # Loop through the number of levels, for level in range(depth): # If on the first level, perform one level of fragmentation on the principle molecule graph: if level == 0: self.fragments_by_level["0"] = self._fragment_one_level( { str(self.mol_graph.molecule.composition.alphabetical_formula) + " E" + str(len(self.mol_graph.graph.edges())): [self.mol_graph] } ) else: num_frags_prev_level = 0 for key in self.fragments_by_level[str(level - 1)]: num_frags_prev_level += len(self.fragments_by_level[str(level - 1)][key]) if num_frags_prev_level == 0: # Nothing left to fragment, so exit the loop: break # If not on the first level, and there are fragments present in the previous level, then # perform one level of fragmentation on all fragments present in the previous level: self.fragments_by_level[str(level)] = self._fragment_one_level( self.fragments_by_level[str(level - 1)] ) if self.prev_unique_frag_dict == {}: self.new_unique_frag_dict = copy.deepcopy(self.all_unique_frag_dict) else: for frag_key in self.all_unique_frag_dict: if frag_key not in self.prev_unique_frag_dict: self.new_unique_frag_dict[frag_key] = copy.deepcopy(self.all_unique_frag_dict[frag_key]) else: for fragment in self.all_unique_frag_dict[frag_key]: found = False for prev_frag in self.prev_unique_frag_dict[frag_key]: if fragment.isomorphic_to(prev_frag): found = True if not found: if frag_key not in self.new_unique_frag_dict: self.new_unique_frag_dict[frag_key] = [fragment] else: self.new_unique_frag_dict[frag_key].append(fragment) self.new_unique_fragments = 0 for frag_key in self.new_unique_frag_dict: self.new_unique_fragments += len(self.new_unique_frag_dict[frag_key]) if self.prev_unique_frag_dict == {}: self.unique_frag_dict = self.new_unique_frag_dict self.total_unique_fragments = self.new_unique_fragments else: self.unique_frag_dict = copy.deepcopy(self.prev_unique_frag_dict) for frag_key in self.new_unique_frag_dict: if frag_key in self.unique_frag_dict: for new_frag in self.new_unique_frag_dict[frag_key]: self.unique_frag_dict[frag_key].append(new_frag) else: self.unique_frag_dict[frag_key] = copy.deepcopy(self.new_unique_frag_dict[frag_key]) self.total_unique_fragments = 0 for frag_key in self.unique_frag_dict: self.total_unique_fragments += len(self.unique_frag_dict[frag_key])