Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ validation, PQSetup also checks the inputs with PQ.
Environment detection reports what is available. It does not establish that a
method, force field, or protocol is scientifically suitable.

PQSetup targets the stable PQ v0.6.4 input schema.
PQSetup writes inputs for the stable PQ v0.7.0 release.

## Run Packages

Expand Down
9 changes: 7 additions & 2 deletions docs/reference/compatibility.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Compatibility
PQ inputs
---------

PQSetup currently writes inputs for the stable PQ v0.6.4 schema. It does not
PQSetup currently writes inputs for the stable PQ v0.7.0 release. It does not
expose unreleased keywords merely because they exist on a development branch.

.. list-table::
Expand All @@ -18,14 +18,19 @@ expose unreleased keywords merely because they exist on a development branch.
- Available
- ``portable`` during export and ``installed`` from the CLI
* - Does not advertise the validation contract
- Available for v0.6.4 inputs
- Available for v0.7.0 inputs
- Not run; local checks remain active
* - Not detected
- Portable export remains available
- Not run; the missing executable is reported

Calculator availability is reported separately from PQ parser support.

The guided QM methods are DFTB+, ASE–DFTB+, ASE–xTB, PySCF, Turbomole,
MACE-MP, and MACE-OFF. PQ 0.7.0 also accepts FeNNol inputs, but PQSetup does
not yet package its binary model file and therefore does not present a partial
FeNNol workflow.

Structure formats
-----------------

Expand Down
3 changes: 2 additions & 1 deletion docs/run-packages.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ Project manifest
* the scientific plan and execution order;
* input, structure, setup-file, and run-script SHA-256 hashes;
* structure and preparation provenance;
* the PQ and calculator environment seen during export;
* the PQ build, calculator readiness, and method availability seen during
export;
* warnings and the result of each PQ validation layer.

The manifest makes it possible to inspect what was prepared without parsing
Expand Down
2 changes: 1 addition & 1 deletion docs/workflow.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ warnings remain visible and are recorded in the project manifest.

The restart relationship and final PQ input are visible before export.

The exported input header identifies PQSetup and the targeted PQ schema. It is
The exported input header identifies PQSetup and the target PQ release. It is
designed to make provenance obvious without obscuring the settings that
matter.

