Config And Manifests¶
The experiment configuration schema. Pydantic validates the config at the boundary — no Hydra, no
DictConfig reaches the core. Every field has a sensible default, so an experiment.yaml only
specifies input references and overrides.
Pydantic configuration schema for a diffBloch experiment.
Config is validated at the boundary: no Hydra, and no DictConfig reaches the core. Every field
carries a sensible default (“defaults as code”), so an experiment.yaml only needs to specify
input references and overrides.
- class diffBloch.config.schema.BlochwaveConfig(*, solver: SolverMethod = 'matrix_exp', absorption: bool = False, rsg: float = 0.66, dsg: float = 0.0015, rocking_curve_sampling: int = 42, mosaicity: bool = False, fixed_n_segments: int = 12, coupling_mode: Literal['union', 'per_tilt'] = 'union', g_max: float = 2.25, sg_max: float = 0.01, union_adaptive: bool = True, union_max_new_beams_pct: float = 0.01, ignore_orientations: tuple[int, ...] = ())[source][source]¶
Bases:
_StrictConfigThe beam-selection, coupling, and dynamical-solver settings for the Bloch-wave simulation.
The structure-factor support grid is not a config field: it is derived as
2xthe solve cutoff (g_max), because a beam set bounded by|g| <= cutoffproducesF(g - h)terms reaching2 * cutoff– so declaring both cutoff and support would let them contradict (one beam cutoff, support derived). The orientation-independent seed pool (from_experiment’s difference-safe candidate set) and the scored-reflection cap (optimize_orientation’s trial-coupling window) both readg_maxdirectly, rather than a separate smaller radius.rsg/dsgare the Klar beam-selection cutoffs androcking_curve_samplingthe tilt count. The shared integration semi-angle is read from the PETS experimental data rather than configured.mosaicity: trueopts into PETS-derived angular mosaic averaging, while the defaultfalsedisables it.- solver: SolverMethod¶
- coupling_mode: Literal['union', 'per_tilt']¶
- to_absorption() Absorption[source][source]¶
Parse the absorption switch into its typed scientific value.
- to_beam_selection(integration: IntegrationGeometry) BeamSelection[source][source]¶
Assemble the
select_beamsvalue-type: the Klar cutoffs + the shared integration.
- to_rocking_curve(integration: IntegrationGeometry) RockingCurve[source][source]¶
Assemble the
integrate_rocking_curvevalue-type: tilt count + the shared integration.The caller passes the PETS-derived value to both this method and
to_beam_selection(), so the beam window and tilt sweep cannot disagree.
- to_policy() UnionCoupling | PerTiltCoupling[source][source]¶
Assemble the selected tilt-dependent Bloch-wave beam-coupling policy.
- to_orientation_selection() OrientationSelection[source][source]¶
Parse zero-based source PETS indices excluded from the whole Bloch experiment.
- model_config = {'extra': 'forbid'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class diffBloch.config.schema.SampleConfig(*, thicknesses: tuple[float, ...]=(820.0, ), mean_thickness_by_dataset: dict[str, float]=<factory>)[source][source]¶
Bases:
_StrictConfigFixed sample properties.
Thickness is captured here because it is a sample/nuisance parameter, not a numerical-accuracy knob. A later refinement stage can make it refinable without splitting its config home.
- seed_thicknesses_for(exp_data: str) tuple[float, ...][source][source]¶
Return this dataset’s configured seed thickness tuple.
- model_config = {'extra': 'forbid'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class diffBloch.config.schema.DataSplitConfig(*, train_test: bool = False, val_frac: float = 0.2)[source][source]¶
Bases:
_StrictConfigTrain/validation split declaration.
train_test=False(default) trains on every rotation – no held-out validation set, matching the behaviour of an experiment.yaml that never mentionssplit.train_test=Trueexcludes an evenly spacedval_fracfraction of rotations from the refinement objective (preprocessing still fits their orientation/thickness) and reports their held-out wR2/R_obs alongside the training-set numbers – opt in per experiment, since it means training sees less data.- model_config = {'extra': 'forbid'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class diffBloch.config.schema.LossMetricsConfig(*, residual: Literal['wr2', 'robs'] = 'wr2')[source][source]¶
Bases:
_StrictConfigThe one residual driving the whole pipeline: preprocess search AND refinement.
residualparses into a matchingLossFn/ScoresFnpair (to_loss()/to_scores()) – the scalar the gradient refinement minimises and the per-thickness vectoroptimize_orientation/optimize_thicknesssearch, off the same metric (seediffBloch.engine.losses). A top-levelExperimentConfigfield (not scoped underrefinement) because it governs preprocessing too, not just the gradient stage; only implemented terms are admissible. Only knobs the default path actually consumes live here – outlier rejection, penalty/nuisance weighting, and gradient-norm reporting are not accepted config keys until a consumer reads them, rather than accepted-but-ignored (cf. penalties, which are Python/API composition, not config).- residual: Literal['wr2', 'robs']¶
- to_loss() LossFn[source][source]¶
Parse the residual into the scalar
LossFnthe gradient refinement minimises.
- to_scores() ScoresFn[source][source]¶
Parse the residual into the per-thickness
ScoresFnthe preprocessing search uses.The exact per-thickness form
to_loss()sums to a scalar – seediffBloch.engine.losses– so the two always agree on one metric.
- model_config = {'extra': 'forbid'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class diffBloch.config.schema.OptimizerConfig(*, name: Literal['lbfgs', 'adam', 'adamw'] = 'adam', lr: float = 0.001)[source][source]¶
Bases:
_StrictConfigExplicit optimizer backend for a refinement stage (matches
OptimizerName).- name: Literal['lbfgs', 'adam', 'adamw']¶
- model_config = {'extra': 'forbid'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class diffBloch.config.schema.TrainableConfig(*, positions: Literal['all', 'none'] = 'all', adp: Literal['all', 'none'] = 'all', occupancy: Literal['all', 'none'] = 'none')[source][source]¶
Bases:
_StrictConfigWhole-group trainable selections for a refinement stage.
A 1:1 edge over
TrainableSpec: each group isallornoneand parses (viato_spec()) into anAtomSelection. Element-filtered selections (e.g. freeze H) are not config: they are Python/API composition (seewith_hydrogen_riding()).- positions: Literal['all', 'none']¶
- adp: Literal['all', 'none']¶
- occupancy: Literal['all', 'none']¶
- to_spec() TrainableSpec[source][source]¶
Parse into the
TrainableSpecthe refinement optimizer consumes.
- model_config = {'extra': 'forbid'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class diffBloch.config.schema.ThicknessNNConfig(*, enabled: bool = True, num_samples: int = 40, sample_thickness: bool = False, form: Literal['min_thickness'] = 'min_thickness', min_thickness: float = 100.0, max_thickness: float = 2000.0, init_seed: int = 0)[source][source]¶
Bases:
_StrictConfigRecorded apparent-thickness neural network used by the default refinement path.
- form: Literal['min_thickness']¶
- to_spec() ApparentThicknessNetwork[source][source]¶
Parse the YAML block into its validated value-type.
- model_config = {'extra': 'forbid'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class diffBloch.config.schema.RefinementConfig(*, steps: int = 40, trainable: TrainableConfig = <factory>, optimizer: OptimizerConfig = <factory>, split: DataSplitConfig = <factory>, thickness_nn: ThicknessNNConfig = <factory>)[source][source]¶
Bases:
_StrictConfigStable execution knobs for the default single-stage app refinement (
run refine).These tune the default path; they do not author a scientific program. Scientific composition (hard constraints such as hydrogen riding, soft penalties, freeze-H masks, multi-stage workflows) is expressed as typed Python/API values – see
build_refinement_model(),build_refinement_problem(), andwith_hydrogen_riding()– and is promoted to config only once the default recipe commits to it as stable public behaviour.- trainable: TrainableConfig¶
- optimizer: OptimizerConfig¶
- split: DataSplitConfig¶
- thickness_nn: ThicknessNNConfig¶
- model_config = {'extra': 'forbid'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class diffBloch.config.schema.NelderMeadOptimizationConfig(*, step_size: float = 0.05, max_iterations: int = 60, x_tolerance: float = 0.001, f_tolerance: float = 0.001, penalize_fewer_reflections: bool = True)[source][source]¶
Bases:
_StrictConfigBounds for the
optimize_orientationlocal Nelder-Mead search (preprocess).The YAML edge: parses (via
to_search()) into the validatedNelderMeadSearchvalue-type.- to_search() NelderMeadSearch[source][source]¶
Parse into the validated value-type the pure
optimize_orientationconsumes.
- model_config = {'extra': 'forbid'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class diffBloch.config.schema.OrientationOptimizationConfig(*, nelder_mead: NelderMeadOptimizationConfig = <factory>)[source][source]¶
Bases:
_StrictConfigBounds for the
optimize_orientationorientation search (preprocess).The YAML edge: parses (via
to_search()) into the validatedNelderMeadSearchvalue-type the pureoptimize_orientationconsumes (thenelder_meadblock), and delegates all validation there (one rule home, no drift).- nelder_mead: NelderMeadOptimizationConfig¶
- to_search() NelderMeadSearch[source][source]¶
Parse into the validated value-type the pure
optimize_orientationconsumes.
- model_config = {'extra': 'forbid'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class diffBloch.config.schema.ThicknessOptimizationConfig(*, min_thickness: float = 5.0, max_thickness: float = 2000.0, n_steps: int = 100, plot: bool = False)[source][source]¶
Bases:
_StrictConfigBounds for the
optimize_thicknessper-rotation grid search (preprocess).The YAML edge: parses (via
to_grid()) into the validatedThicknessGridvalue-type the pureoptimize_thicknessconsumes, and delegates all validation there (one rule home, no drift). Defaults derive from that value-type (_THICKNESS_GRID_DEFAULTS), so the boundary value cannot drift from it either.plotis reporting-only – it selects whether the CLI attaches aThicknessPlotLogger(one wR2-vs-thickness PNG per rotation, default<inputs.structure's directory>/thickness_optim); it never changes the fittedPlan, sodataset_config_digest()excludes it explicitly even when the rest of this block is in scope.- to_grid() ThicknessGrid[source][source]¶
Parse into the validated value-type the pure
optimize_thicknessconsumes.
- model_config = {'extra': 'forbid'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class diffBloch.config.schema.PreprocessConfig(*, optimize_orientation: bool = True, optimize_thickness: bool = True, stage_order: Literal['orientation_first', 'thickness_first']='thickness_first', orientation: OrientationOptimizationConfig = <factory>, thickness: ThicknessOptimizationConfig = <factory>)[source][source]¶
Bases:
_StrictConfigPreprocess-stage configuration (the
Plan -> Plancalibration pipeline).Grouping, not composition: each block configures one preprocess step. Only steps the default run composes get a config block here:
optimize_orientationunderorientationandoptimize_thicknessunderthickness. The optionalconverge_numericsdriver is not in the default recipe, so it has no config block – a caller that composes it constructsConvergenceTest/ConvergenceToleranceat the composition site (which carry their own defaults). Opt-in step config lives with the step, not in an always-present block.- stage_order: Literal['orientation_first', 'thickness_first']¶
- orientation: OrientationOptimizationConfig¶
- thickness: ThicknessOptimizationConfig¶
- model_config = {'extra': 'forbid'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- diffBloch.config.schema.dataset_checkpoint_stem(ref: str) str[source][source]¶
The per-dataset checkpoint name stem for an
exp_dataref:plan.<stem>.npz.Path separators become
__and a.cif_petssuffix is dropped, e.g.undamaged/frame_1.cif_pets -> undamaged__frame_1. Checkpoint identity follows the file, not its position inexp_data, so reordering or inserting datasets never restales another dataset’s checkpoint. Two refs may sanitize to the same stem (a/bvsa__b);Inputsrejects such configs up front rather than letting two datasets share one checkpoint on disk.
- class diffBloch.config.schema.Inputs(*, structure: str, exp_data: str | list[str], multi_dataset: bool = False, load_hydrogens: bool = False, isotropic_displacements_only: bool = False)[source][source]¶
Bases:
_StrictConfigInput references — relative to the experiment directory only (no project-root paths).
- model_config = {'extra': 'forbid'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class diffBloch.config.schema.ExperimentConfig(*, name: str, inputs: Inputs, sample: SampleConfig = <factory>, blochwave: BlochwaveConfig = <factory>, preprocess: PreprocessConfig = <factory>, loss_metrics: LossMetricsConfig = <factory>, refinement: RefinementConfig = <factory>)[source][source]¶
Bases:
_StrictConfigA whole experiment, validated at load. No Hydra, no
DictConfig.- sample: SampleConfig¶
- blochwave: BlochwaveConfig¶
- preprocess: PreprocessConfig¶
- loss_metrics: LossMetricsConfig¶
- refinement: RefinementConfig¶
- to_declaration(integrations: Sequence[IntegrationGeometry]) ExperimentDeclared[source][source]¶
Project the result-determining knobs onto the run’s declaration event.
One more
to_*edge alongsideBlochwaveConfig.to_policy()/to_absorption(): the config already owns every value here, so mapping them lives with it rather than in whichever caller happens to emit the event. Any backend (W&B/Comet hyperparameters, the written summary) reads the run’s settings from this one event instead of being handed the config object.integrationsis one PETS-derived geometry perinputs.exp_dataentry, in that order.
- model_config = {'extra': 'forbid'}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- diffBloch.config.schema.load_config(path: str | Path) ExperimentConfig[source][source]¶
Parse and validate one
experiment.yaml.Fails fast with a
pydantic.ValidationErrorat the boundary, rather than a deferred runtime surprise deep in the pipeline.
Experiment locks hash input bytes only; the preprocess and refinement locks hash generated artifacts.
Experiment lock and checkpoint-lock helpers.
experiment.lock identifies input bytes only; the preprocess and refinement locks identify
generated artifacts and execution identity. Keeping those separate avoids circular provenance and
keeps cache keys stable.
- class diffBloch.config.manifest.InputLock(*, ref: str, sha256: str, bytes: int)[source][source]¶
Bases:
BaseModelHash and size for one input reference.
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class diffBloch.config.manifest.ExperimentLock(*, structure: InputLock, experimental_data: InputLock | list[InputLock])[source][source]¶
Bases:
BaseModelexperiment.lock: exact input identity, never generated outputs.experimental_datais one lock for a single dataset or a list ininputs.exp_dataorder for a pooled (inputs.multi_dataset) experiment.- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class diffBloch.config.manifest.ArtifactHash(*, path: str, sha256: str, bytes: int, media_type: str)[source][source]¶
Bases:
BaseModelHash and media metadata for a generated run artifact.
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class diffBloch.config.manifest.RecipeStep(*, name: str, params: dict[str, Any] | None = None)[source][source]¶
Bases:
BaseModelOne step’s identity in a preprocess recipe: its name + serialized params (or
None).Mirrors
StepRecordas plain, comparable data – the lock stores the recipe as a readable list of these, decoupled from the preprocess step vocabulary.- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class diffBloch.config.manifest.PreprocessLock(*, structure: InputLock, experimental_data: InputLock, authoritative_cell: CellParameters, ignored_rotations: tuple[int, ...], config_digest: str, code_version: str, recipe: list[RecipeStep], plan: ArtifactHash)[source][source]¶
Bases:
BaseModelplan.<stem>.lock: binds one dataset’sPlancheckpoint to everything that determined it.A checkpoint is safe to reuse only when the current run matches on every axis – the structure and this dataset’s input bytes, the authoritative PETS cell shared by the experiment, the dataset-scoped config projection (
dataset_config_digest()), this dataset’s file-local ignored rotations, the software version, and the composed recipe – AND the.npzverifies againstplan. The identity is deliberately per dataset: nothing here hashes the wholeexperiment.lockor the fullinputs.exp_datalist, so adding, removing, or reordering other datasets in a pooled experiment never restales this one’s checkpoint unless the first dataset’s authoritative cell changes. The recipe axis distinguishes checkpoints built from the same inputs and config by different step sequences;code_versionis the software-implementation axis the recipe (step shape + params) cannot capture. The fullcode_versionstring (__version__+g<sha>[.dirty]) is recorded here as a build stamp, but the reuse gate compares only its release__version__(seepreprocess_lock_status()), so the checkpoint survives commits within a release. Identity only: hashes + a readable recipe, never payload.- authoritative_cell: CellParameters¶
- recipe: list[RecipeStep]¶
- plan: ArtifactHash¶
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class diffBloch.config.manifest.RefinementLock(*, plan_lock_sha256s: list[str], refinement_config_digest: str, code_version: str, refined_structure: ArtifactHash, refined_parameters: ArtifactHash)[source][source]¶
Bases:
BaseModelrefinement.lock: binds refined-structure outputs to everything that produced them.The refinement-stage counterpart to
PreprocessLock. Refinement runs on top of already-settled per-datasetPlans, so everything that determines those – inputs, sample, blochwave, preprocess config, recipe – is already pinned byplan_lock_sha256s(the hashes of the exactplan.<stem>.lockfiles this run refined from, ininputs.exp_dataorder). What this lock adds is what refinement itself contributes on top: the refinement-determining config (refinement_config_digest(), which includes the train/valsplit– the split partitions rotations at refinement time and no longer shapes the checkpointed plans) and the code version that ran it, plus hashes of the refined outputs. Verifiable independently of whether the plan locks are still present or match –plan_lock_sha256sis a recorded fact about that run, not a live re-check.- refined_structure: ArtifactHash¶
- refined_parameters: ArtifactHash¶
- model_config = {}¶
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- diffBloch.config.manifest.sha256_file(path: str | Path) str[source][source]¶
Return the SHA256 hex digest for
path.
- diffBloch.config.manifest.input_lock_for(path: str | Path, *, ref: str) InputLock[source][source]¶
Build an
InputLockfor an experiment input file.
- diffBloch.config.manifest.artifact_hash_for(path: str | Path, *, root: str | Path) ArtifactHash[source][source]¶
Build an
ArtifactHashfor a generated run artifact.
- diffBloch.config.manifest.load_experiment(directory: str | Path) tuple[ExperimentConfig, ExperimentLock][source][source]¶
Load
experiment.yaml, verifyingexperiment.lock(inreproducibility/) against input bytes, creating that lock first from the current input bytes if it doesn’t exist yet.First-run convenience: a brand-new experiment directory has no lock to verify against, so there is nothing to protect by refusing to proceed. The lock is created here instead, exactly as
write_experiment_lock()would. An existing lock that no longer matches the input bytes still raises (see_verify_input()): that mismatch is the drift this file exists to catch, and silently rewriting it on every run would defeat the purpose. Delete the lock and rerun, or usediffbloch lock-experiment --force(orwrite_experiment_lock(..., force=True)), to update it after an intentional input change.
- diffBloch.config.manifest.write_experiment_lock(directory: str | Path, *, force: bool = False) ExperimentLock[source][source]¶
Hash
inputs.structureand everyinputs.exp_datafile and writereproducibility/experiment.lock(creating that directory if needed).The
diffbloch lock-experimentCLI command’s implementation. By default this creates a lock only when none exists yet: an existing lock is the experiment’s accepted input baseline, so replacing it requiresforce=True. Useforce=Trueonly after an intentional input change. With unchanged inputs, a forced rewrite reproduces the lock byte-for-byte. If the input bytes changed, replacing the experiment lock invalidates existing plan and refinement locks.
- diffBloch.config.manifest.dataset_config_digest(config: ExperimentConfig, *, exp_data: str) str[source][source]¶
SHA256 of the config that determines one dataset’s settled
Plan– its lock identity.Keyed on the resolved
ExperimentConfig(not theexperiment.yamlbytes): stable under comment/whitespace/field-order edits, sensitive to any validated-value change in scope.sort_keysmakes it order-independent.Scope is an explicit projection onto exactly what determines the settled per-dataset
Plan– so a committed checkpoint is restaled only by a change that could alter it:inputs– rewritten to this dataset’s view:exp_datais the singleexp_dataref the checkpoint belongs to (never the full list – other datasets joining or leaving the pool cannot alter this one’s plan), andmulti_datasetis dropped for the same reason (a dataset’s settled plan is independent of whether it is pooled);sample,blochwave– shape the grid and beams;blochwave.ignore_orientationsis dropped here because it indexes the pooled rotation space – the translated file-local slice lives explicitly inPreprocessLock.ignored_rotationsinstead, so an ignore edit restales exactly the datasets it lands on;preprocess– shapes and configures the fitting steps, butorientation/thicknessonly when the matchingoptimize_orientation/optimize_thicknessflag enables that step (the step’s own params already ride in the recipe axis whenever it actually runs – seeas_step()– so including them here unconditionally would restale a checkpoint over config that provably never touched it), and always excludingthickness.plot(reporting-only, never touches the Plan even whenthicknessis in scope);loss_metrics– the residualoptimize_orientation/optimize_thicknesssearch minimises (to_scores()).
Everything else is excluded because it cannot change the Plan:
name(a label), and all ofrefinement– includingsplit, which partitions rotations at refinement time and no longer shapes the checkpointed plan (it rides inrefinement_config_digest()). This is the config axis of the per-dataset preprocess lock only, not a whole-config identity.
- diffBloch.config.manifest.refinement_config_digest(config: ExperimentConfig) str[source][source]¶
SHA256 of the refinement-determining config – the refinement lock’s config identity.
The complement of
dataset_config_digest(): everything that function excludes from the per-dataset checkpoint’s identity underrefinement(optimizer/steps/trainable/thickness_nn/split) is exactly what determines the gradient-refined result on top of already-settledPlans, so this hashes the wholerefinementsection.splitbelongs here (not in the preprocess digest) because the train/validation partition is applied when the pooled plan is handed to refinement – it never shapes a checkpointed per-dataset plan.loss_metricsis a top-levelExperimentConfigfield (not underrefinement, so this dump never sees it): it determines the preprocess search, so it belongs solely todataset_config_digest().
- diffBloch.config.manifest.code_version() str[source][source]¶
The software-version identity of the compute (checkpoint validity + run-manifest stamp).
Returns
diffBloch.__version__, best-effort suffixed with the git short-SHA and a.dirtymarker when running inside a checkout. Falls back to the bare version in an installed wheel (no git / no repo). This full string is the stamp recorded in the run manifest and the checkpoint lock (it says exactly which build produced an artifact). The checkpoint reuse gate, however, keys only on the release__version__(see_release()), so a committed checkpoint stays reusable across commits within a release – the SHA/.dirtydetail is recorded but does not invalidate. The trade-off is a weaker guard: a physics change without a version bump reuses; release discipline plus--refresh(regenerate) is the escape hatch.
- diffBloch.config.manifest.write_preprocess_lock(path: str | Path, lock: PreprocessLock) None[source][source]¶
Write
plan.lockin a stable, human-readable form (besideexperiment.lock).
- diffBloch.config.manifest.read_preprocess_lock(path: str | Path) PreprocessLock[source][source]¶
Read a
plan.lockwritten bywrite_preprocess_lock().
- diffBloch.config.manifest.write_refinement_lock(path: str | Path, lock: RefinementLock) None[source][source]¶
Write
refinement.lockin a stable, human-readable form (besiderefined_structure.cif).
- diffBloch.config.manifest.read_refinement_lock(path: str | Path) RefinementLock[source][source]¶
Read a
refinement.lockwritten bywrite_refinement_lock().
- diffBloch.config.manifest.preprocess_lock_status(lock: PreprocessLock, *, structure: InputLock, experimental_data: InputLock, authoritative_cell: CellParameters, ignored_rotations: tuple[int, ...], config_digest: str, code_version: str, recipe: list[RecipeStep], plan_path: str | Path, root: str | Path) Literal['reuse', 'resume', 'stale'][source][source]¶
How the checkpoint
lockrelates to the current run’srecipe– the resume verdict."stale"unless the non-recipe axes all match (the structure and this dataset’s input bytes, the authoritative PETS cell, the file-local ignored rotations, the dataset config digest, and the release portion of the software version –_release(), so a differing git SHA within the same release still matches) AND the.npzverifies against the lock’sArtifactHash(a tampered/missing checkpoint is stale). Input identity comparessha256/bytesonly, neverref: renaming a dataset file without changing its bytes moves its checkpoint (new stem) but a lock whose recorded ref differs while the bytes match is still the same measurement. Given those hold:"reuse"when the recipe is identical – the snapshot is exactly this run’s output."resume"when the lock’s recipe is a proper prefix ofrecipe– the run appends steps, so resume from the snapshot and run only the suffix (append-only / tail resume)."stale"otherwise (a middle step differs, or the lock’s recipe is longer).
The caller must refuse recipes containing an opaque step before reaching here (those can never be safely reused); this function assumes a clean, comparable recipe.