(
base_manifest: dict[str, Any],
*,
source_case_dir: Path,
upf_root: Path,
potential_mode: str,
potcar_root: Path | None,
potcar_set: str | None,
paw_runtime_experimental: bool,
timeout_sec: int | None,
)
| 492 | |
| 493 | |
| 494 | def _make_spec( |
| 495 | base_manifest: dict[str, Any], |
| 496 | *, |
| 497 | source_case_dir: Path, |
| 498 | upf_root: Path, |
| 499 | potential_mode: str, |
| 500 | potcar_root: Path | None, |
| 501 | potcar_set: str | None, |
| 502 | paw_runtime_experimental: bool, |
| 503 | timeout_sec: int | None, |
| 504 | ) -> tuple[SimulationSpec, list[str]]: |
| 505 | warnings: list[str] = [] |
| 506 | input_obj = copy.deepcopy(base_manifest.get("input", {})) |
| 507 | if not isinstance(input_obj, dict): |
| 508 | input_obj = {} |
| 509 | settings = input_obj.get("settings") |
| 510 | if not isinstance(settings, dict): |
| 511 | settings = {} |
| 512 | input_obj["settings"] = settings |
| 513 | # Stage-A gate requires stress fields to be present. |
| 514 | settings["compute_stress"] = True |
| 515 | if potential_mode == "NC_PP": |
| 516 | settings["use_nonlocal"] = True |
| 517 | warnings.extend(_fill_missing_incar_scalar_settings(settings, source_case_dir=source_case_dir)) |
| 518 | if potential_mode == "NC_PP" and "mix_beta" not in settings: |
| 519 | settings["mix_beta"] = 0.35 |
| 520 | if not _settings_has_key_case_insensitive(settings, "isym"): |
| 521 | resolved_isym, isym_source = _resolve_missing_isym(source_case_dir) |
| 522 | settings["isym"] = resolved_isym |
| 523 | if isym_source == "default": |
| 524 | warnings.append( |
| 525 | f"input.settings.isym missing; defaulted to ISYM={resolved_isym} to match VASP default" |
| 526 | ) |
| 527 | else: |
| 528 | warnings.append(f"input.settings.isym missing; inferred ISYM={resolved_isym} from {isym_source}") |
| 529 | |
| 530 | xc_raw = str(settings.get("xc", "")).strip().upper() |
| 531 | if xc_raw in {"PBE", "PBESOL"} and not _libxc_available(): |
| 532 | if GATE_ALLOW_XC_FALLBACK: |
| 533 | settings["xc"] = "NONE" |
| 534 | warnings.append(f"libxc unavailable; downgraded xc={xc_raw} to NONE") |
| 535 | else: |
| 536 | warnings.append( |
| 537 | f"libxc unavailable for requested xc={xc_raw}; keeping requested xc and allowing case-level failure" |
| 538 | ) |
| 539 | |
| 540 | # Stage-A gate validates schema/contract compliance and requires true computed |
| 541 | # outputs, but full train settings can be too heavy for iterative CI loops. |
| 542 | # Keep calculations real while bounding pathological cost. |
| 543 | if GATE_BOUNDED_PROFILE: |
| 544 | try: |
| 545 | encut_ev = float(settings.get("encut_ev")) |
| 546 | settings["encut_ev"] = min(encut_ev, GATE_MAX_ENCUT_EV) |
| 547 | except (TypeError, ValueError): |
| 548 | pass |
| 549 | try: |
| 550 | ediff = float(settings.get("ediff")) |
| 551 | if ediff < GATE_MIN_EDIFF: |
no test coverage detected