diff --git a/docs/source/examples.cli.ipynb b/docs/source/examples.cli.ipynb new file mode 100644 index 00000000..ed8c8212 --- /dev/null +++ b/docs/source/examples.cli.ipynb @@ -0,0 +1,123 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "72125898", + "metadata": {}, + "source": [ + "# command line examples" + ] + }, + { + "cell_type": "markdown", + "id": "ad34d6f2", + "metadata": {}, + "source": [ + "## evaluating configurations" + ] + }, + { + "cell_type": "markdown", + "id": "699abc83", + "metadata": {}, + "source": [ + "A very simple example of evaluating energies/forces of a configuration with a machine learning potential.\n", + "\n", + "Potential and input files are stored in `docs/example_files/cli`, and example is written assuming that jupyter server has the current working directory set to the directory containing this example notebook." + ] + }, + { + "cell_type": "markdown", + "id": "54564961", + "metadata": {}, + "source": [ + "### clean up files from previous runs" + ] + }, + { + "cell_type": "markdown", + "id": "fa31f4c8", + "metadata": {}, + "source": [ + "Make sure that old output files are not there, otherwise autoparallelization of workflow will skip the evaluation." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "197b7226", + "metadata": {}, + "outputs": [], + "source": [ + "from pathlib import Path\n", + "Path(\"GAP_evaluated_wfl_eval_configs.xyz\").unlink(missing_ok=True)" + ] + }, + { + "cell_type": "markdown", + "id": "39ed76f2", + "metadata": {}, + "source": [ + "### evaluate using the command line" + ] + }, + { + "cell_type": "markdown", + "id": "b7147883", + "metadata": {}, + "source": [ + "Run a `wfl` command line one liner with standardized arguments for input and output files (all commands), and potential filename (standardized for all machine learning potentials subcommands: `ace`, `gap`, and `mace`)." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6dec15c0", + "metadata": {}, + "outputs": [], + "source": [ + "!wfl eval gap -pf ../example_files/cli/GAP.xml \\\n", + " -i ../example_files/cli/wfl_eval_configs.xyz \\\n", + " -o GAP_evaluated_wfl_eval_configs.xyz" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "f91acc7b", + "metadata": { + "nbsphinx": "hidden" + }, + "outputs": [], + "source": [ + "# test that output is correct (hidden from nbsphinx autogenerated documentation)\n", + "import numpy as np\n", + "import ase.io\n", + "\n", + "for atoms in ase.io.read(\"GAP_evaluated_wfl_eval_configs.xyz\", \":\"):\n", + " assert np.allclose(atoms.arrays[\"ref_error_calc_forces\"], atoms.arrays[\"gap_forces\"], atol=1.0e-6)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tests/test_doc_examples.py b/tests/test_doc_examples.py index d25ab1db..3599cda6 100644 --- a/tests/test_doc_examples.py +++ b/tests/test_doc_examples.py @@ -1,5 +1,7 @@ import os import shutil +import re +from pathlib import Path import json import pytest @@ -10,21 +12,52 @@ def _get_coding_blocks(nb_file): nb = json.load(fo) return [''.join(cell['source']) for cell in nb['cells'] if cell['cell_type'] == 'code'] +def _fix_wfl(code_block): + if re.match(r"!\s*wfl\s+", code_block): + # join lines + code_block = re.sub(r"\\\n", " ", code_block) + # find args for cli runner + args_str = re.sub(r"^!\s*wfl\s+", "", code_block) + # run cli comand + code_block = ("from click.testing import CliRunner\n" + + "from wfl.cli.cli import cli\n" + + "import shlex\n" + + "runner = CliRunner()\n" + + f"runner.invoke(cli, shlex.split(\"\"\"{args_str}\"\"\"))") + + return code_block + @pytest.mark.parametrize( - ('nb_file', 'idx_execute'), + ('nb_file', 'idx_execute', 'wfl_cli'), ( - pytest.param('examples.buildcell.ipynb', 'all', id='buildcell', + pytest.param('examples.buildcell.ipynb', 'all', False, id='buildcell', marks=pytest.mark.skipif(not shutil.which("buildcell"), reason="buildcell not in PATH")), - pytest.param('examples.dimers.ipynb', 'all', id='dimer structures'), - pytest.param('examples.select_fps.ipynb', 'all', id='select fps') + pytest.param('examples.dimers.ipynb', 'all', False, id='dimer structures'), + pytest.param('examples.cli.ipynb', 'all', True, id='command line interface') ) ) -def test_example(tmp_path, nb_file, idx_execute): + +def test_example(tmp_path, monkeypatch, nb_file, idx_execute, wfl_cli): basepath = os.path.join(f'{os.path.dirname(__file__)}/../docs/source') coding_blocks = _get_coding_blocks(f'{basepath}/{nb_file}') - code = '\n'.join([cb_i for idx_i, cb_i in enumerate(coding_blocks) if idx_execute == 'all' or idx_i in idx_execute]) + if wfl_cli: + coding_blocks_exec = [_fix_wfl(cb_i) for idx_i, cb_i in enumerate(coding_blocks) if idx_execute == 'all' or idx_i in idx_execute] + + example_name = nb_file.replace("examples.", "").replace(".ipynb", "") + + source_example_files_dir = (Path(__file__).parent.parent / "docs" / "example_files" / example_name) + if source_example_files_dir.is_dir(): + pytest_example_files_dir = tmp_path / "example_files" / example_name + shutil.copytree(source_example_files_dir, pytest_example_files_dir) + + # examples look for their files under "../example_files//", so move + # down one to allow the ".." to work + (tmp_path / "run").mkdir() + monkeypatch.chdir(tmp_path / "run") + else: + coding_blocks_exec = [cb_i for idx_i, cb_i in enumerate(coding_blocks) if idx_execute == 'all' or idx_i in idx_execute] + + code = '\n'.join(coding_blocks_exec) assert code is not '' - os.chdir(tmp_path) exec(code) - os.chdir('..')