diff --git a/.github/workflows/pytests.yml b/.github/workflows/pytests.yml index cab03127..8b768da3 100644 --- a/.github/workflows/pytests.yml +++ b/.github/workflows/pytests.yml @@ -42,7 +42,7 @@ jobs: - name: Install latest ASE from gitlab run: | - python3 -m pip install git+https://gitlab.com/ase/ase.git + python3 -m pip install ase echo -n "ASE VERSION " python3 -c "import ase; print(ase.__file__, ase.__version__)" diff --git a/README.md b/README.md index d13d5ba0..43341f57 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ The main functions of Workflow is to efficiently parallelise operations over a s For examples and more information see [documentation](https://libatoms.github.io/workflow/) -NOTE: because of the very large time intervals between official ASE releases, `wfl` is typically -set up for (and tested against) the latest ASE gitlab repo `master` branch. Recent changes -that require this support include variable cell minimization using `FrechetCellFilter` and -`Espresso` calculator configuration. See documentation link above for installation instructions. +`wfl` and its dependendensies may be installed via `pip install wfl`. +NOTE: (as of 14 June 2024) `wfl` is only tested against the latest ASE pip release, currently v3.23.0. +For the time being, v3.22.1 is the mininum version listed as a (pip) prerequisite of `wfl`, because +it at least mostly works, but it may not be fully compatible and is not actively tested. # Recent changes diff --git a/docs/source/examples.fhiaims_calculator.ipynb b/docs/source/examples.fhiaims_calculator.ipynb index 2a0f0071..653d02d3 100644 --- a/docs/source/examples.fhiaims_calculator.ipynb +++ b/docs/source/examples.fhiaims_calculator.ipynb @@ -10,12 +10,13 @@ "This example illustrates the usage of Workflow for efficiently performing density-functioncal theory (DFT) calculations with the all-electron electronic structure code [FHI-Aims](https://fhi-aims.org/).\n", "\n", "At first, we initialize a `ConfigSet` with a couple of (periodic and non-periodic) systems stored in `Input_Structures.xyz`.\n", - "We also define an `OutputSpec` to handle the output that will additionally comprise values calculated by FHI-Aims.\\\n", + "We also define an `OutputSpec` to handle the output that will additionally comprise values calculated by FHI-Aims.\n", + "\n", "Next, we define various parameter that will be used during the calculations.\n", "Most of them define parameter settings for the DFT calculation (that will be written in the `control.in` input file of FHI-Aims).\n", "Note that for non-periodic systems the calculations will be performed with the exact same settings, but without parameter specific to periodic systems (e.g. `k_grid`, `compute_analytical_stress`, etc.).\n", - "The last parameter `calculator_exec` defines the command used to call FHI-Aims and start an individual calculation, including specifications for the parallelization of that specific calcuation and excluding any redirection of the output (i.e. without e.g. `>> aims.out`).\\\n", - "Now we define a `calculator` of the form ` (calc_constructor, args, kwargs)` using the `Aims` calculator of Workflow and the parameter dictionary we have just defined.\n", + "\n", + "With these parameters and Workflow's `Aims` Calculator object, we define a `calculator` tuple of the form ` (calc_constructor, args, kwargs)`.\n", "Finally, we apply the `generic.calculate()` function to process all the input structures in parallel (defined via the `autopara_info` settings). The values of the calculated `properties` will be written into `Output_Structures.xyz` with keys that have the specified `output_prefix`. Note that for non-periodic systems the property \"stress\" will be ignored." ] }, @@ -58,7 +59,6 @@ " 'compute_analytical_stress': True,\n", " 'KS_method': 'parallel',\n", " 'k_grid_density': 1e-1,\n", - " 'calculator_exec': 'srun -n2 --exclusive --mem=2GB aims.210313.scalapack.mpi.x',\n", " }\n", "\n", "\n", @@ -78,6 +78,35 @@ ")" ] }, + { + "cell_type": "markdown", + "id": "8ec7a593-9b4f-41cc-a5f9-208440978742", + "metadata": {}, + "source": [ + "As of ASE version 3.23, the file-based calculators rely on a configuration file that specifies the information about the particular installation of the electronic structure code ([see ASE's docummentation](https://wiki.fysik.dtu.dk/ase/ase/calculators/calculators.html#calculator-configuration)). \n", + "\n", + "An example `~/.config/ase/config.ini`:\n", + "\n", + "```\n", + "[aims]\n", + "command=mpirun -n 32 path/to/aims/aims.231208.scalapack.mpi.x\n", + "default_species_directory=path/to/aims/species_defaults/defaults_2020/light/\n", + "```\n", + "\n", + "Alternatively, the species directory maybe included among the keyword arguments:\n", + "\n", + "```\n", + "aims_kwargs[\"species_dir\"] = \"path/to/aims/species_defaults/defaults_2020/light/\"\n", + "```\n", + "\n", + "Instead of the command in ase's config file, a `profile` may be provided among the keyword arguments:\n", + "\n", + "```\n", + "from ase.calculators.aims import AimsProfile\n", + "aims_kwargs[\"profile\"] = AimsProfile(\"mpirun -n 32 path/to/aims/aims.231208.scalapack.mpi.x\")\n", + "```" + ] + }, { "cell_type": "code", "execution_count": null, @@ -101,7 +130,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -115,7 +144,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.8.19" } }, "nbformat": 4, diff --git a/docs/source/examples.index.md b/docs/source/examples.index.md index 4b6568de..ffd6633b 100644 --- a/docs/source/examples.index.md +++ b/docs/source/examples.index.md @@ -7,7 +7,6 @@ Examples often show examples of more than one thing. Below is a list of common o ## Evaluate structures with a calculator - ORCA: [ORCA via python script](examples.orca_python.md) -- MACE: [Parallelize MACE calculator](examples.mace.md) - EMT: - [First example](first_example.md) - [Iterative GAP fitting ](examples.mlip_fitting.md) diff --git a/docs/source/examples.mace.md b/docs/source/examples.mace.md deleted file mode 100644 index a7540f81..00000000 --- a/docs/source/examples.mace.md +++ /dev/null @@ -1,86 +0,0 @@ -# Parallelize MACE calculator - -MACE calculator can be parallelized with the `wfl.calculators.generic.calculate` routine. - -First we define a `ConfigSet` for the inputs and `OutputSpec` to specify how the outputs are handled: - -``` -from wfl.configset import ConfigSet, OutputSpec -inputs = ConfigSet("configs.xyz") -outputs = OutputSpec("configs.mace.xyz") -``` - -Normally, a MACE calculator would be setup like this: - -``` -from mace.calculators.mace import MACECalculator - -# change the following as appropriate -mace_model_fname = "my_mace.model" -dtype="float64" - -#initialise the calculator -my_mace_calc = MACECalculator(model_path=mace_model_fname, dtype=dtype, device="cpu") -``` - -But in Workflow, for `generic.calculate` to parallelize this calculator it needs to be defined as a tuple of `(calc_function, [args], **kwargs)`. In our example, the above code snippet corresponds to - -``` -from wfl.calculators import generic -from mace.calculators.mace import MACECalculator - -# change the following as appropriate -mace_model_fname = "my_mace.model" -dtype="float64" - -my_mace_calc = (MACECalculator, [], {"model_path":mace_model_fname, "default_dtype":"float64", "device":"cpu"}) -``` - -Now we can evaluate multiple structures in parallel over 8 cores (for example) by exporting (before running the Python script) - -``` -export WFL_NUM_PYTHON_SUBPROCESSES=8 -``` - -and calling the `generic.calculate`: - -``` -generic.calculate( - inputs=inputs, - outputs=outputs, - calculator=my_mace_calc, - properties = ["energy", "forces"], - output_prefix="mace_") -``` - -Since `output_prefix` is set to "mace_" and properties are set to "energy" and "forces", the "structures.mace.xyz" file will have `"mace_energy"` entires in `atoms.info` and `"mace_forces"` entries in `atoms.arrays`. - - -## Complete example - -1. `export WFL_NUM_PYTHON_SUBPROCESSES=8` - -2. run the following script: - -``` -from wfl.configset import ConfigSet, OutputSpec -from wfl.calculators import generic -from mace.calculators.mace import MACECalculator - -inputs = ConfigSet("configs.xyz") -outputs = OutputSpec("configs.mace.xyz") - - -# change the following as appropriate -mace_model_fname = "mace_run-123.model.cpu" - -my_mace_calc = (MACECalculator, [], {"model_path":mace_model_fname, "default_dtype":"float64", "device":"cpu"}) - -generic.calculate( - inputs=inputs, - outputs=outputs, - calculator=my_mace_calc, - properties = ["energy", "forces"], - output_prefix="mace_") - -``` diff --git a/docs/source/first_example.md b/docs/source/first_example.md index 26f83e89..665df6c4 100644 --- a/docs/source/first_example.md +++ b/docs/source/first_example.md @@ -82,7 +82,7 @@ from ase.calculators.emt import EMT from wfl.calculators import generic from wfl.autoparallelize import AutoparaInfo from wfl.configset import ConfigSet, OutputSpec -from wfl.autoparallelize.remote info import RemoteInfo() +from wfl.autoparallelize import RemoteInfo from expyre.resources import Resources atoms = [] @@ -124,7 +124,7 @@ python evaluate_emt.py Workflow also allows to submit (remotely) queued jobs automatically, by interfacing with ExPyRe ([docummentation](https://libatoms.github.io/ExPyRe/), [repository](https://github.com/libAtoms/ExPyRe/tree/main/expyre)). In this example, instead of calling the above python script in a queue submission script, the modified python script is called from the head node and the parallelisation mechanism behind `generic.calculate()` sets up and submits the job and returns the results like the script normally would. To enable remote submission, `RemoteInfo` must be added to `AutoparaInfo`. ``` -from wfl.autoparallelize import RemoteInfo() +from wfl.autoparallelize import RemoteInfo from expyre.resources import Resources remote_info = RemoteInfo( @@ -145,12 +145,13 @@ remote_info = RemoteInfo( The available clusters are listed in `config.json` file, by default at `~/.expyre/config.json`: ``` -"local": { "host": null, - "scheduler": "sge", - "commands": ["conda activate myenv"], - "header": ["#$ -pe smp {num_cores}"], - "partitions": {"standard" : {"num_cores": 16, "max_time": "168h", "max_mem": "200GB"}} - } +{"systems": + {"local": { "host": null, + "scheduler": "sge", + "commands": ["conda activate myenv"], + "header": ["#$ -pe smp {num_cores}"], + "partitions": {"standard" : {"num_cores": 16, "max_time": "168h", "max_mem": "200GB"}} + } }} ``` diff --git a/docs/source/index.rst b/docs/source/index.rst index fad2e397..7839db96 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -21,21 +21,16 @@ Quick start that installs all of the mandatory dependencies: .. code-block:: sh - python3 -m pip install git+https://github.com/libAtoms/workflow + python3 -m pip install wfl -.. warning:: +.. note:: - `wfl` requires ASE, so `ase` is listed as a `pip` dependency, - and if not already installed, `pip install` will install the latest - `pypi` release. However, because of the large delay in producing new - releases, the latest `pypi` version is often quite old, and `wfl` - has some functionality that requires a newer version. To ensure - a sufficiently up-to-date version is available, before installing - `wfl` install the latest `ase` from gitlab, with a command such as + (as of 14 June 2024) `wfl` is only tested against the latest + ASE pip release, currently v3.23.0. For the time being, v3.22.1 is + the mininum version listed as a (pip) prerequisite of `wfl`, because + it at least mostly works, but it may not be fully compatible and is + not actively tested. - .. code-block:: sh - - python3 -m pip install git+https://gitlab.com/ase/ase *************************************** Repository diff --git a/docs/source/operations.calculators.md b/docs/source/operations.calculators.md index 356cc36f..cb973601 100644 --- a/docs/source/operations.calculators.md +++ b/docs/source/operations.calculators.md @@ -25,10 +25,5 @@ Further see [autoparallelization page](overview.parallelisation.rst) and [exampl ASE's calculators that write & read files to & from disk must to be modified if they were to be parallelized via Workflow's `generic` calculator. Specifically, each instance of calculator must execute the calculation in a separate folder so processes running in parallel don't attempt to read and write to the same files. Workflow handles the files, as well as creation and clean-up of temporary directories. -Currently, [ORCA], VASP, QuantumEspresso and FHI-Aims are compatible with the `generic` calculator; CASTEP parallelization is accessed via `calculators.dft.evaluate_dft()`. - -## Special calculators - -Finally, there is a (currently broken) non-conventional "Basin Hopping" calculator implementation for ORCA. -`BasinHoppingORCA()` runs multiple single point evaluations perturbing the initial guess of the wavefunction each time. It returns the results corresponding to the global minimum and lowest-energy solution. +Currently, ORCA, VASP, QuantumEspresso, CASTEP and FHI-Aims are compatible with the `generic` calculator. diff --git a/tests/calculators/test_aims.py b/tests/calculators/test_aims.py index eb412e4d..af3fb4e7 100644 --- a/tests/calculators/test_aims.py +++ b/tests/calculators/test_aims.py @@ -1,7 +1,11 @@ import os import pytest +from pathlib import Path + +from packaging.version import Version import numpy as np +import ase from ase import Atoms from ase.build import bulk @@ -9,15 +13,29 @@ from wfl.calculators import generic from wfl.configset import OutputSpec +if Version(ase.__version__) < Version("3.23"): + aims_prerequisites = pytest.mark.skip(reason="Aims tests are only supported for ASE v3.23, please update.") + +else: + + from ase.config import cfg as ase_cfg + from ase.calculators.aims import AimsProfile -aims_prerequisites = pytest.mark.skipif( - condition='ASE_AIMS_COMMAND' not in os.environ or 'AIMS_SPECIES_DIR' not in os.environ - or not os.environ['AIMS_SPECIES_DIR'].endswith('light') - or 'OMP_NUM_THREADS' not in os.environ or os.environ['OMP_NUM_THREADS'] != "1", - reason='Missing env var ASE_AIMS_COMMAND or ASE_SPECIES_DIR or ' + - 'ASE_SPECIES_DIR does not refer to light-settings or ' + - 'OMP_NUM_THREADS or OMP_NUM_THREADS has not correctly been set to 1' - ) + if "aims" in ase_cfg.parser: + profile = AimsProfile.from_config(ase_cfg, "aims") + species_dir = vars(profile).get("default_species_directory", None) + else: + species_dir = None + + aims_prerequisites = pytest.mark.skipif( + condition = 'aims' not in ase_cfg.parser or species_dir is None + or Path(species_dir).name != "light" + or 'OMP_NUM_THREADS' not in os.environ or os.environ['OMP_NUM_THREADS'] != "1", + reason='Missing "aims" in ase\'s configuration file or "default_species_directory" ' + + 'in "aims" configuration or "default_species_directory"" does not refer' + + 'to "light" settings or missing "OMP_NUM_THREADS" or "OMP_NUM_THREADS" ' + + 'is not set to 1.' + ) @pytest.fixture @@ -41,7 +59,7 @@ def parameters_nonperiodic(): } return parameters - +@aims_prerequisites def test_setup_calc_params(parameters_nonperiodic): parameters = parameters_nonperiodic @@ -57,7 +75,7 @@ def test_setup_calc_params(parameters_nonperiodic): parameters.update(parameters_periodic) # needed so new ASE versions don't complain about a lack of configuration - parameters["calculator_exec"] = "_DUMMY_" + parameters["profile"] = AimsProfile("_DUMMY_") # PBC is FFF atoms = Atoms("H") @@ -150,3 +168,6 @@ def test_generic_aims_calculation(tmp_path, parameters_nonperiodic): assert si2.arrays["Aims_forces"][0, 0] == pytest.approx(expected=-0.29253217, abs=1e-3) assert si2.arrays["Aims_forces"][:, 1:] == pytest.approx(0.0) assert si2.arrays["Aims_forces"][0] == pytest.approx(-1 * si2.arrays["Aims_forces"][1]) + + + diff --git a/tests/calculators/test_calc_generic.py b/tests/calculators/test_calc_generic.py index 46cb4151..5e828a9a 100644 --- a/tests/calculators/test_calc_generic.py +++ b/tests/calculators/test_calc_generic.py @@ -1,6 +1,7 @@ import sys from os.path import join from io import StringIO +import pytest import numpy as np from ase import Atoms @@ -138,7 +139,7 @@ def test_generic_autopara_defaults(): sys.stderr = sys.__stderr__ assert "num_inputs_per_python_subprocess=3" in l_stderr.getvalue() - +@pytest.mark.xfail(reason="Waiting for update to work with ASE3.23") def test_generic_DFT_autopara_defaults(tmp_path, monkeypatch): ats = [Atoms('Al2', positions=[[0,0,0], [1,1,1]], cell=[10]*3, pbc=[True]*3) for _ in range(50)] diff --git a/tests/calculators/test_qe.py b/tests/calculators/test_qe.py index 268946b0..22dd9492 100644 --- a/tests/calculators/test_qe.py +++ b/tests/calculators/test_qe.py @@ -57,7 +57,6 @@ def qe_cmd_and_pseudo(tmp_path_factory): return cmd, pspot_file - def test_qe_kpoints(tmp_path, qe_cmd_and_pseudo): qe_cmd, pspot = qe_cmd_and_pseudo diff --git a/complete_pytest.tin b/tests/local_scripts/complete_pytest.tin similarity index 100% rename from complete_pytest.tin rename to tests/local_scripts/complete_pytest.tin diff --git a/tests/local_scripts/gelzinyte.workstation.sh b/tests/local_scripts/gelzinyte.workstation.sh new file mode 100755 index 00000000..44ece984 --- /dev/null +++ b/tests/local_scripts/gelzinyte.workstation.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +export ASE_CONFIG_PATH=${HOME}/.config/ase/pytest.config.ini + +# Aims +pytest -v -s -rxXs ../calculators/test_aims.py + diff --git a/wfl/calculators/aims.py b/wfl/calculators/aims.py index f281ecce..2088fb96 100644 --- a/wfl/calculators/aims.py +++ b/wfl/calculators/aims.py @@ -15,7 +15,6 @@ AimsProfile = None from .wfl_fileio_calculator import WFLFileIOCalculator -from .utils import parse_genericfileio_profile_argv # NOMAD compatible, see https://nomad-lab.eu/prod/rae/gui/uploads _default_keep_files = ["control.in", "geometry.in", "aims.out"] @@ -47,10 +46,6 @@ class Aims(WFLFileIOCalculator, ASE_Aims): scratchdir: str / Path, default None Temporary directory to execute calculations in and delete or copy back results (set by ```keep_files```) if needed. For example, directory on a local disk with fast file I/O. - calculator_exec: str - command for Aims, without any prefix or redirection set. - For example: "srun -n 4 /path/to/aims.*.scalapack.mpi.x". - Mutually exclusive with ```command```. **kwargs: arguments for ase.calculators.aims.Aims See https://wiki.fysik.dtu.dk/ase/_modules/ase/calculators/aims.html. @@ -62,22 +57,9 @@ class Aims(WFLFileIOCalculator, ASE_Aims): wfl_generic_default_autopara_info = {"num_inputs_per_python_subprocess": 1} def __init__(self, keep_files="default", rundir_prefix="run_Aims_", workdir=None, - scratchdir=None, calculator_exec=None, **kwargs): + scratchdir=None, **kwargs): kwargs_command = deepcopy(kwargs) - if calculator_exec is not None: - if "command" in kwargs: - raise ValueError("Cannot specify both calculator_exec and command") - if AimsProfile is None: - # older syntax - kwargs_command["command"] = f"{calculator_exec} > aims.out" - else: - argv = shlex.split(calculator_exec) - try: - kwargs_command["profile"] = AimsProfile(argv=argv) - except TypeError: - binary, parallel_info = parse_genericfileio_profile_argv(argv) - kwargs_command["profile"] = AimsProfile(binary=binary, parallel_info=parallel_info) # WFLFileIOCalculator is a mixin, will call remaining superclass constructors for us super().__init__(keep_files=keep_files, rundir_prefix=rundir_prefix, @@ -143,3 +125,6 @@ def _setup_calc_params(self): or param_i in ['relax_unit_cell', 'external_pressure']] for param_i in rm_parameters: self.parameters.pop(param_i) + + +