diff --git a/HISTORY.rst b/HISTORY.rst index 04821391..e45fb752 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,6 +1,12 @@ ======= History ======= +2023.10.30 -- Extending and cleaning up handling of configurations + * Added ability to name systems and configurations with the IUPAC name, InChI, or + InChIKey of the configuration. + * Generally cleanedup and streamlined the code handling new systems and + configurations. + 2023.9.26.1 -- Bugfix: system naming * Fixed a bug with keeping the current name when updating a system. diff --git a/seamm/node.py b/seamm/node.py index a5845cd3..e3c381b1 100644 --- a/seamm/node.py +++ b/seamm/node.py @@ -455,7 +455,7 @@ def set_gui_data(self, key, value, gui=None): def get_system_configuration( self, P=None, - same_as=None, + same_as="current", first=True, **kwargs, ): @@ -473,12 +473,12 @@ def get_system_configuration( Parameters ---------- P : dict(str, any) = None - The diction of options and values. If none, the default system and + The dictionary of options and values. If none, the default system and configuration are returned as-is. - same_as : _Configuration = None + same_as : _Configuration = "current"" Share atoms, bonds, or cell with this configuration, depending - on other flags. Defaults to None, which results in using the current - configuration + on other flags. Defaults to "current", which results in using the current + configuration. If None, an empty configuration is created/used first : bool = True First configuration of several, which can have different handling than the subsequent ones. @@ -491,12 +491,18 @@ def get_system_configuration( # Get the system system_db = self.get_variable("_system_db") + system = system_db.system + if system is None: + configuration = None + else: + configuration = system.configuration + if same_as == "current": + same_as = configuration + if P is None: # Just return the current system and configuration, creating if needed. - system = system_db.system if system is None: system = system_db.create_system() - configuration = system.configuration if configuration is None: configuration = system.create_configuration() else: @@ -515,34 +521,28 @@ def get_system_configuration( handling = "Create a new configuration" if handling == "Overwrite the current configuration": - system = system_db.system if system is None: system = system_db.create_system() - configuration = system.configuration if configuration is None: configuration = system.create_configuration() elif handling == "Create a new configuration": - system = system_db.system if system is None: system = system_db.create_system() # See if sharing atoms, bonds and/or cell if same_as is None: - current = system.configuration - if current is None: - configuration = system.create_configuration() - else: - configuration = system.copy_configuration( - current, make_current=True - ) + configuration = system.create_configuration() else: configuration = system.copy_configuration( configuration=same_as, make_current=True ) elif handling == "Create a new system and configuration": system = system_db.create_system() - configuration = system.copy_configuration( - configuration=same_as, make_current=True - ) + if same_as is None: + configuration = system.create_configuration() + else: + configuration = system.copy_configuration( + configuration=same_as, make_current=True + ) else: raise ValueError( f"Do not understand how to handle the structure: '{handling}'" diff --git a/seamm/run_flowchart.py b/seamm/run_flowchart.py index 45fc1b6d..bf131afe 100644 --- a/seamm/run_flowchart.py +++ b/seamm/run_flowchart.py @@ -112,7 +112,32 @@ def run_from_jobserver(): db_path = sys.argv[3] cmdline = sys.argv[4:] - run(job_id=job_id, wdir=wdir, db_path=db_path, in_jobserver=True, cmdline=cmdline) + with cd(wdir): + try: + run( + job_id=job_id, + wdir=wdir, + db_path=db_path, + in_jobserver=True, + cmdline=cmdline, + ) + except Exception as e: + path = Path("job_data.json") + if path.exists(): + with path.open("r") as fd: + fd.readline() + data = json.load(fd) + else: + data = {} + + data["state"] = "error" + data["error type"] = type(e).__name__ + data["error message"] = traceback.format_exc() + with path.open("w") as fd: + fd.write("!MolSSI job_data 1.0") + json.dump(data, fd, indent=3, sort_keys=True) + fd.write("\n") + raise def run( diff --git a/seamm/standard_parameters.py b/seamm/standard_parameters.py index 73e31b02..9ed6dbbf 100644 --- a/seamm/standard_parameters.py +++ b/seamm/standard_parameters.py @@ -57,6 +57,9 @@ "keep current name", "use SMILES string", "use Canonical SMILES string", + "use IUPAC name", + "use InChI", + "use InChIKey", ), "format_string": "s", "description": "System name:", @@ -70,6 +73,9 @@ "keep current name", "use SMILES string", "use Canonical SMILES string", + "use IUPAC name", + "use InChI", + "use InChIKey", ), "format_string": "s", "description": "Configuration name:", @@ -78,7 +84,7 @@ } -def structure_handling_description(P): +def structure_handling_description(P, **kwargs): """Return a standard description for how the structure will be handled. Parameters @@ -106,28 +112,43 @@ def structure_handling_description(P): raise ValueError(f"Do not understand how to handle the structure: '{handling}'") sysname = P["system name"] - if sysname == "use SMILES string": - text += " The name of the system will be the SMILES string given." + if sysname == "keep current name": + text += " The name of the system will not be changed." + elif sysname == "use SMILES string": + text += " The name of the system will be its SMILES." elif sysname == "use Canonical SMILES string": - text += " The name of the system will be the canonical SMILES of the structure." + text += " The name of the system will be its canonical SMILES." + elif sysname == "use IUPAC name": + text += " The name of the system will be its IUPAC name." + elif sysname == "use InChI": + text += " The name of the system will be its InChI." + elif sysname == "use InChIKey": + text += " The name of the system will be its InChIKey." else: - text += f" The name of the system will be {sysname}." + tmp = safe_format(sysname, **kwargs) + text += f" The name of the system will be '{tmp}'." confname = P["configuration name"] - if confname == "use SMILES string": - text += " The name of the configuration will be the SMILES string given." + if confname == "keep current name": + text += " The name of the configuration will not be changed." + elif confname == "use SMILES string": + text += " The name of the configuration will be its SMILES." elif confname == "use Canonical SMILES string": - text += ( - " The name of the configuration will be the canonical SMILES of the " - "structure." - ) + text += " The name of the configuration will be its canonical SMILES." + elif confname == "use IUPAC name": + text += " The name of the configuration will be its IUPAC name." + elif confname == "use InChI": + text += " The name of the configuration will be its InChI." + elif confname == "use InChIKey": + text += " The name of the configuration will be its InChIKey." else: - text += f" The name of the configuration will be {confname}." + tmp = safe_format(confname, **kwargs) + text += f" The name of the configuration will be '{tmp}'." return text -def multiple_structure_handling_description(P): +def multiple_structure_handling_description(P, **kwargs): """Return a standard description for how the new structures will be handled. Parameters @@ -148,9 +169,9 @@ def multiple_structure_handling_description(P): if handling == "Overwrite the current configuration": text += "overwrite the current configuration." elif handling == "Create a new configuration": - text += "created as a new configuration of the current system." + text += "be added as a new configuration of the current system." elif handling == "Create a new system and configuration": - text += "created in a new system and configuration." + text += "be added as a new system and configuration." else: raise ValueError(f"Do not understand how to handle the structure: '{handling}'") @@ -164,22 +185,136 @@ def multiple_structure_handling_description(P): raise ValueError(f"Do not understand how to handle the structure: '{handling}'") sysname = P["system name"] - if sysname == "use SMILES string": - text += " The name of the system will be the SMILES string given." + if sysname == "keep current name": + text += " The name of the system will not be changed." + elif sysname == "use SMILES string": + text += " The name of the system will be its SMILES." elif sysname == "use Canonical SMILES string": - text += " The name of the system will be the canonical SMILES of the structure." + text += " The name of the system will be its canonical SMILES." + elif sysname == "use IUPAC name": + text += " The name of the system will be its IUPAC name." + elif sysname == "use InChI": + text += " The name of the system will be its InChI." + elif sysname == "use InChIKey": + text += " The name of the system will be its InChIKey." else: - text += f" The name of the system will be {sysname}." + tmp = safe_format(sysname, **kwargs) + text += f" The name of the system will be '{tmp}'." confname = P["configuration name"] - if confname == "use SMILES string": - text += " The name of the configuration will be the SMILES string given." + if confname == "keep current name": + text += " The name of the configuration will not be changed." + elif confname == "use SMILES string": + text += " The name of the configuration will be its SMILES." elif confname == "use Canonical SMILES string": - text += ( - " The name of the configuration will be the canonical SMILES of the " - "structure." + text += " The name of the configuration will be its canonical SMILES." + elif confname == "use IUPAC name": + text += " The name of the configuration will be its IUPAC name." + elif confname == "use InChI": + text += " The name of the configuration will be its InChI." + elif confname == "use InChIKey": + text += " The name of the configuration will be its InChIKey." + else: + tmp = safe_format(confname, **kwargs) + text += f" The name of the configuration will be '{tmp}'." + + return text + + +def set_names(system, configuration, P, _first=True, **kwargs): + """Set the names of the system and configuration. + + Parameters + ---------- + system : _System + The system being named + + configuration : _Configuration + The configuration being named + + P : dict(str, any) + The dictionary of parameter values, which must contain the standard structure + handling parameters. + + _first : bool + Whether this is the first or a subseqnet structure. + + kwargs : {str: str} + keyword arguments providing values that may be substituted in the names. + + Returns + ------- + str + The text for printing. + """ + sysname = P["system name"] + if sysname == "keep current name": + pass + elif sysname == "use SMILES string": + system.name = configuration.smiles + elif sysname == "use Canonical SMILES string": + system.name = configuration.canonical_smiles + elif sysname == "use IUPAC name": + system.name = configuration.PC_iupac_name(fallback=configuration.formula[0]) + elif sysname == "use InChI": + system.name = configuration.inchi + elif sysname == "use InChIKey": + system.name = configuration.inchikey + else: + system.name = safe_format(sysname, **kwargs) + + confname = P["configuration name"] + if confname == "keep current name": + pass + elif confname == "use SMILES string": + configuration.name = configuration.smiles + elif confname == "use Canonical SMILES string": + configuration.name = configuration.canonical_smiles + elif confname == "use IUPAC name": + configuration.name = configuration.PC_iupac_name( + fallback=configuration.formula[0] ) + elif confname == "use InChI": + configuration.name = configuration.inchi + elif confname == "use InChIKey": + configuration.name = configuration.inchikey + else: + configuration.name = safe_format(confname, **kwargs) + + if _first: + text = "The first structure " + + handling = P["structure handling"] + if handling == "Overwrite the current configuration": + text += "overwrote the current configuration, and was" + elif handling == "Create a new configuration": + text += "was added as a new configuration of the current system" + elif handling == "Create a new system and configuration": + text += "was added as a new system and configuration " + else: + raise ValueError( + f"Do not understand how to handle the structure: '{handling}'" + ) else: - text += f" The name of the configuration will be {confname}." + handling = P["subsequent structure handling"] + text = "This subsequent structure was " + if handling == "Create a new configuration": + text += "created as a new configuration of the current system" + elif handling == "Create a new system and configuration": + text += "created in a new system and configuration" + else: + raise ValueError( + f"Do not understand how to handle the structure: '{handling}'" + ) + text += f" named '{system.name}' / '{configuration.name}'." return text + + +def safe_format(s, *args, **kwargs): + while True: + try: + return s.format(*args, **kwargs) + except KeyError as e: + e = e.args[0] + kwargs[e] = "{%s}" % e