Skip to content

Commit 9001996

Browse files
committed
fix: normalize legacy CALYPSO scalars
Coding-Agent: Codex Codex-Version: codex-cli 0.151.0 Model: gpt-5.6-sol Reasoning-Effort: xhigh
1 parent 9c8fe94 commit 9001996

3 files changed

Lines changed: 124 additions & 14 deletions

File tree

dpgen/generator/arginfo.py

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,11 @@ def model_devi_amber_args() -> list[Argument]:
621621
]
622622

623623

624+
def _is_scalar_or_singleton(value) -> bool:
625+
"""Accept legacy one-item lists while rejecting ambiguous CALYPSO values."""
626+
return not isinstance(value, list) or len(value) == 1
627+
628+
624629
def model_devi_calypso_args() -> list[Argument]:
625630
"""CALYPSO engine arguments."""
626631
doc_model_devi_jobs = (
@@ -707,7 +712,14 @@ def model_devi_calypso_args() -> list[Argument]:
707712
default=[1, 1],
708713
doc=doc_numberofformula,
709714
),
710-
Argument("Volume", float, optional=True, doc=doc_volume),
715+
Argument(
716+
"Volume",
717+
[float, int, list[float], list[int]],
718+
optional=True,
719+
extra_check=_is_scalar_or_singleton,
720+
extra_check_errmsg="Volume must be a scalar or a one-item list.",
721+
doc=doc_volume,
722+
),
711723
Argument(
712724
"DistanceOfIon",
713725
list[list[float]],
@@ -716,17 +728,50 @@ def model_devi_calypso_args() -> list[Argument]:
716728
),
717729
Argument(
718730
"PsoRatio",
719-
float,
731+
[float, int, list[float], list[int]],
720732
optional=True,
721733
default=0.6,
734+
extra_check=_is_scalar_or_singleton,
735+
extra_check_errmsg="PsoRatio must be a scalar or a one-item list.",
722736
doc=doc_psoratio,
723737
),
724-
Argument("PopSize", int, optional=True, default=30, doc=doc_popsize),
725-
Argument("MaxStep", int, optional=True, default=5, doc=doc_maxstep),
726-
Argument("ICode", int, optional=True, default=1, doc=doc_icode),
738+
Argument(
739+
"PopSize",
740+
[int, list[int]],
741+
optional=True,
742+
default=30,
743+
extra_check=_is_scalar_or_singleton,
744+
extra_check_errmsg="PopSize must be an integer or a one-item list.",
745+
doc=doc_popsize,
746+
),
747+
Argument(
748+
"MaxStep",
749+
[int, list[int]],
750+
optional=True,
751+
default=5,
752+
extra_check=_is_scalar_or_singleton,
753+
extra_check_errmsg="MaxStep must be an integer or a one-item list.",
754+
doc=doc_maxstep,
755+
),
756+
Argument(
757+
"ICode",
758+
[int, list[int]],
759+
optional=True,
760+
default=1,
761+
extra_check=_is_scalar_or_singleton,
762+
extra_check_errmsg="ICode must be an integer or a one-item list.",
763+
doc=doc_icode,
764+
),
727765
Argument("Split", str, optional=True, default="T", doc=doc_split),
728766
Argument("VSC", str, optional=True, default="F", doc=doc_vsc),
729-
Argument("MaxNumAtom", int, optional=True, doc=doc_maxnumatom),
767+
Argument(
768+
"MaxNumAtom",
769+
[int, list[int]],
770+
optional=True,
771+
extra_check=_is_scalar_or_singleton,
772+
extra_check_errmsg="MaxNumAtom must be an integer or a one-item list.",
773+
doc=doc_maxnumatom,
774+
),
730775
Argument(
731776
"CtrlRange", list[list[int]], optional=True, doc=doc_ctrlrange
732777
),
@@ -737,7 +782,15 @@ def model_devi_calypso_args() -> list[Argument]:
737782
default=[0.001],
738783
doc=doc_pstress,
739784
),
740-
Argument("fmax", float, optional=True, default=0.01, doc=doc_fmax),
785+
Argument(
786+
"fmax",
787+
[float, int, list[float], list[int]],
788+
optional=True,
789+
default=0.01,
790+
extra_check=_is_scalar_or_singleton,
791+
extra_check_errmsg="fmax must be a scalar or a one-item list.",
792+
doc=doc_fmax,
793+
),
741794
],
742795
),
743796
Argument("calypso_input_path", str, optional=True, doc=doc_calypso_input_path),

dpgen/generator/lib/make_calypso.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,15 @@ def _make_model_devi_buffet(jdata, calypso_run_opt_path):
162162
raise FileNotFoundError("input.dat")
163163

