The public result schemas and scientific meanings are defined in SCIENTIFIC_CONTRACTS.md. The CLI/GUI entry points and validation workflow are documented in the GUI guide, README, and release procedure.
diffractscout.workflow provides the same durable jobs used by the JSON CLI,
local MCP adapter and desktop Tasks page. It wraps existing scientific routines;
the ordinary functions below remain available for direct synchronous use.
See the agent pipeline guide for request semantics,
recovery and scientific interpretation.
import json
from pathlib import Path
from diffractscout import workflow
request_file = Path("examples/agent/analyze-local.json").resolve()
request = workflow.WorkflowRequest.from_dict(
json.loads(request_file.read_text(encoding="utf-8")),
base_dir=request_file.parent,
)
plan = workflow.plan_workflow(request)
if not plan["ready"]:
raise RuntimeError(plan["diagnostics"])
workspace = Path("outputs/agent-workspace").resolve()
job = workflow.submit_job(request, workspace=workspace)
run_id = job["run_id"]
print(run_id, workflow.status_job(run_id, workspace=workspace))Poll status_job until work reaches a terminal state; then inspect diagnostics
and call get_result(run_id, workspace=workspace) for verified artifact metadata.
For a short foreground call, use run_job(request, workspace=workspace) instead
of submit_job. Runtime credentials can be passed as api_key= or supplied
through MP_API_KEY; requests must not contain credentials.
| Function | Purpose |
|---|---|
context(), schema("request") |
Discover capabilities/dependencies and current request fields/defaults |
plan_workflow(request) |
Check local readiness without execution, network access or writes |
run_job(request, workspace=...), submit_job(request, workspace=...) |
Execute in the foreground or submit background work |
list_jobs(workspace=...), status_job(run_id, workspace=...) |
Inspect durable history and progress |
read_events(run_id, workspace=..., after=0, limit=100) |
Read event pages; continue from next_cursor |
resume_job(run_id, workspace=..., background=True), cancel_job(run_id, workspace=...) |
Resume with integrity checks or request cooperative cancellation |
get_result(run_id, workspace=..., table=None, offset=0, limit=100, verify=True) |
Verify artifacts and optionally read a named table page |
load_result_object(run_id, workspace=...) |
Verify and reconstruct the published result object for Python consumers |
run_batch(requests, workspace=..., max_workers=1, background=False) |
Run independent requests serially by default, with explicit finite concurrency |
Pass a WorkflowRequest or request mapping. Use WorkflowRequest.from_dict
with base_dir when the mapping came from a file; ordinary mappings resolve
relative paths from the calling process directory. Omitted output_dir selects
a unique service-managed output. Workspace paths are independent of that base.
schema() and the service validate the contract; duplicating its full field
list in a caller is unnecessary. Batch requests use separate output targets and
retain individual job identities. load_result_object returns the result type
for that workflow; get_result provides a common artifact/table interface
across all workflow types, including fetch and adapt.
from diffractscout import AnalysisSettings, analyze_cifs
result = analyze_cifs(
["cifs/"],
"outputs/run_01",
settings=AnalysisSettings(
input_mode="energy",
energy_keV=83.0,
two_theta_min_deg=0.5,
two_theta_max_deg=15.0,
step_deg=0.005,
fwhm_deg=0.03,
profile_eta=0.5,
include_elasticity=True,
max_profile_points=500_000,
max_reflection_estimate=1_000_000,
),
)
print(result.manifest_path)
for diagnostic in result.diagnostics:
print(diagnostic.level, diagnostic.stage, diagnostic.item, diagnostic.message)analyze_cifs:
- validates run-wide radiation, scan, profile, and resource settings before writes;
- resolves and deduplicates CIF inputs;
- rejects overlap between input and output trees;
- copies source artifacts into a staging result directory;
- pairs elastic sidecars only when
include_elasticity=True; - analyzes each readable phase while recording per-phase diagnostics;
- writes CSV, optional XLSX, provenance, and a manifest;
- verifies the staged bundle;
- atomically moves it into the requested target.
It returns a PipelineResult. An invalid phase can be recorded in diagnostics while other phases complete. CLI exit status is 0 when all analyzable items complete, 3 when a usable bundle contains error diagnostics for one or more items, and 2 when no phase is analyzable or a fatal input/configuration error occurs.
from diffractscout.quick_export import quick_export
result = quick_export(
["cifs/"],
"outputs/energy_bundle",
energy_keV=20.0, # infers input_mode="energy"
include_excel=False,
)quick_export accepts the AnalysisSettings fields as keyword overrides. A
single energy_keV or wavelength_A keyword infers its matching radiation
mode and clears the irrelevant counterpart before analysis. Supplying both,
or supplying one that conflicts with an explicitly supplied input_mode
keyword, raises before the output directory is created. Radiation keywords
override the prior mode in a supplied settings=AnalysisSettings(...) object;
the intentional source_preset="Custom" plus wavelength_A source-mode
contract remains available. Irrelevant wavelength/energy fields are cleared
after selecting or merging settings, immediately before analyze_cifs validates
them: energy mode clears wavelength_A, wavelength mode clears energy_keV,
and source mode follows its preset semantics (built-in sources clear both;
Custom keeps its wavelength and clears energy_keV). Contradictory explicit
radiation overrides still raise; normalization does not loosen conflict
validation.
The .xlsx shortcut remains atomic and does not replace an existing workbook
unless overwrite=True is explicit. Combining an .xlsx target with
include_excel=False raises before output is created; use a directory target
for a CSV-only bundle. If publishing the separate workbook fails after the
bundle is generated, the error identifies the retained bundle workbook.
Bundle Summary retains two_theta_range_deg as the requested settings range,
adds the explicit alias requested_two_theta_range_deg, and separately records
the configured per-phase bounds as analysis_two_theta_range_deg and actual
sample endpoints as profile_sampled_two_theta_range_deg. It also adds
effective_two_theta_range_deg, effective_wavelength_A,
effective_energy_keV, effective_radiation_source, and
source_preset_applied. These fields describe the post-filter analysis when a
phase is available; an empty/no-analysis bundle remains serializable.
import os
from diffractscout.models import DiscoverySettings
from diffractscout.pipeline import discover_candidates
from diffractscout.providers.materials_project import MaterialsProjectProvider
provider = MaterialsProjectProvider(os.environ["MP_API_KEY"])
discovery = discover_candidates(
"Ti-Al-V",
provider,
settings=DiscoverySettings(
mode="near_stable",
e_hull_max_eV_atom=0.05,
max_subsystem_order=3,
max_subsystems=4096,
max_per_subsystem=100,
max_total=50,
),
)DiscoveryResult contains normalized input, queried subsystems, per-subsystem counts, a deterministic candidate list, provider metadata, and warnings. Invalid negative/non-finite energy limits and non-positive count limits are rejected before provider access. The total number of proposed subsystem queries is calculated before materializing the query list; values above max_subsystems are rejected to prevent combinatorial expansion in high-component systems.
from diffractscout.pipeline import export_discovery
result = export_discovery(
"Ti-Al-V",
provider,
"outputs/ti_al_v_candidates",
discovery_settings=DiscoverySettings(max_total=50),
)This produces a verifiable result bundle without downloading structures.
from diffractscout.pipeline import run_pipeline
result = run_pipeline(
"Ti-Al-V",
provider,
"outputs/ti_al_v",
discovery_settings=DiscoverySettings(
mode="near_stable",
e_hull_max_eV_atom=0.05,
max_total=50,
),
analysis_settings=AnalysisSettings(
input_mode="source",
source_preset="Cu Ka",
),
conventional_unit_cell=True,
include_elasticity=True,
confirm_above=50,
)Downloads above confirm_above require authorize_large_download=True. Automatic Materials Project elasticity coupling requires a conventional-standard cell. A primitive-cell run must set include_elasticity=False.
run_pipeline(..., include_elasticity=...) treats the keyword as an optional
override. An omitted override honors AnalysisSettings; an explicit boolean
overrides it:
- when the override is omitted (
None) andanalysis_settingsis supplied, the value inanalysis_settings.include_elasticityis honored; - an explicit
TrueorFalseoverrides that setting; - when both
analysis_settingsand the override are omitted, elasticity remains enabled by default (True).
output_dir: committed bundle directory;discovery: optionalDiscoveryResult;downloads:DownloadArtifactrecords with separate CIF and elasticity status;analyses: successfulPhaseAnalysisrecords;manifest_path: committed manifest path;warnings: deduplicated warning/error summaries;diagnostics: structured stage/item/severity/message records.
- radiation:
input_mode,source_preset,wavelength_A,energy_keV; - window/profile:
two_theta_min_deg,two_theta_max_deg,step_deg,fwhm_deg,profile_eta; - elasticity:
include_elasticity; - safety:
max_profile_points,max_reflection_estimate.
- search:
mode,e_hull_max_eV_atom,exclude_deprecated; - subsystem scope:
max_subsystem_order,max_subsystems; - result scope:
max_per_subsystem,max_total.
max_subsystems defaults to 4096 and is checked before provider access. Raising it should follow a review of the element count, subsystem order, provider rate limits, and intended research scope.
diffractscout.composition.parse_composition_text(text)diffractscout.composition.chemsys_subsystems(elements, max_order=None)diffractscout.selection.validate_discovery_settings(settings)diffractscout.diffraction.validate_analysis_settings(settings)diffractscout.structure.load_structure(path)diffractscout.elasticity.discover_elastic_tensor(cif_path)diffractscout.elasticity.validate_elastic_tensor(matrix_GPa, ...)diffractscout.elasticity.young_modulus_hkl_normal_GPa(tensor, cell, hkl)diffractscout.diffraction.resolve_wavelength(settings)diffractscout.diffraction.simulate_powder_pattern(structure, settings, elastic_tensor=None)diffractscout.validation.verify_bundle(path)
Scientific meanings and units are defined in docs/SCIENTIFIC_CONTRACTS.md.
diffractscout.phase_cif.fetch_prototypes(composition, output_dir, ...) copies
alpha (space group 194), beta (229), or alpha-double-prime (63) prototypes into
stable filenames and writes prototype_index.csv. It does not assign the
requested alloy composition. The first parsed element is the host unless host is supplied. There is no
energy-above-hull cutoff. When no eligible Cmcm candidate is available,
alpha-double-prime uses the packaged COD 1523304 scaffold.
diffractscout.phase_cif.adapt_cif(source, destination, ...) writes a new CIF.
Composition comes from nominal="tc4" (also ti64 or ti-6al-4v),
weight_percent, or atomic_percent. Lattice arguments and fract require
citation. The source file is unchanged. analyze, discover, and run do
not call either function.
from diffractscout import prepare_cifs
result = prepare_cifs("TC4", "outputs/TC4_initial", offline=True)
print(result.exit_code, result.report_path)prepare_cifs accepts nominal, weight_percent, atomic_percent, host,
phases, templates, parameter_file (JSON) or phase_parameters (mapping),
offline, api_key, provider, max_subsystems, max_prototype_attempts
and preview_wavelength_A.
Known Ti-6Al-4V grade aliases default to nominal composition only in this API;
other alloys require explicit percentages. The largest atomic fraction selects
the host; equal major fractions need an explicit host. Explicit phase chemistry
overrides the bulk starting assumption and remains identifiable in provenance.
The result is PrepareCifsResult with per-phase records, artifact paths and
exit_code (0 complete, 3 partial, 2 no usable CIF). A complete result can still
retain prototype lattice parameters; read lattice_basis and the guide.
The function preserves raw sources, validates symmetry and single-orbit
topology, creates a peak preview, and publishes a verified new directory.
It does not infer equilibrium chemistry, read papers, or alter analyze/run.
See the initial CIF contract.
from diffractscout.benchmark import (
run_reference_benchmarks,
verify_benchmark_bundle,
)
report = run_reference_benchmarks(
"outputs/analytic_benchmark",
overwrite=False,
)
assert report["all_passed"]
assert verify_benchmark_bundle("outputs/analytic_benchmark")["ok"]The benchmark bundle contains the exact synthetic CIF fixtures, machine-readable expectations, 45 individual checks, tolerances, package versions, portable runtime metadata, Markdown/JSON reports, and a SHA-256 manifest. Setting SOURCE_DATE_EPOCH fixes generated timestamps for reproducible evidence artifacts.
A provider implements:
class PhaseProvider(Protocol):
name: str
def search_subsystem(...): ...
def download_candidates(...): ...
def metadata(self) -> dict[str, object]: ...A provider must preserve source identity and distinguish service failure from a valid no-data result. It must not synthesize missing property values. DownloadArtifact supports separate status/error and elasticity_status/elasticity_error fields for this purpose.
from diffractscout.validation import verify_bundle
report = verify_bundle("outputs/run_01")
if not report["ok"]:
raise RuntimeError(report["errors"])Verification checks every declared hash and size and rejects unsafe paths, duplicates, symlinks, root escapes, missing files, modified files, and unlisted files.