From ad8e7e728741d6f3be596d97d3b593e80dac6f51 Mon Sep 17 00:00:00 2001 From: kmlefran Date: Tue, 20 Aug 2024 15:16:42 -0400 Subject: [PATCH 1/2] Break up large workchains.py into multiple files --- docs/source/tutorials/aimtogaussian.ipynb | 4 +- docs/source/tutorials/controllers.ipynb | 6 +- docs/source/tutorials/makewfx.ipynb | 4 +- docs/source/tutorials/quantumsoftware.ipynb | 4 +- .../tutorials/substituentparameter.ipynb | 2 +- pyproject.toml | 14 +- src/aiida_aimall/controllers.py | 122 +- .../{data/__init__.py => data.py} | 2 +- src/aiida_aimall/workchains.py | 1036 ----------------- src/aiida_aimall/workchains/__init__.py | 0 src/aiida_aimall/workchains/calcfunctions.py | 317 +++++ src/aiida_aimall/workchains/input.py | 96 ++ src/aiida_aimall/workchains/param_parts.py | 204 ++++ src/aiida_aimall/workchains/qc_programs.py | 264 +++++ src/aiida_aimall/workchains/subparam.py | 203 ++++ tests/conftest.py | 46 +- tests/controllers/test_aimreorcontroller.py | 2 +- .../test_gaussiansubmissioncontroller.py | 4 +- .../test_smilestogaussiancontroller.py | 8 +- .../default/aiida.chk | Bin .../default/aiida.gjf | 0 .../default/aiida.log | 0 .../default/aiida.wfx | 0 tests/workchains/test_calcfunctions.py | 46 +- tests/workchains/test_gaussiantoaim.py | 58 +- tests/workchains/test_smilestogaussian.py | 10 +- tests/workchains/test_subparamchain.py | 78 +- 27 files changed, 1240 insertions(+), 1290 deletions(-) rename src/aiida_aimall/{data/__init__.py => data.py} (98%) delete mode 100644 src/aiida_aimall/workchains.py create mode 100644 src/aiida_aimall/workchains/__init__.py create mode 100644 src/aiida_aimall/workchains/calcfunctions.py create mode 100644 src/aiida_aimall/workchains/input.py create mode 100644 src/aiida_aimall/workchains/param_parts.py create mode 100644 src/aiida_aimall/workchains/qc_programs.py create mode 100644 src/aiida_aimall/workchains/subparam.py rename tests/workchains/fixtures/{aimall.smitog16 => aimall.smitogauss}/default/aiida.chk (100%) rename tests/workchains/fixtures/{aimall.smitog16 => aimall.smitogauss}/default/aiida.gjf (100%) rename tests/workchains/fixtures/{aimall.smitog16 => aimall.smitogauss}/default/aiida.log (100%) rename tests/workchains/fixtures/{aimall.smitog16 => aimall.smitogauss}/default/aiida.wfx (100%) diff --git a/docs/source/tutorials/aimtogaussian.ipynb b/docs/source/tutorials/aimtogaussian.ipynb index 9a31fe6..76fd02a 100644 --- a/docs/source/tutorials/aimtogaussian.ipynb +++ b/docs/source/tutorials/aimtogaussian.ipynb @@ -36,8 +36,8 @@ "from aiida.engine import submit\n", "\n", "load_profile()\n", - "SmilestoGaussianWorkchain = WorkflowFactory('aimall.smitog16')\n", - "builder = SmilestoGaussianWorkchain.get_builder()\n", + "SmilestoGaussianWorkChain = WorkflowFactory('aimall.smitogauss')\n", + "builder = SmilestoGaussianWorkChain.get_builder()\n", "# methyl group\n", "builder.smiles = Str('*C')\n", "builder.gaussian_parameters = Dict(\n", diff --git a/docs/source/tutorials/controllers.ipynb b/docs/source/tutorials/controllers.ipynb index 6656b38..8911876 100644 --- a/docs/source/tutorials/controllers.ipynb +++ b/docs/source/tutorials/controllers.ipynb @@ -59,9 +59,9 @@ "aim_params = parameter_dict={\"naat\": 2, \"nproc\": 2, \"atlaprhocps\": True}\n", "smile_controller = SmilesToGaussianController(\n", " parent_group_label = 'smiles',\n", - " group_label = 'g16_opt',\n", + " group_label = 'gauss_opt',\n", " code_label='gaussian@cedar',\n", - " g16_opt_params=orm.Dict(dict={\n", + " gauss_opt_params=orm.Dict(dict={\n", " 'link0_parameters': {\n", " '%chk':'aiida.chk',\n", " \"%mem\": \"2000MB\", # Currently set to use 8000 MB in .sh files\n", @@ -91,7 +91,7 @@ " group_label = 'gaussian_sp',\n", " max_concurrent = 100,\n", " code_label='gaussian@cedar',\n", - " g16_sp_params=Dict(dict={\n", + " gauss_sp_params=Dict(dict={\n", " 'link0_parameters': {\n", " '%chk':'aiida.chk',\n", " \"%mem\": \"2000MB\",\n", diff --git a/docs/source/tutorials/makewfx.ipynb b/docs/source/tutorials/makewfx.ipynb index a26f0bf..5e0d5f6 100644 --- a/docs/source/tutorials/makewfx.ipynb +++ b/docs/source/tutorials/makewfx.ipynb @@ -67,13 +67,13 @@ "from aiida.engine import submit\n", "\n", "load_profile()\n", - "GenerateWFXToAIMWorkchain = WorkflowFactory(\"aimall.wfxtoaim\")\n", + "GenerateWFXToAIMWorkChain = WorkflowFactory(\"aimall.wfxtoaim\")\n", "AimqbParameters = DataFactory(\"aimall.aimqb\")\n", "# predefined Molden input file in database\n", "single_file = load_node(114541)\n", "aim_params = AimqbParameters({\"naat\": 2, \"nproc\": 2, \"atlaprhocps\": True})\n", "aim_code = load_code(\"aimall@localhost\")\n", - "builder = GenerateWFXToAIMWorkchain.get_builder()\n", + "builder = GenerateWFXToAIMWorkChain.get_builder()\n", "builder.input_file = single_file\n", "builder.aim_params = aim_params\n", "builder.aim_code = aim_code\n", diff --git a/docs/source/tutorials/quantumsoftware.ipynb b/docs/source/tutorials/quantumsoftware.ipynb index a52ee2b..2a9bdfd 100644 --- a/docs/source/tutorials/quantumsoftware.ipynb +++ b/docs/source/tutorials/quantumsoftware.ipynb @@ -53,7 +53,7 @@ "import ase.io\n", "from aiida.engine import submit\n", "load_profile()\n", - "GaussianToAIMWorkChain = WorkflowFactory('aimall.g16toaim')\n", + "GaussianToAIMWorkChain = WorkflowFactory('aimall.gausstoaim')\n", "AimqbParameters = DataFactory('aimall.aimqb')\n", "\n", "gaussian_input = Dict(\n", @@ -81,7 +81,7 @@ "builder.g16_params = gaussian_input\n", "builder.aim_params = aim_input\n", "builder.structure = struct_data\n", - "builder.g16_code = load_code('gaussian@localhost')\n", + "builder.gauss_code = load_code('gaussian@localhost')\n", "builder.aim_code = load_code('aimall@localhost')\n", "submit(builder)" ] diff --git a/docs/source/tutorials/substituentparameter.ipynb b/docs/source/tutorials/substituentparameter.ipynb index b35bd87..a119ec9 100644 --- a/docs/source/tutorials/substituentparameter.ipynb +++ b/docs/source/tutorials/substituentparameter.ipynb @@ -22,7 +22,7 @@ "Inputs are similar to other parts of the package that have already been used. You should provide as inputs to the builder:\n", "\n", "1. structure (`orm.StructureData`)\n", - "2. g16_code - setup code for `Gaussian` Software\n", + "2. gauss_code - setup code for `Gaussian` Software\n", "3. aim_code - setup code for `AIMQB` software\n", "4. aim_params ([AimqbParameters](../reference/api/auto/aiida_aimall/data/index.rst#aiida_aimall.data.AimqbParameters))\n", "5. g16_opt_params (`orm.Dict`) - parameters for the Gaussian optimization calculation\n", diff --git a/pyproject.toml b/pyproject.toml index 397258f..38f4a80 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,20 +79,18 @@ docs = [ [project.entry-points."aiida.calculations"] "aimall.aimqb" = "aiida_aimall.calculations:AimqbCalculation" -"aimall.gaussianwfx" = "aiida_aimall.calculations:GaussianWFXCalculation" [project.entry-points."aiida.parsers"] "aimall.base" = "aiida_aimall.parsers:AimqbBaseParser" "aimall.group" = "aiida_aimall.parsers:AimqbGroupParser" -"aimall.gaussianwfx" = "aiida_aimall.parsers:GaussianWFXParser" [project.entry-points."aiida.workflows"] -"aimall.aimreor" = "aiida_aimall.workchains:AIMAllReor" -"aimall.subparam" = "aiida_aimall.workchains:SubstituentParameterWorkChain" -"aimall.smitog16" = "aiida_aimall.workchains:SmilesToGaussianWorkchain" -"aimall.qmtoaim" = "aiida_aimall.workchains:QMToAIMWorkchain" -"aimall.g16toaim" = "aiida_aimall.workchains:GaussianToAIMWorkChain" -"aimall.wfxtoaim" = "aiida_aimall.workchains:GenerateWFXToAIMWorkchain" +"aimall.aimreor" = "aiida_aimall.workchains.param_parts:AIMAllReorWorkChain" +"aimall.subparam" = "aiida_aimall.workchains.subparam:SubstituentParameterWorkChain" +"aimall.smitogauss" = "aiida_aimall.workchains.param_parts:SmilesToGaussianWorkChain" +"aimall.qmtoaim" = "aiida_aimall.workchains.qc_programs:QMToAIMWorkChain" +"aimall.gausstoaim" = "aiida_aimall.workchains.qc_programs:GaussianToAIMWorkChain" +"aimall.wfxtoaim" = "aiida_aimall.workchains.qc_programs:GenerateWFXToAIMWorkChain" [tool.flit.module] name = "aiida_aimall" diff --git a/src/aiida_aimall/controllers.py b/src/aiida_aimall/controllers.py index cebd679..db56271 100644 --- a/src/aiida_aimall/controllers.py +++ b/src/aiida_aimall/controllers.py @@ -23,7 +23,7 @@ class SmilesToGaussianController(FromGroupSubmissionController): group_label: the string of the group to put the GaussianCalculations in max_concurrent: maximum number of concurrent processes. code_label: label of code, e.g. gaussian@cedar - g16_opt_params: Dict of Gaussian parameters to use + gauss_opt_params: Dict of Gaussian parameters to use wfxgroup: group in which to store the resulting wfx files nprocs: number of processors for gaussian calculation mem_mb: amount of memory in MB for Gaussian calculation @@ -45,7 +45,7 @@ class SmilesToGaussianController(FromGroupSubmissionController): group_label = 'gaussianopt', # Resulting nodes will be in the gaussianopt group max_concurrent = 1, wfxgroup = "opt_wfx" - g16_opt_params = Dict(dict={ + gauss_opt_params = Dict(dict={ 'link0_parameters': { '%chk':'aiida.chk', "%mem": "4000MB", @@ -69,18 +69,18 @@ class SmilesToGaussianController(FromGroupSubmissionController): group_label: str code_label: str max_concurrent: int - g16_opt_params: dict + gauss_opt_params: dict wfxgroup: str nprocs: int mem_mb: int time_s: int - WORKFLOW_ENTRY_POINT = "aimall.smitog16" + WORKFLOW_ENTRY_POINT = "aimall.smitogauss" def __init__( self, code_label: str, - g16_opt_params: dict, + gauss_opt_params: dict, wfxgroup: str, nprocs: int, mem_mb: int, @@ -90,7 +90,7 @@ def __init__( ): super().__init__(*args, **kwargs) self.code_label = code_label - self.g16_opt_params = g16_opt_params + self.gauss_opt_params = gauss_opt_params self.wfxgroup = wfxgroup self.nprocs = nprocs self.mem_mb = mem_mb @@ -107,7 +107,7 @@ def get_inputs_and_processclass_from_extras(self, extras_values): inputs = { "smiles": smiles, "gaussian_code": code, - "gaussian_parameters": Dict(self.g16_opt_params), + "gaussian_parameters": Dict(self.gauss_opt_params), "wfxgroup": Str(self.wfxgroup), "nprocs": Int(self.nprocs), "mem_mb": Int(self.mem_mb), @@ -116,102 +116,6 @@ def get_inputs_and_processclass_from_extras(self, extras_values): return inputs, WorkflowFactory(self.WORKFLOW_ENTRY_POINT) -# class G16FragController(FromGroupSubmissionController): -# """A controller for submitting G16OptWorkChain - -# Args: -# parent_group_label: the string of a group label which contains various structures as orm.Str nodes -# group_label: the string of the group to put the GaussianCalculations in -# max_concurrent: maximum number of concurrent processes. -# code_label: label of code, e.g. gaussian@cedar -# g16_opt_params: Dict of Gaussian parameters to use -# wfxgroup: group in which to store the resulting wfx files - -# Returns: -# Controller object, periodically use run_in_batches to submit new results - -# Note: -# In the typical use case, this is run on the outputs of the MultiFragmentWorkchain, which are by default added -# to the group inp_frag, so make sure `parent_group_label` matches that - -# Example: -# In a typical use case of controllers, it is beneficial to check for new jobs periodically to submit. -# Either there may be new members of the parent_group to run, or some of the currently running jobs have run. -# So once a controller is defined, we can run it in a loop. - -# :: - -# controller = G16FragController( -# code_label='gaussian@localhost', -# parent_group_label = 'struct', # Add structures to run to struct group -# group_label = 'gaussianopt', # Resulting nodes will be in the gaussianopt group -# max_concurrent = 1, -# wfxgroup = "opt_wfx" -# g16_opt_params = Dict(dict={ -# 'link0_parameters': { -# '%chk':'aiida.chk', -# "%mem": "4000MB", -# "%nprocshared": 4, -# }, -# 'functional':'wb97xd', -# 'basis_set':'aug-cc-pvtz', -# 'charge': 0, -# 'multiplicity': 1, -# 'route_parameters': {'nosymmetry':None, 'Output':'WFX', 'opt':None, 'freq':None}, -# "input_parameters": {"output.wfx": None}, -# }) -# ) - -# while True: -# #submit Gaussian batches every hour -# controller.submit_new_batch() -# time.sleep(3600) - -# """ - -# parent_group_label: str -# group_label: str -# code_label: str -# max_concurrent: int -# g16_opt_params: dict -# wfxgroup: str - -# WORKFLOW_ENTRY_POINT = "aimall.g16opt" - -# def __init__( -# self, -# code_label: str, -# g16_opt_params: dict, -# wfxgroup: str, -# *args, -# **kwargs, -# ): -# super().__init__(*args, **kwargs) -# self.code_label = code_label -# self.g16_opt_params = g16_opt_params -# self.wfxgroup = wfxgroup - -# def get_extra_unique_keys(self): -# """Returns a tuple of extras keys in the order needed""" -# return ("smiles",) - -# def get_inputs_and_processclass_from_extras(self, extras_values): -# """Constructs input for a GaussianWFXCalculation from extra_values - -# Note: adjust the metadata options later for 6400MB and 7days runtime -# """ -# code = orm.load_code(self.code_label) -# structure = self.get_parent_node_from_extras(extras_values) -# inputs = { -# "frag_label": Str(extras_values[0]), -# "fragment_dict": structure, -# "g16_code": code, -# "g16_opt_params": Dict(self.g16_opt_params), -# "wfxgroup": Str(self.wfxgroup), -# } -# return inputs, WorkflowFactory(self.WORKFLOW_ENTRY_POINT) - - class AimReorSubmissionController(FromGroupSubmissionController): """A controller for submitting AIMReor Workchains. @@ -419,7 +323,7 @@ class GaussianSubmissionController(FromGroupSubmissionController): max_concurrent: maximum number of concurrent processes. Expected behaviour is to set to a large number since we will be submitting to Cedar which will manage code_label: label of code, e.g. gaussian@cedar - g16_sp_params: dictionary of parameters to use in gaussian calculation + gauss_sp_params: dictionary of parameters to use in gaussian calculation Returns: Controller object, periodically use run_in_batches to submit new results @@ -444,7 +348,7 @@ class GaussianSubmissionController(FromGroupSubmissionController): parent_group_label = 'struct', # Add structures to run to struct group group_label = 'gaussiansp', # Resulting nodes will be in the gaussiansp group max_concurrent = 1, - g16_sp_params = Dict(dict={ + gauss_sp_params = Dict(dict={ 'link0_parameters': { '%chk':'aiida.chk', "%mem": "4000MB", @@ -470,7 +374,7 @@ class GaussianSubmissionController(FromGroupSubmissionController): group_label: str max_concurrent: int code_label: str - g16_sp_params: dict + gauss_sp_params: dict wfxgroup: str # GaussianWFXCalculation entry point as defined in aiida-aimall pyproject.toml CALCULATION_ENTRY_POINT = "gaussian" @@ -478,7 +382,7 @@ class GaussianSubmissionController(FromGroupSubmissionController): def __init__( self, code_label: str, - g16_sp_params: dict, + gauss_sp_params: dict, wfxgroup: str, *args, **kwargs, @@ -486,7 +390,7 @@ def __init__( """Initialize the class, modifying with new values""" super().__init__(*args, **kwargs) self.code_label = code_label - self.g16_sp_params = g16_sp_params + self.gauss_sp_params = gauss_sp_params self.wfxgroup = wfxgroup # @validator("code_label") @@ -515,7 +419,7 @@ def get_inputs_and_processclass_from_extras(self, extras_values): structure = self.get_parent_node_from_extras(extras_values) inputs = { "code": code, - "parameters": Dict(self.g16_sp_params), + "parameters": Dict(self.gauss_sp_params), "structure": structure, "metadata": { "options": { diff --git a/src/aiida_aimall/data/__init__.py b/src/aiida_aimall/data.py similarity index 98% rename from src/aiida_aimall/data/__init__.py rename to src/aiida_aimall/data.py index 11c0a04..36dd3cc 100644 --- a/src/aiida_aimall/data/__init__.py +++ b/src/aiida_aimall/data.py @@ -2,7 +2,7 @@ Data types provided by plugin Upon pip install, AimqbParameters is accessible in AiiDA.data plugins -Using the 'aimall' entry point +Using the 'aimall.aimqb' entry point """ from aiida.orm import Dict diff --git a/src/aiida_aimall/workchains.py b/src/aiida_aimall/workchains.py deleted file mode 100644 index 641fc84..0000000 --- a/src/aiida_aimall/workchains.py +++ /dev/null @@ -1,1036 +0,0 @@ -"""aiida_aimall.workchains -Workchains designed for a workflow starting from a set of cmls, then breaking off into fragment Gaussian Calculations -Needs to be run in part with aiida_aimall.controllers to control local traffic on lab Mac -Example in the works - -Provided Workchains are -MultiFragmentWorkchain, entry point: multifrag -G16OptWorkChain, entry point: g16opt -AimAllReor WorkChain, entry point: aimreor -""" -import io - -# pylint: disable=c-extension-no-member -# pylint:disable=no-member -# pylint:disable=too-many-lines -from string import digits - -import ase.io -from aiida.engine import ToContext, WorkChain, calcfunction, if_ -from aiida.engine.processes import ExitCode -from aiida.orm import ( - Bool, - Code, - Dict, - Int, - List, - SinglefileData, - Str, - StructureData, - load_group, -) -from aiida.orm.extras import EntityExtras -from aiida.plugins.factories import CalculationFactory, DataFactory -from aiida_shell import launch_shell_job -from rdkit import Chem -from rdkit.Chem import AllChem, rdmolops, rdqueries -from rdkit.Chem.MolKey.MolKey import BadMoleculeException -from subproptools.sub_reor import rotate_substituent_aiida - -GaussianCalculation = CalculationFactory("gaussian") -AimqbParameters = DataFactory("aimall.aimqb") -AimqbCalculation = CalculationFactory("aimall.aimqb") - - -@calcfunction -def generate_rotated_structure_aiida(FolderData, atom_dict, cc_dict): - """Rotates the fragment to the defined coordinate system - - Args: - FolderData: aim calculation folder - atom_dict: AIM atom dict - cc_dict: AIM cc_dict - """ - return Dict(rotate_substituent_aiida(FolderData, atom_dict, cc_dict)) - - -def remove(in_list): - """Remove digits from a list of strings. e.g. ['O1','H2','H3'] -> ['O','H','H']""" - remove_digits = str.maketrans("", "", digits) - out_list = [i.translate(remove_digits) for i in in_list] - return out_list - - -@calcfunction -def dict_to_structure(fragment_dict): - """Generate a string of xyz coordinates for Gaussian input file - - :param fragment_dict: - :param type fragment_dict: aiida.orm.nodes.data.dict.Dict - """ - inp_dict = fragment_dict.get_dict() - symbols = inp_dict["atom_symbols"] - symbols = remove(symbols) - coords = inp_dict["geom"] - outstr = "" - outstr += f"{len(symbols)}\n\n" - for i, symbol in enumerate(symbols): - if i != len(symbols) - 1: - outstr = ( - outstr - + symbol - + " " - + str(coords[i][0]) - + " " - + str(coords[i][1]) - + " " - + str(coords[i][2]) - + "\n" - ) - else: - outstr = ( - outstr - + symbol - + " " - + str(coords[i][0]) - + " " - + str(coords[i][1]) - + " " - + str(coords[i][2]) - ) - f = io.StringIO(outstr) - struct_data = StructureData(ase=ase.io.read(f, format="xyz")) - f.close() - return struct_data - - -def calc_multiplicity(mol): - """Calculate the multiplicity of a molecule as 2S +1""" - num_radicals = 0 - for atom in mol.GetAtoms(): - num_radicals += atom.GetNumRadicalElectrons() - multiplicity = num_radicals + 1 - return multiplicity - - -def find_attachment_atoms(mol): - """Given molecule object, find the atoms corresponding to a * and the atom to which that is bound - - Args: - mol: rdkit molecule object - - Returns: - molecule with added hydrogens, the * atom object, and the atom object to which that is attached - - Note: - Assumes that only one * is present in the molecule - """ - # * has atomic number 0 - query = rdqueries.AtomNumEqualsQueryAtom(0) - # add hydrogens now - h_mol_rw = Chem.RWMol(mol) # Change type of molecule object - h_mol_rw = Chem.AddHs(h_mol_rw) - query_ats = h_mol_rw.GetAtomsMatchingQuery(query) - if len(query_ats) != 1: - raise ValueError( - f"Molecule should have one placeholder atom with atomic number 0, found {len(query_ats)}" - ) - zero_at = query_ats[0] - # this will be bonded to one atom - whichever atom in the bond is not *, is the one we are looking for - bond = zero_at.GetBonds()[0] - begin_atom = bond.GetBeginAtom() - if begin_atom.GetSymbol() != "*": - attached_atom = begin_atom - else: - attached_atom = bond.GetEndAtom() - return h_mol_rw, zero_at, attached_atom - - -def reorder_molecule(h_mol_rw, zero_at, attached_atom): - """Reindexes the atoms in a molecule, setting attached_atom to index 0, and zero_at to index 1 - - Args: - h_mol_rw: RWMol rdkit object with explicit hydrogens - zero_at: the placeholder * atom - attached_atom: the atom bonded to * - - Returns: - molecule with reordered indices - """ - zero_at_idx = zero_at.GetIdx() - zero_at.SetAtomicNum(1) - - attached_atom_idx = attached_atom.GetIdx() - # Initialize the new index so that our desired atoms are at the indices we want - first_two_atoms = [attached_atom_idx, zero_at_idx] - # Add the rest of the indices in original order - remaining_idx = [ - atom.GetIdx() - for atom in h_mol_rw.GetAtoms() - if atom.GetIdx() not in first_two_atoms - ] - out_atom_order = first_two_atoms + remaining_idx - reorder_mol = rdmolops.RenumberAtoms(h_mol_rw, out_atom_order) - return reorder_mol - - -def get_xyz(reorder_mol): - """MMFF optimize the molecule to generate xyz coordiantes""" - AllChem.EmbedMolecule(reorder_mol) - # not_optimized will be 0 if done, 1 if more steps needed - max_iters = 200 - for i in range(0, 6): - not_optimized = AllChem.MMFFOptimizeMolecule( - reorder_mol, maxIters=max_iters - ) # Optimize with MMFF94 - # -1 is returned for molecules where there are no heavy atom-heavy atom bonds - # for these, hopefully the embed geometry is good enough - # 0 is returned on successful opt - if not_optimized in [0, -1]: - break - if i == 5: - return "Could not determine xyz coordinates" - max_iters = max_iters + 200 - xyz_block = AllChem.rdmolfiles.MolToXYZBlock( - reorder_mol - ) # pylint:disable=no-member # Store xyz coordinates - split_xyz_block = xyz_block.split("\n") - # first two lines are: number of atoms and blank. Last line is blank - xyz_lines = split_xyz_block[2 : len(split_xyz_block) - 1] - xyz_string = "\n".join([str(item) for item in xyz_lines]) - return xyz_string - - -@calcfunction -def get_substituent_input(smiles: str) -> dict: - """For a given smiles, determine xyz structure, charge, and multiplicity - - Args: - smiles: SMILEs of substituent to run - - Returns: - Dict with keys xyz, charge, multiplicity - - """ - mol = Chem.MolFromSmiles(smiles.value) - if not mol: - raise ValueError( - f"Molecule could not be constructed for substituent input SMILES {smiles.value}" - ) - h_mol_rw, zero_at, attached_atom = find_attachment_atoms(mol) - reorder_mol = reorder_molecule(h_mol_rw, zero_at, attached_atom) - xyz_string = get_xyz(reorder_mol) - if xyz_string == "Could not determine xyz coordinates": - raise BadMoleculeException( - "Maximum iterations exceeded, could not determine xyz coordinates for f{smiles.value}" - ) - reorder_mol.UpdatePropertyCache() - charge = Chem.GetFormalCharge(h_mol_rw) - multiplicity = calc_multiplicity(h_mol_rw) - out_dict = Dict({"xyz": xyz_string, "charge": charge, "multiplicity": multiplicity}) - return out_dict - - -@calcfunction -def generate_structure_data(smiles_dict): - """Take an input xyz string and convert it to StructureData""" - structure_Str = smiles_dict["xyz"] - structure_str = structure_Str - num_atoms = len(structure_str.split("\n")) - xyz_string = f"{num_atoms}\n\n" + structure_str - f = io.StringIO(xyz_string) - struct_data = StructureData(ase=ase.io.read(f, format="xyz")) - f.close() - return struct_data - - -@calcfunction -def parameters_with_cm(parameters, smiles_dict): - """Add charge and multiplicity keys to Gaussian Input""" - parameters_dict = parameters.get_dict() - smiles_dict_dict = smiles_dict.get_dict() - parameters_dict["charge"] = smiles_dict_dict["charge"] - parameters_dict["multiplicity"] = smiles_dict_dict["multiplicity"] - return Dict(parameters_dict) - - -@calcfunction -def get_wfxname_from_gaussianinputs(gaussian_parameters): - """Look for wfx or wfn objects in the retrieved Folder""" - gaussian_dict = gaussian_parameters.get_dict() - if "input_parameters" not in gaussian_dict: - return Str("") - object_names = list(gaussian_dict["input_parameters"].keys()) - wfx_files = [x for x in object_names if "wfx" in x] - if len(wfx_files) >= 1: - return Str(wfx_files[0]) - if len(wfx_files) == 0: - wfn_files = [x for x in object_names if "wfn" in x] - if len(wfn_files) >= 1: - return Str(wfn_files[0]) - if len(wfn_files) == 0: - return Str("") - # should not get here - return Str("") - - -@calcfunction -def create_wfx_from_retrieved(wfxname, retrieved_folder): - """Create wavefunciton Singlefildata from retrieved folder""" - wfx_file_string = retrieved_folder.get_object_content(wfxname.value) - return SinglefileData(io.BytesIO(wfx_file_string.encode())) - - -def validate_shell_code(node, _): - """Validate the shell code, ensuring that it is ShellCode or Str""" - if node.node_type not in [ - "data.core.code.installed.shell.ShellCode.", - "data.core.str.Str.", - ]: - return "the `shell_code` input must be either ShellCode or Str of the command." - return None - - -def validate_file_ext(node, _): - """Validates that the file extension provided for AIM is wfx, wfn or fchk""" - if node.value not in ["wfx", "wfn", "fchk"]: - return "the `aim_file_ext` input must be a valid file format for AIMQB: wfx, wfn, or fchk" - return None - - -class QMToAIMWorkchain(WorkChain): - """Workchain to link quantum chemistry jobs without plugins to AIMAll""" - - @classmethod - def define(cls, spec): - super().define(spec) - spec.input( - "shell_code", - validator=validate_shell_code, # pylint:disable=expression-not-assigned - ) - spec.input("shell_metadata", valid_type=Dict) - spec.input("shell_retrieved", valid_type=List) - spec.input("shell_input_file", valid_type=SinglefileData, required=False) - spec.input("shell_cmdline", valid_type=Str, required=True) - spec.input("wfx_filename", valid_type=Str, required=False) - spec.input("aim_code", valid_type=Code, required=True) - spec.input( - "aim_file_ext", - valid_type=Str, - validator=validate_file_ext, # pylint:disable=expression-not-assigned - required=False, - default=lambda: Str("wfx"), - ) - spec.input("aim_params", valid_type=AimqbParameters, required=True) - spec.input( - "aim_parser", - valid_type=Str, - required=False, - default=lambda: Str("aimall.base"), - ) - spec.input("dry_run", valid_type=Bool, default=lambda: Bool(False)) - - spec.output("parameter_dict", valid_type=Dict) - spec.outline(cls.shell_job, cls.aim, cls.result) - - def shell_job(self): - """Launch a shell job""" - if self.inputs.dry_run.value: - return self.inputs - _, node = launch_shell_job( - self.inputs.shell_code, - arguments=self.inputs.shell_cmdline.value, - nodes={"file": self.inputs.shell_input_file}, - outputs=self.inputs.shell_retrieved.get_list(), - submit=True, - metadata=self.inputs.shell_metadata.get_dict(), - ) - out_dict = {"qm": node} - return ToContext(out_dict) - - def aim(self): - """Launch an AIMQB calculation""" - builder = AimqbCalculation.get_builder() - builder.parameters = self.inputs.aim_params - - if "wfx_filename" not in self.inputs: - wfx_file = ( - self.inputs.shell_input_file.filename.split(".")[0] - + f"_{self.inputs.aim_file_ext.value}" - ) - else: - wfx_file = self.inputs.wfx_filename.value.replace(".", "_") - builder.file = self.ctx.qm.base.links.get_outgoing().get_node_by_label(wfx_file) - builder.code = self.inputs.aim_code - builder.metadata.options.parser_name = self.inputs.aim_parser.value - builder.metadata.options.resources = {"num_machines": 1, "tot_num_mpiprocs": 2} - # future work, enable group - # num_atoms = len( - # self.ctx.prereor_aim.get_outgoing() - # .get_node_by_label("rotated_structure") - # .value.split("\n") - # ) - # # generalize for substrates other than H - # builder.group_atoms = List([x + 1 for x in range(0, num_atoms) if x != 1]) - if self.inputs.dry_run.value: - return self.inputs - process_node = self.submit(builder) - out_dict = {"aim": process_node} - return ToContext(out_dict) - - def result(self): - """Put results in output node""" - self.out( - "parameter_dict", - self.ctx.aim.base.links.get_outgoing().get_node_by_label( - "output_parameters" - ), - ) - - -class GenerateWFXToAIMWorkchain(WorkChain): - """Workchain to generate a wfx file from computational chemistry output files and submit that to an AIMQB Calculation - - Note: - This workchain uses the IOData module of the Ayer's group Horton to generate the wfx files. Supported file formats - include .fchk files, molden files (from Molpro, Orca, PSI4, Turbomole, and Molden), and CP2K atom log files. Further - note that .fchk files can simply be provided directly to an `AimqbCalculation`. - - While IOData accepts other file formats, these formats are the ones available that contain the necessary information - to generate wfc files - """ - - @classmethod - def define(cls, spec): - super().define(spec) - spec.input("input_file", valid_type=SinglefileData) - spec.input("aim_params", valid_type=AimqbParameters) - spec.input("aim_code") - spec.output("output_parameters", valid_type=Dict) - spec.outline(cls.generate_wfx, cls.aim, cls.result) - - def generate_wfx(self): - """Given SinglefileData generates a wfx file if IOData is capable""" - _, node = launch_shell_job( - "iodata-convert", - arguments="{file} output.wfx", - nodes={"file": self.inputs.input_file}, - outputs=["output.wfx"], - submit=True, - ) - out_dict = {"shell_wfx": node} - return ToContext(out_dict) - - def aim(self): - """Run AIM on the generated wfx file""" - builder = AimqbCalculation.get_builder() - builder.parameters = self.inputs.aim_params - builder.file = self.ctx.shell_wfx.base.links.get_outgoing().get_node_by_label( - "output_wfx" - ) - builder.code = self.inputs.aim_code - builder.metadata.options.resources = {"num_machines": 1, "tot_num_mpiprocs": 2} - # generalize for substrates other than H - process_node = self.submit(builder) - out_dict = {"aim": process_node} - return ToContext(out_dict) - - def result(self): - """Put results in output node""" - self.out( - "output_parameters", - self.ctx.aim.base.links.get_outgoing().get_node_by_label( - "output_parameters" - ), - ) - - -class SmilesToGaussianWorkchain(WorkChain): - """Workchain to take a SMILES, generate xyz, charge, and multiplicity""" - - @classmethod - def define(cls, spec): - super().define(spec) - spec.input("smiles") - spec.input("gaussian_parameters") - spec.input("gaussian_code") - spec.input("wfxname", required=False) - spec.input("wfxgroup", required=False) - spec.input("nprocs", default=lambda: Int(4)) - spec.input("mem_mb", default=lambda: Int(6400)) - spec.input("time_s", default=lambda: Int(24 * 7 * 60 * 60)) - spec.input("dry_run", default=lambda: Bool(False)) - spec.output("wfx", valid_type=SinglefileData, required=False) - spec.output("output_parameters", valid_type=Dict) - spec.outline( - cls.get_substituent_inputs_step, # , cls.results - cls.update_parameters_with_cm, - cls.string_to_StructureData, - cls.get_wfx_name, - cls.submit_gaussian, - if_(cls.found_wfx_name)(cls.create_wfx_file), - cls.results, - ) - - def get_substituent_inputs_step(self): - """Given list of substituents and previously done smiles, get input""" - self.ctx.smiles_geom = get_substituent_input(self.inputs.smiles) - - def update_parameters_with_cm(self): - """Update provided Gaussian parameters with charge and multiplicity of substituent""" - self.ctx.gaussian_cm_params = parameters_with_cm( - self.inputs.gaussian_parameters, self.ctx.smiles_geom - ) - - def string_to_StructureData(self): - """Convert an xyz string of molecule geometry to StructureData""" - self.ctx.structure = generate_structure_data(self.ctx.smiles_geom) - - def get_wfx_name(self): - """Find the wavefunction file in the retrieved node""" - if "wfxname" in self.inputs: - self.ctx.wfxname = self.inputs.wfxname - else: - self.ctx.wfxname = get_wfxname_from_gaussianinputs( - self.inputs.gaussian_parameters - ) - - def submit_gaussian(self): - """Submits the gaussian calculation""" - builder = GaussianCalculation.get_builder() - - builder.structure = self.ctx.structure - builder.parameters = self.ctx.gaussian_cm_params - builder.code = self.inputs.gaussian_code - builder.metadata.options.resources = { - "num_machines": 1, - "tot_num_mpiprocs": self.inputs.nprocs.value, - } - builder.metadata.options.max_memory_kb = ( - int(self.inputs.mem_mb.value * 1.25) * 1024 - ) - builder.metadata.options.max_wallclock_seconds = self.inputs.time_s.value - if self.ctx.wfxname.value: - builder.metadata.options.additional_retrieve_list = [self.ctx.wfxname.value] - - if self.inputs.dry_run.value: - return self.inputs - node = self.submit(builder) - out_dict = {"opt": node} - return ToContext(out_dict) - - def found_wfx_name(self): - """Check if we found a wfx or wfn file""" - if self.ctx.wfxname.value: - return True - return False - - def create_wfx_file(self): - """Create a wavefunction file from the retireved folder""" - retrieved_folder = ( - self.ctx["opt"].base.links.get_outgoing().get_node_by_label("retrieved") - ) - wfx_node = create_wfx_from_retrieved(self.ctx.wfxname, retrieved_folder) - wfx_node.base.extras.set("smiles", self.inputs.smiles) - self.ctx.wfxfile = wfx_node - if "wfxgroup" in self.inputs: - wfx_group = load_group(self.inputs.wfxgroup.value) - wfx_group.add_nodes(wfx_node) - - def results(self): - """Store our relevant information as output""" - if "wfxfile" in self.ctx: - self.out("wfx", self.ctx.wfxfile) - self.out( - "output_parameters", - self.ctx["opt"] - .base.links.get_outgoing() - .get_node_by_label("output_parameters"), - ) - - -class AIMAllReor(WorkChain): - """Workchain to run AIM and then reorient the molecule using the results - - Process continues in GaussianSubmissionController""" - - @classmethod - def define(cls, spec): - super().define(spec) - spec.input("aim_params", valid_type=AimqbParameters) - spec.input("file", valid_type=SinglefileData) - # spec.output('aim_dict',valid_type=Dict) - spec.input("aim_code", valid_type=Code) - spec.input("frag_label", valid_type=Str, required=False) - spec.input("aim_group", valid_type=Str, required=False) - spec.input("reor_group", valid_type=Str, required=False) - spec.input("dry_run", valid_type=Bool, default=lambda: Bool(False)) - spec.output("rotated_structure", valid_type=StructureData) - spec.outline(cls.aimall, cls.rotate, cls.dict_to_struct_reor, cls.result) - - def aimall(self): - """submit the aimall calculation""" - builder = AimqbCalculation.get_builder() - builder.code = self.inputs.aim_code - builder.parameters = self.inputs.aim_params - builder.file = self.inputs.file - builder.metadata.options.resources = { - "num_machines": 1, - "tot_num_mpiprocs": 2, - } - if self.inputs.dry_run.value: - return self.inputs - aim_calc = self.submit(builder) - aim_calc.store() - if "aim_group" in self.inputs: - aim_noreor_group = load_group(self.inputs.aim_group) - aim_noreor_group.add_nodes(aim_calc) - out_dict = {"aim": aim_calc} - return ToContext(out_dict) - - def rotate(self): - """perform the rotation""" - aimfolder = ( - self.ctx["aim"].base.links.get_outgoing().get_node_by_label("retrieved") - ) - output_dict = ( - self.ctx["aim"] - .base.links.get_outgoing() - .get_node_by_label("output_parameters") - .get_dict() - ) - atom_props = output_dict["atomic_properties"] - cc_props = output_dict["cc_properties"] - self.ctx.rot_struct_dict = generate_rotated_structure_aiida( - aimfolder, atom_props, cc_props - ) - - def dict_to_struct_reor(self): - """generate the gaussian input from rotated structure""" - structure = dict_to_structure(self.ctx.rot_struct_dict) - structure.store() - if "reor_group" in self.inputs: - reor_struct_group = load_group(self.inputs.reor_group.value) - reor_struct_group.add_nodes(structure) - if "frag_label" in self.inputs: - struct_extras = EntityExtras(structure) - struct_extras.set("smiles", self.inputs.frag_label.value) - self.ctx.rot_structure = structure - - def result(self): - """Parse results""" - self.out("rotated_structure", self.ctx.rot_structure) - - -@calcfunction -def get_wfx(retrieved_folder, wfx_filename): - """Get a wfx file from retrieved folder""" - folder_data = retrieved_folder - # later scan input parameters for filename - wfx_file = SinglefileData( - io.BytesIO(folder_data.get_object_content(wfx_filename.value).encode()) - ) - return wfx_file - - -class BaseInputWorkChain(WorkChain): - """A workchain to generate and validate inputs. One of SinglefileData, Smiles as Str or StructureData should be - provided""" - - @classmethod - def define(cls, spec): - super().define(spec) - spec.input("structure", valid_type=StructureData, required=False) - spec.input("smiles", valid_type=Str, required=False) - spec.input("xyz_file", valid_type=SinglefileData, required=False) - spec.exit_code( - 200, - "ERROR_MULTIPLE_INPUTS", - "the process received two or more of the following inputs: structure, smiles, xyz_file", - ) - spec.exit_code( - 201, - "ERROR_NO_INPUTS", - "None of structure, smiles, xyz_file were provided, at least one must be", - ) - spec.outline( - cls.validate_input, - if_(cls.is_xyz_input)(cls.create_structure_from_xyz), - if_(cls.is_smiles_input)( - cls.get_molecule_inputs_step, cls.string_to_StructureData - ), - if_(cls.is_structure_input)(cls.structure_in_context), - ) - - def is_xyz_input(self): - """Validates if xyz_file was provided as input""" - if "xyz_file" in self.inputs: - return True - return False - - def is_smiles_input(self): - """Validates if smiles was provided as input""" - if "smiles" in self.inputs: - return True - return False - - def is_structure_input(self): - """Validates if structure was provided as input""" - if "structure" in self.inputs: - return True - return False - - # pylint:disable=inconsistent-return-statements - def validate_input(self): - """Check that only one of smiles, structure, or xyz_file was input""" - if "smiles" in self.inputs and ( - "xyz_file" in self.inputs or "structure" in self.inputs - ): - return ExitCode(200) - if "xyz_file" in self.inputs and ( - "smiles" in self.inputs or "structure" in self.inputs - ): - return ExitCode(200) - if "structure" in self.inputs and ( - "xyz_file" in self.inputs or "smiles" in self.inputs - ): - return ExitCode(200) - if ( - "structure" not in self.inputs - and "xyz_file" not in self.inputs - and "smiles" not in self.inputs - ): - return ExitCode(201) - - def create_structure_from_xyz(self): - """Convert the xyzfile to StructureData""" - self.ctx.structure = xyzfile_to_StructureData(self.inputs.xyz_file) - - def structure_in_context(self): - """Store the input structure in context, to make consistent with the results of xyz_file or SMILES input""" - self.ctx.structure = self.inputs.structure - - def get_molecule_inputs_step(self): - """Given list of substituents and previously done smiles, get input""" - self.ctx.smiles_geom = get_molecule_str_from_smiles(self.inputs.smiles) - - def string_to_StructureData(self): - """Convert an xyz string of molecule geometry to StructureData""" - self.ctx.structure = generate_structure_data(self.ctx.smiles_geom) - - -@calcfunction -def get_molecule_str_from_smiles(smiles): - """For a given smiles, determine xyz structure, charge, and multiplicity - - Args: - smiles: SMILEs of substituent to run - - Returns: - Dict with keys xyz, charge, multiplicity - - """ - mol = Chem.MolFromSmiles(smiles.value) - if not mol: - raise ValueError( - f"Molecule could not be constructed for substituent input SMILES {smiles.value}" - ) - h_mol_rw = Chem.RWMol(mol) # Change type of molecule object - h_mol_rw = Chem.AddHs(h_mol_rw) - xyz_string = get_xyz(h_mol_rw) - if xyz_string == "Could not determine xyz coordinates": - raise BadMoleculeException( - "Maximum iterations exceeded, could not determine xyz coordinates for f{smiles.value}" - ) - h_mol_rw.UpdatePropertyCache() - charge = Chem.GetFormalCharge(h_mol_rw) - multiplicity = calc_multiplicity(h_mol_rw) - out_dict = Dict({"xyz": xyz_string, "charge": charge, "multiplicity": multiplicity}) - return out_dict - - -class GaussianToAIMWorkChain(BaseInputWorkChain): - """A workchain to submit a Gaussian calculation and automatically setup an AIMAll calculation on the output""" - - @classmethod - def define(cls, spec): - """Define workchain steps""" - super().define(spec) - spec.input("g16_params", valid_type=Dict, required=True) - spec.input("aim_params", valid_type=AimqbParameters, required=True) - spec.input("g16_code", valid_type=Code) - spec.input( - "frag_label", - valid_type=Str, - help="Label for substituent fragment, stored as extra", - required=False, - ) - spec.input("wfx_group", valid_type=Str, required=False) - spec.input("gaussian_group", valid_type=Str, required=False) - spec.input("aim_code", valid_type=Code) - spec.input("dry_run", valid_type=Bool, default=lambda: Bool(False)) - spec.input("wfx_filename", valid_type=Str, default=lambda: Str("output.wfx")) - spec.output("parameter_dict", valid_type=Dict) - spec.outline( - cls.validate_input, - if_(cls.is_xyz_input)(cls.create_structure_from_xyz), - if_(cls.is_smiles_input)( - cls.get_molecule_inputs_step, cls.string_to_StructureData - ), - if_(cls.is_structure_input)(cls.structure_in_context), - cls.g16, - cls.classify_wfx, - cls.aim, - cls.result, - ) - - def g16(self): - """Run Gaussian calculation""" - builder = GaussianCalculation.get_builder() - builder.structure = self.ctx.structure - builder.parameters = self.inputs.g16_params - builder.code = self.inputs.g16_code - builder.metadata.options.resources = {"num_machines": 1, "tot_num_mpiprocs": 4} - builder.metadata.options.max_memory_kb = int(6400 * 1.25) * 1024 - builder.metadata.options.max_wallclock_seconds = 604800 - builder.metadata.options.additional_retrieve_list = [ - self.inputs.wfx_filename.value - ] - if self.inputs.dry_run.value: - return self.inputs - process_node = self.submit(builder) - if "gaussian_group" in self.inputs: - g16_group = load_group(self.inputs.gaussian_sp_group) - g16_group.add_nodes(process_node) - out_dict = {"g16": process_node} - # self.ctx.standard_wfx = process_node.get_outgoing().get_node_by_label("wfx") - return ToContext(out_dict) - - def classify_wfx(self): - """Add the wavefunction file from the previous step to the correct group and set the extras""" - folder_data = self.ctx.g16.base.links.get_outgoing().get_node_by_label( - "retrieved" - ) - self.ctx.wfx = get_wfx(folder_data, self.inputs.wfx_filename) - # later scan input parameters for filename - - if "wfx_group" in self.inputs: - wf_group = load_group(self.inputs.wfx_group) - wf_group.add_nodes(self.ctx.wfx) - if "frag_label" in self.inputs: - struct_extras = EntityExtras(self.ctx.wfx) - struct_extras.set("smiles", self.inputs.frag_label.value) - - def aim(self): - """Run Final AIM Calculation""" - builder = AimqbCalculation.get_builder() - builder.parameters = self.inputs.aim_params - builder.file = self.ctx.wfx - builder.code = self.inputs.aim_code - # if "frag_label" in self.inputs: - # builder.frag_label = self.inputs.frag_label - builder.metadata.options.resources = {"num_machines": 1, "tot_num_mpiprocs": 2} - num_atoms = len(self.ctx.structure.sites) - # generalize for substrates other than H - builder.group_atoms = List([x + 1 for x in range(0, num_atoms) if x != 1]) - if self.inputs.dry_run.value: - return self.inputs - process_node = self.submit(builder) - out_dict = {"aim": process_node} - return ToContext(out_dict) - - def result(self): - """Put results in output node""" - self.out( - "parameter_dict", - self.ctx.aim.base.links.get_outgoing().get_node_by_label( - "output_parameters" - ), - ) - - -@calcfunction -def xyzfile_to_StructureData(xyz_SFD): - """Convert the xyz file provided as SinglefileData to StructureData""" - with xyz_SFD.as_path() as filepath: - return StructureData(ase=ase.io.read(filepath, format="xyz")) - - -class SubstituentParameterWorkChain(BaseInputWorkChain): - """A workchain to perform the full suite of KLG's substituent parameter determining""" - - @classmethod - def define(cls, spec): - """Define workchain steps""" - super().define(spec) - spec.input("g16_opt_params", valid_type=Dict, required=True) - spec.input("g16_sp_params", valid_type=Dict, required=True) - spec.input("aim_params", valid_type=AimqbParameters, required=True) - spec.input("g16_code", valid_type=Code) - spec.input( - "frag_label", - valid_type=Str, - help="Label for substituent fragment, stored as extra", - required=False, - ) - spec.input("opt_wfx_group", valid_type=Str, required=False) - spec.input("sp_wfx_group", valid_type=Str, required=False) - spec.input("gaussian_opt_group", valid_type=Str, required=False) - spec.input("gaussian_sp_group", valid_type=Str, required=False) - spec.input( - "wfx_filename", - valid_type=Str, - required=False, - default=lambda: Str("output.wfx"), - ) - # spec.input("file", valid_type=SinglefileData) - # spec.output('aim_dict',valid_type=Dict) - spec.input("aim_code", valid_type=Code) - spec.input("dry_run", valid_type=Bool, default=lambda: Bool(False)) - # spec.input("frag_label", valid_type=Str) - # spec.output("rotated_structure", valid_type=Str) - spec.output("parameter_dict", valid_type=Dict) - spec.outline( - cls.validate_input, - if_(cls.is_xyz_input)(cls.create_structure_from_xyz), - if_(cls.is_smiles_input)( - cls.get_substituent_inputs_step, cls.string_to_StructureData - ), - if_(cls.is_structure_input)(cls.structure_in_context), - cls.g16_opt, - cls.classify_opt_wfx, - cls.aim_reor, - cls.g16_sp, - cls.classify_sp_wfx, - cls.aim, - cls.result, - ) - - def get_substituent_inputs_step(self): - """Get a dictionary of the substituent input for a given SMILES""" - self.ctx.smiles_geom = get_substituent_input(self.inputs.smiles) - - def g16_opt(self): - """Submit the Gaussian optimization""" - builder = GaussianCalculation.get_builder() - builder.structure = self.ctx.structure - builder.parameters = self.inputs.g16_opt_params - builder.code = self.inputs.g16_code - builder.metadata.options.resources = {"num_machines": 1, "tot_num_mpiprocs": 4} - builder.metadata.options.max_memory_kb = int(6400 * 1.25) * 1024 - builder.metadata.options.max_wallclock_seconds = 604800 - builder.metadata.options.additional_retrieve_list = [ - self.inputs.wfx_filename.value - ] - if self.inputs.dry_run.value: - return self.inputs - process_node = self.submit(builder) - if "gaussian_opt_group" in self.inputs: - g16_opt_group = load_group(self.inputs.gaussian_opt_group) - g16_opt_group.add_nodes(process_node) - out_dict = {"opt": process_node} - # self.ctx.standard_wfx = process_node.get_outgoing().get_node_by_label("wfx") - return ToContext(out_dict) - - def classify_opt_wfx(self): - """Add the wavefunction file from the previous step to the correct group and set the extras""" - folder_data = self.ctx.opt.base.links.get_outgoing().get_node_by_label( - "retrieved" - ) - # later scan input parameters for filename - wfx_file = get_wfx(folder_data, self.inputs.wfx_filename.value) - self.ctx.opt_wfx = wfx_file - - if "opt_wfx_group" in self.inputs: - opt_wf_group = load_group(self.inputs.opt_wfx_group) - opt_wf_group.add_nodes(wfx_file) - if "frag_label" in self.inputs: - struct_extras = EntityExtras(wfx_file) - struct_extras.set("smiles", self.inputs.frag_label.value) - - def aim_reor(self): - """Submit the Aimqb calculation and reorientation""" - builder = AIMAllReor.get_builder() - builder.aim_params = self.inputs.aim_params - builder.file = self.ctx.opt_wfx - builder.aim_code = self.inputs.aim_code - # builder.dry_run = self.inputs.dry_run - if "frag_label" in self.inputs: - builder.frag_label = self.inputs.frag_label - if self.inputs.dry_run.value: - return self.inputs - process_node = self.submit(builder) - out_dict = {"prereor_aim": process_node} - return ToContext(out_dict) - - def g16_sp(self): - """Run Gaussian Single Point calculation""" - builder = GaussianCalculation.get_builder() - builder.structure = ( - self.ctx.prereor_aim.base.links.get_outgoing().get_node_by_label( - "rotated_structure" - ) - ) - builder.parameters = self.inputs.g16_sp_params - builder.code = self.inputs.g16_code - builder.metadata.options.resources = {"num_machines": 1, "tot_num_mpiprocs": 4} - builder.metadata.options.max_memory_kb = int(6400 * 1.25) * 1024 - builder.metadata.options.max_wallclock_seconds = 604800 - builder.metadata.options.additional_retrieve_list = [ - self.inputs.wfx_filename.value - ] - if self.inputs.dry_run.value: - return self.inputs - process_node = self.submit(builder) - if "gaussian_sp_group" in self.inputs: - g16_sp_group = load_group(self.inputs.gaussian_sp_group) - g16_sp_group.add_nodes(process_node) - out_dict = {"sp": process_node} - # self.ctx.standard_wfx = process_node.get_outgoing().get_node_by_label("wfx") - return ToContext(out_dict) - - def classify_sp_wfx(self): - """Add the wavefunction file from the previous step to the correct group and set the extras""" - folder_data = self.ctx.sp.base.links.get_outgoing().get_node_by_label( - "retrieved" - ) - # later scan input parameters for filename - wfx_file = get_wfx(folder_data, self.inputs.wfx_filename.value) - self.ctx.sp_wfx = wfx_file - - if "sp_wfx_group" in self.inputs: - sp_wf_group = load_group(self.inputs.sp_wfx_group) - sp_wf_group.add_nodes(wfx_file) - if "frag_label" in self.inputs: - struct_extras = EntityExtras(wfx_file) - struct_extras.set("smiles", self.inputs.frag_label.value) - - def aim(self): - """Run Final AIM Calculation""" - builder = AimqbCalculation.get_builder() - builder.parameters = self.inputs.aim_params - builder.file = self.ctx.sp_wfx - builder.code = self.inputs.aim_code - builder.metadata.options.parser_name = "aimall.group" - builder.metadata.options.resources = {"num_machines": 1, "tot_num_mpiprocs": 2} - num_atoms = len( - self.ctx.prereor_aim.base.links.get_outgoing() - .get_node_by_label("rotated_structure") - .sites - ) - # generalize for substrates other than H - builder.group_atoms = List([x + 1 for x in range(0, num_atoms) if x != 1]) - if self.inputs.dry_run.value: - return self.inputs - process_node = self.submit(builder) - out_dict = {"final_aim": process_node} - return ToContext(out_dict) - - def result(self): - """Put results in output node""" - self.out( - "parameter_dict", - self.ctx.final_aim.base.links.get_outgoing().get_node_by_label( - "output_parameters" - ), - ) diff --git a/src/aiida_aimall/workchains/__init__.py b/src/aiida_aimall/workchains/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/aiida_aimall/workchains/calcfunctions.py b/src/aiida_aimall/workchains/calcfunctions.py new file mode 100644 index 0000000..d947236 --- /dev/null +++ b/src/aiida_aimall/workchains/calcfunctions.py @@ -0,0 +1,317 @@ +"""Calcfunctions used throughout workchains""" +# pylint: disable=c-extension-no-member +# pylint:disable=no-member +import io +from string import digits + +import ase.io +from aiida.engine import calcfunction +from aiida.orm import Dict, SinglefileData, Str, StructureData +from rdkit import Chem +from rdkit.Chem import AllChem, rdmolops, rdqueries +from rdkit.Chem.MolKey.MolKey import BadMoleculeException +from subproptools.sub_reor import rotate_substituent_aiida + + +@calcfunction +def generate_rotated_structure_aiida(FolderData, atom_dict, cc_dict): + """Rotates the fragment to the defined coordinate system + + Args: + FolderData: aim calculation folder + atom_dict: AIM atom dict + cc_dict: AIM cc_dict + """ + return Dict(rotate_substituent_aiida(FolderData, atom_dict, cc_dict)) + + +def remove(in_list): + """Remove digits from a list of strings. e.g. ['O1','H2','H3'] -> ['O','H','H']""" + remove_digits = str.maketrans("", "", digits) + out_list = [i.translate(remove_digits) for i in in_list] + return out_list + + +@calcfunction +def dict_to_structure(fragment_dict): + """Generate a string of xyz coordinates for Gaussian input file + + :param fragment_dict: + :param type fragment_dict: aiida.orm.nodes.data.dict.Dict + """ + inp_dict = fragment_dict.get_dict() + symbols = inp_dict["atom_symbols"] + symbols = remove(symbols) + coords = inp_dict["geom"] + outstr = "" + outstr += f"{len(symbols)}\n\n" + for i, symbol in enumerate(symbols): + if i != len(symbols) - 1: + outstr = ( + outstr + + symbol + + " " + + str(coords[i][0]) + + " " + + str(coords[i][1]) + + " " + + str(coords[i][2]) + + "\n" + ) + else: + outstr = ( + outstr + + symbol + + " " + + str(coords[i][0]) + + " " + + str(coords[i][1]) + + " " + + str(coords[i][2]) + ) + f = io.StringIO(outstr) + struct_data = StructureData(ase=ase.io.read(f, format="xyz")) + f.close() + return struct_data + + +def calc_multiplicity(mol): + """Calculate the multiplicity of a molecule as 2S +1""" + num_radicals = 0 + for atom in mol.GetAtoms(): + num_radicals += atom.GetNumRadicalElectrons() + multiplicity = num_radicals + 1 + return multiplicity + + +def find_attachment_atoms(mol): + """Given molecule object, find the atoms corresponding to a * and the atom to which that is bound + + Args: + mol: rdkit molecule object + + Returns: + molecule with added hydrogens, the * atom object, and the atom object to which that is attached + + Note: + Assumes that only one * is present in the molecule + """ + # * has atomic number 0 + query = rdqueries.AtomNumEqualsQueryAtom(0) + # add hydrogens now + h_mol_rw = Chem.RWMol(mol) # Change type of molecule object + h_mol_rw = Chem.AddHs(h_mol_rw) + query_ats = h_mol_rw.GetAtomsMatchingQuery(query) + if len(query_ats) != 1: + raise ValueError( + f"Molecule should have one placeholder atom with atomic number 0, found {len(query_ats)}" + ) + zero_at = query_ats[0] + # this will be bonded to one atom - whichever atom in the bond is not *, is the one we are looking for + bond = zero_at.GetBonds()[0] + begin_atom = bond.GetBeginAtom() + if begin_atom.GetSymbol() != "*": + attached_atom = begin_atom + else: + attached_atom = bond.GetEndAtom() + return h_mol_rw, zero_at, attached_atom + + +def reorder_molecule(h_mol_rw, zero_at, attached_atom): + """Reindexes the atoms in a molecule, setting attached_atom to index 0, and zero_at to index 1 + + Args: + h_mol_rw: RWMol rdkit object with explicit hydrogens + zero_at: the placeholder * atom + attached_atom: the atom bonded to * + + Returns: + molecule with reordered indices + """ + zero_at_idx = zero_at.GetIdx() + zero_at.SetAtomicNum(1) + + attached_atom_idx = attached_atom.GetIdx() + # Initialize the new index so that our desired atoms are at the indices we want + first_two_atoms = [attached_atom_idx, zero_at_idx] + # Add the rest of the indices in original order + remaining_idx = [ + atom.GetIdx() + for atom in h_mol_rw.GetAtoms() + if atom.GetIdx() not in first_two_atoms + ] + out_atom_order = first_two_atoms + remaining_idx + reorder_mol = rdmolops.RenumberAtoms(h_mol_rw, out_atom_order) + return reorder_mol + + +def get_xyz(reorder_mol): + """MMFF optimize the molecule to generate xyz coordiantes""" + AllChem.EmbedMolecule(reorder_mol) + # not_optimized will be 0 if done, 1 if more steps needed + max_iters = 200 + for i in range(0, 6): + not_optimized = AllChem.MMFFOptimizeMolecule( + reorder_mol, maxIters=max_iters + ) # Optimize with MMFF94 + # -1 is returned for molecules where there are no heavy atom-heavy atom bonds + # for these, hopefully the embed geometry is good enough + # 0 is returned on successful opt + if not_optimized in [0, -1]: + break + if i == 5: + return "Could not determine xyz coordinates" + max_iters = max_iters + 200 + xyz_block = AllChem.rdmolfiles.MolToXYZBlock( + reorder_mol + ) # pylint:disable=no-member # Store xyz coordinates + split_xyz_block = xyz_block.split("\n") + # first two lines are: number of atoms and blank. Last line is blank + xyz_lines = split_xyz_block[2 : len(split_xyz_block) - 1] + xyz_string = "\n".join([str(item) for item in xyz_lines]) + return xyz_string + + +@calcfunction +def get_substituent_input(smiles: str) -> dict: + """For a given smiles, determine xyz structure, charge, and multiplicity + + Args: + smiles: SMILEs of substituent to run + + Returns: + Dict with keys xyz, charge, multiplicity + + """ + mol = Chem.MolFromSmiles(smiles.value) + if not mol: + raise ValueError( + f"Molecule could not be constructed for substituent input SMILES {smiles.value}" + ) + h_mol_rw, zero_at, attached_atom = find_attachment_atoms(mol) + reorder_mol = reorder_molecule(h_mol_rw, zero_at, attached_atom) + xyz_string = get_xyz(reorder_mol) + if xyz_string == "Could not determine xyz coordinates": + raise BadMoleculeException( + "Maximum iterations exceeded, could not determine xyz coordinates for f{smiles.value}" + ) + reorder_mol.UpdatePropertyCache() + charge = Chem.GetFormalCharge(h_mol_rw) + multiplicity = calc_multiplicity(h_mol_rw) + out_dict = Dict({"xyz": xyz_string, "charge": charge, "multiplicity": multiplicity}) + return out_dict + + +@calcfunction +def generate_structure_data(smiles_dict): + """Take an input xyz string and convert it to StructureData""" + structure_Str = smiles_dict["xyz"] + structure_str = structure_Str + num_atoms = len(structure_str.split("\n")) + xyz_string = f"{num_atoms}\n\n" + structure_str + f = io.StringIO(xyz_string) + struct_data = StructureData(ase=ase.io.read(f, format="xyz")) + f.close() + return struct_data + + +@calcfunction +def parameters_with_cm(parameters, smiles_dict): + """Add charge and multiplicity keys to Gaussian Input""" + parameters_dict = parameters.get_dict() + smiles_dict_dict = smiles_dict.get_dict() + parameters_dict["charge"] = smiles_dict_dict["charge"] + parameters_dict["multiplicity"] = smiles_dict_dict["multiplicity"] + return Dict(parameters_dict) + + +@calcfunction +def get_wfxname_from_gaussianinputs(gaussian_parameters): + """Look for wfx or wfn objects in the retrieved Folder""" + gaussian_dict = gaussian_parameters.get_dict() + if "input_parameters" not in gaussian_dict: + return Str("") + object_names = list(gaussian_dict["input_parameters"].keys()) + wfx_files = [x for x in object_names if "wfx" in x] + if len(wfx_files) >= 1: + return Str(wfx_files[0]) + if len(wfx_files) == 0: + wfn_files = [x for x in object_names if "wfn" in x] + if len(wfn_files) >= 1: + return Str(wfn_files[0]) + if len(wfn_files) == 0: + return Str("") + # should not get here + return Str("") + + +@calcfunction +def create_wfx_from_retrieved(wfxname, retrieved_folder): + """Create wavefunciton Singlefildata from retrieved folder""" + wfx_file_string = retrieved_folder.get_object_content(wfxname.value) + return SinglefileData(io.BytesIO(wfx_file_string.encode())) + + +def validate_shell_code(node, _): + """Validate the shell code, ensuring that it is ShellCode or Str""" + if node.node_type not in [ + "data.core.code.installed.shell.ShellCode.", + "data.core.str.Str.", + ]: + return "the `shell_code` input must be either ShellCode or Str of the command." + return None + + +def validate_file_ext(node, _): + """Validates that the file extension provided for AIM is wfx, wfn or fchk""" + if node.value not in ["wfx", "wfn", "fchk"]: + return "the `aim_file_ext` input must be a valid file format for AIMQB: wfx, wfn, or fchk" + return None + + +@calcfunction +def get_wfx(retrieved_folder, wfx_filename): + """Get a wfx file from retrieved folder""" + folder_data = retrieved_folder + # later scan input parameters for filename + wfx_file = SinglefileData( + io.BytesIO(folder_data.get_object_content(wfx_filename.value).encode()) + ) + return wfx_file + + +@calcfunction +def get_molecule_str_from_smiles(smiles): + """For a given smiles, determine xyz structure, charge, and multiplicity + + Args: + smiles: SMILEs of substituent to run + + Returns: + Dict with keys xyz, charge, multiplicity + + """ + mol = Chem.MolFromSmiles(smiles.value) + if not mol: + raise ValueError( + f"Molecule could not be constructed for substituent input SMILES {smiles.value}" + ) + h_mol_rw = Chem.RWMol(mol) # Change type of molecule object + h_mol_rw = Chem.AddHs(h_mol_rw) + xyz_string = get_xyz(h_mol_rw) + if xyz_string == "Could not determine xyz coordinates": + raise BadMoleculeException( + "Maximum iterations exceeded, could not determine xyz coordinates for f{smiles.value}" + ) + h_mol_rw.UpdatePropertyCache() + charge = Chem.GetFormalCharge(h_mol_rw) + multiplicity = calc_multiplicity(h_mol_rw) + out_dict = Dict({"xyz": xyz_string, "charge": charge, "multiplicity": multiplicity}) + return out_dict + + +@calcfunction +def xyzfile_to_StructureData(xyz_SFD): + """Convert the xyz file provided as SinglefileData to StructureData""" + with xyz_SFD.as_path() as filepath: + return StructureData(ase=ase.io.read(filepath, format="xyz")) diff --git a/src/aiida_aimall/workchains/input.py b/src/aiida_aimall/workchains/input.py new file mode 100644 index 0000000..84e1e12 --- /dev/null +++ b/src/aiida_aimall/workchains/input.py @@ -0,0 +1,96 @@ +"""Base input workchain""" +from aiida.engine import WorkChain, if_ +from aiida.engine.processes import ExitCode +from aiida.orm import SinglefileData, Str, StructureData + +from aiida_aimall.workchains.calcfunctions import ( + generate_structure_data, + get_molecule_str_from_smiles, + xyzfile_to_StructureData, +) + + +class BaseInputWorkChain(WorkChain): + """A workchain to generate and validate inputs. One of SinglefileData, Smiles as Str or StructureData should be + provided""" + + @classmethod + def define(cls, spec): + super().define(spec) + spec.input("structure", valid_type=StructureData, required=False) + spec.input("smiles", valid_type=Str, required=False) + spec.input("xyz_file", valid_type=SinglefileData, required=False) + spec.exit_code( + 200, + "ERROR_MULTIPLE_INPUTS", + "the process received two or more of the following inputs: structure, smiles, xyz_file", + ) + spec.exit_code( + 201, + "ERROR_NO_INPUTS", + "None of structure, smiles, xyz_file were provided, at least one must be", + ) + spec.outline( + cls.validate_input, + if_(cls.is_xyz_input)(cls.create_structure_from_xyz), + if_(cls.is_smiles_input)( + cls.get_molecule_inputs_step, cls.string_to_StructureData + ), + if_(cls.is_structure_input)(cls.structure_in_context), + ) + + def is_xyz_input(self): + """Validates if xyz_file was provided as input""" + if "xyz_file" in self.inputs: + return True + return False + + def is_smiles_input(self): + """Validates if smiles was provided as input""" + if "smiles" in self.inputs: + return True + return False + + def is_structure_input(self): + """Validates if structure was provided as input""" + if "structure" in self.inputs: + return True + return False + + # pylint:disable=inconsistent-return-statements + def validate_input(self): + """Check that only one of smiles, structure, or xyz_file was input""" + if "smiles" in self.inputs and ( + "xyz_file" in self.inputs or "structure" in self.inputs + ): + return ExitCode(200) + if "xyz_file" in self.inputs and ( + "smiles" in self.inputs or "structure" in self.inputs + ): + return ExitCode(200) + if "structure" in self.inputs and ( + "xyz_file" in self.inputs or "smiles" in self.inputs + ): + return ExitCode(200) + if ( + "structure" not in self.inputs + and "xyz_file" not in self.inputs + and "smiles" not in self.inputs + ): + return ExitCode(201) + + def create_structure_from_xyz(self): + """Convert the xyzfile to StructureData""" + self.ctx.structure = xyzfile_to_StructureData(self.inputs.xyz_file) + + def structure_in_context(self): + """Store the input structure in context, to make consistent with the results of xyz_file or SMILES input""" + self.ctx.structure = self.inputs.structure + + def get_molecule_inputs_step(self): + """Given list of substituents and previously done smiles, get input""" + self.ctx.smiles_geom = get_molecule_str_from_smiles(self.inputs.smiles) + + def string_to_StructureData(self): + """Convert an xyz string of molecule geometry to StructureData""" + self.ctx.structure = generate_structure_data(self.ctx.smiles_geom) diff --git a/src/aiida_aimall/workchains/param_parts.py b/src/aiida_aimall/workchains/param_parts.py new file mode 100644 index 0000000..6a27973 --- /dev/null +++ b/src/aiida_aimall/workchains/param_parts.py @@ -0,0 +1,204 @@ +"""Workchains that are smaller parts of SubstituenParamWorkChain""" +from aiida.engine import ToContext, WorkChain, if_ +from aiida.orm import ( + Bool, + Code, + Dict, + Int, + SinglefileData, + Str, + StructureData, + load_group, +) +from aiida.orm.extras import EntityExtras +from aiida_gaussian.calculations import GaussianCalculation + +from aiida_aimall.calculations import AimqbCalculation +from aiida_aimall.data import AimqbParameters +from aiida_aimall.workchains.calcfunctions import ( + create_wfx_from_retrieved, + dict_to_structure, + generate_rotated_structure_aiida, + generate_structure_data, + get_substituent_input, + get_wfxname_from_gaussianinputs, + parameters_with_cm, +) + + +class SmilesToGaussianWorkChain(WorkChain): + """Workchain to take a SMILES, generate xyz, charge, and multiplicity""" + + @classmethod + def define(cls, spec): + super().define(spec) + spec.input("smiles") + spec.input("gaussian_parameters") + spec.input("gaussian_code") + spec.input("wfxname", required=False) + spec.input("wfxgroup", required=False) + spec.input("nprocs", default=lambda: Int(4)) + spec.input("mem_mb", default=lambda: Int(6400)) + spec.input("time_s", default=lambda: Int(24 * 7 * 60 * 60)) + spec.input("dry_run", default=lambda: Bool(False)) + spec.output("wfx", valid_type=SinglefileData, required=False) + spec.output("output_parameters", valid_type=Dict) + spec.outline( + cls.get_substituent_inputs_step, # , cls.results + cls.update_parameters_with_cm, + cls.string_to_StructureData, + cls.get_wfx_name, + cls.submit_gaussian, + if_(cls.found_wfx_name)(cls.create_wfx_file), + cls.results, + ) + + def get_substituent_inputs_step(self): + """Given list of substituents and previously done smiles, get input""" + self.ctx.smiles_geom = get_substituent_input(self.inputs.smiles) + + def update_parameters_with_cm(self): + """Update provided Gaussian parameters with charge and multiplicity of substituent""" + self.ctx.gaussian_cm_params = parameters_with_cm( + self.inputs.gaussian_parameters, self.ctx.smiles_geom + ) + + def string_to_StructureData(self): + """Convert an xyz string of molecule geometry to StructureData""" + self.ctx.structure = generate_structure_data(self.ctx.smiles_geom) + + def get_wfx_name(self): + """Find the wavefunction file in the retrieved node""" + if "wfxname" in self.inputs: + self.ctx.wfxname = self.inputs.wfxname + else: + self.ctx.wfxname = get_wfxname_from_gaussianinputs( + self.inputs.gaussian_parameters + ) + + def submit_gaussian(self): + """Submits the gaussian calculation""" + builder = GaussianCalculation.get_builder() + + builder.structure = self.ctx.structure + builder.parameters = self.ctx.gaussian_cm_params + builder.code = self.inputs.gaussian_code + builder.metadata.options.resources = { + "num_machines": 1, + "tot_num_mpiprocs": self.inputs.nprocs.value, + } + builder.metadata.options.max_memory_kb = ( + int(self.inputs.mem_mb.value * 1.25) * 1024 + ) + builder.metadata.options.max_wallclock_seconds = self.inputs.time_s.value + if self.ctx.wfxname.value: + builder.metadata.options.additional_retrieve_list = [self.ctx.wfxname.value] + + if self.inputs.dry_run.value: + return self.inputs + node = self.submit(builder) + out_dict = {"opt": node} + return ToContext(out_dict) + + def found_wfx_name(self): + """Check if we found a wfx or wfn file""" + if self.ctx.wfxname.value: + return True + return False + + def create_wfx_file(self): + """Create a wavefunction file from the retireved folder""" + retrieved_folder = ( + self.ctx["opt"].base.links.get_outgoing().get_node_by_label("retrieved") + ) + wfx_node = create_wfx_from_retrieved(self.ctx.wfxname, retrieved_folder) + wfx_node.base.extras.set("smiles", self.inputs.smiles) + self.ctx.wfxfile = wfx_node + if "wfxgroup" in self.inputs: + wfx_group = load_group(self.inputs.wfxgroup.value) + wfx_group.add_nodes(wfx_node) + + def results(self): + """Store our relevant information as output""" + if "wfxfile" in self.ctx: + self.out("wfx", self.ctx.wfxfile) + self.out( + "output_parameters", + self.ctx["opt"] + .base.links.get_outgoing() + .get_node_by_label("output_parameters"), + ) + + +class AIMAllReorWorkChain(WorkChain): + """Workchain to run AIM and then reorient the molecule using the results + + Process continues in GaussianSubmissionController""" + + @classmethod + def define(cls, spec): + super().define(spec) + spec.input("aim_params", valid_type=AimqbParameters) + spec.input("file", valid_type=SinglefileData) + # spec.output('aim_dict',valid_type=Dict) + spec.input("aim_code", valid_type=Code) + spec.input("frag_label", valid_type=Str, required=False) + spec.input("aim_group", valid_type=Str, required=False) + spec.input("reor_group", valid_type=Str, required=False) + spec.input("dry_run", valid_type=Bool, default=lambda: Bool(False)) + spec.output("rotated_structure", valid_type=StructureData) + spec.outline(cls.aimall, cls.rotate, cls.dict_to_struct_reor, cls.result) + + def aimall(self): + """submit the aimall calculation""" + builder = AimqbCalculation.get_builder() + builder.code = self.inputs.aim_code + builder.parameters = self.inputs.aim_params + builder.file = self.inputs.file + # pylint:disable=no-member + builder.metadata.options.resources = { + "num_machines": 1, + "tot_num_mpiprocs": 2, + } + if self.inputs.dry_run.value: + return self.inputs + aim_calc = self.submit(builder) + aim_calc.store() + if "aim_group" in self.inputs: + aim_noreor_group = load_group(self.inputs.aim_group) + aim_noreor_group.add_nodes(aim_calc) + out_dict = {"aim": aim_calc} + return ToContext(out_dict) + + def rotate(self): + """perform the rotation""" + aimfolder = ( + self.ctx["aim"].base.links.get_outgoing().get_node_by_label("retrieved") + ) + output_dict = ( + self.ctx["aim"] + .base.links.get_outgoing() + .get_node_by_label("output_parameters") + .get_dict() + ) + atom_props = output_dict["atomic_properties"] + cc_props = output_dict["cc_properties"] + self.ctx.rot_struct_dict = generate_rotated_structure_aiida( + aimfolder, atom_props, cc_props + ) + + def dict_to_struct_reor(self): + """generate the gaussian input from rotated structure""" + structure = dict_to_structure(self.ctx.rot_struct_dict) + structure.store() + if "reor_group" in self.inputs: + reor_struct_group = load_group(self.inputs.reor_group.value) + reor_struct_group.add_nodes(structure) + if "frag_label" in self.inputs: + struct_extras = EntityExtras(structure) + struct_extras.set("smiles", self.inputs.frag_label.value) + self.ctx.rot_structure = structure + + def result(self): + """Parse results""" + self.out("rotated_structure", self.ctx.rot_structure) diff --git a/src/aiida_aimall/workchains/qc_programs.py b/src/aiida_aimall/workchains/qc_programs.py new file mode 100644 index 0000000..63ad928 --- /dev/null +++ b/src/aiida_aimall/workchains/qc_programs.py @@ -0,0 +1,264 @@ +"""Workchains to interface with AiiDA""" +# pylint: disable=c-extension-no-member +# pylint:disable=no-member +from aiida.engine import ToContext, WorkChain, if_ +from aiida.orm import Bool, Code, Dict, List, SinglefileData, Str, load_group +from aiida.orm.extras import EntityExtras +from aiida_gaussian.calculations import GaussianCalculation +from aiida_shell import launch_shell_job + +from aiida_aimall.calculations import AimqbCalculation +from aiida_aimall.data import AimqbParameters +from aiida_aimall.workchains.calcfunctions import ( + get_wfx, + validate_file_ext, + validate_shell_code, +) +from aiida_aimall.workchains.input import BaseInputWorkChain + + +class QMToAIMWorkChain(WorkChain): + """Workchain to link quantum chemistry jobs without plugins to AIMAll""" + + @classmethod + def define(cls, spec): + super().define(spec) + spec.input( + "shell_code", + validator=validate_shell_code, # pylint:disable=expression-not-assigned + ) + spec.input("shell_metadata", valid_type=Dict) + spec.input("shell_retrieved", valid_type=List) + spec.input("shell_input_file", valid_type=SinglefileData, required=False) + spec.input("shell_cmdline", valid_type=Str, required=True) + spec.input("wfx_filename", valid_type=Str, required=False) + spec.input("aim_code", valid_type=Code, required=True) + spec.input( + "aim_file_ext", + valid_type=Str, + validator=validate_file_ext, # pylint:disable=expression-not-assigned + required=False, + default=lambda: Str("wfx"), + ) + spec.input("aim_params", valid_type=AimqbParameters, required=True) + spec.input( + "aim_parser", + valid_type=Str, + required=False, + default=lambda: Str("aimall.base"), + ) + spec.input("dry_run", valid_type=Bool, default=lambda: Bool(False)) + + spec.output("parameter_dict", valid_type=Dict) + spec.outline(cls.shell_job, cls.aim, cls.result) + + def shell_job(self): + """Launch a shell job""" + if self.inputs.dry_run.value: + return self.inputs + _, node = launch_shell_job( + self.inputs.shell_code, + arguments=self.inputs.shell_cmdline.value, + nodes={"file": self.inputs.shell_input_file}, + outputs=self.inputs.shell_retrieved.get_list(), + submit=True, + metadata=self.inputs.shell_metadata.get_dict(), + ) + out_dict = {"qm": node} + return ToContext(out_dict) + + def aim(self): + """Launch an AIMQB calculation""" + builder = AimqbCalculation.get_builder() + builder.parameters = self.inputs.aim_params + + if "wfx_filename" not in self.inputs: + wfx_file = ( + self.inputs.shell_input_file.filename.split(".")[0] + + f"_{self.inputs.aim_file_ext.value}" + ) + else: + wfx_file = self.inputs.wfx_filename.value.replace(".", "_") + builder.file = self.ctx.qm.base.links.get_outgoing().get_node_by_label(wfx_file) + builder.code = self.inputs.aim_code + builder.metadata.options.parser_name = self.inputs.aim_parser.value + builder.metadata.options.resources = {"num_machines": 1, "tot_num_mpiprocs": 2} + # future work, enable group + # num_atoms = len( + # self.ctx.prereor_aim.get_outgoing() + # .get_node_by_label("rotated_structure") + # .value.split("\n") + # ) + # # generalize for substrates other than H + # builder.group_atoms = List([x + 1 for x in range(0, num_atoms) if x != 1]) + if self.inputs.dry_run.value: + return self.inputs + process_node = self.submit(builder) + out_dict = {"aim": process_node} + return ToContext(out_dict) + + def result(self): + """Put results in output node""" + self.out( + "parameter_dict", + self.ctx.aim.base.links.get_outgoing().get_node_by_label( + "output_parameters" + ), + ) + + +class GaussianToAIMWorkChain(BaseInputWorkChain): + """A workchain to submit a Gaussian calculation and automatically setup an AIMAll calculation on the output""" + + @classmethod + def define(cls, spec): + """Define workchain steps""" + super().define(spec) + spec.input("gauss_params", valid_type=Dict, required=True) + spec.input("aim_params", valid_type=AimqbParameters, required=True) + spec.input("gauss_code", valid_type=Code) + spec.input( + "frag_label", + valid_type=Str, + help="Label for substituent fragment, stored as extra", + required=False, + ) + spec.input("wfx_group", valid_type=Str, required=False) + spec.input("gaussian_group", valid_type=Str, required=False) + spec.input("aim_code", valid_type=Code) + spec.input("dry_run", valid_type=Bool, default=lambda: Bool(False)) + spec.input("wfx_filename", valid_type=Str, default=lambda: Str("output.wfx")) + spec.output("parameter_dict", valid_type=Dict) + spec.outline( + cls.validate_input, + if_(cls.is_xyz_input)(cls.create_structure_from_xyz), + if_(cls.is_smiles_input)( + cls.get_molecule_inputs_step, cls.string_to_StructureData + ), + if_(cls.is_structure_input)(cls.structure_in_context), + cls.gauss, + cls.classify_wfx, + cls.aim, + cls.result, + ) + + def gauss(self): + """Run Gaussian calculation""" + builder = GaussianCalculation.get_builder() + builder.structure = self.ctx.structure + builder.parameters = self.inputs.gauss_params + builder.code = self.inputs.gauss_code + builder.metadata.options.resources = {"num_machines": 1, "tot_num_mpiprocs": 4} + builder.metadata.options.max_memory_kb = int(6400 * 1.25) * 1024 + builder.metadata.options.max_wallclock_seconds = 604800 + builder.metadata.options.additional_retrieve_list = [ + self.inputs.wfx_filename.value + ] + if self.inputs.dry_run.value: + return self.inputs + process_node = self.submit(builder) + if "gaussian_group" in self.inputs: + gauss_group = load_group(self.inputs.gaussian_sp_group) + gauss_group.add_nodes(process_node) + out_dict = {"gauss": process_node} + # self.ctx.standard_wfx = process_node.get_outgoing().get_node_by_label("wfx") + return ToContext(out_dict) + + def classify_wfx(self): + """Add the wavefunction file from the previous step to the correct group and set the extras""" + folder_data = self.ctx.gauss.base.links.get_outgoing().get_node_by_label( + "retrieved" + ) + self.ctx.wfx = get_wfx(folder_data, self.inputs.wfx_filename) + # later scan input parameters for filename + + if "wfx_group" in self.inputs: + wf_group = load_group(self.inputs.wfx_group) + wf_group.add_nodes(self.ctx.wfx) + if "frag_label" in self.inputs: + struct_extras = EntityExtras(self.ctx.wfx) + struct_extras.set("smiles", self.inputs.frag_label.value) + + def aim(self): + """Run Final AIM Calculation""" + builder = AimqbCalculation.get_builder() + builder.parameters = self.inputs.aim_params + builder.file = self.ctx.wfx + builder.code = self.inputs.aim_code + # if "frag_label" in self.inputs: + # builder.frag_label = self.inputs.frag_label + builder.metadata.options.resources = {"num_machines": 1, "tot_num_mpiprocs": 2} + num_atoms = len(self.ctx.structure.sites) + # generalize for substrates other than H + builder.group_atoms = List([x + 1 for x in range(0, num_atoms) if x != 1]) + if self.inputs.dry_run.value: + return self.inputs + process_node = self.submit(builder) + out_dict = {"aim": process_node} + return ToContext(out_dict) + + def result(self): + """Put results in output node""" + self.out( + "parameter_dict", + self.ctx.aim.base.links.get_outgoing().get_node_by_label( + "output_parameters" + ), + ) + + +class GenerateWFXToAIMWorkChain(WorkChain): + """Workchain to generate a wfx file from computational chemistry output files and submit that to an AIMQB Calculation + + Note: + This workchain uses the IOData module of the Ayer's group Horton to generate the wfx files. Supported file formats + include .fchk files, molden files (from Molpro, Orca, PSI4, Turbomole, and Molden), and CP2K atom log files. Further + note that .fchk files can simply be provided directly to an `AimqbCalculation`. + + While IOData accepts other file formats, these formats are the ones available that contain the necessary information + to generate wfc files + """ + + @classmethod + def define(cls, spec): + super().define(spec) + spec.input("input_file", valid_type=SinglefileData) + spec.input("aim_params", valid_type=AimqbParameters) + spec.input("aim_code") + spec.output("output_parameters", valid_type=Dict) + spec.outline(cls.generate_wfx, cls.aim, cls.result) + + def generate_wfx(self): + """Given SinglefileData generates a wfx file if IOData is capable""" + _, node = launch_shell_job( + "iodata-convert", + arguments="{file} output.wfx", + nodes={"file": self.inputs.input_file}, + outputs=["output.wfx"], + submit=True, + ) + out_dict = {"shell_wfx": node} + return ToContext(out_dict) + + def aim(self): + """Run AIM on the generated wfx file""" + builder = AimqbCalculation.get_builder() + builder.parameters = self.inputs.aim_params + builder.file = self.ctx.shell_wfx.base.links.get_outgoing().get_node_by_label( + "output_wfx" + ) + builder.code = self.inputs.aim_code + builder.metadata.options.resources = {"num_machines": 1, "tot_num_mpiprocs": 2} + # generalize for substrates other than H + process_node = self.submit(builder) + out_dict = {"aim": process_node} + return ToContext(out_dict) + + def result(self): + """Put results in output node""" + self.out( + "output_parameters", + self.ctx.aim.base.links.get_outgoing().get_node_by_label( + "output_parameters" + ), + ) diff --git a/src/aiida_aimall/workchains/subparam.py b/src/aiida_aimall/workchains/subparam.py new file mode 100644 index 0000000..a20b602 --- /dev/null +++ b/src/aiida_aimall/workchains/subparam.py @@ -0,0 +1,203 @@ +"""aiida_aimall.workchains +Workchains designed for a workflow starting from a set of cmls, then breaking off into fragment Gaussian Calculations +Needs to be run in part with aiida_aimall.controllers to control local traffic on lab Mac +Example in the works + +Provided Workchains are +MultiFragmentWorkchain, entry point: multifrag +gaussOptWorkChain, entry point: gaussopt +AimAllReor WorkChain, entry point: aimreor +""" +# pylint: disable=c-extension-no-member +# pylint:disable=no-member +# pylint:disable=too-many-lines +from aiida.engine import ToContext, if_ +from aiida.orm import Bool, Code, Dict, List, Str, load_group +from aiida.orm.extras import EntityExtras +from aiida_gaussian.calculations import GaussianCalculation + +from aiida_aimall.calculations import AimqbCalculation +from aiida_aimall.data import AimqbParameters +from aiida_aimall.workchains.calcfunctions import get_substituent_input, get_wfx +from aiida_aimall.workchains.input import BaseInputWorkChain +from aiida_aimall.workchains.param_parts import AIMAllReorWorkChain + + +class SubstituentParameterWorkChain(BaseInputWorkChain): + """A workchain to perform the full suite of KLG's substituent parameter determining""" + + @classmethod + def define(cls, spec): + """Define workchain steps""" + super().define(spec) + spec.input("gauss_opt_params", valid_type=Dict, required=True) + spec.input("gauss_sp_params", valid_type=Dict, required=True) + spec.input("aim_params", valid_type=AimqbParameters, required=True) + spec.input("gauss_code", valid_type=Code) + spec.input( + "frag_label", + valid_type=Str, + help="Label for substituent fragment, stored as extra", + required=False, + ) + spec.input("opt_wfx_group", valid_type=Str, required=False) + spec.input("sp_wfx_group", valid_type=Str, required=False) + spec.input("gaussian_opt_group", valid_type=Str, required=False) + spec.input("gaussian_sp_group", valid_type=Str, required=False) + spec.input( + "wfx_filename", + valid_type=Str, + required=False, + default=lambda: Str("output.wfx"), + ) + # spec.input("file", valid_type=SinglefileData) + # spec.output('aim_dict',valid_type=Dict) + spec.input("aim_code", valid_type=Code) + spec.input("dry_run", valid_type=Bool, default=lambda: Bool(False)) + # spec.input("frag_label", valid_type=Str) + # spec.output("rotated_structure", valid_type=Str) + spec.output("parameter_dict", valid_type=Dict) + spec.outline( + cls.validate_input, + if_(cls.is_xyz_input)(cls.create_structure_from_xyz), + if_(cls.is_smiles_input)( + cls.get_substituent_inputs_step, cls.string_to_StructureData + ), + if_(cls.is_structure_input)(cls.structure_in_context), + cls.gauss_opt, + cls.classify_opt_wfx, + cls.aim_reor, + cls.gauss_sp, + cls.classify_sp_wfx, + cls.aim, + cls.result, + ) + + def get_substituent_inputs_step(self): + """Get a dictionary of the substituent input for a given SMILES""" + self.ctx.smiles_geom = get_substituent_input(self.inputs.smiles) + + def gauss_opt(self): + """Submit the Gaussian optimization""" + builder = GaussianCalculation.get_builder() + builder.structure = self.ctx.structure + builder.parameters = self.inputs.gauss_opt_params + builder.code = self.inputs.gauss_code + builder.metadata.options.resources = {"num_machines": 1, "tot_num_mpiprocs": 4} + builder.metadata.options.max_memory_kb = int(6400 * 1.25) * 1024 + builder.metadata.options.max_wallclock_seconds = 604800 + builder.metadata.options.additional_retrieve_list = [ + self.inputs.wfx_filename.value + ] + if self.inputs.dry_run.value: + return self.inputs + process_node = self.submit(builder) + if "gaussian_opt_group" in self.inputs: + gauss_opt_group = load_group(self.inputs.gaussian_opt_group) + gauss_opt_group.add_nodes(process_node) + out_dict = {"opt": process_node} + # self.ctx.standard_wfx = process_node.get_outgoing().get_node_by_label("wfx") + return ToContext(out_dict) + + def classify_opt_wfx(self): + """Add the wavefunction file from the previous step to the correct group and set the extras""" + folder_data = self.ctx.opt.base.links.get_outgoing().get_node_by_label( + "retrieved" + ) + # later scan input parameters for filename + wfx_file = get_wfx(folder_data, self.inputs.wfx_filename.value) + self.ctx.opt_wfx = wfx_file + + if "opt_wfx_group" in self.inputs: + opt_wf_group = load_group(self.inputs.opt_wfx_group) + opt_wf_group.add_nodes(wfx_file) + if "frag_label" in self.inputs: + struct_extras = EntityExtras(wfx_file) + struct_extras.set("smiles", self.inputs.frag_label.value) + + def aim_reor(self): + """Submit the Aimqb calculation and reorientation""" + builder = AIMAllReorWorkChain.get_builder() + builder.aim_params = self.inputs.aim_params + builder.file = self.ctx.opt_wfx + builder.aim_code = self.inputs.aim_code + # builder.dry_run = self.inputs.dry_run + if "frag_label" in self.inputs: + builder.frag_label = self.inputs.frag_label + if self.inputs.dry_run.value: + return self.inputs + process_node = self.submit(builder) + out_dict = {"prereor_aim": process_node} + return ToContext(out_dict) + + def gauss_sp(self): + """Run Gaussian Single Point calculation""" + builder = GaussianCalculation.get_builder() + builder.structure = ( + self.ctx.prereor_aim.base.links.get_outgoing().get_node_by_label( + "rotated_structure" + ) + ) + builder.parameters = self.inputs.gauss_sp_params + builder.code = self.inputs.gauss_code + builder.metadata.options.resources = {"num_machines": 1, "tot_num_mpiprocs": 4} + builder.metadata.options.max_memory_kb = int(6400 * 1.25) * 1024 + builder.metadata.options.max_wallclock_seconds = 604800 + builder.metadata.options.additional_retrieve_list = [ + self.inputs.wfx_filename.value + ] + if self.inputs.dry_run.value: + return self.inputs + process_node = self.submit(builder) + if "gaussian_sp_group" in self.inputs: + gauss_sp_group = load_group(self.inputs.gaussian_sp_group) + gauss_sp_group.add_nodes(process_node) + out_dict = {"sp": process_node} + # self.ctx.standard_wfx = process_node.get_outgoing().get_node_by_label("wfx") + return ToContext(out_dict) + + def classify_sp_wfx(self): + """Add the wavefunction file from the previous step to the correct group and set the extras""" + folder_data = self.ctx.sp.base.links.get_outgoing().get_node_by_label( + "retrieved" + ) + # later scan input parameters for filename + wfx_file = get_wfx(folder_data, self.inputs.wfx_filename.value) + self.ctx.sp_wfx = wfx_file + + if "sp_wfx_group" in self.inputs: + sp_wf_group = load_group(self.inputs.sp_wfx_group) + sp_wf_group.add_nodes(wfx_file) + if "frag_label" in self.inputs: + struct_extras = EntityExtras(wfx_file) + struct_extras.set("smiles", self.inputs.frag_label.value) + + def aim(self): + """Run Final AIM Calculation""" + builder = AimqbCalculation.get_builder() + builder.parameters = self.inputs.aim_params + builder.file = self.ctx.sp_wfx + builder.code = self.inputs.aim_code + builder.metadata.options.parser_name = "aimall.group" + builder.metadata.options.resources = {"num_machines": 1, "tot_num_mpiprocs": 2} + num_atoms = len( + self.ctx.prereor_aim.base.links.get_outgoing() + .get_node_by_label("rotated_structure") + .sites + ) + # generalize for substrates other than H + builder.group_atoms = List([x + 1 for x in range(0, num_atoms) if x != 1]) + if self.inputs.dry_run.value: + return self.inputs + process_node = self.submit(builder) + out_dict = {"final_aim": process_node} + return ToContext(out_dict) + + def result(self): + """Put results in output node""" + self.out( + "parameter_dict", + self.ctx.final_aim.base.links.get_outgoing().get_node_by_label( + "output_parameters" + ), + ) diff --git a/tests/conftest.py b/tests/conftest.py index 531ce3f..9565929 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -451,10 +451,10 @@ def _generate_shelljob_inputs(fixture_localhost, filepath_tests): @pytest.fixture -def generate_g16_inputs(): +def generate_gauss_inputs(): """Generates inputs of a default aimqb calculation""" - def _generate_g16_inputs(fixture_code): + def _generate_gauss_inputs(fixture_code): """Return only those inputs the parser will expect to be there""" gaussian_input = Dict( { @@ -486,17 +486,17 @@ def _generate_g16_inputs(fixture_code): } return AttributeDict(inputs) - return _generate_g16_inputs + return _generate_gauss_inputs @pytest.fixture -def generate_workchain_smitog16( +def generate_workchain_smitogauss( generate_workchain, generate_calc_job_node, fixture_code ): """Generate an instance of a ``SmilesToGaussianWorkchain``.""" - def _generate_workchain_smitog16( - exit_code=None, inputs=None, return_inputs=False, g16_outputs=None + def _generate_workchain_smitogauss( + exit_code=None, inputs=None, return_inputs=False, gauss_outputs=None ): """Generate an instance of a ``SmilesToGaussianWorkchain``. @@ -509,7 +509,7 @@ def _generate_workchain_smitog16( from aiida.common import LinkType from plumpy import ProcessState - entry_point = "aimall.smitog16" + entry_point = "aimall.smitogauss" if inputs is None: gaussian_input = Dict( @@ -546,8 +546,8 @@ def _generate_workchain_smitog16( gaussian_node = generate_calc_job_node(inputs={"parameters": Dict()}) process.ctx.children = [gaussian_node] - if g16_outputs is not None: - for link_label, output_node in g16_outputs.items(): + if gauss_outputs is not None: + for link_label, output_node in gauss_outputs.items(): output_node.base.links.add_incoming( gaussian_node, link_type=LinkType.CREATE, link_label=link_label ) @@ -559,7 +559,7 @@ def _generate_workchain_smitog16( return process - return _generate_workchain_smitog16 + return _generate_workchain_smitogauss @pytest.fixture @@ -623,10 +623,10 @@ def _generate_workchain_subparam( ) aiminputs = AimqbParameters({"naat": 2, "nproc": 2, "atlaprhocps": True}) inputs = { - "g16_opt_params": gaussian_opt_input, - "g16_sp_params": gaussian_sp_input, + "gauss_opt_params": gaussian_opt_input, + "gauss_sp_params": gaussian_sp_input, "aim_params": aiminputs, - "g16_code": fixture_code("gaussian"), + "gauss_code": fixture_code("gaussian"), "frag_label": Str("*C"), # "opt_wfx_group": Str("group1"), # "sp_wfx_group": Str("group2"), @@ -688,12 +688,12 @@ def _generate_workchain_subparam( @pytest.fixture -def generate_workchain_g16toaim( +def generate_workchain_gausstoaim( generate_workchain, generate_calc_job_node, fixture_code ): """Generate an instance of a ``GaussianToAIMWorkChain``.""" - def _generate_workchain_g16toaim( + def _generate_workchain_gausstoaim( exit_code=None, inputs=None, return_inputs=False, @@ -712,7 +712,7 @@ def _generate_workchain_g16toaim( from aiida.common import LinkType from plumpy import ProcessState - entry_point = "aimall.g16toaim" + entry_point = "aimall.gausstoaim" if inputs is None: gaussian_input = Dict( @@ -738,10 +738,10 @@ def _generate_workchain_g16toaim( struct_data = StructureData(ase=ase.io.read(f, format="xyz")) f.close() inputs = { - "g16_params": gaussian_input, + "gauss_params": gaussian_input, "aim_params": aiminputs, "structure": struct_data, - "g16_code": fixture_code("gaussian"), + "gauss_code": fixture_code("gaussian"), "frag_label": Str("*C"), # "opt_wfx_group": Str("group1"), # "sp_wfx_group": Str("group2"), @@ -754,10 +754,10 @@ def _generate_workchain_g16toaim( wfx_string = "5\n\n C -0.1 2.0 -0.02\nH 0.3 1.0 -0.02\nH 0.3 2.5 0.8\nH 0.3 2.5 -0.9\nH -1.2 2.0 -0.02" xyz_data = SinglefileData(io.BytesIO(wfx_string.encode())) inputs = { - "g16_params": gaussian_input, + "gauss_params": gaussian_input, "aim_params": aiminputs, "xyz_file": xyz_data, - "g16_code": fixture_code("gaussian"), + "gauss_code": fixture_code("gaussian"), "frag_label": Str("*C"), # "opt_wfx_group": Str("group1"), # "sp_wfx_group": Str("group2"), @@ -768,10 +768,10 @@ def _generate_workchain_g16toaim( } elif input_type == "smiles": inputs = { - "g16_params": gaussian_input, + "gauss_params": gaussian_input, "aim_params": aiminputs, "smiles": Str("C"), - "g16_code": fixture_code("gaussian"), + "gauss_code": fixture_code("gaussian"), "frag_label": Str("*C"), # "opt_wfx_group": Str("group1"), # "sp_wfx_group": Str("group2"), @@ -808,7 +808,7 @@ def _generate_workchain_g16toaim( aim_node.set_exit_status(exit_code.status) return process - return _generate_workchain_g16toaim + return _generate_workchain_gausstoaim @pytest.fixture diff --git a/tests/controllers/test_aimreorcontroller.py b/tests/controllers/test_aimreorcontroller.py index c041f51..4418cfc 100644 --- a/tests/controllers/test_aimreorcontroller.py +++ b/tests/controllers/test_aimreorcontroller.py @@ -53,4 +53,4 @@ def test_aimreor_controller(fixture_code): assert "aim_params" in ins assert "file" in ins assert "reor_group" in ins - assert wf.get_name() == "AIMAllReor" + assert wf.get_name() == "AIMAllReorWorkChain" diff --git a/tests/controllers/test_gaussiansubmissioncontroller.py b/tests/controllers/test_gaussiansubmissioncontroller.py index e06db79..06cba66 100644 --- a/tests/controllers/test_gaussiansubmissioncontroller.py +++ b/tests/controllers/test_gaussiansubmissioncontroller.py @@ -21,7 +21,7 @@ def test_unstored_parentgrouplabel_returns_error(): max_concurrent=1, code_label="test.aimall.aimqb", wfxgroup="test", - g16_sp_params={}, + gauss_sp_params={}, ) assert str(excinfo.value) == "No result was found" @@ -39,7 +39,7 @@ def test_gaussiansubmission_controller(fixture_code): group_label="gaussian_sp", max_concurrent=1, code_label=code.label + "@" + code.computer.label, - g16_sp_params={}, + gauss_sp_params={}, wfxgroup="test", ) assert con.get_extra_unique_keys() == ("smiles",) diff --git a/tests/controllers/test_smilestogaussiancontroller.py b/tests/controllers/test_smilestogaussiancontroller.py index b74cde7..a419f46 100644 --- a/tests/controllers/test_smilestogaussiancontroller.py +++ b/tests/controllers/test_smilestogaussiancontroller.py @@ -17,7 +17,7 @@ def test_unstored_parentgrouplabel_returns_error(): group_label="opt_workchain", code_label="test.aimall.aimqb", max_concurrent=1, - g16_opt_params={}, + gauss_opt_params={}, wfxgroup="wfx", nprocs=4, mem_mb=6400, @@ -27,7 +27,7 @@ def test_unstored_parentgrouplabel_returns_error(): @pytest.mark.usefixtures("aiida_profile") -def test_aimreor_controller(fixture_code): +def test_smigauss_controller(fixture_code): """Test that error returns when groups are not defined""" gr = Group(label="smitogaussian") @@ -39,7 +39,7 @@ def test_aimreor_controller(fixture_code): group_label="opt_workchain", code_label=code.label, max_concurrent=1, - g16_opt_params={}, + gauss_opt_params={}, wfxgroup="wfx", nprocs=4, mem_mb=6400, @@ -62,4 +62,4 @@ def test_aimreor_controller(fixture_code): assert "nprocs" in ins assert "mem_mb" in ins assert "time_s" in ins - assert wf.get_name() == "SmilesToGaussianWorkchain" + assert wf.get_name() == "SmilesToGaussianWorkChain" diff --git a/tests/workchains/fixtures/aimall.smitog16/default/aiida.chk b/tests/workchains/fixtures/aimall.smitogauss/default/aiida.chk similarity index 100% rename from tests/workchains/fixtures/aimall.smitog16/default/aiida.chk rename to tests/workchains/fixtures/aimall.smitogauss/default/aiida.chk diff --git a/tests/workchains/fixtures/aimall.smitog16/default/aiida.gjf b/tests/workchains/fixtures/aimall.smitogauss/default/aiida.gjf similarity index 100% rename from tests/workchains/fixtures/aimall.smitog16/default/aiida.gjf rename to tests/workchains/fixtures/aimall.smitogauss/default/aiida.gjf diff --git a/tests/workchains/fixtures/aimall.smitog16/default/aiida.log b/tests/workchains/fixtures/aimall.smitogauss/default/aiida.log similarity index 100% rename from tests/workchains/fixtures/aimall.smitog16/default/aiida.log rename to tests/workchains/fixtures/aimall.smitogauss/default/aiida.log diff --git a/tests/workchains/fixtures/aimall.smitog16/default/aiida.wfx b/tests/workchains/fixtures/aimall.smitogauss/default/aiida.wfx similarity index 100% rename from tests/workchains/fixtures/aimall.smitog16/default/aiida.wfx rename to tests/workchains/fixtures/aimall.smitogauss/default/aiida.wfx diff --git a/tests/workchains/test_calcfunctions.py b/tests/workchains/test_calcfunctions.py index 3989ed9..2c53ac5 100644 --- a/tests/workchains/test_calcfunctions.py +++ b/tests/workchains/test_calcfunctions.py @@ -5,7 +5,7 @@ from rdkit import Chem from subproptools import qtaim_extract as qt -from aiida_aimall import workchains as aimw +from aiida_aimall.workchains import calcfunctions as cf def test_generate_structure_data(): @@ -17,24 +17,24 @@ def test_generate_structure_data(): ) # "C 0.0 0.0 0.0\nH -1.0 0.0 0.0\nH 1.0 1.0 0.0\nH 1.0 -1.0 1.0\n H 1.0 -1.0 -1.0" - structure_data = aimw.generate_structure_data(test_Str) + structure_data = cf.generate_structure_data(test_Str) assert isinstance(structure_data, StructureData) def test_calc_multiplicity(): """Tests calc_multiplicity function""" mol1 = Chem.MolFromSmiles("C") - assert aimw.calc_multiplicity(mol1) == 1 + assert cf.calc_multiplicity(mol1) == 1 mol2 = Chem.MolFromSmiles("[CH3]") - assert aimw.calc_multiplicity(mol2) == 2 + assert cf.calc_multiplicity(mol2) == 2 mol3 = Chem.MolFromSmiles("[CH2][CH2]") - assert aimw.calc_multiplicity(mol3) == 3 + assert cf.calc_multiplicity(mol3) == 3 def test_find_attachment_atoms(): """Tests find_attachment_atoms function""" mol = Chem.MolFromSmiles("*C") - mol_rw, zero_at, attached_atom = aimw.find_attachment_atoms(mol) + mol_rw, zero_at, attached_atom = cf.find_attachment_atoms(mol) num_hs = 0 # explicit hydrogens should have been added - check that for atom in mol_rw.GetAtoms(): @@ -56,17 +56,17 @@ def test_find_attachment_exceptions(): mol = Chem.MolFromSmiles("C") # Check that find_attachment returns errors with number of * != 1 with pytest.raises(ValueError): - aimw.find_attachment_atoms(mol) + cf.find_attachment_atoms(mol) mol2 = Chem.MolFromSmiles("*C*") with pytest.raises(ValueError): - aimw.find_attachment_atoms(mol2) + cf.find_attachment_atoms(mol2) def test_reorder_molecule(): """Test reorder_molecule""" mol = Chem.MolFromSmiles("CN*") - mol_rw, zero_at, attached_atom = aimw.find_attachment_atoms(mol) - reorder_mol = aimw.reorder_molecule(mol_rw, zero_at, attached_atom) + mol_rw, zero_at, attached_atom = cf.find_attachment_atoms(mol) + reorder_mol = cf.reorder_molecule(mol_rw, zero_at, attached_atom) # Atom 1 (index 0) should be N soince it is attached to * assert reorder_mol.GetAtomWithIdx(0).GetSymbol() == "N" # Atom 2 (index 1) was a *, should now be H @@ -79,9 +79,9 @@ def test_reorder_molecule(): def test_get_xyz(): """Test get_xyz""" mol = Chem.MolFromSmiles("CN*") - mol_rw, zero_at, attached_atom = aimw.find_attachment_atoms(mol) - reorder_mol = aimw.reorder_molecule(mol_rw, zero_at, attached_atom) - out_xyz = aimw.get_xyz(reorder_mol) + mol_rw, zero_at, attached_atom = cf.find_attachment_atoms(mol) + reorder_mol = cf.reorder_molecule(mol_rw, zero_at, attached_atom) + out_xyz = cf.get_xyz(reorder_mol) # out_xyz should be a string that when split on newlines is equal in length to number of atoms assert isinstance(out_xyz, str) split_xyz = out_xyz.split("\n") @@ -91,7 +91,7 @@ def test_get_xyz(): def test_get_substituent_input(): """Test get_substituent_input""" - out_Dict = aimw.get_substituent_input(Str("*C")) + out_Dict = cf.get_substituent_input(Str("*C")) out_dict = out_Dict.get_dict() # output should be Dict with xyz, charge, and multiplpcity keys assert isinstance(out_Dict, Dict) @@ -104,7 +104,7 @@ def test_parameters_with_cm(): """Test parameters_with_cm""" parameter_dict = Dict({}) smiles_dict = Dict({"charge": 0, "multiplicity": 1}) - out_Dict = aimw.parameters_with_cm(parameter_dict, smiles_dict) + out_Dict = cf.parameters_with_cm(parameter_dict, smiles_dict) assert isinstance(out_Dict, Dict) out_dict = out_Dict.get_dict() # charge and multiplicity matching the smiles_dict should be in out_Dict @@ -118,10 +118,10 @@ def test_validate_shell_code(): """Test validate_shell_code""" str_node = Str("aimall") # Str node is valid input, should return None - assert not aimw.validate_shell_code(str_node, "foo") + assert not cf.validate_shell_code(str_node, "foo") int_node = Int(1) # Int node is invalid input, should return string error message - res = aimw.validate_shell_code(int_node, "foo") + res = cf.validate_shell_code(int_node, "foo") assert ( res == "the `shell_code` input must be either ShellCode or Str of the command." ) @@ -131,15 +131,15 @@ def test_validate_file_ext(): """Test validate_file_ext - provided file extension should be wfx, wfn or fchk""" # these should all return None, so check for not None wfx_node = Str("wfx") - assert not aimw.validate_file_ext(wfx_node, "foo") + assert not cf.validate_file_ext(wfx_node, "foo") wfn_node = Str("wfn") - assert not aimw.validate_file_ext(wfn_node, "foo") + assert not cf.validate_file_ext(wfn_node, "foo") fchk_node = Str("fchk") - assert not aimw.validate_file_ext(fchk_node, "foo") + assert not cf.validate_file_ext(fchk_node, "foo") # log extension should result in error log_node = Str("log") err_str = "the `aim_file_ext` input must be a valid file format for AIMQB: wfx, wfn, or fchk" - assert aimw.validate_file_ext(log_node, "log") == err_str + assert cf.validate_file_ext(log_node, "log") == err_str def test_generate_rotated_structure_aiida(generate_workchain_folderdata): @@ -163,7 +163,7 @@ def test_generate_rotated_structure_aiida(generate_workchain_folderdata): ) for x in atom_list } - rot_Dict = aimw.generate_rotated_structure_aiida(aim_folder, a_props, cc_dict) + rot_Dict = cf.generate_rotated_structure_aiida(aim_folder, a_props, cc_dict) rot_dict = rot_Dict.get_dict() assert isinstance(rot_Dict, Dict) assert "atom_symbols" in rot_dict @@ -181,5 +181,5 @@ def test_dict_to_structure(): str_dict = Dict( {"atom_symbols": ["H", "H"], "geom": [[-0.5, 0.0, 0.0], [0.5, 0.0, 0.0]]} ) - str_str = aimw.dict_to_structure(str_dict) + str_str = cf.dict_to_structure(str_dict) assert isinstance(str_str, StructureData) diff --git a/tests/workchains/test_gaussiantoaim.py b/tests/workchains/test_gaussiantoaim.py index b7e1530..8ceeb47 100644 --- a/tests/workchains/test_gaussiantoaim.py +++ b/tests/workchains/test_gaussiantoaim.py @@ -5,9 +5,9 @@ from plumpy.utils import AttributesFrozendict -def test_setup(generate_workchain_g16toaim): +def test_setup(generate_workchain_gausstoaim): """Test creation of `AimReorWorkChain`.""" - process = generate_workchain_g16toaim() + process = generate_workchain_gausstoaim() assert isinstance(process.inputs, AttributesFrozendict) @@ -15,9 +15,9 @@ def test_setup(generate_workchain_g16toaim): # pylint:disable=too-many-arguments # pylint:disable=too-many-locals def test_default_structure( - generate_workchain_g16toaim, + generate_workchain_gausstoaim, fixture_localhost, - generate_g16_inputs, + generate_gauss_inputs, generate_calc_job_node, fixture_code, ): @@ -26,7 +26,7 @@ def test_default_structure( entry_point_calc_job_gauss = "aimall.gaussianwfx" name = "default" # create the workchain node - wkchain = generate_workchain_g16toaim(input_type="structure") + wkchain = generate_workchain_gausstoaim(input_type="structure") # test the first workchain step assert wkchain.validate_input() is None assert wkchain.is_xyz_input() is False @@ -35,18 +35,18 @@ def test_default_structure( assert wkchain.structure_in_context() is None assert "structure" in wkchain.ctx assert isinstance(wkchain.ctx.structure, StructureData) - gaussian_inputs = wkchain.g16() + gaussian_inputs = wkchain.gauss() assert isinstance(gaussian_inputs, AttributesFrozendict) # Generate mock CalcJobNodes and needed outputs for the gaussian optimization - g16_node = generate_calc_job_node( + gauss_node = generate_calc_job_node( entry_point_name=entry_point_calc_job_gauss, computer=fixture_localhost, test_name=name, - inputs=generate_g16_inputs(fixture_code), + inputs=generate_gauss_inputs(fixture_code), test_folder_type="workchains", ) - g16_node.store() - wkchain.ctx.g16 = g16_node + gauss_node.store() + wkchain.ctx.gauss = gauss_node assert wkchain.classify_wfx() is None assert "wfx" in wkchain.ctx @@ -62,7 +62,7 @@ def test_default_structure( entry_point_calc_job_aim, fixture_localhost, name, - generate_g16_inputs(fixture_code), + generate_gauss_inputs(fixture_code), test_folder_type="workchains", ) wkchain.ctx.aim = aim_sp_node @@ -79,9 +79,9 @@ def test_default_structure( def test_default_xyz( - generate_workchain_g16toaim, + generate_workchain_gausstoaim, fixture_localhost, - generate_g16_inputs, + generate_gauss_inputs, generate_calc_job_node, fixture_code, ): @@ -90,7 +90,7 @@ def test_default_xyz( entry_point_calc_job_gauss = "aimall.gaussianwfx" name = "default" # create the workchain node - wkchain = generate_workchain_g16toaim(input_type="xyz") + wkchain = generate_workchain_gausstoaim(input_type="xyz") # test the first workchain step assert wkchain.validate_input() is None assert wkchain.is_xyz_input() is True @@ -99,18 +99,18 @@ def test_default_xyz( assert wkchain.create_structure_from_xyz() is None assert "structure" in wkchain.ctx assert isinstance(wkchain.ctx.structure, StructureData) - gaussian_inputs = wkchain.g16() + gaussian_inputs = wkchain.gauss() assert isinstance(gaussian_inputs, AttributesFrozendict) # Generate mock CalcJobNodes and needed outputs for the gaussian optimization - g16_node = generate_calc_job_node( + gauss_node = generate_calc_job_node( entry_point_name=entry_point_calc_job_gauss, computer=fixture_localhost, test_name=name, - inputs=generate_g16_inputs(fixture_code), + inputs=generate_gauss_inputs(fixture_code), test_folder_type="workchains", ) - g16_node.store() - wkchain.ctx.g16 = g16_node + gauss_node.store() + wkchain.ctx.gauss = gauss_node assert wkchain.classify_wfx() is None assert "wfx" in wkchain.ctx @@ -126,7 +126,7 @@ def test_default_xyz( entry_point_calc_job_aim, fixture_localhost, name, - generate_g16_inputs(fixture_code), + generate_gauss_inputs(fixture_code), test_folder_type="workchains", ) wkchain.ctx.aim = aim_sp_node @@ -143,9 +143,9 @@ def test_default_xyz( def test_default_smiles( - generate_workchain_g16toaim, + generate_workchain_gausstoaim, fixture_localhost, - generate_g16_inputs, + generate_gauss_inputs, generate_calc_job_node, fixture_code, ): @@ -154,7 +154,7 @@ def test_default_smiles( entry_point_calc_job_gauss = "aimall.gaussianwfx" name = "default" # create the workchain node - wkchain = generate_workchain_g16toaim(input_type="smiles") + wkchain = generate_workchain_gausstoaim(input_type="smiles") # test the first workchain step assert wkchain.validate_input() is None assert wkchain.is_xyz_input() is False @@ -169,18 +169,18 @@ def test_default_smiles( assert wkchain.string_to_StructureData() is None assert "structure" in wkchain.ctx assert isinstance(wkchain.ctx.structure, StructureData) - gaussian_inputs = wkchain.g16() + gaussian_inputs = wkchain.gauss() assert isinstance(gaussian_inputs, AttributesFrozendict) # Generate mock CalcJobNodes and needed outputs for the gaussian optimization - g16_node = generate_calc_job_node( + gauss_node = generate_calc_job_node( entry_point_name=entry_point_calc_job_gauss, computer=fixture_localhost, test_name=name, - inputs=generate_g16_inputs(fixture_code), + inputs=generate_gauss_inputs(fixture_code), test_folder_type="workchains", ) - g16_node.store() - wkchain.ctx.g16 = g16_node + gauss_node.store() + wkchain.ctx.gauss = gauss_node assert wkchain.classify_wfx() is None assert "wfx" in wkchain.ctx @@ -196,7 +196,7 @@ def test_default_smiles( entry_point_calc_job_aim, fixture_localhost, name, - generate_g16_inputs(fixture_code), + generate_gauss_inputs(fixture_code), test_folder_type="workchains", ) wkchain.ctx.aim = aim_sp_node diff --git a/tests/workchains/test_smilestogaussian.py b/tests/workchains/test_smilestogaussian.py index fc6393a..58bdc20 100644 --- a/tests/workchains/test_smilestogaussian.py +++ b/tests/workchains/test_smilestogaussian.py @@ -17,20 +17,20 @@ def test_setup(generate_workchain_aimreor): # pylint:disable=too-many-arguments # pylint:disable=too-many-locals def test_default( - generate_workchain_smitog16, + generate_workchain_smitogauss, fixture_localhost, - generate_g16_inputs, + generate_gauss_inputs, generate_calc_job_node, fixture_code, filepath_tests, ): """Test the default inputs of `SmilesToGaussianWorkchain""" - entry_point_name = "aimall.smitog16" + entry_point_name = "aimall.smitogauss" entry_point_calc_job = "gaussian" test = "default" name = "default" # create the workchain node - wkchain = generate_workchain_smitog16() + wkchain = generate_workchain_smitogauss() # Run the first step - this should put the smiles_geom Dict in context # Note that calling the function in the assert statement does run it assert wkchain.get_substituent_inputs_step() is None @@ -58,7 +58,7 @@ def test_default( entry_point_calc_job, fixture_localhost, name, - generate_g16_inputs(fixture_code), + generate_gauss_inputs(fixture_code), test_folder_type="workchains", ) node.store() diff --git a/tests/workchains/test_subparamchain.py b/tests/workchains/test_subparamchain.py index b0699e8..2c6e1d5 100644 --- a/tests/workchains/test_subparamchain.py +++ b/tests/workchains/test_subparamchain.py @@ -20,7 +20,7 @@ def test_default_structure( generate_workchain_subparam, fixture_localhost, generate_workchain_aimreor, - generate_g16_inputs, + generate_gauss_inputs, generate_calc_job_node, fixture_code, ): @@ -40,21 +40,21 @@ def test_default_structure( assert wkchain.structure_in_context() is None assert "structure" in wkchain.ctx assert isinstance(wkchain.ctx.structure, StructureData) - gaussian_inputs = wkchain.g16_opt() + gaussian_inputs = wkchain.gauss_opt() assert isinstance(gaussian_inputs, AttributesFrozendict) # Generate mock CalcJobNodes and needed outputs for the gaussian optimization - g16_node = generate_calc_job_node( + gauss_node = generate_calc_job_node( entry_point_name=entry_point_calc_job_gauss, computer=fixture_localhost, test_name=name, - inputs=generate_g16_inputs(fixture_code), + inputs=generate_gauss_inputs(fixture_code), test_folder_type="workchains", ) - g16_node.store() - wkchain.ctx.opt = g16_node + gauss_node.store() + wkchain.ctx.opt = gauss_node # gaussian_folder = generate_workchain_folderdata(entry_point_name, gaussianopttest) # gaussian_folder.base.links.add_incoming( - # g16_node, link_type=LinkType.CREATE, link_label="retrieved" + # gauss_node, link_type=LinkType.CREATE, link_label="retrieved" # ) # gaussian_folder.store() assert wkchain.classify_opt_wfx() is None @@ -77,17 +77,17 @@ def test_default_structure( ) # Test gaussian single point - g16_sp_inputs = wkchain.g16_sp() - assert isinstance(g16_sp_inputs, AttributesFrozendict) + gauss_sp_inputs = wkchain.gauss_sp() + assert isinstance(gauss_sp_inputs, AttributesFrozendict) # generate mock gaussian singlepoint outputs - g16_sp_node = generate_calc_job_node( + gauss_sp_node = generate_calc_job_node( entry_point_calc_job_gauss, fixture_localhost, name, - generate_g16_inputs(fixture_code), + generate_gauss_inputs(fixture_code), test_folder_type="workchains", ) - wkchain.ctx.sp = g16_sp_node + wkchain.ctx.sp = gauss_sp_node assert wkchain.classify_sp_wfx() is None assert "sp_wfx" in wkchain.ctx @@ -99,7 +99,7 @@ def test_default_structure( entry_point_calc_job_aim, fixture_localhost, name, - generate_g16_inputs(fixture_code), + generate_gauss_inputs(fixture_code), test_folder_type="workchains", ) wkchain.ctx.final_aim = aim_sp_node @@ -119,7 +119,7 @@ def test_default_xyz( generate_workchain_subparam, fixture_localhost, generate_workchain_aimreor, - generate_g16_inputs, + generate_gauss_inputs, generate_calc_job_node, fixture_code, ): @@ -137,21 +137,21 @@ def test_default_xyz( assert wkchain.create_structure_from_xyz() is None assert "structure" in wkchain.ctx assert isinstance(wkchain.ctx.structure, StructureData) - gaussian_inputs = wkchain.g16_opt() + gaussian_inputs = wkchain.gauss_opt() assert isinstance(gaussian_inputs, AttributesFrozendict) # Generate mock CalcJobNodes and needed outputs for the gaussian optimization - g16_node = generate_calc_job_node( + gauss_node = generate_calc_job_node( entry_point_name=entry_point_calc_job_gauss, computer=fixture_localhost, test_name=name, - inputs=generate_g16_inputs(fixture_code), + inputs=generate_gauss_inputs(fixture_code), test_folder_type="workchains", ) - g16_node.store() - wkchain.ctx.opt = g16_node + gauss_node.store() + wkchain.ctx.opt = gauss_node # gaussian_folder = generate_workchain_folderdata(entry_point_name, gaussianopttest) # gaussian_folder.base.links.add_incoming( - # g16_node, link_type=LinkType.CREATE, link_label="retrieved" + # gauss_node, link_type=LinkType.CREATE, link_label="retrieved" # ) # gaussian_folder.store() assert wkchain.classify_opt_wfx() is None @@ -174,17 +174,17 @@ def test_default_xyz( ) # Test gaussian single point - g16_sp_inputs = wkchain.g16_sp() - assert isinstance(g16_sp_inputs, AttributesFrozendict) + gauss_sp_inputs = wkchain.gauss_sp() + assert isinstance(gauss_sp_inputs, AttributesFrozendict) # generate mock gaussian singlepoint outputs - g16_sp_node = generate_calc_job_node( + gauss_sp_node = generate_calc_job_node( entry_point_calc_job_gauss, fixture_localhost, name, - generate_g16_inputs(fixture_code), + generate_gauss_inputs(fixture_code), test_folder_type="workchains", ) - wkchain.ctx.sp = g16_sp_node + wkchain.ctx.sp = gauss_sp_node assert wkchain.classify_sp_wfx() is None assert "sp_wfx" in wkchain.ctx @@ -196,7 +196,7 @@ def test_default_xyz( entry_point_calc_job_aim, fixture_localhost, name, - generate_g16_inputs(fixture_code), + generate_gauss_inputs(fixture_code), test_folder_type="workchains", ) wkchain.ctx.final_aim = aim_sp_node @@ -216,7 +216,7 @@ def test_default_smiles( generate_workchain_subparam, fixture_localhost, generate_workchain_aimreor, - generate_g16_inputs, + generate_gauss_inputs, generate_calc_job_node, fixture_code, ): @@ -241,21 +241,21 @@ def test_default_smiles( assert wkchain.string_to_StructureData() is None assert "structure" in wkchain.ctx assert isinstance(wkchain.ctx.structure, StructureData) - gaussian_inputs = wkchain.g16_opt() + gaussian_inputs = wkchain.gauss_opt() assert isinstance(gaussian_inputs, AttributesFrozendict) # Generate mock CalcJobNodes and needed outputs for the gaussian optimization - g16_node = generate_calc_job_node( + gauss_node = generate_calc_job_node( entry_point_name=entry_point_calc_job_gauss, computer=fixture_localhost, test_name=name, - inputs=generate_g16_inputs(fixture_code), + inputs=generate_gauss_inputs(fixture_code), test_folder_type="workchains", ) - g16_node.store() - wkchain.ctx.opt = g16_node + gauss_node.store() + wkchain.ctx.opt = gauss_node # gaussian_folder = generate_workchain_folderdata(entry_point_name, gaussianopttest) # gaussian_folder.base.links.add_incoming( - # g16_node, link_type=LinkType.CREATE, link_label="retrieved" + # gauss_node, link_type=LinkType.CREATE, link_label="retrieved" # ) # gaussian_folder.store() assert wkchain.classify_opt_wfx() is None @@ -278,17 +278,17 @@ def test_default_smiles( ) # Test gaussian single point - g16_sp_inputs = wkchain.g16_sp() - assert isinstance(g16_sp_inputs, AttributesFrozendict) + gauss_sp_inputs = wkchain.gauss_sp() + assert isinstance(gauss_sp_inputs, AttributesFrozendict) # generate mock gaussian singlepoint outputs - g16_sp_node = generate_calc_job_node( + gauss_sp_node = generate_calc_job_node( entry_point_calc_job_gauss, fixture_localhost, name, - generate_g16_inputs(fixture_code), + generate_gauss_inputs(fixture_code), test_folder_type="workchains", ) - wkchain.ctx.sp = g16_sp_node + wkchain.ctx.sp = gauss_sp_node assert wkchain.classify_sp_wfx() is None assert "sp_wfx" in wkchain.ctx @@ -300,7 +300,7 @@ def test_default_smiles( entry_point_calc_job_aim, fixture_localhost, name, - generate_g16_inputs(fixture_code), + generate_gauss_inputs(fixture_code), test_folder_type="workchains", ) wkchain.ctx.final_aim = aim_sp_node From 922e337d5591a7314b8d698b39ff60936627dadc Mon Sep 17 00:00:00 2001 From: kmlefran Date: Wed, 28 Aug 2024 15:10:16 -0400 Subject: [PATCH 2/2] Split workchains into smaller files --- README.md | 6 +- .../auto/aiida_aimall/calculations/index.rst | 6 +- .../auto/aiida_aimall/controllers/index.rst | 58 +- .../api/auto/aiida_aimall/data/index.rst | 7 +- .../reference/api/auto/aiida_aimall/index.rst | 4 +- .../api/auto/aiida_aimall/parsers/index.rst | 2 +- .../workchains/calcfunctions/index.rst | 206 +++++++ .../auto/aiida_aimall/workchains/index.rst | 513 +----------------- .../aiida_aimall/workchains/input/index.rst | 82 +++ .../workchains/param_parts/index.rst | 118 ++++ .../workchains/qc_programs/index.rst | 130 +++++ .../workchains/subparam/index.rst | 75 +++ docs/source/tutorials/aimtogaussian.ipynb | 4 +- docs/source/tutorials/makewfx.ipynb | 2 +- docs/source/tutorials/quantumsoftware.ipynb | 4 +- .../tutorials/substituentparameter.ipynb | 2 +- src/aiida_aimall/calculations.py | 8 +- src/aiida_aimall/controllers.py | 45 +- src/aiida_aimall/data.py | 3 - src/aiida_aimall/parsers.py | 2 +- src/aiida_aimall/workchains/calcfunctions.py | 150 ++++- src/aiida_aimall/workchains/input.py | 11 +- src/aiida_aimall/workchains/param_parts.py | 2 +- src/aiida_aimall/workchains/qc_programs.py | 6 +- src/aiida_aimall/workchains/subparam.py | 24 +- 25 files changed, 843 insertions(+), 627 deletions(-) create mode 100644 docs/source/reference/api/auto/aiida_aimall/workchains/calcfunctions/index.rst create mode 100644 docs/source/reference/api/auto/aiida_aimall/workchains/input/index.rst create mode 100644 docs/source/reference/api/auto/aiida_aimall/workchains/param_parts/index.rst create mode 100644 docs/source/reference/api/auto/aiida_aimall/workchains/qc_programs/index.rst create mode 100644 docs/source/reference/api/auto/aiida_aimall/workchains/subparam/index.rst diff --git a/README.md b/README.md index a29a593..9ae0a97 100644 --- a/README.md +++ b/README.md @@ -44,13 +44,13 @@ A plugin to interface AIMAll with AiiDA * [`docs/`](docs/): Source code of documentation for [Read the Docs](http://aiida-diff.readthedocs.io/en/latest/) * [`examples/`](examples/): An example of how to link the four controllers in an overall workflow * [`tests/`](tests/): Basic regression tests using the [pytest](https://docs.pytest.org/en/latest/) framework (submitting a calculation, ...). Install `pip install -e .[testing]` and run `pytest`. - * [`conftest.py`](conftest.py): Configuration of fixtures for [pytest](https://docs.pytest.org/en/latest/) + * [`conftest.py`](PythonPackages/aiida-aimall/tests/conftest.py): Configuration of fixtures for [pytest](https://docs.pytest.org/en/latest/) * [`.gitignore`](.gitignore): Telling git which files to ignore * [`.pre-commit-config.yaml`](.pre-commit-config.yaml): Configuration of [pre-commit hooks](https://pre-commit.com/) that sanitize coding style and check for syntax errors. Enable via `pip install -e .[pre-commit] && pre-commit install` * [`.readthedocs.yml`](.readthedocs.yml): Configuration of documentation build for [Read the Docs](https://readthedocs.org/) * [`.isort.cfg`](.isort.cfg): Configuration to make isort and black precommit actions compatible -* [`LICENSE`](LICENSE): License for your plugin -* [`README.md`](README.md): This file +* [`LICENSE`](PythonPackages/aiida-aimall/LICENSE): License for your plugin +* [`README.md`](PythonPackages/aiida-aimall/README.md): This file * [`pyproject.toml`](setup.json): Python package metadata for registration on [PyPI](https://pypi.org/) and the [AiiDA plugin registry](https://aiidateam.github.io/aiida-registry/) (including entry points) diff --git a/docs/source/reference/api/auto/aiida_aimall/calculations/index.rst b/docs/source/reference/api/auto/aiida_aimall/calculations/index.rst index b3cc1b9..deb4ada 100644 --- a/docs/source/reference/api/auto/aiida_aimall/calculations/index.rst +++ b/docs/source/reference/api/auto/aiida_aimall/calculations/index.rst @@ -5,11 +5,7 @@ .. autoapi-nested-parse:: - Calculations provided by aiida_aimall. - - Upon pip install, AimqbCalculation is accessible in AiiDA.calculations plugins - Using the 'aimall' entry point, and GaussianWFXCalculation is accessible with the 'gaussianwfx' - entry point + `CalcJob` implementation for the aimqb executable of AIMAll. diff --git a/docs/source/reference/api/auto/aiida_aimall/controllers/index.rst b/docs/source/reference/api/auto/aiida_aimall/controllers/index.rst index 2c04627..de3a4c2 100644 --- a/docs/source/reference/api/auto/aiida_aimall/controllers/index.rst +++ b/docs/source/reference/api/auto/aiida_aimall/controllers/index.rst @@ -5,11 +5,13 @@ .. autoapi-nested-parse:: - aiida_aimall.controllers + Subclasses of `FromGroupSubmissionController` designed to prevent too many running processes - Subclasses of FromGroupSubmissionController designed to manage local traffic on lab Macs to prevent to many running processes + The entire :func:`aiida_aimall.workchains.subparam.SubstituentParameterWorkChain` can be replicated + by linking these together. - Provides controllers for the AimReor WorkChain, AimQBCalculations, and GaussianWFXCalculations + Provides controllers for the `AimReorWorkChain`, `AimqbCalculations`, `GaussianCalculation` + and `SmilesToGaussianWorkChain`. @@ -51,18 +53,18 @@ Attributes -.. py:class:: SmilesToGaussianController(code_label: str, g16_opt_params: dict, wfxgroup: str, nprocs: int, mem_mb: int, time_s: int, *args, **kwargs) +.. py:class:: SmilesToGaussianController(code_label: str, gauss_opt_params: dict, wfxgroup: str, nprocs: int, mem_mb: int, time_s: int, *args, **kwargs) Bases: :py:obj:`aiida_submission_controller.FromGroupSubmissionController` - A controller for submitting SmilesToGaussianWorkchain + A controller for submitting :func:`aiida_aimall.workchains.param_parts.SmilesToGaussianWorkChain` :param parent_group_label: the string of a group label which contains various SMILES as orm.Str nodes :param group_label: the string of the group to put the GaussianCalculations in :param max_concurrent: maximum number of concurrent processes. :param code_label: label of code, e.g. gaussian@cedar - :param g16_opt_params: Dict of Gaussian parameters to use + :param gauss_opt_params: Dict of Gaussian parameters to use :param wfxgroup: group in which to store the resulting wfx files :param nprocs: number of processors for gaussian calculation :param mem_mb: amount of memory in MB for Gaussian calculation @@ -83,8 +85,8 @@ Attributes parent_group_label = 'input_smiles', # Add structures to run to input_smiles group group_label = 'gaussianopt', # Resulting nodes will be in the gaussianopt group max_concurrent = 1, - wfxgroup = "opt_wfx" - g16_opt_params = Dict(dict={ + wfxgroup = "opt_wfx", + gauss_opt_params = Dict(dict={ 'link0_parameters': { '%chk':'aiida.chk', "%mem": "4000MB", @@ -93,8 +95,11 @@ Attributes 'functional':'wb97xd', 'basis_set':'aug-cc-pvtz', 'route_parameters': { 'opt':None, 'freq':None}, - }) - ) + }), + nprocs = 4, + mem_mb = 6400, + time_s = 24*3600*7 + ) while True: #submit Gaussian batches every hour @@ -121,7 +126,7 @@ Attributes - .. py:attribute:: g16_opt_params + .. py:attribute:: gauss_opt_params :type: dict @@ -147,7 +152,7 @@ Attributes .. py:attribute:: WORKFLOW_ENTRY_POINT - :value: 'aimall.smitog16' + :value: 'aimall.smitogauss' @@ -158,7 +163,7 @@ Attributes .. py:method:: get_inputs_and_processclass_from_extras(extras_values) - Constructs input for a GaussianWFXCalculation from extra_values + Constructs input for a GaussianCalculation from extra_values @@ -167,7 +172,7 @@ Attributes Bases: :py:obj:`aiida_submission_controller.FromGroupSubmissionController` - A controller for submitting AIMReor Workchains. + A controller for submitting :func:`aiida_aimall.workchains.param_parts.AIMAllReorWorkChain`. :param parent_group_label: the string of a group label which contains various structures as orm.Str nodes :param group_label: the string of the group to put the GaussianCalculations in @@ -180,8 +185,8 @@ Attributes .. note:: - A typical use case is using this as a controller on wfx files created by GaussianWFXCalculation. In that case, - match the `parent_group_label` here to the `wfxgroup` provided to the GaussianWFXCalculation. + A typical use case is using this as a controller on wfx files created by GaussianCalculation. In that case, + match the `parent_group_label` here to the `wfxgroup` provided to the GaussianCalculation. In GaussianOptWorkchain, this is `opt_wfx` by default .. rubric:: Example @@ -249,7 +254,7 @@ Attributes .. py:method:: get_inputs_and_processclass_from_extras(extras_values) - Constructs input for a AimReor Workchain from extra_values + Constructs input for a :func:`aiida_aimall.workchains.param_parts.AIMAllReorWorkChain` from extra_values @@ -258,21 +263,22 @@ Attributes Bases: :py:obj:`aiida_submission_controller.FromGroupSubmissionController` - A controller for submitting AimQB calculations. + A controller for submitting :func:`aiida_aimall.calculations.AimqbCalculation`. :param parent_group_label: the string of a group label which contains various structures as orm.Str nodes :param group_label: the string of the group to put the GaussianCalculations in :param max_concurrent: maximum number of concurrent processes. Expected behaviour is to set to a large number since we will be submitting to Cedar which will manage :param code_label: label of code, e.g. gaussian@cedar - :param aimparameters: dict of parameters for running AimQB, to be converted to AimqbParameters by the controller + :param aimparameters: dict of parameters for running AimQB, to be converted to + :func:`aiida_aimall.data.AimqbParameters` by the controller :returns: Controller object, periodically use run_in_batches to submit new results .. note:: - A typical use case is using this as a controller on wfx files created by GaussianWFXCalculation. In that case, - match the `parent_group_label` here to the `wfxgroup` provided to the GaussianWFXCalculation. + A typical use case is using this as a controller on wfx files created by `GaussianCalculation`. In that case, + match the `parent_group_label` here to the `wfxgroup` provided to the `GaussianCalculation`. In GaussianSubmissionController, this is `reor_wfx` .. rubric:: Example @@ -344,19 +350,19 @@ Attributes -.. py:class:: GaussianSubmissionController(code_label: str, g16_sp_params: dict, wfxgroup: str, *args, **kwargs) +.. py:class:: GaussianSubmissionController(code_label: str, gauss_sp_params: dict, wfxgroup: str, *args, **kwargs) Bases: :py:obj:`aiida_submission_controller.FromGroupSubmissionController` - A controller for submitting Gaussian calculations. + A controller for submitting `GaussianCalculation`. :param parent_group_label: the string of a group label which contains various structures as orm.Str nodes :param group_label: the string of the group to put the GaussianCalculations in :param max_concurrent: maximum number of concurrent processes. Expected behaviour is to set to a large number since we will be submitting to Cedar which will manage :param code_label: label of code, e.g. gaussian@cedar - :param g16_sp_params: dictionary of parameters to use in gaussian calculation + :param gauss_sp_params: dictionary of parameters to use in gaussian calculation :returns: Controller object, periodically use run_in_batches to submit new results @@ -383,7 +389,7 @@ Attributes parent_group_label = 'struct', # Add structures to run to struct group group_label = 'gaussiansp', # Resulting nodes will be in the gaussiansp group max_concurrent = 1, - g16_sp_params = Dict(dict={ + gauss_sp_params = Dict(dict={ 'link0_parameters': { '%chk':'aiida.chk', "%mem": "4000MB", @@ -423,7 +429,7 @@ Attributes - .. py:attribute:: g16_sp_params + .. py:attribute:: gauss_sp_params :type: dict diff --git a/docs/source/reference/api/auto/aiida_aimall/data/index.rst b/docs/source/reference/api/auto/aiida_aimall/data/index.rst index c3b9588..1c0a4f3 100644 --- a/docs/source/reference/api/auto/aiida_aimall/data/index.rst +++ b/docs/source/reference/api/auto/aiida_aimall/data/index.rst @@ -7,13 +7,10 @@ Data types provided by plugin - Upon pip install, AimqbParameters is accessible in AiiDA.data plugins - Using the 'aimall' entry point - -Package Contents ----------------- +Module Contents +--------------- Classes ~~~~~~~ diff --git a/docs/source/reference/api/auto/aiida_aimall/index.rst b/docs/source/reference/api/auto/aiida_aimall/index.rst index fad6edd..9b103c4 100644 --- a/docs/source/reference/api/auto/aiida_aimall/index.rst +++ b/docs/source/reference/api/auto/aiida_aimall/index.rst @@ -17,7 +17,7 @@ Subpackages :titlesonly: :maxdepth: 3 - data/index.rst + workchains/index.rst Submodules @@ -28,8 +28,8 @@ Submodules calculations/index.rst controllers/index.rst + data/index.rst parsers/index.rst - workchains/index.rst Package Contents diff --git a/docs/source/reference/api/auto/aiida_aimall/parsers/index.rst b/docs/source/reference/api/auto/aiida_aimall/parsers/index.rst index 2f8f63c..13ca52c 100644 --- a/docs/source/reference/api/auto/aiida_aimall/parsers/index.rst +++ b/docs/source/reference/api/auto/aiida_aimall/parsers/index.rst @@ -7,7 +7,7 @@ Parsers provided by aiida_aimall. - Register parsers via the "aiida.parsers" entry point in setup.json. + Register parsers via the "aiida.parsers" entry point in pyproject.toml. diff --git a/docs/source/reference/api/auto/aiida_aimall/workchains/calcfunctions/index.rst b/docs/source/reference/api/auto/aiida_aimall/workchains/calcfunctions/index.rst new file mode 100644 index 0000000..8758066 --- /dev/null +++ b/docs/source/reference/api/auto/aiida_aimall/workchains/calcfunctions/index.rst @@ -0,0 +1,206 @@ +:py:mod:`aiida_aimall.workchains.calcfunctions` +=============================================== + +.. py:module:: aiida_aimall.workchains.calcfunctions + +.. autoapi-nested-parse:: + + Calcfunctions used throughout workchains + + + +Module Contents +--------------- + + +Functions +~~~~~~~~~ + +.. autoapisummary:: + + aiida_aimall.workchains.calcfunctions.generate_rotated_structure_aiida + aiida_aimall.workchains.calcfunctions.remove_numcharss_from_strlist + aiida_aimall.workchains.calcfunctions.dict_to_structure + aiida_aimall.workchains.calcfunctions.calc_multiplicity + aiida_aimall.workchains.calcfunctions.find_attachment_atoms + aiida_aimall.workchains.calcfunctions.reorder_molecule + aiida_aimall.workchains.calcfunctions.get_xyz + aiida_aimall.workchains.calcfunctions.get_substituent_input + aiida_aimall.workchains.calcfunctions.generate_structure_data + aiida_aimall.workchains.calcfunctions.parameters_with_cm + aiida_aimall.workchains.calcfunctions.get_wfxname_from_gaussianinputs + aiida_aimall.workchains.calcfunctions.create_wfx_from_retrieved + aiida_aimall.workchains.calcfunctions.validate_shell_code + aiida_aimall.workchains.calcfunctions.validate_file_ext + aiida_aimall.workchains.calcfunctions.get_molecule_str_from_smiles + aiida_aimall.workchains.calcfunctions.xyzfile_to_StructureData + + + +.. py:function:: generate_rotated_structure_aiida(FolderData, atom_dict, cc_dict) + + Rotates the fragment to the defined coordinate system + + :param FolderData: aim calculation folder + :param atom_dict: AIM atom dict + :param cc_dict: AIM cc_dict + + :returns: + + Dict with keys 'atom_symbols' and 'geom' containing atomic symbols and the + the rotated geometry. + + +.. py:function:: remove_numcharss_from_strlist(in_list) + + Remove digits from a list of strings. e.g. ['O1','H2','H3'] -> ['O','H','H'] + + :param in_list: input list to remove digits from + + :returns: output list with the numerical digits removed from each element + + .. note:: + + The intention for this list is to convert numered atomic symbols, e.g. from Gaussian + to just symbols + + +.. py:function:: dict_to_structure(fragment_dict: aiida.orm.Dict) + + Generate a StructureData for Gaussian inputs + + :param fragment_dict: AiiDA orm.Dict with keys 'atom_symbols' and 'geom' + + :returns: StructureData for the molecule + + .. note:: + + input can be generated, for example, by + :func:`aiida_aimall.workchains.calcfunctions.generate_rotated_structure_aiida` + + +.. py:function:: calc_multiplicity(mol) + + Calculate the multiplicity of a molecule as 2S +1 + + Loops over the atoms in the molecule and gets number of radical electrons, + then converts that number to the multiplicity. + + :param mol: rdkit.Chem molecule object + + :returns: integer number of multiplicity + + +.. py:function:: find_attachment_atoms(mol) + + Given molecule object, find the atoms corresponding to a * and the atom to which that is bound + + :param mol: rdkit molecule object + + :returns: molecule with added hydrogens, the * atom object, and the atom object to which that is attached + + .. note:: Assumes that only one * is present in the molecule + + +.. py:function:: reorder_molecule(h_mol_rw, zero_at, attached_atom) + + Reindexes the atoms in a molecule, setting attached_atom to index 0, and zero_at to index 1 + + :param h_mol_rw: RWMol rdkit object with explicit hydrogens + :param zero_at: the placeholder * atom + :param attached_atom: the atom bonded to * + + :returns: molecule with reordered indices + + +.. py:function:: get_xyz(reorder_mol) + + MMFF optimize the molecule to generate xyz coordiantes + + :param reorder_mol: rdkit.Chem molecule output, output of :func:`aiida_aimall.workchains.calcfunctions.reorder_molecule` + + :returns: string of the geometry block of an .xyz file + + +.. py:function:: get_substituent_input(smiles: str) -> dict + + For a given smiles, determine xyz structure, charge, and multiplicity + + :param smiles: SMILEs of substituent to run + + :returns: Dict with keys xyz, charge, multiplicity + + :raises ValueError: if molecule cannot be built from SMILES + + +.. py:function:: generate_structure_data(smiles_dict) + + Take an input xyz string and convert it to StructureData + + :param smiles_dict: output of :func:`aiida_aimall.workchains.calcfunctions.get_substituent_input` + + :returns: StructureData of the molecule + + +.. py:function:: parameters_with_cm(parameters, smiles_dict) + + Add charge and multiplicity keys to Gaussian Input + + :param parameters: dictionary to be provided to GaussianCalculation + :param smiles_dict: `aiida_aimall.workchains.calcfunctions.get_substituent_input` + + :returns: Dict of Gaussian parameters updated with charge and multiplicity + + +.. py:function:: get_wfxname_from_gaussianinputs(gaussian_parameters) + + Find the .wfx filename from gaussian_parameters + + Check if input parameters was provided to gaussian_parameters, and if so, look for + .wfx file names supplied. If it was, return the first .wfx filename found + + :param gaussian_parameters: input dictionary to be provided to GaussianCalculation + + :returns: Str of .wfx filename + + +.. py:function:: create_wfx_from_retrieved(wfxname, retrieved_folder) + + Create wavefunction SinglefileData from retrieved folder + + :param wfxname: Str of the name of a .wfx file to get from the retrieved folder + :param retrieved_folder: FolderData of a completed GaussianCalculation + + :returns: SinglefileData of the .wfx file to find in the FolderData + + +.. py:function:: validate_shell_code(node, _) + + Validate the shell code, ensuring that it is ShellCode or Str + + :param node: input node to check the type for ShellCode or Str + + :returns: None if the type is ShellCode or Str, or error string if node is not + + +.. py:function:: validate_file_ext(node, _) + + Validates that the file extension provided for AIM is wfx, wfn or fchk + + :param node: node to check the value of to ensure it is in a supported format + + :returns: None if the type is ShellCode or Str, or error string if node is not + + +.. py:function:: get_molecule_str_from_smiles(smiles) + + For a given smiles, determine xyz structure, charge, and multiplicity + + :param smiles: SMILEs of substituent to run + + :returns: Dict with keys xyz, charge, multiplicity + + +.. py:function:: xyzfile_to_StructureData(xyz_SFD) + + Convert the xyz file provided as SinglefileData to StructureData diff --git a/docs/source/reference/api/auto/aiida_aimall/workchains/index.rst b/docs/source/reference/api/auto/aiida_aimall/workchains/index.rst index b5e80a1..2ea9a66 100644 --- a/docs/source/reference/api/auto/aiida_aimall/workchains/index.rst +++ b/docs/source/reference/api/auto/aiida_aimall/workchains/index.rst @@ -3,506 +3,15 @@ .. py:module:: aiida_aimall.workchains -.. autoapi-nested-parse:: - aiida_aimall.workchains - Workchains designed for a workflow starting from a set of cmls, then breaking off into fragment Gaussian Calculations - Needs to be run in part with aiida_aimall.controllers to control local traffic on lab Mac - Example in the works - - Provided Workchains are - MultiFragmentWorkchain, entry point: multifrag - G16OptWorkChain, entry point: g16opt - AimAllReor WorkChain, entry point: aimreor - - - -Module Contents ---------------- - -Classes -~~~~~~~ - -.. autoapisummary:: - - aiida_aimall.workchains.QMToAIMWorkchain - aiida_aimall.workchains.GenerateWFXToAIMWorkchain - aiida_aimall.workchains.SmilesToGaussianWorkchain - aiida_aimall.workchains.AIMAllReor - aiida_aimall.workchains.BaseInputWorkChain - aiida_aimall.workchains.GaussianToAIMWorkChain - aiida_aimall.workchains.SubstituentParameterWorkChain - - - -Functions -~~~~~~~~~ - -.. autoapisummary:: - - aiida_aimall.workchains.generate_rotated_structure_aiida - aiida_aimall.workchains.remove - aiida_aimall.workchains.dict_to_structure - aiida_aimall.workchains.calc_multiplicity - aiida_aimall.workchains.find_attachment_atoms - aiida_aimall.workchains.reorder_molecule - aiida_aimall.workchains.get_xyz - aiida_aimall.workchains.get_substituent_input - aiida_aimall.workchains.generate_structure_data - aiida_aimall.workchains.parameters_with_cm - aiida_aimall.workchains.get_wfxname_from_gaussianinputs - aiida_aimall.workchains.create_wfx_from_retrieved - aiida_aimall.workchains.validate_shell_code - aiida_aimall.workchains.validate_file_ext - aiida_aimall.workchains.get_wfx - aiida_aimall.workchains.get_molecule_str_from_smiles - aiida_aimall.workchains.xyzfile_to_StructureData - - - -Attributes -~~~~~~~~~~ - -.. autoapisummary:: - - aiida_aimall.workchains.GaussianCalculation - aiida_aimall.workchains.AimqbParameters - aiida_aimall.workchains.AimqbCalculation - - -.. py:data:: GaussianCalculation - - - -.. py:data:: AimqbParameters - - - -.. py:data:: AimqbCalculation - - - -.. py:function:: generate_rotated_structure_aiida(FolderData, atom_dict, cc_dict) - - Rotates the fragment to the defined coordinate system - - :param FolderData: aim calculation folder - :param atom_dict: AIM atom dict - :param cc_dict: AIM cc_dict - - -.. py:function:: remove(in_list) - - Remove digits from a list of strings. e.g. ['O1','H2','H3'] -> ['O','H','H'] - - -.. py:function:: dict_to_structure(fragment_dict) - - Generate a string of xyz coordinates for Gaussian input file - - :param fragment_dict: - :param type fragment_dict: aiida.orm.nodes.data.dict.Dict - - -.. py:function:: calc_multiplicity(mol) - - Calculate the multiplicity of a molecule as 2S +1 - - -.. py:function:: find_attachment_atoms(mol) - - Given molecule object, find the atoms corresponding to a * and the atom to which that is bound - - :param mol: rdkit molecule object - - :returns: molecule with added hydrogens, the * atom object, and the atom object to which that is attached - - .. note:: Assumes that only one * is present in the molecule - - -.. py:function:: reorder_molecule(h_mol_rw, zero_at, attached_atom) - - Reindexes the atoms in a molecule, setting attached_atom to index 0, and zero_at to index 1 - - :param h_mol_rw: RWMol rdkit object with explicit hydrogens - :param zero_at: the placeholder * atom - :param attached_atom: the atom bonded to * - - :returns: molecule with reordered indices - - -.. py:function:: get_xyz(reorder_mol) - - MMFF optimize the molecule to generate xyz coordiantes - - -.. py:function:: get_substituent_input(smiles: str) -> dict - - For a given smiles, determine xyz structure, charge, and multiplicity - - :param smiles: SMILEs of substituent to run - - :returns: Dict with keys xyz, charge, multiplicity - - -.. py:function:: generate_structure_data(smiles_dict) - - Take an input xyz string and convert it to StructureData - - -.. py:function:: parameters_with_cm(parameters, smiles_dict) - - Add charge and multiplicity keys to Gaussian Input - - -.. py:function:: get_wfxname_from_gaussianinputs(gaussian_parameters) - - Look for wfx or wfn objects in the retrieved Folder - - -.. py:function:: create_wfx_from_retrieved(wfxname, retrieved_folder) - - Create wavefunciton Singlefildata from retrieved folder - - -.. py:function:: validate_shell_code(node, _) - - Validate the shell code, ensuring that it is ShellCode or Str - - -.. py:function:: validate_file_ext(node, _) - - Validates that the file extension provided for AIM is wfx, wfn or fchk - - -.. py:class:: QMToAIMWorkchain(inputs: dict | None = None, logger: logging.Logger | None = None, runner: aiida.engine.runners.Runner | None = None, enable_persistence: bool = True) - - - Bases: :py:obj:`aiida.engine.WorkChain` - - Workchain to link quantum chemistry jobs without plugins to AIMAll - - .. py:method:: define(spec) - :classmethod: - - Define the specification of the process, including its inputs, outputs and known exit codes. - - A `metadata` input namespace is defined, with optional ports that are not stored in the database. - - - - .. py:method:: shell_job() - - Launch a shell job - - - .. py:method:: aim() - - Launch an AIMQB calculation - - - .. py:method:: result() - - Put results in output node - - - -.. py:class:: GenerateWFXToAIMWorkchain(inputs: dict | None = None, logger: logging.Logger | None = None, runner: aiida.engine.runners.Runner | None = None, enable_persistence: bool = True) - - - Bases: :py:obj:`aiida.engine.WorkChain` - - Workchain to generate a wfx file from computational chemistry output files and submit that to an AIMQB Calculation - - .. note:: - - This workchain uses the IOData module of the Ayer's group Horton to generate the wfx files. Supported file formats - include .fchk files, molden files (from Molpro, Orca, PSI4, Turbomole, and Molden), and CP2K atom log files. Further - note that .fchk files can simply be provided directly to an `AimqbCalculation`. - - While IOData accepts other file formats, these formats are the ones available that contain the necessary information - to generate wfc files - - .. py:method:: define(spec) - :classmethod: - - Define the specification of the process, including its inputs, outputs and known exit codes. - - A `metadata` input namespace is defined, with optional ports that are not stored in the database. - - - - .. py:method:: generate_wfx() - - Given SinglefileData generates a wfx file if IOData is capable - - - .. py:method:: aim() - - Run AIM on the generated wfx file - - - .. py:method:: result() - - Put results in output node - - - -.. py:class:: SmilesToGaussianWorkchain(inputs: dict | None = None, logger: logging.Logger | None = None, runner: aiida.engine.runners.Runner | None = None, enable_persistence: bool = True) - - - Bases: :py:obj:`aiida.engine.WorkChain` - - Workchain to take a SMILES, generate xyz, charge, and multiplicity - - .. py:method:: define(spec) - :classmethod: - - Define the specification of the process, including its inputs, outputs and known exit codes. - - A `metadata` input namespace is defined, with optional ports that are not stored in the database. - - - - .. py:method:: get_substituent_inputs_step() - - Given list of substituents and previously done smiles, get input - - - .. py:method:: update_parameters_with_cm() - - Update provided Gaussian parameters with charge and multiplicity of substituent - - - .. py:method:: string_to_StructureData() - - Convert an xyz string of molecule geometry to StructureData - - - .. py:method:: get_wfx_name() - - Find the wavefunction file in the retrieved node - - - .. py:method:: submit_gaussian() - - Submits the gaussian calculation - - - .. py:method:: found_wfx_name() - - Check if we found a wfx or wfn file - - - .. py:method:: create_wfx_file() - - Create a wavefunction file from the retireved folder - - - .. py:method:: results() - - Store our relevant information as output - - - -.. py:class:: AIMAllReor(inputs: dict | None = None, logger: logging.Logger | None = None, runner: aiida.engine.runners.Runner | None = None, enable_persistence: bool = True) - - - Bases: :py:obj:`aiida.engine.WorkChain` - - Workchain to run AIM and then reorient the molecule using the results - - Process continues in GaussianSubmissionController - - .. py:method:: define(spec) - :classmethod: - - Define the specification of the process, including its inputs, outputs and known exit codes. - - A `metadata` input namespace is defined, with optional ports that are not stored in the database. - - - - .. py:method:: aimall() - - submit the aimall calculation - - - .. py:method:: rotate() - - perform the rotation - - - .. py:method:: dict_to_struct_reor() - - generate the gaussian input from rotated structure - - - .. py:method:: result() - - Parse results - - - -.. py:function:: get_wfx(retrieved_folder, wfx_filename) - - Get a wfx file from retrieved folder - - -.. py:class:: BaseInputWorkChain(inputs: dict | None = None, logger: logging.Logger | None = None, runner: aiida.engine.runners.Runner | None = None, enable_persistence: bool = True) - - - Bases: :py:obj:`aiida.engine.WorkChain` - - A workchain to generate and validate inputs. One of SinglefileData, Smiles as Str or StructureData should be - provided - - .. py:method:: define(spec) - :classmethod: - - Define the specification of the process, including its inputs, outputs and known exit codes. - - A `metadata` input namespace is defined, with optional ports that are not stored in the database. - - - - .. py:method:: is_xyz_input() - - Validates if xyz_file was provided as input - - - .. py:method:: is_smiles_input() - - Validates if smiles was provided as input - - - .. py:method:: is_structure_input() - - Validates if structure was provided as input - - - .. py:method:: validate_input() - - Check that only one of smiles, structure, or xyz_file was input - - - .. py:method:: create_structure_from_xyz() - - Convert the xyzfile to StructureData - - - .. py:method:: structure_in_context() - - Store the input structure in context, to make consistent with the results of xyz_file or SMILES input - - - .. py:method:: get_molecule_inputs_step() - - Given list of substituents and previously done smiles, get input - - - .. py:method:: string_to_StructureData() - - Convert an xyz string of molecule geometry to StructureData - - - -.. py:function:: get_molecule_str_from_smiles(smiles) - - For a given smiles, determine xyz structure, charge, and multiplicity - - :param smiles: SMILEs of substituent to run - - :returns: Dict with keys xyz, charge, multiplicity - - -.. py:class:: GaussianToAIMWorkChain(inputs: dict | None = None, logger: logging.Logger | None = None, runner: aiida.engine.runners.Runner | None = None, enable_persistence: bool = True) - - - Bases: :py:obj:`BaseInputWorkChain` - - A workchain to submit a Gaussian calculation and automatically setup an AIMAll calculation on the output - - .. py:method:: define(spec) - :classmethod: - - Define workchain steps - - - .. py:method:: g16() - - Run Gaussian calculation - - - .. py:method:: classify_wfx() - - Add the wavefunction file from the previous step to the correct group and set the extras - - - .. py:method:: aim() - - Run Final AIM Calculation - - - .. py:method:: result() - - Put results in output node - - - -.. py:function:: xyzfile_to_StructureData(xyz_SFD) - - Convert the xyz file provided as SinglefileData to StructureData - - -.. py:class:: SubstituentParameterWorkChain(inputs: dict | None = None, logger: logging.Logger | None = None, runner: aiida.engine.runners.Runner | None = None, enable_persistence: bool = True) - - - Bases: :py:obj:`BaseInputWorkChain` - - A workchain to perform the full suite of KLG's substituent parameter determining - - .. py:method:: define(spec) - :classmethod: - - Define workchain steps - - - .. py:method:: get_substituent_inputs_step() - - Get a dictionary of the substituent input for a given SMILES - - - .. py:method:: g16_opt() - - Submit the Gaussian optimization - - - .. py:method:: classify_opt_wfx() - - Add the wavefunction file from the previous step to the correct group and set the extras - - - .. py:method:: aim_reor() - - Submit the Aimqb calculation and reorientation - - - .. py:method:: g16_sp() - - Run Gaussian Single Point calculation - - - .. py:method:: classify_sp_wfx() - - Add the wavefunction file from the previous step to the correct group and set the extras - - - .. py:method:: aim() - - Run Final AIM Calculation - - - .. py:method:: result() - - Put results in output node +Submodules +---------- +.. toctree:: + :titlesonly: + :maxdepth: 1 + + calcfunctions/index.rst + input/index.rst + param_parts/index.rst + qc_programs/index.rst + subparam/index.rst diff --git a/docs/source/reference/api/auto/aiida_aimall/workchains/input/index.rst b/docs/source/reference/api/auto/aiida_aimall/workchains/input/index.rst new file mode 100644 index 0000000..726e2f5 --- /dev/null +++ b/docs/source/reference/api/auto/aiida_aimall/workchains/input/index.rst @@ -0,0 +1,82 @@ +:py:mod:`aiida_aimall.workchains.input` +======================================= + +.. py:module:: aiida_aimall.workchains.input + +.. autoapi-nested-parse:: + + Base input workchain + + + +Module Contents +--------------- + +Classes +~~~~~~~ + +.. autoapisummary:: + + aiida_aimall.workchains.input.BaseInputWorkChain + + + + +.. py:class:: BaseInputWorkChain(inputs: dict | None = None, logger: logging.Logger | None = None, runner: aiida.engine.runners.Runner | None = None, enable_persistence: bool = True) + + + Bases: :py:obj:`aiida.engine.WorkChain` + + A workchain to generate and validate inputs. One of SinglefileData, Smiles as Str or StructureData should be + provided + + .. py:method:: define(spec) + :classmethod: + + Define the specification of the process, including its inputs, outputs and known exit codes. + + A `metadata` input namespace is defined, with optional ports that are not stored in the database. + + + + .. py:method:: is_xyz_input() + + Validates if xyz_file was provided as input + + + .. py:method:: is_smiles_input() + + Validates if smiles was provided as input + + + .. py:method:: is_structure_input() + + Validates if structure was provided as input + + + .. py:method:: validate_input() + + Check that only one of smiles, structure, or xyz_file was input + + + .. py:method:: create_structure_from_xyz() + + Convert the xyzfile to StructureData. Calls + :func:`aiida_aimall.workchains.calcfunctions.xyzfile_to_StructureData` + + + .. py:method:: structure_in_context() + + Store the input structure in context, to make consistent with the results of xyz_file or SMILES input. + + + .. py:method:: get_molecule_inputs_step() + + Given list of substituents and previously done smiles, get input. + Calls :func:`aiida_aimall.workchains.calcfunctions.get_molecule_str_from_smiles` + + + .. py:method:: string_to_StructureData() + + Convert an xyz string of molecule geometry to StructureData. + Calls :func:`aiida_aimall.workchains.calcfunctions.generate_structure_data` diff --git a/docs/source/reference/api/auto/aiida_aimall/workchains/param_parts/index.rst b/docs/source/reference/api/auto/aiida_aimall/workchains/param_parts/index.rst new file mode 100644 index 0000000..4615e42 --- /dev/null +++ b/docs/source/reference/api/auto/aiida_aimall/workchains/param_parts/index.rst @@ -0,0 +1,118 @@ +:py:mod:`aiida_aimall.workchains.param_parts` +============================================= + +.. py:module:: aiida_aimall.workchains.param_parts + +.. autoapi-nested-parse:: + + Workchains that are smaller parts of SubstituenParamWorkChain + + + +Module Contents +--------------- + +Classes +~~~~~~~ + +.. autoapisummary:: + + aiida_aimall.workchains.param_parts.SmilesToGaussianWorkChain + aiida_aimall.workchains.param_parts.AIMAllReorWorkChain + + + + +.. py:class:: SmilesToGaussianWorkChain(inputs: dict | None = None, logger: logging.Logger | None = None, runner: aiida.engine.runners.Runner | None = None, enable_persistence: bool = True) + + + Bases: :py:obj:`aiida.engine.WorkChain` + + Workchain to take a SMILES, generate xyz, charge, and multiplicity + + .. py:method:: define(spec) + :classmethod: + + Define the specification of the process, including its inputs, outputs and known exit codes. + + A `metadata` input namespace is defined, with optional ports that are not stored in the database. + + + + .. py:method:: get_substituent_inputs_step() + + Given list of substituents and previously done smiles, get input + + + .. py:method:: update_parameters_with_cm() + + Update provided Gaussian parameters with charge and multiplicity of substituent + + + .. py:method:: string_to_StructureData() + + Convert an xyz string of molecule geometry to StructureData + + + .. py:method:: get_wfx_name() + + Find the wavefunction file in the retrieved node + + + .. py:method:: submit_gaussian() + + Submits the gaussian calculation + + + .. py:method:: found_wfx_name() + + Check if we found a wfx or wfn file + + + .. py:method:: create_wfx_file() + + Create a wavefunction file from the retireved folder + + + .. py:method:: results() + + Store our relevant information as output + + + +.. py:class:: AIMAllReorWorkChain(inputs: dict | None = None, logger: logging.Logger | None = None, runner: aiida.engine.runners.Runner | None = None, enable_persistence: bool = True) + + + Bases: :py:obj:`aiida.engine.WorkChain` + + Workchain to run AIM and then reorient the molecule using the results + + Process continues in GaussianSubmissionController + + .. py:method:: define(spec) + :classmethod: + + Define the specification of the process, including its inputs, outputs and known exit codes. + + A `metadata` input namespace is defined, with optional ports that are not stored in the database. + + + + .. py:method:: aimall() + + submit the aimall calculation + + + .. py:method:: rotate() + + perform the rotation + + + .. py:method:: dict_to_struct_reor() + + generate the gaussian input from rotated structure + + + .. py:method:: result() + + Parse results diff --git a/docs/source/reference/api/auto/aiida_aimall/workchains/qc_programs/index.rst b/docs/source/reference/api/auto/aiida_aimall/workchains/qc_programs/index.rst new file mode 100644 index 0000000..0625c46 --- /dev/null +++ b/docs/source/reference/api/auto/aiida_aimall/workchains/qc_programs/index.rst @@ -0,0 +1,130 @@ +:py:mod:`aiida_aimall.workchains.qc_programs` +============================================= + +.. py:module:: aiida_aimall.workchains.qc_programs + +.. autoapi-nested-parse:: + + Workchains to interface various quantum software with AiiDA + + + +Module Contents +--------------- + +Classes +~~~~~~~ + +.. autoapisummary:: + + aiida_aimall.workchains.qc_programs.QMToAIMWorkChain + aiida_aimall.workchains.qc_programs.GaussianToAIMWorkChain + aiida_aimall.workchains.qc_programs.GenerateWFXToAIMWorkChain + + + + +.. py:class:: QMToAIMWorkChain(inputs: dict | None = None, logger: logging.Logger | None = None, runner: aiida.engine.runners.Runner | None = None, enable_persistence: bool = True) + + + Bases: :py:obj:`aiida.engine.WorkChain` + + Workchain to link quantum chemistry jobs without plugins to AIMAll + + .. py:method:: define(spec) + :classmethod: + + Define the specification of the process, including its inputs, outputs and known exit codes. + + A `metadata` input namespace is defined, with optional ports that are not stored in the database. + + + + .. py:method:: shell_job() + + Launch a shell job + + + .. py:method:: aim() + + Launch an AIMQB calculation + + + .. py:method:: result() + + Put results in output node + + + +.. py:class:: GaussianToAIMWorkChain(inputs: dict | None = None, logger: logging.Logger | None = None, runner: aiida.engine.runners.Runner | None = None, enable_persistence: bool = True) + + + Bases: :py:obj:`aiida_aimall.workchains.input.BaseInputWorkChain` + + A workchain to submit a Gaussian calculation and automatically setup an AIMAll calculation on the output + + .. py:method:: define(spec) + :classmethod: + + Define workchain steps + + + .. py:method:: gauss() + + Run Gaussian calculation + + + .. py:method:: classify_wfx() + + Add the wavefunction file from the previous step to the correct group and set the extras + + + .. py:method:: aim() + + Run Final AIM Calculation + + + .. py:method:: result() + + Put results in output node + + + +.. py:class:: GenerateWFXToAIMWorkChain(inputs: dict | None = None, logger: logging.Logger | None = None, runner: aiida.engine.runners.Runner | None = None, enable_persistence: bool = True) + + + Bases: :py:obj:`aiida.engine.WorkChain` + + Workchain to generate a wfx file from computational chemistry output files and submit that to an AIMQB Calculation + + .. note:: + + This workchain uses the IOData module of the Ayer's group Horton to generate the wfx files. Supported file formats + include .fchk files, molden files (from Molpro, Orca, PSI4, Turbomole, and Molden), and CP2K atom log files. Further + note that .fchk files can simply be provided directly to an `AimqbCalculation`. + + While IOData accepts other file formats, these formats are the ones available that contain the necessary information + to generate wfc files + + .. py:method:: define(spec) + :classmethod: + + Define the specification of the process, including its inputs, outputs and known exit codes. + + A `metadata` input namespace is defined, with optional ports that are not stored in the database. + + + + .. py:method:: generate_wfx() + + Given SinglefileData generates a wfx file if IOData is capable + + + .. py:method:: aim() + + Run AIM on the generated wfx file + + + .. py:method:: result() + + Put results in output node diff --git a/docs/source/reference/api/auto/aiida_aimall/workchains/subparam/index.rst b/docs/source/reference/api/auto/aiida_aimall/workchains/subparam/index.rst new file mode 100644 index 0000000..c8c6a93 --- /dev/null +++ b/docs/source/reference/api/auto/aiida_aimall/workchains/subparam/index.rst @@ -0,0 +1,75 @@ +:py:mod:`aiida_aimall.workchains.subparam` +========================================== + +.. py:module:: aiida_aimall.workchains.subparam + +.. autoapi-nested-parse:: + + `WorkChain` for calculating substituent parameters developed by authors + + + +Module Contents +--------------- + +Classes +~~~~~~~ + +.. autoapisummary:: + + aiida_aimall.workchains.subparam.SubstituentParameterWorkChain + + + + +.. py:class:: SubstituentParameterWorkChain(inputs: dict | None = None, logger: logging.Logger | None = None, runner: aiida.engine.runners.Runner | None = None, enable_persistence: bool = True) + + + Bases: :py:obj:`aiida_aimall.workchains.input.BaseInputWorkChain` + + A workchain to perform the full suite of KLG's substituent parameter determining + + .. py:method:: define(spec) + :classmethod: + + Define workchain steps + + + .. py:method:: get_substituent_inputs_step() + + Get a dictionary of the substituent input for a given SMILES + + + .. py:method:: gauss_opt() + + Submit the Gaussian optimization + + + .. py:method:: classify_opt_wfx() + + Add the wavefunction file from the previous step to the correct group and set the extras + + + .. py:method:: aim_reor() + + Submit the Aimqb calculation and reorientation + + + .. py:method:: gauss_sp() + + Run Gaussian Single Point calculation + + + .. py:method:: classify_sp_wfx() + + Add the wavefunction file from the previous step to the correct group and set the extras + + + .. py:method:: aim() + + Run Final AIM Calculation + + + .. py:method:: result() + + Put results in output node diff --git a/docs/source/tutorials/aimtogaussian.ipynb b/docs/source/tutorials/aimtogaussian.ipynb index 76fd02a..275152e 100644 --- a/docs/source/tutorials/aimtogaussian.ipynb +++ b/docs/source/tutorials/aimtogaussian.ipynb @@ -11,9 +11,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Though not explicitly directly integrated with AIMAll software, `aiida-aimall` provides a workchain to take a [SMILES](https://www.daylight.com/dayhtml/doc/theory/theory.smiles.html) string of a substituent as input, and generate and run a Gaussian calculation. This is provided in part due to workflows used by the authors in automating creation of data for large numbers of substituents automatically. The [SmilesToGaussianWorkchain](../reference/api/auto/aiida_aimall/workchains/index.rst#aiida_aimall.workchains.SmilesToGaussianWorkchain). SmilesToGaussianWorkchain provides this functionality. It uses the RDKit package to convert the SMILES input to a molecular geometry through an MMFF94 optimization, and then provides that to the Gaussian calculation.\n", + "Though not explicitly directly integrated with AIMAll software, `aiida-aimall` provides a workchain to take a [SMILES](https://www.daylight.com/dayhtml/doc/theory/theory.smiles.html) string of a substituent as input, and generate and run a Gaussian calculation. This is provided in part due to workflows used by the authors in automating creation of data for large numbers of substituents automatically. The [SmilesToGaussianWorkchain](../reference/api/auto/aiida_aimall/workchains/param_parts/index.rst#aiida_aimall.workchains.param_parts.SmilesToGaussianWorkchain). SmilesToGaussianWorkchain provides this functionality. It uses the RDKit package to convert the SMILES input to a molecular geometry through an MMFF94 optimization, and then provides that to the Gaussian calculation.\n", "\n", - "The SMILES provides should have exactly one asterisk indicating the point of attachment of the substituent to the substrate. **In the generated geometry, this \\* will be replaced with a hydrogen.** For example a valid input SMILES is \"\\*C\" for a methyl group, but a methylene group with SMILES \"\\*C\\*\" or methane with SMILES \"C\" are invalid SMILES for input in this `WorkChain`. If such inputs are passed, an exception will be raised. SMILES without placeholder atoms can be provided to [GaussianToAIMWorkChain](../reference/api/auto/aiida_aimall/workchains/index.rst#aiida_aimall.workchains.GaussianToAIMWorkChain)." + "The SMILES provides should have exactly one asterisk indicating the point of attachment of the substituent to the substrate. **In the generated geometry, this \\* will be replaced with a hydrogen.** For example a valid input SMILES is \"\\*C\" for a methyl group, but a methylene group with SMILES \"\\*C\\*\" or methane with SMILES \"C\" are invalid SMILES for input in this `WorkChain`. If such inputs are passed, an exception will be raised. SMILES without placeholder atoms can be provided to [GaussianToAIMWorkChain](../reference/api/auto/aiida_aimall/workchains/qc_programs/index.rst#aiida_aimall.workchains.qc_programs.GaussianToAIMWorkChain)." ] }, { diff --git a/docs/source/tutorials/makewfx.ipynb b/docs/source/tutorials/makewfx.ipynb index 5e0d5f6..7a27301 100644 --- a/docs/source/tutorials/makewfx.ipynb +++ b/docs/source/tutorials/makewfx.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Many different versions of computational software for electronic structure calculations are in use. Should you wish to run AIM calculations, but may not have wfx files as the output of this software, `aiida-aimall` provides functionality to generate wfx files from some output and automatically run AIM calculations on the outputs in the [GenerateWFXToAIMWorkchain](../reference/api/auto/aiida_aimall/workchains/index.rst#aiida_aimall.workchains.GenerateWFXToAIMWorkchain). To do this, we use the `qc-iodata` package of Paul Ayer's Horton software\n", + "Many different versions of computational software for electronic structure calculations are in use. Should you wish to run AIM calculations, but may not have wfx files as the output of this software, `aiida-aimall` provides functionality to generate wfx files from some output and automatically run AIM calculations on the outputs in the [GenerateWFXToAIMWorkchain](../reference/api/auto/aiida_aimall/workchains/qc_programs/index.rst#aiida_aimall.workchains.qc_programs.GenerateWFXToAIMWorkchain). To do this, we use the `qc-iodata` package of Paul Ayer's Horton software\n", "\n", "Supported output file formats are:\n", " * Molden\n", diff --git a/docs/source/tutorials/quantumsoftware.ipynb b/docs/source/tutorials/quantumsoftware.ipynb index 2a9bdfd..8201553 100644 --- a/docs/source/tutorials/quantumsoftware.ipynb +++ b/docs/source/tutorials/quantumsoftware.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The primary electronic structure software that `aiida-aimall` is integrated with is Gaussian software. Here, a base functionality is provided to run an AIMQB calculation on the .wfx file resulting from a Gaussian calcuation. The [GaussianToAIMWorkChain](../reference/api/auto/aiida_aimall/workchains/index.rst#aiida_aimall.workchains.GaussianToAIMWorkChain) accepts any **one** of the following as inputs:\n", + "The primary electronic structure software that `aiida-aimall` is integrated with is Gaussian software. Here, a base functionality is provided to run an AIMQB calculation on the .wfx file resulting from a Gaussian calcuation. The [GaussianToAIMWorkChain](../reference/api/auto/aiida_aimall/workchains/qc_programs/index.rst#aiida_aimall.workchains.qc_programs.GaussianToAIMWorkChain) accepts any **one** of the following as inputs:\n", " - `smiles`, the SMILES of the molecule provided as type `orm.Str`\n", " - `structure`, an `orm.StructureData` of the molecule\n", " - `xyz_file`, a `orm.SinglefileData` of a .xyz file\n", @@ -553,7 +553,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Options are provided for integrating other electronic structure software through the use of `aiida-shell`, as provided in the [QMToAIMWorkchain](../reference/api/auto/aiida_aimall/workchains/index.rst#aiida_aimall.workchains.QMToAIMWorkchain). An example is given here for ORCA software. For this, you can provide a setup `ShellCode` instance for the code being run, or a `Str` of the command used to run the software. \n", + "Options are provided for integrating other electronic structure software through the use of `aiida-shell`, as provided in the [QMToAIMWorkchain](../reference/api/auto/aiida_aimall/workchains/qc_programs/index.rst#aiida_aimall.workchains.qc_programs.QMToAIMWorkchain). An example is given here for ORCA software. For this, you can provide a setup `ShellCode` instance for the code being run, or a `Str` of the command used to run the software. \n", "\n", "### Author's Note on `shell_code` input options\n", "\n", diff --git a/docs/source/tutorials/substituentparameter.ipynb b/docs/source/tutorials/substituentparameter.ipynb index a119ec9..81e91e1 100644 --- a/docs/source/tutorials/substituentparameter.ipynb +++ b/docs/source/tutorials/substituentparameter.ipynb @@ -11,7 +11,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The substituent parameter protocol developed by the authors is able to be fully automated in `aiida-aimall` through use of the [SubstituentParameterWorkchain](../reference/api/auto/aiida_aimall/workchains/index.rst#aiida_aimall.workchains.SubstituentParameterWorkChain). This workchain undertakes the following steps\n", + "The substituent parameter protocol developed by the authors is able to be fully automated in `aiida-aimall` through use of the [SubstituentParameterWorkchain](../reference/api/auto/aiida_aimall/workchains/subparam/index.rst#aiida_aimall.workchains.subparam.SubstituentParameterWorkChain). This workchain undertakes the following steps\n", "\n", "1. Gaussian optimization of the input structure\n", "2. performs AIM on the output wfx file\n", diff --git a/src/aiida_aimall/calculations.py b/src/aiida_aimall/calculations.py index 0eb7fa0..770ade4 100644 --- a/src/aiida_aimall/calculations.py +++ b/src/aiida_aimall/calculations.py @@ -1,10 +1,4 @@ -"""Calculations provided by aiida_aimall. - -Upon pip install, AimqbCalculation is accessible in AiiDA.calculations plugins -Using the 'aimall' entry point, and GaussianWFXCalculation is accessible with the 'gaussianwfx' -entry point - -""" +"""`CalcJob` implementation for the aimqb executable of AIMAll.""" from aiida.common import datastructures from aiida.engine import CalcJob from aiida.orm import Dict, Int, List, SinglefileData diff --git a/src/aiida_aimall/controllers.py b/src/aiida_aimall/controllers.py index db56271..1dee574 100644 --- a/src/aiida_aimall/controllers.py +++ b/src/aiida_aimall/controllers.py @@ -1,8 +1,11 @@ -"""aiida_aimall.controllers +"""Subclasses of `FromGroupSubmissionController` designed to prevent too many running processes -Subclasses of FromGroupSubmissionController designed to manage local traffic on lab Macs to prevent to many running processes +The entire :func:`aiida_aimall.workchains.subparam.SubstituentParameterWorkChain` can be replicated +by linking these together. + +Provides controllers for the `AimReorWorkChain`, `AimqbCalculations`, `GaussianCalculation` +and `SmilesToGaussianWorkChain`. -Provides controllers for the AimReor WorkChain, AimQBCalculations, and GaussianWFXCalculations """ from aiida import orm @@ -16,7 +19,7 @@ class SmilesToGaussianController(FromGroupSubmissionController): - """A controller for submitting SmilesToGaussianWorkchain + """A controller for submitting :func:`aiida_aimall.workchains.param_parts.SmilesToGaussianWorkChain` Args: parent_group_label: the string of a group label which contains various SMILES as orm.Str nodes @@ -44,7 +47,7 @@ class SmilesToGaussianController(FromGroupSubmissionController): parent_group_label = 'input_smiles', # Add structures to run to input_smiles group group_label = 'gaussianopt', # Resulting nodes will be in the gaussianopt group max_concurrent = 1, - wfxgroup = "opt_wfx" + wfxgroup = "opt_wfx", gauss_opt_params = Dict(dict={ 'link0_parameters': { '%chk':'aiida.chk', @@ -54,8 +57,11 @@ class SmilesToGaussianController(FromGroupSubmissionController): 'functional':'wb97xd', 'basis_set':'aug-cc-pvtz', 'route_parameters': { 'opt':None, 'freq':None}, - }) - ) + }), + nprocs = 4, + mem_mb = 6400, + time_s = 24*3600*7 + ) while True: #submit Gaussian batches every hour @@ -101,7 +107,7 @@ def get_extra_unique_keys(self): return ("smiles",) def get_inputs_and_processclass_from_extras(self, extras_values): - """Constructs input for a GaussianWFXCalculation from extra_values""" + """Constructs input for a GaussianCalculation from extra_values""" code = orm.load_code(self.code_label) smiles = self.get_parent_node_from_extras(extras_values) inputs = { @@ -117,7 +123,7 @@ def get_inputs_and_processclass_from_extras(self, extras_values): class AimReorSubmissionController(FromGroupSubmissionController): - """A controller for submitting AIMReor Workchains. + """A controller for submitting :func:`aiida_aimall.workchains.param_parts.AIMAllReorWorkChain`. Args: parent_group_label: the string of a group label which contains various structures as orm.Str nodes @@ -131,8 +137,8 @@ class AimReorSubmissionController(FromGroupSubmissionController): Controller object, periodically use run_in_batches to submit new results Note: - A typical use case is using this as a controller on wfx files created by GaussianWFXCalculation. In that case, - match the `parent_group_label` here to the `wfxgroup` provided to the GaussianWFXCalculation. + A typical use case is using this as a controller on wfx files created by GaussianCalculation. In that case, + match the `parent_group_label` here to the `wfxgroup` provided to the GaussianCalculation. In GaussianOptWorkchain, this is `opt_wfx` by default Example: @@ -199,7 +205,7 @@ def get_extra_unique_keys(self): return ("smiles",) def get_inputs_and_processclass_from_extras(self, extras_values): - """Constructs input for a AimReor Workchain from extra_values""" + """Constructs input for a :func:`aiida_aimall.workchains.param_parts.AIMAllReorWorkChain` from extra_values""" code = orm.load_code(self.code_label) # AimqbParameters = DataFactory("aimall") @@ -214,22 +220,23 @@ def get_inputs_and_processclass_from_extras(self, extras_values): class AimAllSubmissionController(FromGroupSubmissionController): - """A controller for submitting AimQB calculations. + """A controller for submitting :func:`aiida_aimall.calculations.AimqbCalculation`. Args: parent_group_label: the string of a group label which contains various structures as orm.Str nodes group_label: the string of the group to put the GaussianCalculations in max_concurrent: maximum number of concurrent processes. Expected behaviour is to set to a large number - since we will be submitting to Cedar which will manage + since we will be submitting to Cedar which will manage code_label: label of code, e.g. gaussian@cedar - aimparameters: dict of parameters for running AimQB, to be converted to AimqbParameters by the controller + aimparameters: dict of parameters for running AimQB, to be converted to + :func:`aiida_aimall.data.AimqbParameters` by the controller Returns: Controller object, periodically use run_in_batches to submit new results Note: - A typical use case is using this as a controller on wfx files created by GaussianWFXCalculation. In that case, - match the `parent_group_label` here to the `wfxgroup` provided to the GaussianWFXCalculation. + A typical use case is using this as a controller on wfx files created by `GaussianCalculation`. In that case, + match the `parent_group_label` here to the `wfxgroup` provided to the `GaussianCalculation`. In GaussianSubmissionController, this is `reor_wfx` Example: @@ -315,13 +322,13 @@ def get_inputs_and_processclass_from_extras(self, extras_values): class GaussianSubmissionController(FromGroupSubmissionController): - """A controller for submitting Gaussian calculations. + """A controller for submitting `GaussianCalculation`. Args: parent_group_label: the string of a group label which contains various structures as orm.Str nodes group_label: the string of the group to put the GaussianCalculations in max_concurrent: maximum number of concurrent processes. Expected behaviour is to set to a large number - since we will be submitting to Cedar which will manage + since we will be submitting to Cedar which will manage code_label: label of code, e.g. gaussian@cedar gauss_sp_params: dictionary of parameters to use in gaussian calculation diff --git a/src/aiida_aimall/data.py b/src/aiida_aimall/data.py index 36dd3cc..a7275c5 100644 --- a/src/aiida_aimall/data.py +++ b/src/aiida_aimall/data.py @@ -1,8 +1,5 @@ """ Data types provided by plugin - -Upon pip install, AimqbParameters is accessible in AiiDA.data plugins -Using the 'aimall.aimqb' entry point """ from aiida.orm import Dict diff --git a/src/aiida_aimall/parsers.py b/src/aiida_aimall/parsers.py index c9b41bf..b112f0b 100644 --- a/src/aiida_aimall/parsers.py +++ b/src/aiida_aimall/parsers.py @@ -2,7 +2,7 @@ """ Parsers provided by aiida_aimall. -Register parsers via the "aiida.parsers" entry point in setup.json. +Register parsers via the "aiida.parsers" entry point in pyproject.toml. """ import numpy as np diff --git a/src/aiida_aimall/workchains/calcfunctions.py b/src/aiida_aimall/workchains/calcfunctions.py index d947236..f76169f 100644 --- a/src/aiida_aimall/workchains/calcfunctions.py +++ b/src/aiida_aimall/workchains/calcfunctions.py @@ -21,29 +21,56 @@ def generate_rotated_structure_aiida(FolderData, atom_dict, cc_dict): FolderData: aim calculation folder atom_dict: AIM atom dict cc_dict: AIM cc_dict + + Returns: + Dict with keys 'atom_symbols' and 'geom' containing atomic symbols and the + the rotated geometry. + """ return Dict(rotate_substituent_aiida(FolderData, atom_dict, cc_dict)) -def remove(in_list): - """Remove digits from a list of strings. e.g. ['O1','H2','H3'] -> ['O','H','H']""" +def remove_numcharss_from_strlist(in_list): + """Remove digits from a list of strings. e.g. ['O1','H2','H3'] -> ['O','H','H'] + + Args: + in_list: input list to remove digits from + + Returns: + output list with the numerical digits removed from each element + + Note: + The intention for this list is to convert numered atomic symbols, e.g. from Gaussian + to just symbols + + """ remove_digits = str.maketrans("", "", digits) out_list = [i.translate(remove_digits) for i in in_list] return out_list @calcfunction -def dict_to_structure(fragment_dict): - """Generate a string of xyz coordinates for Gaussian input file +def dict_to_structure(fragment_dict: Dict): + """Generate a StructureData for Gaussian inputs + + Args: + fragment_dict: AiiDA orm.Dict with keys 'atom_symbols' and 'geom' + + Returns: + StructureData for the molecule + + Note: + input can be generated, for example, by + :func:`aiida_aimall.workchains.calcfunctions.generate_rotated_structure_aiida` - :param fragment_dict: - :param type fragment_dict: aiida.orm.nodes.data.dict.Dict """ inp_dict = fragment_dict.get_dict() symbols = inp_dict["atom_symbols"] - symbols = remove(symbols) + symbols = remove_numcharss_from_strlist(symbols) coords = inp_dict["geom"] + # outstr is xyz file contents outstr = "" + # numatoms then blank line, then coordinates and symbols for each atom outstr += f"{len(symbols)}\n\n" for i, symbol in enumerate(symbols): if i != len(symbols) - 1: @@ -69,6 +96,7 @@ def dict_to_structure(fragment_dict): + " " + str(coords[i][2]) ) + # create StructureData from .xyz file string f = io.StringIO(outstr) struct_data = StructureData(ase=ase.io.read(f, format="xyz")) f.close() @@ -76,7 +104,18 @@ def dict_to_structure(fragment_dict): def calc_multiplicity(mol): - """Calculate the multiplicity of a molecule as 2S +1""" + """Calculate the multiplicity of a molecule as 2S +1 + + Loops over the atoms in the molecule and gets number of radical electrons, + then converts that number to the multiplicity. + + Args: + mol: rdkit.Chem molecule object + + Returns: + integer number of multiplicity + + """ num_radicals = 0 for atom in mol.GetAtoms(): num_radicals += atom.GetNumRadicalElectrons() @@ -146,7 +185,15 @@ def reorder_molecule(h_mol_rw, zero_at, attached_atom): def get_xyz(reorder_mol): - """MMFF optimize the molecule to generate xyz coordiantes""" + """MMFF optimize the molecule to generate xyz coordiantes + + Args: + reorder_mol: rdkit.Chem molecule output, output of :func:`aiida_aimall.workchains.calcfunctions.reorder_molecule` + + Returns: + string of the geometry block of an .xyz file + + """ AllChem.EmbedMolecule(reorder_mol) # not_optimized will be 0 if done, 1 if more steps needed max_iters = 200 @@ -182,19 +229,26 @@ def get_substituent_input(smiles: str) -> dict: Returns: Dict with keys xyz, charge, multiplicity + Raise: + ValueError: if molecule cannot be built from SMILES + """ mol = Chem.MolFromSmiles(smiles.value) + # If the mol could not be built, mol will be None if not mol: raise ValueError( f"Molecule could not be constructed for substituent input SMILES {smiles.value}" ) + # add hydrogens to the molecule, and find the atom to put at origin and the atom attached to it h_mol_rw, zero_at, attached_atom = find_attachment_atoms(mol) + # Set zero_at to be the first atom, and attached atom as the second, by number reorder_mol = reorder_molecule(h_mol_rw, zero_at, attached_atom) xyz_string = get_xyz(reorder_mol) if xyz_string == "Could not determine xyz coordinates": raise BadMoleculeException( "Maximum iterations exceeded, could not determine xyz coordinates for f{smiles.value}" ) + # get store charge, multiplicity and geometry reorder_mol.UpdatePropertyCache() charge = Chem.GetFormalCharge(h_mol_rw) multiplicity = calc_multiplicity(h_mol_rw) @@ -204,11 +258,21 @@ def get_substituent_input(smiles: str) -> dict: @calcfunction def generate_structure_data(smiles_dict): - """Take an input xyz string and convert it to StructureData""" + """Take an input xyz string and convert it to StructureData + + Args: + smiles_dict: output of :func:`aiida_aimall.workchains.calcfunctions.get_substituent_input` + + Returns: + StructureData of the molecule + + """ structure_Str = smiles_dict["xyz"] structure_str = structure_Str + # Use the geometry string and create the xyz string for a full .xyz file num_atoms = len(structure_str.split("\n")) xyz_string = f"{num_atoms}\n\n" + structure_str + # Convert string to StructureData by encoding it as a file f = io.StringIO(xyz_string) struct_data = StructureData(ase=ase.io.read(f, format="xyz")) f.close() @@ -217,7 +281,16 @@ def generate_structure_data(smiles_dict): @calcfunction def parameters_with_cm(parameters, smiles_dict): - """Add charge and multiplicity keys to Gaussian Input""" + """Add charge and multiplicity keys to Gaussian Input + + Args: + parameters: dictionary to be provided to GaussianCalculation + smiles_dict: `aiida_aimall.workchains.calcfunctions.get_substituent_input` + + Returns: + Dict of Gaussian parameters updated with charge and multiplicity + + """ parameters_dict = parameters.get_dict() smiles_dict_dict = smiles_dict.get_dict() parameters_dict["charge"] = smiles_dict_dict["charge"] @@ -227,7 +300,18 @@ def parameters_with_cm(parameters, smiles_dict): @calcfunction def get_wfxname_from_gaussianinputs(gaussian_parameters): - """Look for wfx or wfn objects in the retrieved Folder""" + """Find the .wfx filename from gaussian_parameters + + Check if input parameters was provided to gaussian_parameters, and if so, look for + .wfx file names supplied. If it was, return the first .wfx filename found + + Args: + gaussian_parameters: input dictionary to be provided to GaussianCalculation + + Returns: + Str of .wfx filename + + """ gaussian_dict = gaussian_parameters.get_dict() if "input_parameters" not in gaussian_dict: return Str("") @@ -247,13 +331,30 @@ def get_wfxname_from_gaussianinputs(gaussian_parameters): @calcfunction def create_wfx_from_retrieved(wfxname, retrieved_folder): - """Create wavefunciton Singlefildata from retrieved folder""" + """Create wavefunction SinglefileData from retrieved folder + + Args: + wfxname: Str of the name of a .wfx file to get from the retrieved folder + retrieved_folder: FolderData of a completed GaussianCalculation + + Returns: + SinglefileData of the .wfx file to find in the FolderData + + """ wfx_file_string = retrieved_folder.get_object_content(wfxname.value) return SinglefileData(io.BytesIO(wfx_file_string.encode())) def validate_shell_code(node, _): - """Validate the shell code, ensuring that it is ShellCode or Str""" + """Validate the shell code, ensuring that it is ShellCode or Str + + Args: + node: input node to check the type for ShellCode or Str + + Returns: + None if the type is ShellCode or Str, or error string if node is not + + """ if node.node_type not in [ "data.core.code.installed.shell.ShellCode.", "data.core.str.Str.", @@ -263,23 +364,20 @@ def validate_shell_code(node, _): def validate_file_ext(node, _): - """Validates that the file extension provided for AIM is wfx, wfn or fchk""" + """Validates that the file extension provided for AIM is wfx, wfn or fchk + + Args: + node: node to check the value of to ensure it is in a supported format + + Returns: + None if the type is ShellCode or Str, or error string if node is not + + """ if node.value not in ["wfx", "wfn", "fchk"]: return "the `aim_file_ext` input must be a valid file format for AIMQB: wfx, wfn, or fchk" return None -@calcfunction -def get_wfx(retrieved_folder, wfx_filename): - """Get a wfx file from retrieved folder""" - folder_data = retrieved_folder - # later scan input parameters for filename - wfx_file = SinglefileData( - io.BytesIO(folder_data.get_object_content(wfx_filename.value).encode()) - ) - return wfx_file - - @calcfunction def get_molecule_str_from_smiles(smiles): """For a given smiles, determine xyz structure, charge, and multiplicity diff --git a/src/aiida_aimall/workchains/input.py b/src/aiida_aimall/workchains/input.py index 84e1e12..a404dba 100644 --- a/src/aiida_aimall/workchains/input.py +++ b/src/aiida_aimall/workchains/input.py @@ -80,17 +80,20 @@ def validate_input(self): return ExitCode(201) def create_structure_from_xyz(self): - """Convert the xyzfile to StructureData""" + """Convert the xyzfile to StructureData. Calls + :func:`aiida_aimall.workchains.calcfunctions.xyzfile_to_StructureData`""" self.ctx.structure = xyzfile_to_StructureData(self.inputs.xyz_file) def structure_in_context(self): - """Store the input structure in context, to make consistent with the results of xyz_file or SMILES input""" + """Store the input structure in context, to make consistent with the results of xyz_file or SMILES input.""" self.ctx.structure = self.inputs.structure def get_molecule_inputs_step(self): - """Given list of substituents and previously done smiles, get input""" + """Given list of substituents and previously done smiles, get input. + Calls :func:`aiida_aimall.workchains.calcfunctions.get_molecule_str_from_smiles`""" self.ctx.smiles_geom = get_molecule_str_from_smiles(self.inputs.smiles) def string_to_StructureData(self): - """Convert an xyz string of molecule geometry to StructureData""" + """Convert an xyz string of molecule geometry to StructureData. + Calls :func:`aiida_aimall.workchains.calcfunctions.generate_structure_data`""" self.ctx.structure = generate_structure_data(self.ctx.smiles_geom) diff --git a/src/aiida_aimall/workchains/param_parts.py b/src/aiida_aimall/workchains/param_parts.py index 6a27973..96bd6f0 100644 --- a/src/aiida_aimall/workchains/param_parts.py +++ b/src/aiida_aimall/workchains/param_parts.py @@ -1,4 +1,5 @@ """Workchains that are smaller parts of SubstituenParamWorkChain""" +# pylint:disable=no-member from aiida.engine import ToContext, WorkChain, if_ from aiida.orm import ( Bool, @@ -155,7 +156,6 @@ def aimall(self): builder.code = self.inputs.aim_code builder.parameters = self.inputs.aim_params builder.file = self.inputs.file - # pylint:disable=no-member builder.metadata.options.resources = { "num_machines": 1, "tot_num_mpiprocs": 2, diff --git a/src/aiida_aimall/workchains/qc_programs.py b/src/aiida_aimall/workchains/qc_programs.py index 63ad928..def8339 100644 --- a/src/aiida_aimall/workchains/qc_programs.py +++ b/src/aiida_aimall/workchains/qc_programs.py @@ -1,4 +1,4 @@ -"""Workchains to interface with AiiDA""" +"""Workchains to interface various quantum software with AiiDA""" # pylint: disable=c-extension-no-member # pylint:disable=no-member from aiida.engine import ToContext, WorkChain, if_ @@ -10,7 +10,7 @@ from aiida_aimall.calculations import AimqbCalculation from aiida_aimall.data import AimqbParameters from aiida_aimall.workchains.calcfunctions import ( - get_wfx, + create_wfx_from_retrieved, validate_file_ext, validate_shell_code, ) @@ -169,7 +169,7 @@ def classify_wfx(self): folder_data = self.ctx.gauss.base.links.get_outgoing().get_node_by_label( "retrieved" ) - self.ctx.wfx = get_wfx(folder_data, self.inputs.wfx_filename) + self.ctx.wfx = create_wfx_from_retrieved(self.inputs.wfx_filename, folder_data) # later scan input parameters for filename if "wfx_group" in self.inputs: diff --git a/src/aiida_aimall/workchains/subparam.py b/src/aiida_aimall/workchains/subparam.py index a20b602..02e53a4 100644 --- a/src/aiida_aimall/workchains/subparam.py +++ b/src/aiida_aimall/workchains/subparam.py @@ -1,13 +1,4 @@ -"""aiida_aimall.workchains -Workchains designed for a workflow starting from a set of cmls, then breaking off into fragment Gaussian Calculations -Needs to be run in part with aiida_aimall.controllers to control local traffic on lab Mac -Example in the works - -Provided Workchains are -MultiFragmentWorkchain, entry point: multifrag -gaussOptWorkChain, entry point: gaussopt -AimAllReor WorkChain, entry point: aimreor -""" +"""`WorkChain` for calculating substituent parameters developed by authors""" # pylint: disable=c-extension-no-member # pylint:disable=no-member # pylint:disable=too-many-lines @@ -18,7 +9,10 @@ from aiida_aimall.calculations import AimqbCalculation from aiida_aimall.data import AimqbParameters -from aiida_aimall.workchains.calcfunctions import get_substituent_input, get_wfx +from aiida_aimall.workchains.calcfunctions import ( + create_wfx_from_retrieved, + get_substituent_input, +) from aiida_aimall.workchains.input import BaseInputWorkChain from aiida_aimall.workchains.param_parts import AIMAllReorWorkChain @@ -105,7 +99,9 @@ def classify_opt_wfx(self): "retrieved" ) # later scan input parameters for filename - wfx_file = get_wfx(folder_data, self.inputs.wfx_filename.value) + wfx_file = create_wfx_from_retrieved( + self.inputs.wfx_filename.value, folder_data + ) self.ctx.opt_wfx = wfx_file if "opt_wfx_group" in self.inputs: @@ -162,7 +158,9 @@ def classify_sp_wfx(self): "retrieved" ) # later scan input parameters for filename - wfx_file = get_wfx(folder_data, self.inputs.wfx_filename.value) + wfx_file = create_wfx_from_retrieved( + self.inputs.wfx_filename.value, folder_data + ) self.ctx.sp_wfx = wfx_file if "sp_wfx_group" in self.inputs: