diff --git a/src/scm/plams/interfaces/adfsuite/ams.py b/src/scm/plams/interfaces/adfsuite/ams.py index 13115ace..e164a096 100644 --- a/src/scm/plams/interfaces/adfsuite/ams.py +++ b/src/scm/plams/interfaces/adfsuite/ams.py @@ -3281,7 +3281,13 @@ def unspec(value: T) -> Union[T, str]: return special[spec_type](value) return value - def serialize(key: str, value: Any, indent: int, end: str = "End") -> str: + def serialize( + key: str, + value: Any, + indent: int, + end: str = "End", + parent_key: Optional[str] = None, + ) -> str: """Given a *key* and its corresponding *value* from the |Settings| instance produce a snippet of the input file representing this pair. If the value is a nested |Settings| instance, use recursive calls to build the snippet for the entire block. Indent the result with *indent* spaces. @@ -3300,9 +3306,9 @@ def serialize(key: str, value: Any, indent: int, end: str = "End") -> str: while f"_{i}" in value: if isinstance(value[f"_{i}"], Settings): for ckey in value[f"_{i}"]: - ret += serialize(ckey, value[f"_{i}"][ckey], indent + 2) + ret += serialize(ckey, value[f"_{i}"][ckey], indent + 2, parent_key=key) else: - ret += serialize("", value["_" + str(i)], indent + 2) + ret += serialize("", value["_" + str(i)], indent + 2, parent_key=key) i += 1 # Figure out the order in which we should serialize the entries in the block @@ -3329,22 +3335,22 @@ def serialize(key: str, value: Any, indent: int, end: str = "End") -> str: split_key = key.lower().split() split_ckey = ckey.lower().split() if len(split_key) > 0 and split_key[0] == "engine" and ckey.lower() == "input": - ret += serialize(ckey, cvalue, indent + 2, "EndInput") + ret += serialize(ckey, cvalue, indent + 2, "EndInput", parent_key=key) # REB: For the hybrid engine. How to deal with the space in ckey (Engine DFTB)? Replace by underscore? elif len(split_ckey) > 0 and split_ckey[0] == "engine": engine = " ".join(ckey.split("_")) - ret += serialize(engine, cvalue, indent + 2, end="EndEngine") + "\n" + ret += serialize(engine, cvalue, indent + 2, end="EndEngine", parent_key=key) + "\n" else: - ret += serialize(ckey, cvalue, indent + 2) + ret += serialize(ckey, cvalue, indent + 2, parent_key=key) # Close block - if key.lower() == "input": + if key.lower() == "input" and (parent_key or "").lower() != "plumed": end = "endinput" ret += " " * indent + end + "\n" elif isinstance(value, list): for el in value: - ret += serialize(key, el, indent, end) + ret += serialize(key, el, indent, end, parent_key=parent_key) elif value == "" or value is True: ret += " " * indent + key + "\n" elif value is False or value is None: diff --git a/unit_tests/test_amsjob.py b/unit_tests/test_amsjob.py index 4d35f769..5ab1cac9 100644 --- a/unit_tests/test_amsjob.py +++ b/unit_tests/test_amsjob.py @@ -17,6 +17,25 @@ from test_helpers import skip_if_no_scm_pisa, skip_if_no_scm_base +class TestAMSJobInputSerialization: + def test_plumed_input_uses_normal_end(self): + settings = Settings() + settings.input.ams.MolecularDynamics.Plumed.Input._1 = "PRINT ARG=x FILE=COLVAR" + + generated = AMSJob(settings=settings).get_input() + + assert re.search(r"Plumed\n\s+Input\n\s+PRINT ARG=x FILE=COLVAR\n\s+End\n\s+End", generated) + assert "endinput" not in generated.lower() + + def test_engine_input_still_uses_endinput(self): + settings = Settings() + settings.input.External.Input._1 = "some external-engine input" + + generated = AMSJob(settings=settings).get_input() + + assert re.search(r"Engine External\n\s+Input\n\s+some external-engine input\n\s+endinput", generated) + + class TestAMSJob: """ Test suite for AMSJob without using PISA / CS for input.