diff --git a/docs/quickcli.md b/docs/quickcli.md index e9003715..b1f07baa 100644 --- a/docs/quickcli.md +++ b/docs/quickcli.md @@ -10,6 +10,17 @@ dpgen2 submit input.json where `input.json` is the input script. A guide of writing the script is found [here](inputscript). When a workflow is submitted, a ID (WFID) of the workflow will be printed for later reference. +### Run locally without Bohrium or Kubernetes + +dflow debug mode executes workflow steps on the local machine. Enable it before submitting: + +```bash +export DFLOW_MODE=debug +dpgen2 submit input.json +``` + +In this mode, omit `bohrium_config` or set it to `null`; no username, password, or project ID is required. `dflow_config` and `dflow_s3_config` may also be omitted unless a local customization needs them. Commands referenced by the step configurations must be installed and runnable on the local machine. The legacy `DFLOW_DEBUG=1` environment variable remains supported. + ## Check the convergence of a workflow The convergence of stages of the workflow can be checked by the `status` command. It prints the indexes of the finished stages, iterations, and the accurate, candidate and failed ratio of explored configurations of each iteration. ```bash diff --git a/dpgen2/entrypoint/args.py b/dpgen2/entrypoint/args.py index df11ff7f..9c7b916e 100644 --- a/dpgen2/entrypoint/args.py +++ b/dpgen2/entrypoint/args.py @@ -312,7 +312,7 @@ def caly_args(): dict, run_expl_caly_conf_args(), optional=True, - default=RunLmp.normalize_config({}), + default={}, doc=doc_config, ), Argument( @@ -808,7 +808,10 @@ def dpgen_step_config_args(default_config): def submit_args(default_step_config=normalize_step_dict({})): - doc_bohrium_config = "Configurations for the Bohrium platform." + doc_bohrium_config = ( + "Configurations for the Bohrium platform. Omit this section or set it " + "to null when running locally with dflow debug mode." + ) doc_step_configs = "Configurations for executing dflow steps" doc_upload_python_packages = "Upload python package, for debug purpose" doc_inputs = "The input parameter and artifacts for dpgen2" diff --git a/dpgen2/entrypoint/common.py b/dpgen2/entrypoint/common.py index 0d0af9e8..0bd8a698 100644 --- a/dpgen2/entrypoint/common.py +++ b/dpgen2/entrypoint/common.py @@ -29,7 +29,9 @@ def global_config_workflow( # dflow_config, dflow_s3_config workflow_config_from_dict(wf_config) - if os.getenv("DFLOW_DEBUG"): + # dflow documents DFLOW_MODE=debug, while older DPGEN2 examples use + # DFLOW_DEBUG. Accept both before touching any remote-platform credentials. + if os.getenv("DFLOW_DEBUG") or os.getenv("DFLOW_MODE", "").lower() == "debug": dflow.config["mode"] = "debug" return None diff --git a/tests/entrypoint/test_local_mode.py b/tests/entrypoint/test_local_mode.py new file mode 100644 index 00000000..83575544 --- /dev/null +++ b/tests/entrypoint/test_local_mode.py @@ -0,0 +1,46 @@ +import os +import unittest +from unittest import ( + mock, +) + +import dflow + +# isort: off +from .context import ( + dpgen2, +) +from dpgen2.entrypoint.common import ( + global_config_workflow, +) + +# isort: on + + +class TestLocalMode(unittest.TestCase): + @mock.patch("dpgen2.entrypoint.common.bohrium_config_from_dict") + @mock.patch.dict(os.environ, {"DFLOW_MODE": "debug"}, clear=True) + def test_dflow_mode_debug_skips_bohrium_configuration(self, mocked_bohrium): + previous_mode = dflow.config.get("mode") + self.addCleanup(dflow.config.__setitem__, "mode", previous_mode) + + global_config_workflow( + { + # Invalid on purpose: debug mode must return before reading it. + "bohrium_config": {"username": "not-used"}, + } + ) + + self.assertEqual(dflow.config["mode"], "debug") + mocked_bohrium.assert_not_called() + + @mock.patch("dpgen2.entrypoint.common.bohrium_config_from_dict") + @mock.patch.dict(os.environ, {"DFLOW_DEBUG": "1"}, clear=True) + def test_legacy_dflow_debug_skips_bohrium_configuration(self, mocked_bohrium): + previous_mode = dflow.config.get("mode") + self.addCleanup(dflow.config.__setitem__, "mode", previous_mode) + + global_config_workflow({"bohrium_config": {"username": "not-used"}}) + + self.assertEqual(dflow.config["mode"], "debug") + mocked_bohrium.assert_not_called() diff --git a/tests/entrypoint/test_submit_args.py b/tests/entrypoint/test_submit_args.py index ec7e4582..95ac41ec 100644 --- a/tests/entrypoint/test_submit_args.py +++ b/tests/entrypoint/test_submit_args.py @@ -11,12 +11,16 @@ import dpdata import numpy as np +from dargs import ( + Argument, +) # isort: off from .context import ( dpgen2, ) from dpgen2.entrypoint.args import ( + caly_args, normalize, ) from dpgen2.op import ( @@ -155,6 +159,16 @@ def test_bohrium(self): }, ) + def test_calypso_default_config_matches_schema(self): + """Keep an omitted CALYPSO config free of LAMMPS-only defaults.""" + config_arg = next(arg for arg in caly_args() if arg.name == "config") + schema = Argument("calypso", dict, [config_arg]) + + normalized = schema.normalize_value({}) + + schema.check_value(normalized, strict=True) + self.assertEqual(normalized["config"], {}) + old_str = textwrap.dedent( """