From 1e3e231dfc954bc645b6dcca09e43a431a5625ab Mon Sep 17 00:00:00 2001 From: Paul Saxe Date: Thu, 25 Jul 2024 15:34:39 -0400 Subject: [PATCH] Bugfix and improvements * Bugfix: Fixed issue with the initial seamm.ini file, created if it is missing from the installation. * Added the ability to set the number of points in the trajectories rather than the sampling rate. * Added diagnositic information and timings to available results. --- HISTORY.rst | 7 ++ lammps_step/energy.py | 21 ++++- lammps_step/lammps.py | 173 +++++++++++++++++++++++----------------- lammps_step/metadata.py | 98 +++++++++++++++++++++++ 4 files changed, 225 insertions(+), 74 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index de3a182..f20e647 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,6 +1,13 @@ ======= History ======= +2024.7.25 -- Bugfix and improvements + * Bugfix: Fixed issue with the initial seamm.ini file, created if it is missing from + the installation. + * Added the ability to set the number of points in the trajectories rather than the + sampling rate. + * Added diagnositic information and timings to available results. + 2024.7.21.1 -- Minor internal change for GUI * Switched to new functionality in the SEAMM widgets to simplify the layout of the trajectories panel. diff --git a/lammps_step/energy.py b/lammps_step/energy.py index 4c6fb72..b4b31e0 100644 --- a/lammps_step/energy.py +++ b/lammps_step/energy.py @@ -50,6 +50,11 @@ def header(self): """A printable header for this section of output""" return "Step {}: {}".format(".".join(str(e) for e in self._id), self.title) + @property + def results(self): + """The storage for the results in the main LAMMPS step.""" + return self.flowchart.parent._results + @property def version(self): """The semantic version of this module.""" @@ -180,6 +185,20 @@ def analyze(self, indent="", data={}, table=None, output=[], **kwargs): energy = float(tmp[i]) data["energy"] = energy data["energy,units"] = "kcal/mol" + if line.startswith("Loop time of"): + try: + tmp = line.split() + _time = round(float(tmp[3]), 2) + _procs = int(tmp[5]) + _steps = int(tmp[8]) + _natoms = int(tmp[11]) + _type = self._calculation + self.results[f"t_{_type}"] = _time + self.results[f"np_{_type}"] = _procs + self.results[f"steps_{_type}"] = _steps + self.results[f"natoms_{_type}"] = _natoms + except Exception as _e: + print(f"LAMMPS loop time: {_e}") # See if forces have been dumped wdir = Path(self.directory) @@ -226,6 +245,6 @@ def analyze(self, indent="", data={}, table=None, output=[], **kwargs): # Put any requested results into variables or tables self.store_results( configuration=configuration, - data=data, + data=data | self.results, create_tables=self.parameters["create tables"].get(), ) diff --git a/lammps_step/lammps.py b/lammps_step/lammps.py index c60324a..0b4a717 100644 --- a/lammps_step/lammps.py +++ b/lammps_step/lammps.py @@ -33,7 +33,7 @@ from seamm_ff_util import tabulate_angle import seamm_util import seamm_util.printing as printing -from seamm_util import CompactJSONEncoder +from seamm_util import CompactJSONEncoder, Configuration from seamm_util.printing import FormattedText as __ # from pymbar import timeseries @@ -171,6 +171,8 @@ def __init__( self._trajectory = [] self._data = {} + self._results = {} # Sotrage for computational and timing results + super().__init__( flowchart=flowchart, title="LAMMPS", extension=extension, logger=logger ) @@ -185,6 +187,11 @@ def git_revision(self): """The git version of this module.""" return lammps_step.__git_revision__ + @property + def results(self): + """The storage for results.""" + return self._results + @staticmethod def box_to_cell(lx, ly, lz, xy, xz, yz): """Convert the LAMMPS box definition to cell parameters.""" @@ -261,53 +268,56 @@ def create_parser(self): action="store_true", help="whether to write out html files for graphs, etc.", ) - parser.add_argument( - parser_name, - "--modules", - nargs="*", - default=None, - help="the environment modules to load for LAMMPS", - ) - parser.add_argument( - parser_name, - "--gpu-modules", - nargs="*", - default=None, - help="the environment modules to load for the GPU version of LAMMPS", - ) - parser.add_argument( - parser_name, - "--lammps-path", - default=None, - help="the path to the LAMMPS executables", - ) - parser.add_argument( - parser_name, - "--lammps-serial", - default="lmp_serial", - help="the serial version of LAMMPS", - ) - parser.add_argument( - parser_name, - "--lammps-mpi", - default="lmp_mpi", - help="the mpi version of LAMMPS", - ) - parser.add_argument( - parser_name, - "--cmd-args", - default="", - help="the command-line arguments for LAMMPS, e.g. '-k on'", - ) - parser.add_argument( - parser_name, - "--gpu-cmd-args", - default="", - help="the command-line arguments for GPU version of LAMMPS, e.g. '-k on'", - ) - parser.add_argument( - parser_name, "--mpiexec", default="mpiexec", help="the mpi executable" - ) + if False: + parser.add_argument( + parser_name, + "--modules", + nargs="*", + default=None, + help="the environment modules to load for LAMMPS", + ) + parser.add_argument( + parser_name, + "--gpu-modules", + nargs="*", + default=None, + help="the environment modules to load for the GPU version of LAMMPS", + ) + parser.add_argument( + parser_name, + "--lammps-path", + default=None, + help="the path to the LAMMPS executables", + ) + parser.add_argument( + parser_name, + "--lammps-serial", + default="lmp_serial", + help="the serial version of LAMMPS", + ) + parser.add_argument( + parser_name, + "--lammps-mpi", + default="lmp_mpi", + help="the mpi version of LAMMPS", + ) + parser.add_argument( + parser_name, + "--cmd-args", + default="", + help="the command-line arguments for LAMMPS, e.g. '-k on'", + ) + parser.add_argument( + parser_name, + "--gpu-cmd-args", + default="", + help=( + "the command-line arguments for GPU version of LAMMPS, e.g. '-k on'" + ), + ) + parser.add_argument( + parser_name, "--mpiexec", default="mpiexec", help="the mpi executable" + ) return result @@ -382,6 +392,9 @@ def run(self): self.logger.error("LAMMPS run(): there is no structure!") raise RuntimeError("LAMMPS run(): there is no structure!") + # Initialize storage + self._results = {} + next_node = super().run(printer) # Get the options @@ -661,44 +674,51 @@ def _execute_single_sim(self, files, np=1, return_files=None): ini_dir = Path(self.global_options["root"]).expanduser() path = ini_dir / "lammps.ini" - if path.exists(): - full_config.read(ini_dir / "lammps.ini") - full_config.set("local", "_origin_", f"{ini_dir / 'lammps.ini'}") - - # If the section we need doesn't exists, get the default - if not path.exists() or executor_type not in full_config: + # If the config file doesn't exists, get the default + if not path.exists(): resources = importlib.resources.files("lammps_step") / "data" ini_text = (resources / "lammps.ini").read_text() - full_config.read_string(ini_text) - full_config.set("local", "_origin_", "lammps default ini file") + txt_config = Configuration(path) + txt_config.from_string(ini_text) + + # Work out the conda info needed + txt_config.set_value("local", "conda", os.environ["CONDA_EXE"]) + txt_config.set_value("local", "conda-environment", "seamm-lammps") + txt_config.save() + printer.normal(f"Wrote the LAMMPS configuration file to {path}") + printer.normal("") + + full_config.read(ini_dir / "lammps.ini") - # Getting desperate! Look for an executable in the path if executor_type not in full_config: path = shutil.which("lmp") - if path is None: + mpi_path = shutil.which("mpirun") + if path is None or mpi_path is None: raise RuntimeError( f"No section for '{executor_type}' in LAMMPS ini file " f"({ini_dir / 'lammps.ini'}), nor in the defaults, nor " "in the path!" ) else: - full_config.add_section(executor_type) - full_config.set(executor_type, "installation", "local") + txt_config = Configuration(path) + txt_config.add_section(executor_type) + txt_config.set_value(executor_type, "installation", "local") + txt_config.set_value( + executor_type, + "code", + f"{mpi_path} -np {{NTASKS}} {path}", + ) + txt_config.set_value( + executor_type, + "python", + f"mpirun -np {{NTASKS}} {shutil.which('python')}", + ) + txt_config.save() + printer.normal(f"Wrote the LAMMPS configuration file to {path}") + printer.normal("") + full_config.read(ini_dir / "lammps.ini") full_config.set(executor_type, "code", str(path)) - # And we may need python! - full_config.set("local", "items", f"{full_config.items('local')}") - if not full_config.has_option(executor_type, "python"): - full_config.set(executor_type, "python", "mpirun -np {NTASKS} python") - full_config.set(executor_type, "_python source_", "default value") - - # If the ini file does not exist, write it out! - if not path.exists(): - with path.open("w") as fd: - full_config.write(fd) - printer.normal(f"Wrote the LAMMPS configuration file to {path}") - printer.normal("") - config = dict(full_config.items(executor_type)) # Use the matching version of the seamm-mopac image by default. config["version"] = self.version @@ -1675,6 +1695,13 @@ def analyze(self, indent="", nodes=None, **kwargs): sections[section] = [] elif section is not None: sections[section].append(line) + if line.startswith("Total wall time:"): + try: + h, m, s = line.split()[3].split(":") + _time = 3600 * float(h) + 60 * float(m) + float(s) + self.results["t_lammps_wall"] = _time + except Exception as _e: + print(f"Wall time exception {_e}") for node in nodes: for value in node.description: diff --git a/lammps_step/metadata.py b/lammps_step/metadata.py index 40f20ef..8c2c74f 100644 --- a/lammps_step/metadata.py +++ b/lammps_step/metadata.py @@ -486,4 +486,102 @@ "type": "float", "units": "", }, + # Timings + "t_lammps_wall": { + "calculation": [ + "energy", + "minimization", + "nve", + "nvt", + "npt", + ], + "description": "The wall clock time for LAMMPS", + "dimensionality": "scalar", + "type": "float", + "units": "s", + }, + "t_nve": { + "calculation": ["nve"], + "description": "The time for the NVE step", + "dimensionality": "scalar", + "type": "float", + "units": "s", + }, + "np_nve": { + "calculation": ["nve"], + "description": "The number of processors for the NVE step", + "dimensionality": "scalar", + "type": "integer", + "units": "s", + }, + "nsteps_nve": { + "calculation": ["nve"], + "description": "The number of steps in the NVE step", + "dimensionality": "scalar", + "type": "integer", + "units": "s", + }, + "natoms_nve": { + "calculation": ["nve"], + "description": "The number of atoms in the NVE step", + "dimensionality": "scalar", + "type": "integer", + "units": "s", + }, + "t_nvt": { + "calculation": ["nvt"], + "description": "The time for the NVT step", + "dimensionality": "scalar", + "type": "float", + "units": "s", + }, + "np_nvt": { + "calculation": ["nvt"], + "description": "The number of processors for the NVT step", + "dimensionality": "scalar", + "type": "integer", + "units": "s", + }, + "nsteps_nvt": { + "calculation": ["nvt"], + "description": "The number of steps in the NVT step", + "dimensionality": "scalar", + "type": "integer", + "units": "s", + }, + "natoms_nvt": { + "calculation": ["nvt"], + "description": "The number of atoms in the NVT step", + "dimensionality": "scalar", + "type": "integer", + "units": "s", + }, + "t_npt": { + "calculation": ["npt"], + "description": "The time for the NPT step", + "dimensionality": "scalar", + "type": "float", + "units": "s", + }, + "np_npt": { + "calculation": ["npt"], + "description": "The number of processors for the NPT step", + "dimensionality": "scalar", + "type": "integer", + "units": "s", + }, + "nsteps_npt": { + "calculation": ["npt"], + "description": "The number of steps in the NPT step", + "dimensionality": "scalar", + "type": "integer", + "units": "s", + }, + "natoms_npt": { + "calculation": ["npt"], + "description": "The number of atoms in the NPT step", + "dimensionality": "scalar", + "type": "integer", + "units": "s", + }, }