From 9c8fe94aa7d434e5c7f14a9e188127c6c3cde6b0 Mon Sep 17 00:00:00 2001 From: "njzjz-bot (driven by OpenClaw (model: custom-chat-jinzhezeng-group/gpt-5.5))[bot]" <48687836+njzjz-bot@users.noreply.github.com> Date: Wed, 17 Jun 2026 18:45:24 +0000 Subject: [PATCH 1/3] fix(generator): document CALYPSO model deviation args Add CALYPSO-specific arginfo without accepting list-valued settings that the generator currently cannot consume, and keep the common model-deviation selection keys valid for CALYPSO runs. Authored by OpenClaw (model: custom-chat-jinzhezeng-group/gpt-5.5) --- dpgen/generator/arginfo.py | 225 ++++++++++++++++++++++++++++++++++++- 1 file changed, 224 insertions(+), 1 deletion(-) diff --git a/dpgen/generator/arginfo.py b/dpgen/generator/arginfo.py index 0f8abb865..2cb268878 100644 --- a/dpgen/generator/arginfo.py +++ b/dpgen/generator/arginfo.py @@ -621,16 +621,239 @@ def model_devi_amber_args() -> list[Argument]: ] +def model_devi_calypso_args() -> list[Argument]: + """CALYPSO engine arguments.""" + doc_model_devi_jobs = ( + "Settings for CALYPSO structure generation and model deviation. " + "Each dict in the list describes the CALYPSO input used for one or more " + "iterations selected by `times`." + ) + doc_times = "List of iteration indices when this CALYPSO job should be executed." + doc_nameofatoms = "Element symbols of the chemical species." + doc_numberofatoms = "Number of atoms for each chemical species in one formula unit." + doc_numberofformula = "Range of formula units per cell as [min, max]." + doc_volume = "Volume per formula unit in angstrom^3. If not provided, CALYPSO determines it automatically." + doc_distanceofion = "Minimal distances between atom types in angstrom. Shape should match the number of species." + doc_psoratio = ( + "Proportion of structures generated by the PSO algorithm, between 0.0 and 1.0." + ) + doc_popsize = "Population size for structure generation." + doc_maxstep = "Maximum number of CALYPSO optimization steps." + doc_icode = "CALYPSO interface code for local optimization, such as 1 for VASP." + doc_split = "Whether to split calculations. Use 'T' or 'F'." + doc_vsc = "Variable stoichiometry control. Use 'T' to enable or 'F' to disable." + doc_maxnumatom = ( + "Maximum number of atoms in the unit cell. Required when VSC is 'T'." + ) + doc_ctrlrange = "Variation range for each atom type. Required when VSC is 'T'." + doc_pstress = "Target pressure list in GPa. One CALYPSO input directory is created for each pressure." + doc_fmax = "Force convergence criterion for local optimization, in eV/angstrom." + doc_calypso_input_path = ( + "Path to a directory containing pre-existing CALYPSO input.dat files. " + "When set, DP-GEN copies those files instead of generating input.dat from " + "the CALYPSO fields in model_devi_jobs." + ) + doc_model_devi_max_iter = "Maximum iteration index when using calypso_input_path." + doc_vsc_mode = ( + "Enable variable stoichiometry mode when using external CALYPSO input files." + ) + doc_model_devi_dt = ( + "Timestep retained for compatibility with existing CALYPSO parameter files." + ) + doc_model_devi_skip = ( + "Number of structures skipped during model deviation selection." + ) + doc_model_devi_f_trust_lo = "Lower bound of force model deviation for selection." + doc_model_devi_f_trust_hi = "Upper bound of force model deviation for selection." + doc_model_devi_v_trust_lo = "Lower bound of virial model deviation for selection." + doc_model_devi_v_trust_hi = "Upper bound of virial model deviation for selection." + doc_model_devi_e_trust_lo = "Lower bound of energy model deviation for selection." + doc_model_devi_e_trust_hi = "Upper bound of energy model deviation for selection." + doc_model_devi_clean_traj = ( + "Whether to clean large trajectory folders after model deviation." + ) + doc_model_devi_adapt_trust_lo = ( + "Adaptively determine the lower force and virial trust levels." + ) + doc_model_devi_numb_candi_f = "See model_devi_adapt_trust_lo." + doc_model_devi_numb_candi_v = "See model_devi_adapt_trust_lo." + doc_model_devi_perc_candi_f = "See model_devi_adapt_trust_lo." + doc_model_devi_perc_candi_v = "See model_devi_adapt_trust_lo." + doc_model_devi_f_avg_relative = ( + "Normalize force model deviations by the RMS force magnitude." + ) + doc_use_relative = "Calculate relative force model deviation." + doc_epsilon = "Level parameter for computing the relative force model deviation." + doc_use_relative_v = "Calculate relative virial model deviation." + doc_epsilon_v = "Level parameter for computing the relative virial model deviation." + + return [ + Argument( + "model_devi_jobs", + list, + optional=False, + repeat=True, + doc=doc_model_devi_jobs, + sub_fields=[ + Argument("times", list[int], optional=False, doc=doc_times), + Argument("NameOfAtoms", list[str], optional=False, doc=doc_nameofatoms), + Argument( + "NumberOfAtoms", list[int], optional=False, doc=doc_numberofatoms + ), + Argument( + "NumberOfFormula", + list[int], + optional=True, + default=[1, 1], + doc=doc_numberofformula, + ), + Argument("Volume", float, optional=True, doc=doc_volume), + Argument( + "DistanceOfIon", + list[list[float]], + optional=False, + doc=doc_distanceofion, + ), + Argument( + "PsoRatio", + float, + optional=True, + default=0.6, + doc=doc_psoratio, + ), + Argument("PopSize", int, optional=True, default=30, doc=doc_popsize), + Argument("MaxStep", int, optional=True, default=5, doc=doc_maxstep), + Argument("ICode", int, optional=True, default=1, doc=doc_icode), + Argument("Split", str, optional=True, default="T", doc=doc_split), + Argument("VSC", str, optional=True, default="F", doc=doc_vsc), + Argument("MaxNumAtom", int, optional=True, doc=doc_maxnumatom), + Argument( + "CtrlRange", list[list[int]], optional=True, doc=doc_ctrlrange + ), + Argument( + "PSTRESS", + list[float], + optional=True, + default=[0.001], + doc=doc_pstress, + ), + Argument("fmax", float, optional=True, default=0.01, doc=doc_fmax), + ], + ), + Argument("calypso_input_path", str, optional=True, doc=doc_calypso_input_path), + Argument( + "model_devi_max_iter", int, optional=True, doc=doc_model_devi_max_iter + ), + Argument("vsc", bool, optional=True, default=False, doc=doc_vsc_mode), + Argument("model_devi_dt", float, optional=True, doc=doc_model_devi_dt), + Argument("model_devi_skip", int, optional=False, doc=doc_model_devi_skip), + Argument( + "model_devi_f_trust_lo", + [float, list[float], dict], + optional=False, + doc=doc_model_devi_f_trust_lo, + ), + Argument( + "model_devi_f_trust_hi", + [float, list[float], dict], + optional=False, + doc=doc_model_devi_f_trust_hi, + ), + Argument( + "model_devi_v_trust_lo", + [float, list[float], dict], + optional=True, + default=1e10, + doc=doc_model_devi_v_trust_lo, + ), + Argument( + "model_devi_v_trust_hi", + [float, list[float], dict], + optional=True, + default=1e10, + doc=doc_model_devi_v_trust_hi, + ), + Argument( + "model_devi_e_trust_lo", + [float, list[float], dict], + optional=True, + default=1e10, + doc=doc_model_devi_e_trust_lo, + ), + Argument( + "model_devi_e_trust_hi", + [float, list[float], dict], + optional=True, + default=1e10, + doc=doc_model_devi_e_trust_hi, + ), + Argument( + "model_devi_adapt_trust_lo", + bool, + optional=True, + doc=doc_model_devi_adapt_trust_lo, + ), + Argument( + "model_devi_numb_candi_f", + int, + optional=True, + doc=doc_model_devi_numb_candi_f, + ), + Argument( + "model_devi_numb_candi_v", + int, + optional=True, + doc=doc_model_devi_numb_candi_v, + ), + Argument( + "model_devi_perc_candi_f", + float, + optional=True, + doc=doc_model_devi_perc_candi_f, + ), + Argument( + "model_devi_perc_candi_v", + float, + optional=True, + doc=doc_model_devi_perc_candi_v, + ), + Argument( + "model_devi_f_avg_relative", + bool, + optional=True, + doc=doc_model_devi_f_avg_relative, + ), + Argument( + "model_devi_clean_traj", + [bool, int], + optional=True, + default=True, + doc=doc_model_devi_clean_traj, + ), + Argument( + "use_relative", bool, optional=True, default=False, doc=doc_use_relative + ), + Argument("epsilon", float, optional=True, doc=doc_epsilon), + Argument( + "use_relative_v", bool, optional=True, default=False, doc=doc_use_relative_v + ), + Argument("epsilon_v", float, optional=True, doc=doc_epsilon_v), + ] + + def model_devi_args() -> list[Variant]: doc_model_devi_engine = "Engine for the model deviation task." doc_amber = "Amber DPRc engine. The command argument in the machine file should be path to sander." + doc_calypso = ( + "CALYPSO structure generation engine for crystal structure prediction." + ) return [ Variant( "model_devi_engine", [ Argument("lammps", dict, model_devi_lmp_args(), doc="LAMMPS"), Argument("amber", dict, model_devi_amber_args(), doc=doc_amber), - Argument("calypso", dict, [], doc="TODO: add doc"), + Argument("calypso", dict, model_devi_calypso_args(), doc=doc_calypso), Argument("gromacs", dict, [], doc="TODO: add doc"), ], default_tag="lammps", From 900199602b0bfcbfcb6173a910122deb9044a236 Mon Sep 17 00:00:00 2001 From: njzjz-bot Date: Sun, 30 Aug 2026 00:21:51 +0800 Subject: [PATCH 2/3] fix: normalize legacy CALYPSO scalars Coding-Agent: Codex Codex-Version: codex-cli 0.151.0 Model: gpt-5.6-sol Reasoning-Effort: xhigh --- dpgen/generator/arginfo.py | 67 ++++++++++++++++++++++++++--- dpgen/generator/lib/make_calypso.py | 23 +++++++--- tests/test_calypso_arginfo.py | 48 +++++++++++++++++++++ 3 files changed, 124 insertions(+), 14 deletions(-) create mode 100644 tests/test_calypso_arginfo.py diff --git a/dpgen/generator/arginfo.py b/dpgen/generator/arginfo.py index 2cb268878..29d7ef80b 100644 --- a/dpgen/generator/arginfo.py +++ b/dpgen/generator/arginfo.py @@ -621,6 +621,11 @@ def model_devi_amber_args() -> list[Argument]: ] +def _is_scalar_or_singleton(value) -> bool: + """Accept legacy one-item lists while rejecting ambiguous CALYPSO values.""" + return not isinstance(value, list) or len(value) == 1 + + def model_devi_calypso_args() -> list[Argument]: """CALYPSO engine arguments.""" doc_model_devi_jobs = ( @@ -707,7 +712,14 @@ def model_devi_calypso_args() -> list[Argument]: default=[1, 1], doc=doc_numberofformula, ), - Argument("Volume", float, optional=True, doc=doc_volume), + Argument( + "Volume", + [float, int, list[float], list[int]], + optional=True, + extra_check=_is_scalar_or_singleton, + extra_check_errmsg="Volume must be a scalar or a one-item list.", + doc=doc_volume, + ), Argument( "DistanceOfIon", list[list[float]], @@ -716,17 +728,50 @@ def model_devi_calypso_args() -> list[Argument]: ), Argument( "PsoRatio", - float, + [float, int, list[float], list[int]], optional=True, default=0.6, + extra_check=_is_scalar_or_singleton, + extra_check_errmsg="PsoRatio must be a scalar or a one-item list.", doc=doc_psoratio, ), - Argument("PopSize", int, optional=True, default=30, doc=doc_popsize), - Argument("MaxStep", int, optional=True, default=5, doc=doc_maxstep), - Argument("ICode", int, optional=True, default=1, doc=doc_icode), + Argument( + "PopSize", + [int, list[int]], + optional=True, + default=30, + extra_check=_is_scalar_or_singleton, + extra_check_errmsg="PopSize must be an integer or a one-item list.", + doc=doc_popsize, + ), + Argument( + "MaxStep", + [int, list[int]], + optional=True, + default=5, + extra_check=_is_scalar_or_singleton, + extra_check_errmsg="MaxStep must be an integer or a one-item list.", + doc=doc_maxstep, + ), + Argument( + "ICode", + [int, list[int]], + optional=True, + default=1, + extra_check=_is_scalar_or_singleton, + extra_check_errmsg="ICode must be an integer or a one-item list.", + doc=doc_icode, + ), Argument("Split", str, optional=True, default="T", doc=doc_split), Argument("VSC", str, optional=True, default="F", doc=doc_vsc), - Argument("MaxNumAtom", int, optional=True, doc=doc_maxnumatom), + Argument( + "MaxNumAtom", + [int, list[int]], + optional=True, + extra_check=_is_scalar_or_singleton, + extra_check_errmsg="MaxNumAtom must be an integer or a one-item list.", + doc=doc_maxnumatom, + ), Argument( "CtrlRange", list[list[int]], optional=True, doc=doc_ctrlrange ), @@ -737,7 +782,15 @@ def model_devi_calypso_args() -> list[Argument]: default=[0.001], doc=doc_pstress, ), - Argument("fmax", float, optional=True, default=0.01, doc=doc_fmax), + Argument( + "fmax", + [float, int, list[float], list[int]], + optional=True, + default=0.01, + extra_check=_is_scalar_or_singleton, + extra_check_errmsg="fmax must be a scalar or a one-item list.", + doc=doc_fmax, + ), ], ), Argument("calypso_input_path", str, optional=True, doc=doc_calypso_input_path), diff --git a/dpgen/generator/lib/make_calypso.py b/dpgen/generator/lib/make_calypso.py index 42d5e7f69..b315c6057 100644 --- a/dpgen/generator/lib/make_calypso.py +++ b/dpgen/generator/lib/make_calypso.py @@ -162,6 +162,15 @@ def _make_model_devi_buffet(jdata, calypso_run_opt_path): raise FileNotFoundError("input.dat") +def _unwrap_calypso_scalar(value, name): + """Normalize a legacy one-item CALYPSO list to its scalar value.""" + if not isinstance(value, list): + return value + if len(value) != 1: + raise ValueError(f"{name} must be a scalar or a one-item list") + return value[0] + + def _make_model_devi_native_calypso(iter_index, model_devi_jobs, calypso_run_opt_path): for iiidx, jobbs in enumerate(model_devi_jobs): if iter_index in jobbs.get("times"): @@ -176,12 +185,12 @@ def _make_model_devi_native_calypso(iter_index, model_devi_jobs, calypso_run_opt nameofatoms = cur_job.get("NameOfAtoms") numberofatoms = cur_job.get("NumberOfAtoms") numberofformula = cur_job.get("NumberOfFormula", [1, 1]) - volume = cur_job.get("Volume") + volume = _unwrap_calypso_scalar(cur_job.get("Volume"), "Volume") distanceofion = cur_job.get("DistanceOfIon") - psoratio = cur_job.get("PsoRatio", 0.6) - popsize = cur_job.get("PopSize", 30) - maxstep = cur_job.get("MaxStep", 5) - icode = cur_job.get("ICode", 1) + psoratio = _unwrap_calypso_scalar(cur_job.get("PsoRatio", 0.6), "PsoRatio") + popsize = _unwrap_calypso_scalar(cur_job.get("PopSize", 30), "PopSize") + maxstep = _unwrap_calypso_scalar(cur_job.get("MaxStep", 5), "MaxStep") + icode = _unwrap_calypso_scalar(cur_job.get("ICode", 1), "ICode") split = cur_job.get("Split", "T") # Cluster @@ -192,10 +201,10 @@ def _make_model_devi_native_calypso(iter_index, model_devi_jobs, calypso_run_opt ctrlrange = None vsc = cur_job.get("VSC", "F") if vsc == "T": - maxnumatom = cur_job.get("MaxNumAtom") + maxnumatom = _unwrap_calypso_scalar(cur_job.get("MaxNumAtom"), "MaxNumAtom") ctrlrange = cur_job.get("CtrlRange") # Optimization - fmax = cur_job.get("fmax", 0.01) + fmax = _unwrap_calypso_scalar(cur_job.get("fmax", 0.01), "fmax") # pstress is a List which contains the target stress pstress = cur_job.get("PSTRESS", [0.001]) # pressures diff --git a/tests/test_calypso_arginfo.py b/tests/test_calypso_arginfo.py new file mode 100644 index 000000000..702bf24aa --- /dev/null +++ b/tests/test_calypso_arginfo.py @@ -0,0 +1,48 @@ +import unittest + +from dargs import Argument + +from dpgen.generator.arginfo import model_devi_args +from dpgen.generator.lib.make_calypso import _unwrap_calypso_scalar + + +class TestCalypsoArginfo(unittest.TestCase): + def test_legacy_singleton_scalars(self): + """The checked-in CALYPSO list spelling remains schema-compatible.""" + arginfo = Argument("model_devi", dict, sub_variants=model_devi_args()) + data = { + "model_devi_engine": "calypso", + "model_devi_skip": 0, + "model_devi_f_trust_lo": 0.05, + "model_devi_f_trust_hi": 0.15, + "model_devi_jobs": [ + { + "times": [0], + "NameOfAtoms": ["Mg"], + "NumberOfAtoms": [1], + "Volume": [30], + "DistanceOfIon": [[1.4]], + "PsoRatio": [0.6], + "PopSize": [30], + "MaxStep": [5], + "ICode": [1], + "VSC": "T", + "MaxNumAtom": [20], + "CtrlRange": [[1, 20]], + "fmax": [0.01], + } + ], + } + + normalized = arginfo.normalize_value(data) + arginfo.check_value(normalized, strict=True) + + def test_runtime_unwraps_singleton_scalars(self): + self.assertEqual(_unwrap_calypso_scalar([30], "Volume"), 30) + self.assertEqual(_unwrap_calypso_scalar(0.6, "PsoRatio"), 0.6) + with self.assertRaisesRegex(ValueError, "one-item list"): + _unwrap_calypso_scalar([1, 2], "PopSize") + + +if __name__ == "__main__": + unittest.main() From 5e3f9593d5609c328b103b88ada74713b354952c Mon Sep 17 00:00:00 2001 From: njzjz-bot Date: Sun, 30 Aug 2026 01:36:26 +0800 Subject: [PATCH 3/3] fix: accept CALYPSO shuffle setting Coding-Agent: Codex Codex-Version: codex-cli 0.151.0 Model: gpt-5.6-sol Reasoning-Effort: xhigh --- dpgen/generator/arginfo.py | 7 +++++++ tests/test_calypso_arginfo.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/dpgen/generator/arginfo.py b/dpgen/generator/arginfo.py index 29d7ef80b..efc4fc367 100644 --- a/dpgen/generator/arginfo.py +++ b/dpgen/generator/arginfo.py @@ -799,6 +799,13 @@ def model_devi_calypso_args() -> list[Argument]: ), Argument("vsc", bool, optional=True, default=False, doc=doc_vsc_mode), Argument("model_devi_dt", float, optional=True, doc=doc_model_devi_dt), + Argument( + "shuffle_poscar", + bool, + optional=True, + default=False, + doc="Shuffle atoms in generated configurations before downstream use.", + ), Argument("model_devi_skip", int, optional=False, doc=doc_model_devi_skip), Argument( "model_devi_f_trust_lo", diff --git a/tests/test_calypso_arginfo.py b/tests/test_calypso_arginfo.py index 702bf24aa..d652d873b 100644 --- a/tests/test_calypso_arginfo.py +++ b/tests/test_calypso_arginfo.py @@ -1,4 +1,6 @@ +import json import unittest +from pathlib import Path from dargs import Argument @@ -43,6 +45,34 @@ def test_runtime_unwraps_singleton_scalars(self): with self.assertRaisesRegex(ValueError, "one-item list"): _unwrap_calypso_scalar([1, 2], "PopSize") + def test_checked_in_example_is_schema_compatible(self): + """The maintained example's CALYPSO section passes strict validation.""" + param_file = ( + Path(__file__).parent.parent + / "examples" + / "run" + / "dp-calypso-vasp" + / "param.json" + ) + with open(param_file) as fp: + example = json.load(fp) + + model_devi_keys = { + "model_devi_engine", + "model_devi_jobs", + "model_devi_dt", + "model_devi_skip", + "model_devi_f_trust_lo", + "model_devi_f_trust_hi", + "model_devi_clean_traj", + "shuffle_poscar", + "vsc", + } + data = {key: example[key] for key in model_devi_keys} + arginfo = Argument("model_devi", dict, sub_variants=model_devi_args()) + normalized = arginfo.normalize_value(data) + arginfo.check_value(normalized, strict=True) + if __name__ == "__main__": unittest.main()