def __init__(self, reactants: List[Union[Molecule, MoleculeGraph]], products: List[Molecule], autots_variables: Dict, gen_variables: Dict, spin_multiplicity: Optional[int] = None): self.reactants = list() for reactant in reactants: self.reactants.append(mol_to_mol_graph(reactant)) self.products = list() for product in products: self.products.append(mol_to_mol_graph(product)) self.autots_variables = autots_variables self.gen_variables = gen_variables rct_sum = int(sum([m.molecule.charge for m in self.reactants])) pro_sum = int(sum([m.molecule.charge for m in self.products])) if rct_sum != pro_sum: raise ValueError("Reactant and product charges do not match!") self.autots_variables["charge"] = rct_sum nelectrons = int(sum([m.molecule._nelectrons for m in self.reactants])) if spin_multiplicity is None: if len(self.reactants) == len(self.products) == 1: self.autots_variables["multiplicity"] = self.reactants[0].molecule.spin_multiplicity else: self.autots_variables["multiplicity"] = 1 if nelectrons % 2 == 0 else 2 else: if (spin_multiplicity % 2) == (nelectrons % 2): raise ValueError("Invalid spin multiplicity: {} with {} electrons!".format(spin_multiplicity, nelectrons)) else: self.autots_variables["multiplicity"] = spin_multiplicity
def test_mol_to_mol_graph(self): mol = Molecule.from_file( (test_dir / "molecules" / "li2co3_1.xyz").as_posix()) mg = MoleculeGraph.with_local_env_strategy(mol, OpenBabelNN()) mg = metal_edge_extender(mg) self.assertEqual(mg, mol_to_mol_graph(mol))
def __init__(self, molecule: Union[Molecule, MoleculeGraph], job_type: Union[str, JaguarJobType], path: Union[str, Path], schrodinger_dir: Optional[Union[str, Path]] = "SCHRODINGER", job_name: Optional[str] = None, num_cores: Optional[int] = 40, host: Optional[str] = None, save_scratch: Optional[bool] = False, input_params: Optional[Dict] = None): """ :param molecule: :param job_type: :param path: :param schrodinger_dir: :param job_name: :param num_cores: :param host: :param save_scratch: :param input_params: """ self.molecule = mol_to_mol_graph(molecule) if isinstance(job_type, str): if job_type.lower() not in job_type_mapping: raise ValueError("Job type {} unknown!".format(job_type)) self.job_type = job_type_mapping[job_type.lower()] else: self.job_type = job_type if isinstance(path, Path): self.path = path else: self.path = Path(path) if schrodinger_dir == "SCHRODINGER": self.schrodinger_dir = Path(os.environ["SCHRODINGER"]) else: if isinstance(schrodinger_dir, str): self.schrodinger_dir = Path(schrodinger_dir) else: self.schrodinger_dir = schrodinger_dir self.job_name = job_name self.num_cores = num_cores self.host = host self.save_scratch = save_scratch self.input_params = input_params
def __init__(self, reactants: List[Union[Molecule, MoleculeGraph]], products: List[Union[Molecule, MoleculeGraph]], path: Union[str, Path], schrodinger_dir: Optional[Union[str, Path]] = "SCHRODINGER", job_name: Optional[str] = None, num_cores: Optional[int] = 40, host: Optional[str] = None, save_scratch: Optional[bool] = False, input_params: Optional[Dict] = None): """ Args: reactants (list of Molecule objects): the reactants of the reaction to be examined products (list of Molecule objects): the products of the reaction to be examined path (str): The directory where this calculation will take place. schrodinger_dir (str): A path to the Schrodinger Suite of software. This is used to call AutoTS and other utilities. By default, this is "$SCHRODINGER", which should be an environment variable set at the time of installation. job_name (str): If provided (default None), this will set the name of the job in Schrodinger's jobcontrol system. num_cores (int): How many cores should the program be parallelized over (default 40). When multiple subjobs need to be run simultaneously, AutoTS will distribute these cores automatically between subjobs host (str): Which host should the calculation be run on? By default, this is "localhost", which should generally mean that the calculation is run on the current node without using a queueing system save_scratch (bool): If True (default False), save a *.zip file containing the contents of the calculation scratch directory input_params (dict): Keywords and associated values to be provided to TSSet """ self.reactants = list() self.products = list() for reactant in reactants: self.reactants.append(mol_to_mol_graph(reactant)) for product in products: self.products.append(mol_to_mol_graph(product)) if isinstance(path, Path): self.path = path else: self.path = Path(path) if schrodinger_dir == "SCHRODINGER": self.schrodinger_dir = Path(os.environ["SCHRODINGER"]) else: if isinstance(schrodinger_dir, str): self.schrodinger_dir = Path(schrodinger_dir) else: self.schrodinger_dir = schrodinger_dir self.job_name = job_name self.num_cores = num_cores self.host = host self.save_scratch = save_scratch self.input_params = input_params
def insert_autots_calculation( self, reactants: Union[List[Molecule], List[MoleculeGraph]], products: Union[List[Molecule], List[MoleculeGraph]], spin_multiplicity: Optional[int] = None, name: Optional[str] = None, input_params: Optional[Dict] = None, tags: Optional[Dict] = None, priority: Optional[int] = None, include_reaction_graph: Optional[bool] = False, additional_data: Optional[Dict] = None): """ Add a reaction to the "queue" (self.autots_queue_collection collection). Args: reactants (list of Molecule objects): The reactants of the reaction. Can be separated molecules or a reaction complex products (list of Molecule objects): The products of the reaction. Can be separated molecules or a reaction complex name (str, or None): Name of the reaction. No input_params (Dict, or None): Dictionary with all input parameters for this calculation. These keywords and the associated values will be provided to TSSet (or, eventually, JaguarSet). tags (Dict, or None): Dictionary with some calculation metadata Ex: {"class": "production", "time": 3} priority (int, or None): To identify jobs that should or should not be run, calculations can be prioritized. The higher the priority, the more important the calculation. If the priority is None (default), then the job will not be selected unless chosen specifically by ID or other query. If the number is negative (< 0), the calculation will never be selected. include_reaction_graph (bool): Should a reaction graph be generated from the reactant and product MoleculeGraphs? This might be skipped because it can be costly to perform subgraph isomorphisms and identify the appropriate reaction graph. additional_data (dict): Any additional data that should be stored with a calculation. Returns: None """ entry = {"state": "READY"} if len(reactants) == 0 or len(products) == 0: raise ValueError("reactants and products must be non-empty lists!") entry["reactants"] = list() entry["products"] = list() for reactant in reactants: entry["reactants"].append(mol_to_mol_graph(reactant)) for product in products: entry["products"].append(mol_to_mol_graph(product)) rct_charge = sum([m.molecule.charge for m in entry["reactants"]]) pro_charge = sum([m.molecule.charge for m in entry["products"]]) if rct_charge != pro_charge: raise ValueError( "Reactants and products do not have the same charge!") entry["charge"] = rct_charge rct_nelectrons = sum( [m.molecule._nelectrons for m in entry["reactants"]]) pro_nelectrons = sum( [m.molecule._nelectrons for m in entry["products"]]) if rct_nelectrons != pro_nelectrons: raise ValueError( "Reactants and products do not have the same number of electrons!" ) entry["nelectrons"] = int(rct_nelectrons) if spin_multiplicity is None: if len(entry["reactants"]) == len(entry["products"]) == 1: entry["spin_multiplicity"] = entry["reactants"][ 0].molecule.spin_multiplicity else: entry["spin_multiplicity"] = 1 if entry[ "nelectrons"] % 2 == 0 else 2 else: if (spin_multiplicity % 2) == (entry["nelectrons"] % 2): raise ValueError( "Invalid spin multiplicity: {} with {} electrons!".format( spin_multiplicity, entry["nelectrons"])) else: entry["spin_multiplicity"] = spin_multiplicity if name is None: rct_names = [ m.molecule.composition.alphabetical_formula + "_" + str(m.molecule.charge) for m in entry["reactants"] ] pro_names = [ m.molecule.composition.alphabetical_formula + "_" + str(m.molecule.charge) for m in entry["products"] ] entry["name"] = " + ".join(rct_names) + " -> " + " + ".join( pro_names) else: entry["name"] = name entry["input"] = input_params entry["tags"] = tags entry["priority"] = priority union_rct = union_molgraph(entry["reactants"]) union_pro = union_molgraph(entry["products"]) entry["molgraph_rct"] = union_rct.as_dict() entry["molgraph_pro"] = union_pro.as_dict() if include_reaction_graph: reaction_graph = get_reaction_graphs(union_rct, union_pro, allowed_form=2, allowed_break=2, stop_at_one=True) if len(reaction_graph) == 0: raise RuntimeError("No valid reaction could be found between " "reactants and products!") entry["reaction_graph"] = reaction_graph[0].as_dict() else: entry["reaction_graph"] = None entry["reactants"] = [r.as_dict() for r in entry["reactants"]] entry["products"] = [p.as_dict() for p in entry["products"]] entry["rxnid"] = self.database["counter"].find_one_and_update( {"_id": "rxnid"}, {"$inc": { "c": 1 }}, return_document=ReturnDocument.AFTER)["c"] entry["created_on"] = datetime.datetime.now(datetime.timezone.utc) entry["updated_on"] = datetime.datetime.now(datetime.timezone.utc) entry["additional_data"] = additional_data doc = jsanitize(entry, allow_bson=True) self.database[self.autots_queue_collection].update_one( {"rxnid": doc["rxnid"]}, {"$set": doc}, upsert=True)
def insert_jaguar_calculation(self, molecule: Union[Molecule, MoleculeGraph], job_type: Union[str, JaguarJobType], name: Optional[str] = None, input_params: Optional[Dict] = None, tags: Optional[Dict] = None, priority: Optional[int] = None, additional_data: Optional[Dict] = None): """ Add a calculation to the "queue" (self.jaguar_queue_collection collection). Args: molecule (Molecule or MoleculeGraph object): the molecule to be subjected to this calculation. name (str, or None): Name of the reaction. No input_params (Dict, or None): Dictionary with all input parameters for this calculation. These keywords and the associated values will be provided to TSSet (or, eventually, JaguarSet). tags (Dict, or None): Dictionary with some calculation metadata Ex: {"class": "production", "time": 3} priority (int, or None): To identify jobs that should or should not be run, calculations can be prioritized. The higher the priority, the more important the calculation. If the priority is None (default), then the job will not be selected unless chosen specifically by ID or other query. If the number is negative (< 0), the calculation will never be selected. include_reaction_graph (bool): Should a reaction graph be generated from the reactant and product MoleculeGraphs? This might be skipped because it can be costly to perform subgraph isomorphisms and identify the appropriate reaction graph. additional_data (dict): Any additional data that should be stored with a calculation. Returns: None """ entry = {"state": "READY"} mg = mol_to_mol_graph(molecule) entry["molecule"] = mg.as_dict() entry["charge"] = mg.molecule.charge entry["nelectrons"] = int(mg.molecule._nelectrons) entry["spin_multiplicity"] = mg.molecule.spin_multiplicity if name is None: entry["name"] = entry["molecule"].composition.alphabetical_formula entry["name"] += "_" + str(entry["charge"]) entry["name"] += "({})".format(entry["spin_multiplicity"]) else: entry["name"] = name entry["input"] = input_params entry["tags"] = tags entry["priority"] = priority if isinstance(job_type, str): entry["job_type"] = job_type.lower() else: entry["job_type"] = job_type.name.lower() entry["calcid"] = self.database["counter"].find_one_and_update( {"_id": "calcid"}, {"$inc": { "c": 1 }}, return_document=ReturnDocument.AFTER)["c"] entry["created_on"] = datetime.datetime.now(datetime.timezone.utc) entry["updated_on"] = datetime.datetime.now(datetime.timezone.utc) entry["additional_data"] = additional_data doc = jsanitize(entry, allow_bson=True) self.database[self.jaguar_queue_collection].update_one( {"calcid": doc["calcid"]}, {"$set": doc}, upsert=True)