From 3ff7b5ebe7a4ca160493a95f773d80243cff74ed Mon Sep 17 00:00:00 2001 From: Paul Saxe Date: Wed, 20 Mar 2024 16:39:24 -0400 Subject: [PATCH 1/7] Updating for changes in Python --- versioneer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/versioneer.py b/versioneer.py index 64fea1c8..3aa5da37 100644 --- a/versioneer.py +++ b/versioneer.py @@ -339,9 +339,9 @@ def get_config_from_root(root): # configparser.NoOptionError (if it lacks "VCS="). See the docstring at # the top of versioneer.py for instructions on writing your setup.cfg . setup_cfg = os.path.join(root, "setup.cfg") - parser = configparser.SafeConfigParser() + parser = configparser.ConfigParser() with open(setup_cfg, "r") as f: - parser.readfp(f) + parser.read_file(f) VCS = parser.get("versioneer", "VCS") # mandatory def get(parser, name): From 08f62dff637696b217b772b7f76d32c00d115d6a Mon Sep 17 00:00:00 2001 From: Paul Saxe Date: Wed, 20 Mar 2024 16:40:18 -0400 Subject: [PATCH 2/7] Fixed formatting --- lammps_step/velocities.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lammps_step/velocities.py b/lammps_step/velocities.py index ef2693fb..92a5501b 100644 --- a/lammps_step/velocities.py +++ b/lammps_step/velocities.py @@ -94,13 +94,13 @@ def get_input(self, extras=None): system_db = self.get_variable("_system_db") configuration = system_db.system.configuration if configuration.periodicity == 3: - P[ - "remove_momentum" - ] = "remove translational but not rotational momentum" + P["remove_momentum"] = ( + "remove translational but not rotational momentum" + ) else: - P[ - "remove_momentum" - ] = "remove both translational and rotational momentum" + P["remove_momentum"] = ( + "remove both translational and rotational momentum" + ) if P["seed"] == "random": P["seed"] = int(random.random() * 2**31) From ac9e57296d8da3e3c0b538cfb000bc9507317a22 Mon Sep 17 00:00:00 2001 From: Paul Saxe Date: Wed, 20 Mar 2024 16:40:38 -0400 Subject: [PATCH 3/7] Updated for new installer and to create Docker image --- HISTORY.rst | 5 + devtools/docker/Dockerfile | 8 + devtools/docker/environment.yml | 10 + lammps_step/data/configuration.txt | 35 -- lammps_step/data/lammps.ini | 87 ++++ lammps_step/installer.py | 628 ++++++++--------------------- lammps_step/lammps.py | 46 ++- 7 files changed, 310 insertions(+), 509 deletions(-) create mode 100644 devtools/docker/Dockerfile create mode 100644 devtools/docker/environment.yml create mode 100644 lammps_step/data/lammps.ini diff --git a/HISTORY.rst b/HISTORY.rst index d22296cc..16d7e220 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,6 +1,11 @@ ======= History ======= +2024.3.20 -- Switched to new installation scheme + * Fully support ~/SEAMM/lammps.ini + * Updated to new installer + * Support for Conda and Docker installation. + 2024.1.18 -- Restructured to support running in containers. 2023.11.7 -- Bugfix: properties that are constant diff --git a/devtools/docker/Dockerfile b/devtools/docker/Dockerfile new file mode 100644 index 00000000..8625088b --- /dev/null +++ b/devtools/docker/Dockerfile @@ -0,0 +1,8 @@ +FROM molssi/mamba141 + +COPY ./environment.yml /root/environment.yml + +RUN mamba env update -f /root/environment.yml + +WORKDIR /home +ENTRYPOINT ["lmp_serial"] diff --git a/devtools/docker/environment.yml b/devtools/docker/environment.yml new file mode 100644 index 00000000..8b1270ed --- /dev/null +++ b/devtools/docker/environment.yml @@ -0,0 +1,10 @@ +name: base +channels: + - conda-forge +dependencies: + - python + # Executables, etc. + - openmpi + - lammps + - openkim-models + - mpi4py diff --git a/lammps_step/data/configuration.txt b/lammps_step/data/configuration.txt index eeae85ae..ae37b84a 100644 --- a/lammps_step/data/configuration.txt +++ b/lammps_step/data/configuration.txt @@ -22,41 +22,6 @@ # html = False -# Information about where/how the executables are installed -# installation may be 'path', 'conda' or 'modules'. If a module is -# specified it will be loaded and those executables used. In this -# case, any path specified using lammps-path will be ignored. - -installation = not installed -conda-environment = -modules = -gpu-modules = - -# The path to the executables. Can be empty or not present, in which -# case the default PATH is used. If a path is given, lmp_serial and -# lmp_mpi from this location will be used. If mpiexec is also present -# it will be used; otherwise mpiexec from the normal PATH will be -# used. If mpiexec or lmp_mpi is not found, only the serial version of -# LAMMPS will be used. Conversely, if lmp_serial is not present, -# lmp_mpi will always be used, though possible on just one core for -# smaller calculations. -# -# Ignored if a module is used. The default is to use the PATH -# environment variable. - -lammps-path = - -# The various LAMMPS executables. You should leave these as their default -# values unless you have a special need. These values are combined with -# lammps-path to get the location of the executables. -# -# If you are running from a queueing system such as SLURM, if MPI was compiled -# to know about the batch system, you should be able to use something like -# 'srun' or 'mpirun' with no further arguments to run parallel tasks. If -# you are not lucky, and need the parameters, you can add them to the command -# line like this: -# -# mpiexec = mpirun -n {NTASKS} -H {NODELIST} -npernode {NTASKS_PER_NODE} # # SEAMM picks up the environment variables such as SLURM_NTASKS, strips the # prefix from them and replaces any instances in the command line that are diff --git a/lammps_step/data/lammps.ini b/lammps_step/data/lammps.ini new file mode 100644 index 00000000..8bf08478 --- /dev/null +++ b/lammps_step/data/lammps.ini @@ -0,0 +1,87 @@ +# Configuration options for how to run LAMMPS + +[docker] +# The code to use. This may maybe more than just the name of the code, and variables in +# braces {} will be expanded. For example: +# code = mpirun -np {NTASKS} lmp +# would expand {NTASKS} to the number of tasks and run the command +# +# If you are running from a queueing system such as SLURM, if MPI was compiled +# to know about the batch system, you should be able to use something like +# 'srun' or 'mpirun' with no further arguments to run parallel tasks. If +# you are not lucky, and need the parameters, you can add them to the command +# line like this: +# +# code = mpirun -n {NTASKS} -H {NODELIST} -npernode {NTASKS_PER_NODE} + +code = mpirun -n {NTASKS} lmp + +# The name and location of the Docker container to use, optionally with the version + +container = ghcr.io/molssi-seamm/seamm-lammps:{version} + +# In addition, you can specify the platform to use. This is useful on e.g. Macs with +# app silicon (M1, M3...) where the default platform is linux/arm64 but some containers +# are only available for linux/amd64. + +platform = linux/amd64 + +[local] +# The type of local installation to use. Options are: +# conda: Use a conda environment +# modules: Use the modules system +# local: Use a local installation +# docker: Use a Docker container +# By default SEAMM installs LAMMPS using conda. + +installation = conda + +# The command line to use, which should start with the executable followed by any options. +# Variables in braces {} will be expanded. For example: +# +# code = mpirun -np {NTASKS} lmp +# +# would expand {NTASKS} to the number of tasks and run the command. +# For a 'local' installation, the command line should include the full path to the +# executable or it should be in the path. +# +# If you are running from a queueing system such as SLURM, if MPI was compiled +# to know about the batch system, you should be able to use something like +# 'srun' or 'mpirun' with no further arguments to run parallel tasks. If +# you are not lucky, and need the parameters, you can add them to the command +# line like this: +# +# code = mpirun -n {NTASKS} -H {NODELIST} -npernode {NTASKS_PER_NODE} + +code = mpirun -n {NTASKS} lmp + +######################### conda section ############################ +# The full path to the conda executable: + +# conda = + +# The Conda environment to use. This is either the name or full path. + +# conda-environment = seamm-lammps + +######################### modules section ############################ +# The modules to load to run LAMMPS, as a list of strings. +# For example, to load the modules lammps and openmpi, you would use: +# modules = lammps openmpi + +# modules = + +######################### local section ############################ +# The full path to the LAMMPS executable should be in the 'code' option. + +######################### docker section ############################ +# The name and location of the Docker container to use, optionally with the version. +# {version} will be expanded to the version of the plug-in. + +# container = ghcr.io/molssi-seamm/seamm-lammps:{version} + +# In addition, you can specify the platform to use. This is useful on e.g. Macs with +# app silicon (M1, M3...) where the default platform is linux/arm64 but some containers +# are only available for linux/amd64. + +platform = linux/amd64 diff --git a/lammps_step/installer.py b/lammps_step/installer.py index 8f3d2afa..5381e854 100644 --- a/lammps_step/installer.py +++ b/lammps_step/installer.py @@ -7,9 +7,10 @@ """ import logging -import pkg_resources +import os from pathlib import Path -import shutil +import pkg_resources +import platform import subprocess import seamm_installer @@ -64,507 +65,194 @@ def __init__(self, logger=logger): logger.debug("Initializing the LAMMPS installer object.") self.section = "lammps-step" - self.path_name = "lammps-path" - self.executables = ["lmp_serial", "lmp_mpi"] + self.executables = ["lmp"] self.resource_path = Path(pkg_resources.resource_filename(__name__, "data/")) + + # The environment.yaml file for Conda installations. + logger.debug(f"data directory: {self.resource_path}") + self.environment_file = self.resource_path / "seamm-lammps.yml" + + def check(self): + """Check the status of the LAMMPS installation.""" + print("Checking the LAMMPS installation.") + # What Conda environment is the default? - data = self.configuration.get_values(self.section) + path = self.configuration.path.parent / "lammps.ini" + if not path.exists(): + text = (self.resource_path / "lammps.ini").read_text() + path.write_text(text) + print(f" The lammps.ini file did not exist. Created {path}") + + self.exe_config.path = path + + # Get the current values + data = self.exe_config.get_values("local") + if "conda-environment" in data and data["conda-environment"] != "": self.environment = data["conda-environment"] else: self.environment = "seamm-lammps" - # The environment.yaml file for Conda installations. - path = Path(pkg_resources.resource_filename(__name__, "data/")) - logger.debug(f"data directory: {path}") - self.environment_file = path / "seamm-lammps.yml" + super().check() - def check(self): - """Check the installation and fix errors if requested. + def install(self): + """Install LAMMPS in a conda environment.""" + print("Installing LAMMPS.") - If the option `yes` is present and True, this method will attempt to - correct any errors in the configuration file. Use `--yes` on the - command line to enable this. + # What Conda environment is the default? + path = self.configuration.path.parent / "lammps.ini" + if not path.exists(): + text = (self.resource_path / "lammps.ini").read_text() + path.write_text(text) + print(f" The lammps.ini file did not exist. Created {path}") - The information in the configuration file is: + self.exe_config.path = path - installation - How LAMMPS is installed. One of `path`, `modules` or `conda` - conda-environment - The Conda environment if and only if `installation` = `conda` - modules - The environment modules if `installation` = `modules` - lammps-path - The path where the LAMMPS executables are. Automatically - defined if `installation` is `conda` or `modules`, but given - by the user is it is `path`. + # Get the current values + data = self.exe_config.get_values("local") - Returns - ------- - bool - True if everything is OK, False otherwise. If `yes` is given as an - option, the return value is after fixing the configuration. - """ - self.logger.debug("Entering check method.") - if not self.configuration.section_exists(self.section): - if self.options.yes or self.ask_yes_no( - "There is no section for the LAMMPS step in the configuration " - f" file ({self.configuration.path}).\nAdd one?", - default="yes", - ): - self.check_configuration_file() - print( - f"Added the {self.section} section to the configuration file " - f"{self.configuration.path}" - ) - - # Get the values from the configuration - data = self.configuration.get_values(self.section) - - # Save the initial values, if any, of the key configuration variables - if "lammps-path" in data and data["lammps-path"] != "": - path = Path(data["lammps-path"]).expanduser().resolve() - initial_lammps_path = path - else: - initial_lammps_path = None - if "installation" in data and data["installation"] != "": - initial_installation = data["installation"] - else: - initial_installation = None if "conda-environment" in data and data["conda-environment"] != "": - initial_conda_environment = data["conda-environment"] - else: - initial_conda_environment = None - if "modules" in data and data["modules"] != "": - initial_modules = data["modules"] - else: - initial_modules = None - - # Is there a valid lammps-path? - self.logger.debug( - "Checking for executables in the initial lammps-path " - f"{initial_lammps_path}." - ) - if initial_lammps_path is None or not self.have_executables( - initial_lammps_path - ): - lammps_path = None + self.environment = data["conda-environment"] else: - lammps_path = initial_lammps_path - self.logger.debug(f"initial-lammps-path = {initial_lammps_path}.") + self.environment = "seamm-lammps" - # Is there an installation indicated? - if initial_installation in ("path", "conda", "modules"): - installation = initial_installation - else: - installation = None - self.logger.debug(f"initial-installation = {initial_installation}.") - - if installation == "conda": - # Is there a conda environment? - conda_environment = None - if initial_conda_environment is None or not self.conda.exists( - initial_conda_environment - ): - if lammps_path is not None: - # see if this path corresponds to a Conda environment - for tmp in self.conda.environments: - tmp_path = self.conda.path(tmp) / "bin" - if tmp_path == lammps_path: - conda_environment = tmp - break - if conda_environment is not None: - if self.options.yes or self.ask_yes_no( - "The Conda environment in the config file " - "is not correct.\n" - f"It should be {conda_environment}. Fix?", - default="yes", - ): - self.configuration.set_value( - self.section, "installation", "conda" - ) - self.configuration.set_value( - self.section, "conda-environment", conda_environment - ) - self.configuration.set_value(self.section, "modules", "") - self.configuration.save() - print( - "Corrected the conda environment to " - f"{conda_environment}" - ) - else: - # Have a Conda environment! - conda_path = self.conda.path(initial_conda_environment) / "bin" - self.logger.debug( - f"Checking for executables in conda-path: {conda_path}." - ) - if self.have_executables(conda_path): - # All is good! - conda_environment = initial_conda_environment - if lammps_path is None: - if self.options.yes or self.ask_yes_no( - "The lammps-path in the config file is not set," - f"but the Conda environment {conda_environment} " - "is.\nFix the lammps-path?", - default="yes", - ): - lammps_path = conda_path - self.configuration.set_value( - self.section, "lammps-path", lammps_path - ) - self.configuration.set_value(self.section, "modules", "") - self.configuration.save() - print(f"Set the lammps-path to {conda_path}") - elif lammps_path != conda_path: - if self.options.yes or self.ask_yes_no( - f"The lammps-path in the config file {lammps_path}" - "is different from that for the Conda " - f"environment {conda_environment} is.\n" - "Use the path from the Conda environment?", - default="yes", - ): - lammps_path = conda_path - self.configuration.set_value( - self.section, "lammps-path", lammps_path - ) - self.configuration.set_value(self.section, "modules", "") - self.configuration.save() - print(f"Changed the lammps-path to {conda_path}") - else: - # Everything is fine! - pass - if installation == "modules": - print(f"Can't check the actual modules {initial_modules} yet") - if initial_conda_environment is not None: - if self.options.yes or self.ask_yes_no( - "A Conda environment is given: " - f"{initial_conda_environment}.\n" - "A Conda environment should not be used when using " - "modules. Remove it from the configuration?", - default="yes", - ): - self.configuration.set_value(self.section, "conda-environment", "") - self.configuration.save() - print( - "Using modules, so removed the conda-environment from " - "the configuration" - ) - else: - if lammps_path is None: - # No path or executables in the path! - environments = self.conda.environments - if self.environment in environments: - # Make sure it is first! - environments.remove(self.environment) - environments.insert(0, self.environment) - for tmp in environments: - tmp_path = self.conda.path(tmp) / "bin" - if self.have_executables(tmp_path): - if self.options.yes or self.ask_yes_no( - "There are no valid executables in the lammps-path" - " in the config file, but there are in the Conda " - f"environment {tmp}.\n" - "Use them?", - default="yes", - ): - conda_environment = tmp - lammps_path = tmp_path - self.configuration.set_value( - self.section, "lammps-path", lammps_path - ) - self.configuration.set_value( - self.section, "installation", "conda" - ) - self.configuration.set_value( - self.section, "conda-environment", conda_environment - ) - self.configuration.set_value(self.section, "modules", "") - self.configuration.save() - print( - "Will use the conda environment " - f"'{conda_environment}'" - ) - break - if lammps_path is None: - # Haven't found it. Check in the path. - lammps_path = self.executables_in_path() - if lammps_path is not None: - if self.options.yes or self.ask_yes_no( - "Found LAMMPS executables in the PATH at " - f"{lammps_path}\n" - "Use them?", - default="yes", - ): - self.configuration.set_value( - self.section, "installation", "path" - ) - self.configuration.set_value( - self.section, "conda-environment", "" - ) - self.configuration.set_value(self.section, "modules", "") - self.configuration.save() - print("Using the LAMMPS executables at {lammps_path}") - - if lammps_path is None: - # Can't find LAMMPS - print("Cannot find LAMMPS executables. You will need to install them.") - if ( - initial_installation is not None - and initial_installation != "not installed" - ): - if self.options.yes or self.ask_yes_no( - "The configuration file indicates that LAMMPS " - "is installed, but it can't be found.\n" - "Fix the configuration file?", - default="yes", - ): - self.configuration.set_value( - self.section, "installation", "not installed" - ) - self.configuration.set_value(self.section, "lammps-path", "") - self.configuration.set_value( - self.section, "conda-environment", "" - ) - self.configuration.set_value(self.section, "modules", "") - self.configuration.save() - print( - "Since no LAMMPS executables were found, cleared " - "the configuration." - ) - else: - print("The check completed successfully.") - - def check_configuration_file(self): - """Checks that the lammps-step section is in the configuration file.""" - if not self.configuration.section_exists(self.section): - # Get the text of the data - path = Path(pkg_resources.resource_filename(__name__, "data/")) - path = path / "configuration.txt" - text = path.read_text() - - # Add it to the configuration file and write to disk. - self.configuration.add_section(self.section, text) - self.configuration.save() - - def have_executables(self, path): - """Check whether the executables are found at the given path. + if platform.system() == "Darwin" and platform.machine() == "arm64": + os.environ["CONDA_SUBDIR"] = "osx-64" + elif platform.system() == "Linux" and platform.machine() == "arm64": + os.environ["CONDA_SUBDIR"] = "linux-64" - Parameters - ---------- - path : pathlib.Path - The directory to check. + super().install() - Returns - ------- - bool - True if at least one of the LAMMPS executables is found. - """ - for executable in self.executables: - tmp_path = path / executable - if tmp_path.exists(): - self.logger.debug(f"Found executables in {path}") - return True - self.logger.debug(f"Did not find executables in {path}") - return False + def show(self): + """Show the status of the LAMMPS installation.""" + print("Showing the LAMMPS installation.") - def executables_in_path(self): - """Check whether the executables are found in the PATH. + # What Conda environment is the default? + path = self.configuration.path.parent / "lammps.ini" + if not path.exists(): + text = (self.resource_path / "lammps.ini").read_text() + path.write_text(text) + print(f" The lammps.ini file does not exist at {path}") + print(" The 'check' command will create it if LAMMPS is installed.") + print(" Otherwise 'install' will install LAMMPS.") + return + + self.exe_config.path = path + + if not self.exe_config.section_exists("local"): + print( + " LAMMPS is not configured: there is no 'local' section in " + f" {path}." + ) + return - Returns - ------- - pathlib.Path - The path where the executables are, or None. - """ - path = None - for executable in self.executables: - path = shutil.which(executable) - if path is not None: - path = Path(path).expanduser().resolve() - break - return path + # Get the current values + data = self.exe_config.get_values("local") - def install(self): - """Install LAMMPS using a Conda environment.""" - if self.configuration.section_exists(self.section): - # Get the values from the configuration - data = self.configuration.get_values(self.section) - if "installation" in data: - initial_installation = data["installation"] - if initial_installation == "path": - print("Using LAMMPS for the path given by the user.") - return - elif initial_installation == "modules": - print("Using LAMMPS from the modules given by the user.") - return + if "conda-environment" in data and data["conda-environment"] != "": + self.environment = data["conda-environment"] else: - # Update the configuration file. - self.check_configuration_file() - - print( - f"Installing Conda environment '{self.environment}'. This " - "may take a minute or two." - ) - self.conda.create_environment(self.environment_file, name=self.environment) - path = self.conda.path(self.environment) / "bin" - self.configuration.set_value(self.section, "lammps-path", str(path)) - self.configuration.set_value(self.section, "installation", "conda") - self.configuration.set_value( - self.section, "conda-environment", self.environment - ) - self.configuration.set_value(self.section, "modules", "") - self.configuration.save() - print("Done!\n") + self.environment = "seamm-lammps" - def show(self): - """Show the current installation status.""" - self.logger.debug("Entering show") + super().show() - # See if LAMMPS is already registered in the configuration file - if not self.configuration.section_exists(self.section): + def uninstall(self): + """Uninstall the LAMMPS installation.""" + print("Uninstall the LAMMPS installation.") + + # What Conda environment is the default? + path = self.configuration.path.parent / "lammps.ini" + if not path.exists(): + text = (self.resource_path / "lammps.ini").read_text() + path.write_text(text) print( - "There is no section in the configuration file for the " - "LAMMPS step (lammps-step)." + f"""" The lammps.ini file does not exist at {path} + Perhaps LAMMPS is not installed, but if it is the 'check' command may locate it + and create the ini file, after which 'uninstall' will remove it.""" ) - data = self.configuration.get_values(self.section) - - # Keep track of where executables are - serial = None - mpi = None - mpiexec = None - - # Is the path in the configuration file? - if "lammps-path" in data: - conf_path = Path(data["lammps-path"]).expanduser().resolve() - if (conf_path / "lmp_serial").exists(): - serial = conf_path / "lmp_serial" - serial_version = self.exe_version(serial) - if (conf_path / "mpiexec").exists(): - mpiexec = conf_path / "mpiexec" - else: - mpiexec = shutil.which("mpiexec") - if (conf_path / "lmp_mpi").exists(): - mpi = conf_path / "lmp_mpi" - if mpiexec is None: - mpi_version = "unknown" - else: - mpi_version = self.exe_version(mpi, mpiexec) - - extra = f"from path {conf_path}." - if "installation" in data: - installation = data["installation"] - if installation == "conda": - if "conda-environment" in data and data["conda-environment"] != "": - extra = ( - "from Conda environment " f"{data['conda-environment']}." - ) - else: - extra = "from an unknown Conda environment." - elif installation == "modules": - if "modules" in data and data["modules"] != "": - print(f"from module(s) {data['modules']}.") - else: - print("from unknown modules.") - return - elif installation == "path": - print(f"from user-defined path {conf_path}.") - return - - if serial is not None: - if mpi is not None: - if serial_version == mpi_version: - print( - "LAMMPS serial and mpi executables, version " - f"'{serial_version}'" - ) - else: - print( - "LAMMPS serial executable, version " - f"'{serial_version}', and mpi executable, " - f"version {mpi_version}" - ) - print(extra) - else: - print(f"LAMMPS serial executable, version {serial_version}") - print(extra) - elif mpi is not None: - print(f"LAMMPS mpi executable, version {mpi_version}") - print(extra) - else: - print("LAMMPS is not configured to run.") + return + + self.exe_config.path = path + + if not self.exe_config.section_exists("local"): + print( + f"""" The lammps.ini file at {path} does not have local section. + Perhaps LAMMPS is not installed, but if it is the 'check' command may locate it + and update the ini file, after which 'uninstall' will remove it.""" + ) + return + + # Get the current values + data = self.exe_config.get_values("local") + + if "conda-environment" in data and data["conda-environment"] != "": + self.environment = data["conda-environment"] else: - print("LAMMPS is not configured to run.") - - # Look in the PATH, but only record if not same as in the conf file - tmp = shutil.which("lmp_serial") - if tmp is not None: - tmp = Path(tmp).expanduser().resolve() - if serial is not None and serial != tmp: - version = self.exe_version(tmp) - print( - f"Another serial executable of LAMMPS (version {version}) " - "is in the PATH:\n" - f" {tmp}" - ) - tmp = shutil.which("lmp_mpi") - if tmp is not None: - tmp = Path(tmp).expanduser().resolve() - if mpi is not None and mpi != tmp: - if mpiexec is None: - version = "unknown" - else: - version = self.exe_version(tmp, mpiexec) - print( - f"Another mpi executable of LAMMPS (version {version}) " - "is in the PATH:\n" - f" {tmp}" - ) - - def exe_version(self, path, mpiexec=None): + self.environment = "seamm-lammps" + + super().uninstall() + + def update(self): + """Updates the LAMMPS installation.""" + print("Updating the LAMMPS installation.") + + # What Conda environment is the default? + path = self.configuration.path.parent / "lammps.ini" + if not path.exists(): + text = (self.resource_path / "lammps.ini").read_text() + path.write_text(text) + print(f" The lammps.ini file did not exist. Created {path}") + + self.exe_config.path = path + + # Get the current values + data = self.exe_config.get_values("local") + + if "conda-environment" in data and data["conda-environment"] != "": + self.environment = data["conda-environment"] + else: + self.environment = "seamm-lammps" + + super().update() + + def exe_version(self, config): """Get the version of the LAMMPS executable. Parameters ---------- - path : pathlib.Path - Path to the executable. + config : dict + Dictionary of options for running LAMMPS Returns ------- - str + "LAMMPS", str The version reported by LAMMPS, or 'unknown'. """ - if mpiexec is not None: - try: - result = subprocess.run( - [mpiexec, str(path), "-log", "none"], - stdin=subprocess.DEVNULL, - capture_output=True, - text=True, - ) - except Exception: - version = "unknown" - else: - version = "unknown" - lines = result.stdout.splitlines() - if len(lines) > 0: - line = lines[0] - if line[0:8] == "LAMMPS (": - version = line[8:].rstrip(")") + environment = config["conda-environment"] + conda = config["conda"] + if environment[0] == "~": + environment = str(Path(environment).expanduser()) + command = f"'{conda}' run --live-stream -p '{environment}'" + elif Path(environment).is_absolute(): + command = f"'{conda}' run --live-stream -p '{environment}'" + else: + command = f"'{conda}' run --live-stream -n '{environment}'" + command += " lmp -h" + try: + result = subprocess.run( + command, + stdin=subprocess.DEVNULL, + capture_output=True, + text=True, + shell=True, + ) + except Exception: + version = "unknown" else: - try: - result = subprocess.run( - [str(path), "-log", "none"], - stdin=subprocess.DEVNULL, - capture_output=True, - text=True, - ) - except Exception: - version = "unknown" - else: - version = "unknown" - lines = result.stdout.splitlines() - if len(lines) > 0: - line = lines[0] - if line[0:8] == "LAMMPS (": - version = line[8:].rstrip(")") - - return version + version = "unknown" + lines = result.stdout.splitlines() + if len(lines) > 1: + line = lines[1] + version = " ".join(line.split()[6:]) + + return "LAMMPS", version diff --git a/lammps_step/lammps.py b/lammps_step/lammps.py index c733f5b6..33475f1e 100644 --- a/lammps_step/lammps.py +++ b/lammps_step/lammps.py @@ -5,6 +5,7 @@ import configparser from contextlib import contextmanager import copy +import importlib import json import logging from math import sqrt, exp, degrees, radians, cos, acos @@ -13,6 +14,7 @@ import os.path import pkg_resources import pprint +import shutil import string import sys import traceback @@ -651,12 +653,48 @@ def _execute_single_sim(self, files, np=1, return_files=None): cl = {"NTASKS": np} ce = seamm_exec.computational_environment(cl) - # Get the configuration for the LAMMPS executables, and the executor - ini_dir = Path(self.global_options["root"]).expanduser() + executor = self.flowchart.executor + + # Read configuration file for LAMMPS if it exists + executor_type = executor.name full_config = configparser.ConfigParser() - full_config.read(ini_dir / "lammps.ini") + ini_dir = Path(self.global_options["root"]).expanduser() + path = ini_dir / "lammps.ini" - executor = self.flowchart.executor + if path.exists(): + full_config.read(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: + resources = importlib.resources.files("lammps_step") / "data" + ini_text = (resources / "lammps.ini").read_text() + full_config.read_string(ini_text) + + # Getting desperate! Look for an executable in the path + if executor_type not in full_config: + path = shutil.which("lammps") + if 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[executor_type] = { + "installation": "local", + "code": str(path), + } + + # 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 executor_type = executor.name if executor_type not in full_config: From 2cd8d3e4afc40c1dd8c1c4a12e217d5496a0af36 Mon Sep 17 00:00:00 2001 From: Paul Saxe Date: Wed, 20 Mar 2024 19:39:35 -0400 Subject: [PATCH 4/7] Adding defaults channel for missing requirements. --- devtools/conda-envs/test_env.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/devtools/conda-envs/test_env.yaml b/devtools/conda-envs/test_env.yaml index 84994c84..6092d479 100644 --- a/devtools/conda-envs/test_env.yaml +++ b/devtools/conda-envs/test_env.yaml @@ -2,6 +2,7 @@ name: test channels: - conda-forge + - defaults dependencies: # Base depends From b738a7a41993ba1a691bc0880e3654d15e2bd179 Mon Sep 17 00:00:00 2001 From: Paul Saxe Date: Wed, 20 Mar 2024 19:42:08 -0400 Subject: [PATCH 5/7] Pinning python to 3.11 --- devtools/conda-envs/test_env.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devtools/conda-envs/test_env.yaml b/devtools/conda-envs/test_env.yaml index 6092d479..1a250715 100644 --- a/devtools/conda-envs/test_env.yaml +++ b/devtools/conda-envs/test_env.yaml @@ -6,7 +6,7 @@ channels: dependencies: # Base depends - - python + - python==3.11 - pip # SEAMM From b1f65574a5f7959104a041111ca905add366e793 Mon Sep 17 00:00:00 2001 From: Paul Saxe Date: Wed, 20 Mar 2024 19:49:58 -0400 Subject: [PATCH 6/7] Reverting to unpinned Python version --- devtools/conda-envs/test_env.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devtools/conda-envs/test_env.yaml b/devtools/conda-envs/test_env.yaml index 1a250715..6092d479 100644 --- a/devtools/conda-envs/test_env.yaml +++ b/devtools/conda-envs/test_env.yaml @@ -6,7 +6,7 @@ channels: dependencies: # Base depends - - python==3.11 + - python - pip # SEAMM From 28f2d7b71758ac6ceca4cfb555d390e22bb4034a Mon Sep 17 00:00:00 2001 From: Paul Saxe Date: Thu, 21 Mar 2024 09:51:53 -0400 Subject: [PATCH 7/7] Moving pymbar to pip from conda to see if that fixes version issues. --- devtools/conda-envs/test_env.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devtools/conda-envs/test_env.yaml b/devtools/conda-envs/test_env.yaml index 6092d479..380234d8 100644 --- a/devtools/conda-envs/test_env.yaml +++ b/devtools/conda-envs/test_env.yaml @@ -11,7 +11,6 @@ dependencies: # SEAMM - kim-query - - pymbar>=4.0 - seamm - seamm-ff-util - statsmodels @@ -30,6 +29,7 @@ dependencies: # Pip-only installs - pip: + - pymbar>=4.0 - seamm-exec # Documentation - sphinx-copybutton