Skip to content

Commit f300d39

Browse files
committed
fix: accept legacy CALYPSO scalar lists
Coding-Agent: Codex Codex-Version: codex-cli 0.151.0 Model: gpt-5.6-sol Reasoning-Effort: xhigh
1 parent 7c6a2aa commit f300d39

4 files changed

Lines changed: 102 additions & 14 deletions

File tree

dpgen/generator/arginfo.py

Lines changed: 26 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_jobs_args() -> Argument:
625630
"""Return native CALYPSO structure-generation settings."""
626631
return Argument(
@@ -660,8 +665,10 @@ def model_devi_calypso_jobs_args() -> Argument:
660665
),
661666
Argument(
662667
"Volume",
663-
[None, float, int],
668+
[None, float, int, list[float], list[int]],
664669
optional=True,
670+
extra_check=_is_scalar_or_singleton,
671+
extra_check_errmsg="Volume must be a scalar or a one-item list.",
665672
doc="Volume per formula unit in cubic Angstrom.",
666673
),
667674
Argument(
@@ -672,30 +679,38 @@ def model_devi_calypso_jobs_args() -> Argument:
672679
),
673680
Argument(
674681
"PsoRatio",
675-
float,
682+
[float, int, list[float], list[int]],
676683
optional=True,
677684
default=0.6,
685+
extra_check=_is_scalar_or_singleton,
686+
extra_check_errmsg="PsoRatio must be a scalar or a one-item list.",
678687
doc="Fraction of structures generated by particle-swarm optimization.",
679688
),
680689
Argument(
681690
"PopSize",
682-
int,
691+
[int, list[int]],
683692
optional=True,
684693
default=30,
694+
extra_check=_is_scalar_or_singleton,
695+
extra_check_errmsg="PopSize must be an integer or a one-item list.",
685696
doc="CALYPSO population size.",
686697
),
687698
Argument(
688699
"MaxStep",
689-
int,
700+
[int, list[int]],
690701
optional=True,
691702
default=5,
703+
extra_check=_is_scalar_or_singleton,
704+
extra_check_errmsg="MaxStep must be an integer or a one-item list.",
692705
doc="Maximum particle-swarm optimization steps.",
693706
),
694707
Argument(
695708
"ICode",
696-
int,
709+
[int, list[int]],
697710
optional=True,
698711
default=1,
712+
extra_check=_is_scalar_or_singleton,
713+
extra_check_errmsg="ICode must be an integer or a one-item list.",
699714
doc="CALYPSO local-optimization interface code.",
700715
),
701716
Argument(
@@ -714,8 +729,10 @@ def model_devi_calypso_jobs_args() -> Argument:
714729
),
715730
Argument(
716731
"MaxNumAtom",
717-
int,
732+
[int, list[int]],
718733
optional=True,
734+
extra_check=_is_scalar_or_singleton,
735+
extra_check_errmsg="MaxNumAtom must be an integer or a one-item list.",
719736
doc="Maximum atoms per cell when VSC is enabled.",
720737
),
721738
Argument(
@@ -733,9 +750,11 @@ def model_devi_calypso_jobs_args() -> Argument:
733750
),
734751
Argument(
735752
"fmax",
736-
float,
753+
[float, int, list[float], list[int]],
737754
optional=True,
738755
default=0.01,
756+
extra_check=_is_scalar_or_singleton,
757+
extra_check_errmsg="fmax must be a scalar or a one-item list.",
739758
doc="Force convergence threshold in eV/Angstrom.",
740759
),
741760
Argument(

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/generator/test_calypso.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import os
22
import sys
3+
import tempfile
34
import unittest
45
from pathlib import Path
56

67
import numpy as np
78

9+
from dpgen.generator.lib.make_calypso import _make_model_devi_native_calypso
10+
811
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
912
__package__ = "generator"
1013

@@ -132,6 +135,36 @@ def test_make_calypso_input(self):
132135
os.remove("input.dat")
133136
break
134137

138+
def test_native_input_normalizes_singleton_scalars(self):
139+
"""Legacy singleton lists are rendered as scalar CALYPSO values."""
140+
job = {
141+
"times": [0],
142+
"NameOfAtoms": ["Mg"],
143+
"NumberOfAtoms": [1],
144+
"NumberOfFormula": [1, 1],
145+
"Volume": [30],
146+
"DistanceOfIon": [[1.4]],
147+
"PsoRatio": [0.6],
148+
"PopSize": [5],
149+
"MaxStep": [3],
150+
"ICode": [1],
151+
"VSC": "T",
152+
"MaxNumAtom": [20],
153+
"CtrlRange": [[1, 20]],
154+
"PSTRESS": [0.0],
155+
"fmax": [0.01],
156+
}
157+
with tempfile.TemporaryDirectory() as tmpdir:
158+
run_path = Path(tmpdir) / "calypso.000"
159+
run_path.mkdir()
160+
_make_model_devi_native_calypso(0, [job], [str(run_path)])
161+
generated = (run_path / "input.dat").read_text()
162+
163+
self.assertIn("Volume = 30", generated)
164+
self.assertIn("PsoRatio = 0.6", generated)
165+
self.assertIn("PopSize = 5", generated)
166+
self.assertNotIn("[", generated)
167+
135168
def test_parse_calypso_input(self):
136169
ret = make_calypso_input(
137170
["Mg", "Al", "Cu"],

tests/test_calypso_arginfo.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,33 @@ def test_external_input_mode(self):
6262
normalized = self.arginfo.normalize_value(data)
6363
self.arginfo.check_value(normalized, strict=True)
6464

65+
def test_legacy_singleton_scalars(self):
66+
"""Checked-in CALYPSO inputs retain their historical list spelling."""
67+
data = {
68+
"model_devi_engine": "calypso",
69+
**self.selection,
70+
"model_devi_jobs": [
71+
{
72+
"times": [0],
73+
"NameOfAtoms": ["Mg"],
74+
"NumberOfAtoms": [1],
75+
"Volume": [30],
76+
"DistanceOfIon": [[1.4]],
77+
"PsoRatio": [0.6],
78+
"PopSize": [30],
79+
"MaxStep": [5],
80+
"ICode": [1],
81+
"VSC": "T",
82+
"MaxNumAtom": [20],
83+
"CtrlRange": [[1, 20]],
84+
"fmax": [0.01],
85+
}
86+
],
87+
}
88+
89+
normalized = self.arginfo.normalize_value(data)
90+
self.arginfo.check_value(normalized, strict=True)
91+
6592

6693
if __name__ == "__main__":
6794
unittest.main()

0 commit comments

Comments
 (0)