diff --git a/HISTORY.rst b/HISTORY.rst index f2b555f..b96ffff 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,6 +1,10 @@ ======= History ======= +2023.10.30 -- Updated to standard structure handling + * Adds IUPAC names, InChI and InChIKey as possible names for configurations + * Cleaned up output to be properly indented and laid out. + 2023.8.30 -- Support for spacegroup symmetry 2023.7.27 -- Bugfix: printing bond order info diff --git a/mopac_step/__init__.py b/mopac_step/__init__.py index 5f411f5..fe29e3d 100644 --- a/mopac_step/__init__.py +++ b/mopac_step/__init__.py @@ -43,40 +43,6 @@ # Handle versioneer from ._version import get_versions -# Parameters used for handling the structure if it is changed. -structure_handling_parameters = { - "structure handling": { - "default": "be put in a new configuration", - "kind": "enum", - "default_units": "", - "enumeration": ( - "overwrite the current configuration", - "be put in a new configuration", - ), - "format_string": "s", - "description": "Optimized structure will", - "help_text": ( - "Whether to overwrite the current configuration, or create a new " - "configuration or system and configuration for the new structure" - ), - }, - "configuration name": { - "default": "optimized with ", - "kind": "string", - "default_units": "", - "enumeration": ( - "optimized with ", - "keep current name", - "use SMILES string", - "use Canonical SMILES string", - "use configuration number", - ), - "format_string": "s", - "description": "Configuration name:", - "help_text": "The name for the new configuration", - }, -} - __author__ = """Paul Saxe""" __email__ = "psaxe@molssi.org" versions = get_versions() diff --git a/mopac_step/energy.py b/mopac_step/energy.py index 5bd393a..62f24a9 100644 --- a/mopac_step/energy.py +++ b/mopac_step/energy.py @@ -34,6 +34,7 @@ def __init__(self, flowchart=None, title="Energy", extension=None): self._calculation = "energy" self._model = None self._metadata = mopac_step.metadata + self._use_mozyme = None self.parameters = mopac_step.EnergyParameters() self.description = "A single point energy calculation" @@ -83,13 +84,17 @@ def description_text(self, P=None): ) # MOZYME localized molecular orbitals. - if P["MOZYME"] == "always": + if ["MOZYME"] == "always" or ( + self._use_mozyme is not None and self._use_mozyme + ): text += ( "\n\nThe SCF will be solved using localized molecular orbitals " "(MOZYME), which is faster than the traditional method for larger " "systems." ) used_mozyme = True + elif self._use_mozyme is not None and not self._use_mozyme: + used_mozyme = False elif P["MOZYME"] == "for larger systems": text += ( "\n\nThe SCF will be solved using localized molecular orbitals " @@ -171,9 +176,19 @@ def get_input(self): if isinstance(PP[key], units_class): PP[key] = "{:~P}".format(PP[key]) + if P["MOZYME"] == "always": + self._use_mozyme = True + elif ( + P["MOZYME"] == "for larger systems" + and configuration.n_atoms >= P["nMOZYME"] + ): + self._use_mozyme = True + else: + self._use_mozyme = False + # Save the description for later printing self.description = [] - self.description.append(__(self.description_text(PP), **PP, indent=self.indent)) + self.description.append(__(self.description_text(PP), **PP, indent=4 * " ")) # Start gathering the keywords keywords = copy.deepcopy(P["extra keywords"]) @@ -748,16 +763,14 @@ def analyze(self, indent="", data_sections=[], out_sections=[], table=None): else: data = data_sections[0] - if "GRADIENT_NORM" in data: - tmp = data["GRADIENT_NORM"] - table["Property"].append("Gradient Norm") - table["Value"].append(f"{tmp:.2f}") - table["Units"].append("kcal/mol/Å") - if "POINT_GROUP" in data and data["POINT_GROUP"] != "": - text += "The molecule has {POINT_GROUP} symmetry." + table["Property"].append("Symmetry") + table["Value"].append(data["POINT_GROUP"]) + table["Units"].append("") else: - text += "The symmetry of the molecule was not determined." + table["Property"].append("Symmetry") + table["Value"].append("?") + table["Units"].append("") if "HEAT_OF_FORMATION" in data: tmp = data["HEAT_OF_FORMATION"] @@ -794,10 +807,16 @@ def analyze(self, indent="", data_sections=[], out_sections=[], table=None): text += ( " You followed up with an exact calculation. A large " "difference in the energy could indicate a problem " - "with the localcized molecular orbitals. Check the MOPAC " + "with the localized molecular orbitals. Check the MOPAC " "output carefully!" ) + if "GRADIENT_NORM" in data: + tmp = data["GRADIENT_NORM"] + table["Property"].append("Gradient Norm") + table["Value"].append(f"{tmp:.2f}") + table["Units"].append("kcal/mol/Å") + if "SPIN_COMPONENT" in data: tmp = data["SPIN_COMPONENT"] table["Property"].append("Sz") @@ -995,9 +1014,9 @@ def analyze(self, indent="", data_sections=[], out_sections=[], table=None): ) ) - text = str(__(text, **data, indent=self.indent + 4 * " ")) + text = str(__(text, **data, indent=8 * " ")) text += "\n\n" - text += textwrap.indent("\n".join(text_lines), self.indent + 7 * " ") + text += textwrap.indent("\n".join(text_lines), 12 * " ") if "BOND_ORDERS" in data: text += self._bond_orders( @@ -1019,7 +1038,7 @@ def analyze(self, indent="", data_sections=[], out_sections=[], table=None): elif "CPU_TIME" in data: t0 = data_sections[0]["CPU_TIME"] text = f"This calculation took {t0:.2f} s." - printer.normal(str(__(text, **data, indent=self.indent + 4 * " "))) + printer.normal(str(__(text, **data, indent=4 * " "))) # Put any requested results into variables or tables self.store_results( @@ -1073,41 +1092,34 @@ def _bond_orders(self, control, bond_order_matrix, configuration): table = { "i": [name[i] for i in bond_i], "j": [name[j] for j in bond_j], - "bond order": orders, + "bond order": [f"{o:6.3f}" for o in orders], "bond multiplicity": bond_order_str, } tmp = tabulate( table, headers="keys", - tablefmt="pretty", + tablefmt="psql", disable_numparse=True, - colalign=("center", "center", "right", "center"), + colalign=("center", "center", "center", "center"), ) length = len(tmp.splitlines()[0]) text_lines.append("\n") text_lines.append("Bond Orders".center(length)) - text_lines.append( - tabulate( - table, - headers="keys", - tablefmt="psql", - colalign=("center", "center", "decimal", "center"), - ) - ) + text_lines.append(tmp) text += "\n\n" - text += textwrap.indent("\n".join(text_lines), self.indent + 7 * " ") + text += textwrap.indent("\n".join(text_lines), 12 * " ") if control == "yes, and apply to structure": ids = configuration.atoms.ids iatoms = [ids[i] for i in bond_i] jatoms = [ids[j] for j in bond_j] - configuration.bonds.delete() + configuration.bonds.new_bondset() configuration.bonds.append(i=iatoms, j=jatoms, bondorder=bond_order) text2 = ( "\nReplaced the bonds in the configuration with those from the " "calculated bond orders.\n" ) - text += str(__(text2, indent=self.indent + 4 * " ")) + text += str(__(text2, indent=8 * " ")) return text diff --git a/mopac_step/energy_parameters.py b/mopac_step/energy_parameters.py index 7c856dc..2558563 100644 --- a/mopac_step/energy_parameters.py +++ b/mopac_step/energy_parameters.py @@ -293,5 +293,10 @@ def __init__(self, defaults={}, data=None): parameters given in the class""" super().__init__( - defaults={**EnergyParameters.parameters, **defaults}, data=data + defaults={ + **EnergyParameters.parameters, + **seamm.standard_parameters.structure_handling_parameters, + **defaults, + }, + data=data, ) diff --git a/mopac_step/forceconstants.py b/mopac_step/forceconstants.py index 30e1d7b..5768d28 100644 --- a/mopac_step/forceconstants.py +++ b/mopac_step/forceconstants.py @@ -124,7 +124,7 @@ def get_input(self): # Save the description for later printing self.description = [] - self.description.append(__(self.description_text(PP), **PP, indent=self.indent)) + self.description.append(__(self.description_text(PP), **PP, indent=4 * " ")) _, configuration = self.get_system_configuration(None) diff --git a/mopac_step/forceconstants_parameters.py b/mopac_step/forceconstants_parameters.py index b2d87c0..87508bb 100644 --- a/mopac_step/forceconstants_parameters.py +++ b/mopac_step/forceconstants_parameters.py @@ -90,8 +90,16 @@ def __init__(self, defaults={}, data=None): super().__init__( defaults={ **ForceconstantsParameters.parameters, - **mopac_step.structure_handling_parameters, **defaults, }, data=data, ) + + # Do any local editing of defaults + tmp = self["system name"] + tmp._data["enumeration"] = (*tmp.enumeration, "MOPAC standard orientation") + tmp.default = "keep current name" + + tmp = self["configuration name"] + tmp._data["enumeration"] = ["MOPAC standard orientation", *tmp.enumeration] + tmp.default = "MOPAC standard optimization" diff --git a/mopac_step/ir.py b/mopac_step/ir.py index 73da77e..3de2d21 100644 --- a/mopac_step/ir.py +++ b/mopac_step/ir.py @@ -113,7 +113,7 @@ def get_input(self): # Save the description for later printing self.description = [] - self.description.append(__(self.description_text(PP), **PP, indent=self.indent)) + self.description.append(__(self.description_text(PP), **PP, indent=4 * " ")) # Remove the 1SCF keyword from the energy setup inputs = super().get_input() @@ -221,7 +221,7 @@ def analyze(self, indent="", data_sections=[], out_sections=[], table=None): colalign=("center", "decimal", "decimal", "decimal", "left"), ) text_lines += "\n" - text = textwrap.indent(text_lines, self.indent + 7 * " ") + text = textwrap.indent(text_lines, 8 * " ") printer.normal(text) # And the vibrational modes to a csv file diff --git a/mopac_step/ir_parameters.py b/mopac_step/ir_parameters.py index c10ceff..121318c 100644 --- a/mopac_step/ir_parameters.py +++ b/mopac_step/ir_parameters.py @@ -35,8 +35,16 @@ def __init__(self, defaults={}, data=None): super().__init__( defaults={ **IRParameters.parameters, - **mopac_step.structure_handling_parameters, **defaults, }, data=data, ) + + # Do any local editing of defaults + tmp = self["system name"] + tmp._data["enumeration"] = (*tmp.enumeration, "MOPAC standard orientation") + tmp.default = "keep current name" + + tmp = self["configuration name"] + tmp._data["enumeration"] = ["MOPAC standard orientation", *tmp.enumeration] + tmp.default = "MOPAC standard optimization" diff --git a/mopac_step/lewis_structure.py b/mopac_step/lewis_structure.py index 24197fb..0f3e4af 100644 --- a/mopac_step/lewis_structure.py +++ b/mopac_step/lewis_structure.py @@ -446,7 +446,7 @@ def analyze(self, indent="", lines=[], n_calculations=None): if same: iatoms = [ids[i] for i in bonds["i"]] jatoms = [ids[j] for j in bonds["j"]] - configuration.bonds.delete() + configuration.new_bondset() configuration.bonds.append( i=iatoms, j=jatoms, bondorder=bonds["bondorder"] ) @@ -462,7 +462,7 @@ def analyze(self, indent="", lines=[], n_calculations=None): iatoms.append(ids[i]) jatoms.append(ids[j]) bondorders.append(1) - configuration.bonds.delete() + configuration.new_bondset() configuration.bonds.append( i=iatoms, j=jatoms, bondorder=bonds["bondorder"] ) @@ -479,4 +479,4 @@ def analyze(self, indent="", lines=[], n_calculations=None): t_total = data["CPU_TIME"] text += f"\nThe Lewis structure took a total of {t_total:.2f} s.\n" - printer.normal(textwrap.indent(text, self.indent + 4 * " ")) + printer.normal(textwrap.indent(text, 4 * " ")) diff --git a/mopac_step/optimization.py b/mopac_step/optimization.py index b10d240..564789a 100644 --- a/mopac_step/optimization.py +++ b/mopac_step/optimization.py @@ -82,22 +82,11 @@ def description_text(self, P=None): text += "\n\nThe energy and forces will be c" + energy_description[1:] text += "\n\n" - text += "The optimized structures will {structure handling} " - - confname = P["configuration name"] - if confname == "use SMILES string": - text += "using SMILES as its name." - elif confname == "use Canonical SMILES string": - text += "using canonical SMILES as its name." - elif confname == "keep current name": - text += "keeping the current name." - elif confname == "optimized with ": - text += "with 'optimized with {hamiltonian}' as its name." - elif confname == "use configuration number": - text += "using the index of the configuration (1, 2, ...) as its name." + if self.is_expr(P["hamiltonian"]): + kwargs = {} else: - confname = confname.replace("", P["hamiltonian"]) - text += f"with '{confname}' as its name." + kwargs = {"Hamiltonian": P["hamiltonian"]} + text += seamm.standard_parameters.structure_handling_description(P, **kwargs) return self.header + "\n" + __(text, **P, indent=4 * " ").__str__() @@ -257,7 +246,7 @@ def analyze(self, indent="", data_sections=[], out_sections=[], table=None): context=seamm.flowchart_variables._data ) - system, starting_configuration = self.get_system_configuration(None) + starting_system, starting_configuration = self.get_system_configuration(None) # Get the data. data = data_sections[0] @@ -390,38 +379,11 @@ def analyze(self, indent="", data_sections=[], out_sections=[], table=None): ) # Update the structure + periodicity = starting_configuration.periodicity if "ATOM_X_OPT" in data or "ATOM_X_UPDATED" in data: - periodicity = starting_configuration.periodicity - if ( - "structure handling" in P - and P["structure handling"] == "be put in a new configuration" - ): - if P["bond orders"] == "yes, and apply to structure": - configuration = system.create_configuration( - periodicity=periodicity, - atomset=starting_configuration.atomset, - cell_id=starting_configuration.cell_id, - symmetry=starting_configuration.symmetry_id, - ) - else: - configuration = system.create_configuration( - periodicity=periodicity, - atomset=starting_configuration.atomset, - bondset=starting_configuration.bondset, - cell_id=starting_configuration.cell_id, - symmetry=starting_configuration.symmetry_id, - ) - configuration.charge = starting_configuration.charge - configuration.spin_multiplicity = ( - starting_configuration.spin_multiplicity - ) - # Add initial coordinates so symmetry is handled properly - configuration.atoms.set_coordinates( - starting_configuration.atoms.get_coordinates(asymmetric=True) - ) - else: - configuration = starting_configuration - + system, configuration = self.get_system_configuration( + P, same_as=starting_configuration + ) if periodicity != 0 and P["LatticeOpt"]: if "TRANS_VECTS" in data: vectors = data["TRANS_VECTS"] @@ -465,42 +427,34 @@ def analyze(self, indent="", data_sections=[], out_sections=[], table=None): configuration.atoms.set_coordinates(xyz, fractionals=False) - # And the name of the configuration. - if "configuration name" in P: - if P["configuration name"] == "use SMILES string": - configuration.name = configuration.smiles - elif P["configuration name"] == "use Canonical SMILES string": - configuration.name = configuration.canonical_smiles - elif P["configuration name"] == "keep current name": - pass - elif P["configuration name"] == "optimized with ": - configuration.name = f"optimized with {P['hamiltonian']}" - else: - confname = P["configuration name"] - confname = confname.replace("", P["hamiltonian"]) - configuration.name = confname + text += seamm.standard_parameters.set_names( + system, configuration, P, _first=True, Hamiltonian=P["hamiltonian"] + ) # Write the structure out for viewing. directory = Path(self.directory) directory.mkdir(parents=True, exist_ok=True) - # MMCIF file has bonds - try: - path = directory / "optimized.mmcif" - path.write_text(configuration.to_mmcif_text()) - except Exception: - message = "Error creating the mmcif file\n\n" + traceback.format_exc() - logger.warning(message) - # CIF file has cell - if configuration.periodicity == 3: + if periodicity == 0: + configuration.to_sdf(directory / "optimized.sdf") + else: + # MMCIF file has bonds try: - path = directory / "optimized.cif" - path.write_text(configuration.to_cif_text()) + path = directory / "optimized.mmcif" + path.write_text(configuration.to_mmcif_text()) except Exception: - message = "Error creating the cif file\n\n" + traceback.format_exc() + message = "Error creating the mmcif file\n\n" + traceback.format_exc() logger.warning(message) - - printer.normal(__(text, **data, indent=self.indent + 4 * " ")) + # CIF file has cell + if configuration.periodicity == 3: + try: + path = directory / "optimized.cif" + path.write_text(configuration.to_cif_text()) + except Exception: + message = "Error creating the cif file\n\n" + traceback.format_exc() + logger.warning(message) + + printer.normal(__(text, **data, indent=8 * " ")) super().analyze( indent=indent, diff --git a/mopac_step/optimization_parameters.py b/mopac_step/optimization_parameters.py index 9dec241..234df2e 100644 --- a/mopac_step/optimization_parameters.py +++ b/mopac_step/optimization_parameters.py @@ -121,8 +121,19 @@ def __init__(self, defaults={}, data=None): super().__init__( defaults={ **OptimizationParameters.parameters, - **mopac_step.structure_handling_parameters, **defaults, }, data=data, ) + + # Do any local editing of defaults + tmp = self["structure handling"] + tmp.description = "Structure handling:" + + tmp = self["system name"] + tmp._data["enumeration"] = (*tmp.enumeration, "optimized with {Hamiltonian}") + tmp.default = "keep current name" + + tmp = self["configuration name"] + tmp._data["enumeration"] = ["optimized with {Hamiltonian}", *tmp.enumeration] + tmp.default = "optimized with {Hamiltonian}" diff --git a/mopac_step/thermodynamics.py b/mopac_step/thermodynamics.py index 0d67666..d651470 100644 --- a/mopac_step/thermodynamics.py +++ b/mopac_step/thermodynamics.py @@ -117,7 +117,7 @@ def get_input(self): # Save the description for later printing self.description = [] - self.description.append(__(self.description_text(PP), **PP, indent=self.indent)) + self.description.append(__(self.description_text(PP), **PP, indent=4 * "")) # Convert values with units to the right units, and remove # the unit string. @@ -245,7 +245,7 @@ def analyze(self, indent="", data_sections=[], out_sections=[], table=None): tablefmt="psql", ) text_lines += "\n" - text = textwrap.indent(text_lines, self.indent + 7 * " ") + text = textwrap.indent(text_lines, 8 * " ") printer.normal(text) with open(directory / "thermodynamics.csv", "w", newline="") as fd: diff --git a/mopac_step/thermodynamics_parameters.py b/mopac_step/thermodynamics_parameters.py index 884b3e5..ba754c0 100644 --- a/mopac_step/thermodynamics_parameters.py +++ b/mopac_step/thermodynamics_parameters.py @@ -67,8 +67,16 @@ def __init__(self, defaults={}, data=None): super().__init__( defaults={ **ThermodynamicsParameters.parameters, - **mopac_step.structure_handling_parameters, **defaults, }, data=data, ) + + # Do any local editing of defaults + tmp = self["system name"] + tmp._data["enumeration"] = (*tmp.enumeration, "MOPAC standard orientation") + tmp.default = "keep current name" + + tmp = self["configuration name"] + tmp._data["enumeration"] = ["MOPAC standard orientation", *tmp.enumeration] + tmp.default = "MOPAC standard optimization" diff --git a/mopac_step/tk_optimization.py b/mopac_step/tk_optimization.py index 2f25f7c..4b076d6 100644 --- a/mopac_step/tk_optimization.py +++ b/mopac_step/tk_optimization.py @@ -3,6 +3,7 @@ """The graphical part of a MOPAC Energy node""" import mopac_step +import seamm import seamm_widgets as sw import tkinter as tk import tkinter.ttk as ttk @@ -45,7 +46,7 @@ def create_dialog(self, title="MOPAC Optimization"): for key in mopac_step.OptimizationParameters.parameters: self[key] = P[key].widget(oframe) row += 1 - for key in mopac_step.structure_handling_parameters: + for key in seamm.standard_parameters.structure_handling_parameters: self[key] = P[key].widget(oframe) row += 1 @@ -119,7 +120,7 @@ def reset_optimization_frame(self, widget=None): widgets_2.append(self["dmax"]) row += 1 - for key in ("structure handling", "configuration name"): + for key in ("structure handling", "system name", "configuration name"): self[key].grid(row=row, column=0, columnspan=2, sticky=tk.EW) widgets.append(self[key]) row += 1