Expand Down
137 changes: 91 additions & 46 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
MM_MODES,
mmModeLabel,
packagedSetupFileName,
preferredRunner,
qmSetupFileSpecs,
recommendedRunnerScript,
selectedExternalQMScript,
Expand Down Expand Up @@ -783,15 +784,27 @@ export default function App() {
.then((value) => {
if (!current) return;
setBootstrap(value);
const preferred =
value.runners.find((runner) => runner.id === "ase_xtb") ??
value.runners.find((runner) => runner.supported);
const preferred = preferredRunner(value.runners);
if (preferred) {
setSetup((existing) =>
isMolecularMechanics(existing) || existing.runner
? existing
: { ...existing, runner: preferred.id },
);
setSetup((existing) => {
const selected = value.runners.find(
(runner) => runner.id === existing.runner,
);
if (
isMolecularMechanics(existing) ||
(existing.runner && selected?.available_in_pq !== false)
) {
return existing;
}
return {
...existing,
runner: preferred.id,
runner_script: recommendedRunnerScript(
value.pq.external_qm,
preferred.id,
),
};
});
}
})
.catch((error) => {
Expand Down Expand Up @@ -901,6 +914,9 @@ export default function App() {
setup.runner &&
!selectedRunnerStatus?.ready,
);
const pqMethodUnavailable = Boolean(
!molecularMechanics && selectedRunnerStatus?.available_in_pq === false,
);
const portableValidationAvailable = Boolean(
bootstrap?.pq.validation_scopes.includes("portable"),
);
Expand Down Expand Up @@ -964,7 +980,10 @@ export default function App() {
const stepState = useMemo<Record<StepId, "ok" | "warn" | "idle">>(
() => ({
system: analysis.valid ? "ok" : "warn",
method: !methodReady || calculatorMissing ? "warn" : "ok",
method:
!methodReady || calculatorMissing || pqMethodUnavailable
? "warn"
: "ok",
conditions: diagnostics.some(
(item) =>
item.severity === "error" &&
Expand All @@ -984,6 +1003,7 @@ export default function App() {
calculatorMissing,
diagnostics,
methodReady,
pqMethodUnavailable,
ready,
rendered,
],
Expand Down Expand Up @@ -1132,9 +1152,12 @@ export default function App() {
id: `calculator-${runner.id}`,
group: "Scientific setup",
label: runner.label,
detail: runner.ready
? "Calculator ready"
: `${runner.detail} Inputs can still be created.`,
detail:
runner.available_in_pq === false
? "Selected PQ build does not include this method. Inputs can still be created."
: runner.ready
? "Calculator ready"
: `${runner.detail} Inputs can still be created.`,
keywords: [
"calculator",
"runner",
Expand All @@ -1147,10 +1170,16 @@ export default function App() {
chooseCalculator(runner.id);
goToControl("method");
setNotice({
kind: runner.ready ? "success" : "info",
message: runner.ready
? `${runner.label} selected.`
: `${runner.label} selected but was not detected.`,
kind:
runner.ready && runner.available_in_pq !== false
? "success"
: "info",
message:
runner.available_in_pq === false
? `${runner.label} selected. Use a PQ build that includes it when running.`
: runner.ready
? `${runner.label} selected.`
: `${runner.label} selected but was not detected.`,
});
},
}),
Expand Down Expand Up @@ -1792,9 +1821,7 @@ export default function App() {
return;
}

const preferred =
bootstrap?.runners.find((runner) => runner.id === "ase_xtb") ??
bootstrap?.runners.find((runner) => runner.supported);
const preferred = preferredRunner(bootstrap?.runners ?? []);
setSetup((existing) => ({
...existing,
preset_id: null,
Expand Down Expand Up @@ -2001,7 +2028,7 @@ export default function App() {
</span>
</span>
<span className="version">
Schema {bootstrap.target_pq_release}
Input target {bootstrap.target_pq_release}
</span>
</>
) : bootstrapError ? (
Expand Down Expand Up @@ -2159,7 +2186,7 @@ export default function App() {
</span>
<span aria-hidden="true">·</span>
<span>
Target schema <strong>{bootstrap.target_pq_release}</strong>
Input target <strong>{bootstrap.target_pq_release}</strong>
</span>
</div>
)}
Expand Down Expand Up @@ -2211,11 +2238,14 @@ export default function App() {
.filter((runner) => runner.supported)
.map((runner) => {
const selected = setup.runner === runner.id;
const runnerState = runner.ready
? "ready"
: runner.installed
const runnerState =
runner.available_in_pq === false
? "incomplete"
: "missing";
: runner.ready
? "ready"
: runner.installed
? "incomplete"
: "missing";
return (
<div
className={`calculator-option ${
Expand Down Expand Up @@ -2247,24 +2277,31 @@ export default function App() {
<span
className={`runner-state ${runnerState}`}
>
{runner.ready
? "Ready"
: runner.installed
? "Setup incomplete"
: "Not detected"}
{runner.available_in_pq === false
? "PQ build mismatch"
: runner.ready
? "Ready"
: runner.installed
? "Setup incomplete"
: "Not detected"}
</span>
</label>
{selected && !runner.ready && (
<div
className="calculator-warning"
role="status"
>
<CircleAlert size={14} aria-hidden="true" />
<span>
{runner.detail} Inputs can still be created.
</span>
</div>
)}
{selected &&
(!runner.ready ||
runner.available_in_pq === false) && (
<div
className="calculator-warning"
role="status"
>
<CircleAlert size={14} aria-hidden="true" />
<span>
{runner.available_in_pq === false
? `Selected PQ build does not include ${runner.label}. `
: `${runner.detail} `}
Inputs can still be created.
</span>
</div>
)}
</div>
);
})}
Expand Down Expand Up @@ -3389,10 +3426,16 @@ export default function App() {
</small>
</span>
</li>
<li className={methodReady && !calculatorMissing ? "ok" : "warn"}>
<li
className={
methodReady && !calculatorMissing && !pqMethodUnavailable
? "ok"
: "warn"
}
>
<StatusDot
status={
methodReady && !calculatorMissing
methodReady && !calculatorMissing && !pqMethodUnavailable
? "ok"
: methodReady || molecularMechanics
? "warn"
Expand Down Expand Up @@ -3420,9 +3463,11 @@ export default function App() {
? `Add ${missingMethodFiles.length} required ${
missingMethodFiles.length === 1 ? "file" : "files"
}.`
: calculatorMissing
? `${selectedMethodLabel} was not detected.`
: `${selectedMethodLabel} is ready.`}
: pqMethodUnavailable
? `Selected PQ build does not include ${selectedCalculatorLabel}.`
: calculatorMissing
? `${selectedMethodLabel} was not detected.`
: `${selectedMethodLabel} is ready.`}
</small>
</span>
</li>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/conditionOptions.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, it } from "vitest";
import { MANOSTATS, THERMOSTATS } from "./conditionOptions";

describe("PQ 0.6.4 condition options", () => {
describe("PQ 0.7.0 condition options", () => {
it("uses the exact thermostat keywords", () => {
expect(THERMOSTATS.map((option) => option.value)).toEqual([
"berendsen",
Expand Down
50 changes: 49 additions & 1 deletion frontend/src/method.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ import {
missingSetupFileRoles,
mmModeLabel,
packagedSetupFileName,
preferredRunner,
qmSetupFileSpecs,
recommendedRunnerScript,
selectedExternalQMScript,
setupFileSpecs,
} from "./method";
import type { ExternalQMCapabilities, SetupFile } from "./types";
import type {
ExternalQMCapabilities,
RunnerStatus,
SetupFile,
} from "./types";

const files: SetupFile[] = [
{ role: "moldescriptor", name: "molecules.dat", content: "water" },
Expand All @@ -21,6 +26,41 @@ const files: SetupFile[] = [
{ role: "intra_nonbonded", name: "intra.dat", content: "pairs" },
];

function runner(
id: string,
availableInPQ: boolean | null,
ready = true,
): RunnerStatus {
return {
id,
label: id,
supported: true,
installed: ready,
ready,
executable: null,
version: null,
available_in_pq: availableInPQ,
detail: ready ? "Detected." : "Not detected.",
};
}

describe("calculator preference", () => {
it("does not default to a method missing from the selected PQ build", () => {
expect(
preferredRunner([
runner("ase_xtb", false),
runner("dftbplus", true),
])?.id,
).toBe("dftbplus");
expect(
preferredRunner([
runner("ase_xtb", null),
runner("dftbplus", null),
])?.id,
).toBe("ase_xtb");
});
});

describe("molecular mechanics method", () => {
it("exposes only files used by each force-field mode", () => {
expect(setupFileSpecs("off").map((file) => file.role)).toEqual([
Expand Down Expand Up @@ -75,6 +115,14 @@ describe("QM companion files", () => {
expect(defaultSetupFileName("dftb_template")).toBe("dftb_in.template");
});

it("requires an explicit PySCF method without installed capabilities", () => {
expect(recommendedRunnerScript(null, "pyscf")).toBeNull();
expect(selectedExternalQMScript(null, "pyscf", null)).toBeNull();
expect(
selectedExternalQMScript(null, "pyscf", "pyscf_hf.py")?.name,
).toBe("pyscf_hf.py");
});

it("uses advertised scripts, labels, and dependencies", () => {
const capabilities: ExternalQMCapabilities = {
script_mode: "bundled_or_full_path",
Expand Down
Loading