164164

165+
def _unwrap_calypso_scalar(value, name):
166+
"""Normalize a legacy one-item CALYPSO list to its scalar value."""
167+
if not isinstance(value, list):
168+
return value
169+
if len(value) != 1:
170+
raise ValueError(f"{name} must be a scalar or a one-item list")
171+
return value[0]
172+
173+
165174
def _make_model_devi_native_calypso(iter_index, model_devi_jobs, calypso_run_opt_path):
166175
for iiidx, jobbs in enumerate(model_devi_jobs):
167176
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
176185
nameofatoms = cur_job.get("NameOfAtoms")
177186
numberofatoms = cur_job.get("NumberOfAtoms")
178187
numberofformula = cur_job.get("NumberOfFormula", [1, 1])
179-
volume = cur_job.get("Volume")
188+
volume = _unwrap_calypso_scalar(cur_job.get("Volume"), "Volume")
180189
distanceofion = cur_job.get("DistanceOfIon")
181-
psoratio = cur_job.get("PsoRatio", 0.6)
182-
popsize = cur_job.get("PopSize", 30)
183-
maxstep = cur_job.get("MaxStep", 5)
184-
icode = cur_job.get("ICode", 1)
190+
psoratio = _unwrap_calypso_scalar(cur_job.get("PsoRatio", 0.6), "PsoRatio")
191+
popsize = _unwrap_calypso_scalar(cur_job.get("PopSize", 30), "PopSize")
192+
maxstep = _unwrap_calypso_scalar(cur_job.get("MaxStep", 5), "MaxStep")
193+
icode = _unwrap_calypso_scalar(cur_job.get("ICode", 1), "ICode")
185194
split = cur_job.get("Split", "T")
186195
# Cluster
187196

@@ -192,10 +201,10 @@ def _make_model_devi_native_calypso(iter_index, model_devi_jobs, calypso_run_opt
192201
ctrlrange = None
193202
vsc = cur_job.get("VSC", "F")
194203
if vsc == "T":
195-
maxnumatom = cur_job.get("MaxNumAtom")
204+
maxnumatom = _unwrap_calypso_scalar(cur_job.get("MaxNumAtom"), "MaxNumAtom")
196205
ctrlrange = cur_job.get("CtrlRange")
197206
# Optimization
198-
fmax = cur_job.get("fmax", 0.01)
207+
fmax = _unwrap_calypso_scalar(cur_job.get("fmax", 0.01), "fmax")
199208
# pstress is a List which contains the target stress
200209
pstress = cur_job.get("PSTRESS", [0.001])
201210
# pressures

tests/test_calypso_arginfo.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import unittest
2+
3+
from dargs import Argument
4+
5+
from dpgen.generator.arginfo import model_devi_args
6+
from dpgen.generator.lib.make_calypso import _unwrap_calypso_scalar
7+
8+
9+
class TestCalypsoArginfo(unittest.TestCase):
10+
def test_legacy_singleton_scalars(self):
11+
"""The checked-in CALYPSO list spelling remains schema-compatible."""
12+
arginfo = Argument("model_devi", dict, sub_variants=model_devi_args())
13+
data = {
14+
"model_devi_engine": "calypso",
15+
"model_devi_skip": 0,
16+
"model_devi_f_trust_lo": 0.05,
17+
"model_devi_f_trust_hi": 0.15,
18+
"model_devi_jobs": [
19+
{
20+
"times": [0],
21+
"NameOfAtoms": ["Mg"],
22+
"NumberOfAtoms": [1],
23+
"Volume": [30],
24+
"DistanceOfIon": [[1.4]],
25+
"PsoRatio": [0.6],
26+
"PopSize": [30],
27+
"MaxStep": [5],
28+
"ICode": [1],
29+
"VSC": "T",
30+
"MaxNumAtom": [20],
31+
"CtrlRange": [[1, 20]],
32+
"fmax": [0.01],
33+
}
34+
],
35+
}
36+
37+
normalized = arginfo.normalize_value(data)
38+
arginfo.check_value(normalized, strict=True)
39+
40+
def test_runtime_unwraps_singleton_scalars(self):
41+
self.assertEqual(_unwrap_calypso_scalar([30], "Volume"), 30)
42+
self.assertEqual(_unwrap_calypso_scalar(0.6, "PsoRatio"), 0.6)
43+
with self.assertRaisesRegex(ValueError, "one-item list"):
44+
_unwrap_calypso_scalar([1, 2], "PopSize")
45+
46+
47+
if __name__ == "__main__":
48+
unittest.main()

0 commit comments

Comments
 (0)