Use Goldilocks to generate inputs from a script. First complete the
installation and asset setup. Run the examples in
order in the same Python session, or save them in a script and run
uv run your_script.py.
advise() diagnoses a structure against an HPC profile. A decision it
cannot make is marked unavailable or blocked rather than raising, so you can
inspect what is known before deciding whether to generate anything.
from goldilocks_core.examples.structures import structure
from goldilocks_core.inputs.hpc import load_hpc_profile
from goldilocks_core.inputs.structure import PathStructureSource, normalize_structure
from goldilocks_core.service import advise
source = PathStructureSource(structure("Si.cif"))
normalized = normalize_structure(source)
silicon = normalized.structure
hpc = load_hpc_profile("scarf")
advice = advise(silicon, hpc=hpc)Replace structure("Si.cif") with a path to your own CIF or POSCAR.
normalized.source identifies the file you read (its name, format, sha256
and size), which a published bundle records.
load_hpc_profile loads a named profile from inputs/profiles/, the same
profiles --hpc names on the CLI (see the CLI reference).
advise() runs every analysis/advisor stage (structure facts, functional
and pseudopotential selection, k-point sampling, resource sizing, ...) and
returns one Advice holding a FieldState per decision.
Each field on Advice is a tri-state Resolved/Unavailable/Blocked
value, never a bare number:
from goldilocks_core.resolution import Resolved
functional = advice.system.functional
if isinstance(functional, Resolved):
print(functional.value, functional.source)
k_sampling = advice.step.kpoints.k_sampling
if isinstance(k_sampling, Resolved):
print(k_sampling.value.mesh)This prints the selected functional, the source tier that picked it
(human/ml/llm/heuristic), and the k-point mesh. Model-dependent
values can change with the installed model, which a model-decided field
names in provenance.model (with what it returned); review warnings and
check convergence rather than treating the recommendation as a verified
result.
advice.records() returns every decision as a plain {name: FieldState}
dict of the advisors' typed values (resolution.records_document turns it
into the JSON the CLI, HTTP and goldilocks.json publish), and
advice.warnings() flattens every record's warnings into one list, each
naming the record it came from in record, if you want to inspect
everything at once instead of field by field.
Pass overrides built by build_overrides for the settings you want to
control — the same validation path --set/HTTP/MCP use, so an unknown key
or a badly-shaped value fails the same way here as anywhere else. Name the
task: each setting reaches the steps of that task it applies to, and a
setting no step of the task reads (press on an scf) is refused:
from goldilocks_core.set_overrides import build_overrides
assignments = {"k_grid": (4, 4, 4)}
overrides = build_overrides(assignments, task="scf_single_point")
advice = advise(silicon, hpc=hpc, overrides=overrides)
k_sampling = advice.step.kpoints.k_sampling
if isinstance(k_sampling, Resolved):
print(k_sampling.value.mesh, k_sampling.source)The mesh is now (4, 4, 4) with source == "human". An explicit grid
bypasses the k-point model; other settings are still recommended. See
goldilocks settings (or the CLI reference) for every overridable
key.
advise() does not refuse an incomplete request; check()/generate() do,
if something the run needs is still unavailable/blocked or the settings
contradict each other. Always call check() first and inspect
report.blocking rather than relying on generate()'s exception alone. It
holds one Finding per root cause: a stable code, the settings it concerns
(fields), a message to show and its shorter summary, and remedies,
override patches that each resolve it (a None value clears that override):
from goldilocks_core.service import check, generate
from goldilocks_core.steps import default_shared_context
report = check(advice)
if not report.ok:
for finding in report.blocking:
print("blocked:", finding.message)
for remedy in finding.remedies:
print(" try overrides:", remedy)
else:
ctx = default_shared_context()
steps = generate(advice, report, ctx=ctx)
print([step.name for step in steps])generate() renders the actual Quantum ESPRESSO input file(s) as Step
objects, raising AdviceIncomplete if called with a non-ok report — this
is a hard boundary, not the primary way to learn what is missing.
Turn generated steps into a submission script and a publishable bundle, then choose where the result goes:
from goldilocks_core.bundle import ArchiveOutput, DirectoryOutput, bundle_files, publish
from goldilocks_core.service import render_submission, to_bundle_input
script = render_submission(advice, hpc, "quantum_espresso", steps)
bundle_input = to_bundle_input(
advice,
steps,
script,
ctx,
code="quantum_espresso",
hpc=hpc,
assignments=assignments,
source=normalized.source,
)The keywords name the request the bundle answers, which goldilocks.json
records under request with the task (to_bundle_input is the
scf_single_point task's) and the goldilocks-core version: the code, the HPC
profile, the overrides as submitted (the assignments you passed to
build_overrides, steps.<step>. prefixes kept) and the input structure's
name, format, sha256 and size. A structure you built in memory
(InMemoryStructureSource) has no sha256 or size.
| Output | Result |
|---|---|
bundle_files(bundle_input) |
Keep the result in memory |
publish(bundle_input, DirectoryOutput("si-run")) |
Write a new directory |
publish(bundle_input, ArchiveOutput("si-run.zip")) |
Write a ZIP with the same contents |
Choose a destination that does not already exist; publish refuses to
overwrite one. publish returns a Publication with path, kind, and
the list of published file paths.
normalize_structure is also the entry point for structure-only
inspection, without running any advisor:
inspection = normalize_structure(source).inspection
print(inspection["structure"]["reduced_formula"])
print(inspection["canonical_cif"])Five tasks run more than one Quantum ESPRESSO step: dos (an scf, a denser
nscf pass and dos.x), band (an scf, pw.x along a band path and
bands.x) and the three phonon tasks (an scf, a relax or a vc-relax, then
ph.x, q2r.x, and matdyn.x for the phonon DOS and for the dispersion).
Each has its own five functions, named after the task
(advise_dos ... to_bundle_input_dos, advise_band ...
to_bundle_input_band; the phonon tasks share advise_phonon ...
to_bundle_input_phonon), which advise the task's steps together, then
generate one input per step. TASK_PHASES in goldilocks_core.service
holds every task's five functions by task name, as the CLI, HTTP and MCP
look them up.
from goldilocks_core.service import (
advise_dos,
check_dos,
generate_dos,
render_submission_dos,
to_bundle_input_dos,
)
dos_assignments = {"steps.nscf.k_grid": (12, 12, 12), "delta_e": 0.05}
dos_overrides = build_overrides(dos_assignments, task="dos")
dos_advice = advise_dos(silicon, hpc=hpc, overrides=dos_overrides)
dos_report = check_dos(dos_advice)
if dos_report.ok:
dos_steps = generate_dos(dos_advice, dos_report, ctx=ctx)
dos_script = render_submission_dos(dos_advice, hpc, "quantum_espresso", dos_steps)
dos_bundle = to_bundle_input_dos(
dos_advice,
dos_steps,
dos_script,
ctx,
code="quantum_espresso",
hpc=hpc,
assignments=dos_assignments,
source=normalized.source,
)
print([step.name for step in dos_steps])The task's steps are scf, nscf and dos. steps.nscf.k_grid sets the
nscf mesh only, and delta_e, a dos.x setting, reaches the dos step. A
setting given without a step reaches every step it applies to: a flat
k_grid sets both pw.x steps, and dos_advice.warnings() then carries an
overrides.applies_to_multiple_steps notice naming them.
dos_advice.records() keys the shared system-level fields and the task's
one job unprefixed, the nscf step's own fields under an nscf_-prefixed
name, and the DOS decision itself under "dos". Unless the request sets
them, the nscf step takes its k-point spacing (0.10 Å⁻¹) and occupations
(tetrahedra_opt) from the dos protocol, aiida-quantumespresso's pdos.yaml
(balanced), ahead of the ML tier: nscf_k_sampling and nscf_occupations
read heuristic and name the protocol in source_note. A value you set
replaces the protocol's field by field and reads human; a shift alone
keeps the protocol's mesh, and a degauss alone keeps tetrahedra_opt.
The three steps run in one allocation, so there is one job, sized over both
pw.x steps. pw.x counts max_seconds from its own start, so the job
splits 95% of the walltime between the scf and the nscf by estimated
workload (job.max_seconds_by_step), and each input gets its own share;
dos.x runs in what is left. Both pw.x steps run on the whole allocation;
dos.x runs on one task of it (srun --nodes=1 --ntasks=1), because every
extra task would repeat the whole DOS and dos.x aborts when a task owns no
FFT plane.
from goldilocks_core.service import (
advise_band,
check_band,
generate_band,
render_submission_band,
)
band_overrides = build_overrides({"k_path_distance": 0.05}, task="band")
band_advice = advise_band(silicon, hpc=hpc, overrides=band_overrides)
band_report = check_band(band_advice)
if band_report.ok:
band_steps = generate_band(band_advice, band_report, ctx=ctx)
band_script = render_submission_band(
band_advice, hpc, "quantum_espresso", band_steps
)
print([step.name for step in band_steps])
k_path = band_advice.records()["k_path"]
if isinstance(k_path, Resolved):
print(k_path.value.lattice_type, k_path.value.n_kpoints)
print(k_path.value.primitive_cells)The task's steps are scf, bands (pw.x with calculation='bands') and
bands_x (bands.x); a collinear spin-polarized run gets bands_x_up and
bands_x_down instead, one bands.x run per spin channel.
k_path_distance, the largest spacing between neighbouring points of the
path in Å⁻¹ (2π included, as for k_distance), reaches the bands step only.
The k-mesh, occupations and SCF-loop settings set the scf step only, since
the bands step runs the path with the scf step's occupations and no SCF
loop; nbnd, extra_bands, conv_thr, npool and ndiag given without a
step reach both pw.x steps, with the overrides.applies_to_multiple_steps
notice.
band_advice.records() keys the shared fields, the scf step's own, the
task's one job and the bands step's path, k_path, unprefixed, and the
bands step's other records under bands_: bands_occupations,
bands_nbnd, bands_convergence and bands_parallelisation. Unless the
request sets them, the bands step takes its k_path_distance (0.025 Å⁻¹)
and three times the occupied bands from aiida-quantumespresso's bands.yaml
protocol, read heuristic with the protocol in source_note.
The path comes from the symmetry of the cell that is written: here the FCC
path of diamond. The bundled Si.cif is diamond's 8-atom conventional
cell, so primitive_cells is 4 and k_path carries the
k_path.folded_bands notice: at each point of the path, the bands of four
points of the primitive cell's zone appear together. Upload a primitive cell
for unfolded bands. See the band structure path
for slabs, wires, magnets and what the path does not cover.
advise_phonon(silicon, task="relax-phonon", hpc=hpc, overrides=...)
advises a phonon task: task is phonon, relax-phonon or
vc-relax-phonon, and the overrides come from build_overrides(..., task="relax-phonon") for the same task. check_phonon, generate_phonon,
render_submission_phonon and to_bundle_input_phonon take the
PhononAdvice it returns, as the dos functions take a DosAdvice, and
TASK_PHASES["relax-phonon"] holds all five with the task bound.
generate_phonon returns the first pw.x step (scf, relax or
vc-relax), d3hess under DFT-D3, then ph, q2r, phonon_dos and
dispersion, matdyn.x along the band task's path.
The advice's records() keys the first step's records and the task's one
job unprefixed, as for a single pw.x step, with fft_grid, the dense FFT
grid ph.x accepts, and, for a relaxation, relax; then q_sampling and
phonon (the ph step), q2r, phonon_dos and k_path, the dispersion
step's path. It is the path a band task would take at the same
k_path_distance, except on fixed occupations without isolation='slab',
where matdyn.x adds the non-analytic term: each Γ inside a branch is then
listed twice and each segment into Γ lands on it exactly, which
source_note says and n_kpoints counts. PhononAdvice.first is the
first step's own Advice. Its k-mesh is raised against the q-mesh unless the
request sets it, and a relaxation converges to forc_conv_thr 1e-4 Ry/Bohr
unless the request sets that. A phonon job splits no walltime between its
steps: job.max_seconds_by_step is empty, and job.timer_seconds, 95% of the
walltime, is when the submission script's timer stops pw.x or ph.x cleanly.
See the phonon tasks for the q-mesh, the
dispersion, the refusals and how a stopped job resumes.
See Recommendations to interpret other settings, or Architecture to extend the computation workflow.