# How PowderLine works In a GUI refinement program, the model lives in a project and a session. You load data, set flags, refine, look and adjust. The steps you took are in your head, or in a project file only that program can read. PowderLine makes the whole refinement **structured data**. A recipe in gives a result out, the same way every time and whoever (or whatever) supplied the recipe. Five ideas make that work. ## 1. A recipe is the whole refinement, as structured data A recipe is a nested dictionary with a fixed shape: ```mermaid flowchart TB subgraph recipe["recipe"] direction TB F["Frame
schema_name: gsasii.rietveld
schema versions · metadata"] subgraph payload["payload"] direction LR C["Shared by every engine
data (2θ, I, weights)
fit window
phases: space group, cell, atoms"] N["The engine's own
instrument · background
scale, broadening
refinement controls"] end end ``` Because it is structured data, a recipe can be built by a script, stored in a database, sent between services, filled from a live data stream, or written by a GUI or an LLM. In Python it is a `dict`. When it is saved or sent it is JSON. The data arrays are inside the recipe, so nothing depends on file paths. ### Every refinable number carries its flag A refinable value is a short list: the value, then `true` to refine it or `false` to hold it. Engines that accept limits take two more entries, a minimum and a maximum (`null` for no limit): ::::{tab-set} :sync-group: engine :::{tab-item} GSAS-II :sync: gsasii ```json "a": [4.15682, true] ``` `[value, refine]`. GSAS-II recipes have no limits. ::: :::{tab-item} TOPAS :sync: topas ```json "a": [4.15682, true, 4.10, 4.20] ``` `[value, refine, min, max]`. ::: :::{tab-item} easydiffraction :sync: easydiffraction ```json "a": [4.15682, true, null, null] ``` `[value, refine, min, max]`; `null` means no limit. ::: :::: Support differs per engine: GSAS-II takes no limits, and what a limit means differs between TOPAS and easydiffraction (see [Recipe format](../reference/recipe-format.md#bounds)). → More in [Recipes](recipes.md). ## 2. One recipe, one engine The recipe's `schema_name` names the engine and the workflow, for example `gsasii.rietveld` or `topas.spf`. The instrument, background and controls are written in **that engine's own terms and units**. GSAS-II's `U` is in centidegrees², TOPAS's is in degrees². A GSAS-II recipe states GSAS-II's numbers. PowderLine never translates between engines. Translating would mean guessing, and guessing is how two programs end up refining two different models. The structural part (space group, cell, atoms) is shared, so a structure carries over from one engine's recipe to another's. [Recipe format](../reference/recipe-format.md) says exactly what moves and what must be rewritten. ## 3. Check first, then run ```mermaid flowchart LR A[recipe] --> V{"Validate
no engine needed"} V -- problems --> X["Every error listed
at its field, with the fix"] V -- valid --> W["+ warnings
(defaults applied, notes)"] W --> R["Run in the engine
named by the recipe"] R -- engine failed --> F["Error raised
+ failure record"] R -- success --> S["Results
tables · uniform Rwp
output recipe"] ``` **Validation** reads the recipe against a strict definition of what it may contain (its *schema*). It also checks the crystallography: does the cell fit the space group? Is each atom exactly on a special position, or clearly off it? Is every parameter tied by symmetry stated, with the same flag? Validation **never changes your recipe quietly**. Suppose an engine would silently alter something: snap a coordinate, raise a value to a floor, or misread a space-group setting. PowderLine then refuses the recipe and tells you what to write instead. Validation needs no engine installed. A recipe can be checked in a web service, a notebook or a pipeline before it goes to the machine that runs it. Because every error names its field and its fix, a program or an LLM can correct its own recipe. :::{important} Validation checks that a recipe is **well-formed and consistent with its symmetry**. It does not judge whether your refinement choices are sensible. See [Good refinement practice](refinement-practice.md). ::: → More in [Validation](validation.md). ## 4. Results you can compare and chain Every engine returns the same things: - **The same tables**: refined parameters with uncertainties, unit cell, reflection list, the fitted profile. - **The same statistics**: PowderLine computes Rwp, Rexp and GoF itself, over the same data points, whichever engine ran. The engine's own values are kept alongside. - **An output recipe**: your recipe with the refined values written back. It is a valid recipe, ready to run again. Results come back as Python objects (tables as DataFrames), and are also written as files. The output recipe makes automation simple: each step's result is the next step's input. ```mermaid flowchart LR C["Calibrant
LaB6 recipe"] -- "refined instrument" --> S1["Sample
25 °C"] S1 -- "output recipe
+ next pattern" --> S2["Sample
50 °C"] S2 -- "output recipe
+ next pattern" --> S3["…"] ``` → More in [Results](results.md) and [Run a series](../how-to/sequential-refinement.md). ## 5. Metadata and provenance travel with the analysis A recipe can carry a free-form `metadata` block: a sample ID, a scan ID, the data's location in your catalog, how the recipe was generated. PowderLine never reads it, but carries it through. Every run gets its own ID. The output recipe records which run produced it, which engine and version ran, and which PowderLine version. It records only that one run, so to trace a whole chain of analyses, keep each step's input recipe and result together. ```mermaid flowchart LR DATA["Measurement
scan ID in metadata"] --> R1["Recipe"] R1 --> RUN1["Run
run_id = A"] RUN1 --> O1["Output recipe
parent_run_id = A
engine, versions"] O1 --> RUN2["Run
run_id = B"] ``` → More in [Results](results.md#provenance). ## Where the engines fit PowderLine contains no refinement code. Each engine is reached through a **gateway**: a small adapter that knows that engine's recipe format, how to drive it and how to read its output. ```mermaid flowchart TB U["Any driver: GUI, notebook,
pipeline, agent"] --> API["PowderLine core
recipe checks · statistics · reports"] API --> GG["GSAS-II gateway"] --> G[(GSAS-II)] API --> TG["TOPAS gateway"] --> T[(TOPAS)] API --> EG["easydiffraction gateway"] --> E[(easydiffraction)] ``` You install only the engines you use. Adding a fourth engine means writing a fourth gateway; nothing else changes. See [Architecture](../development/architecture.md). ## Further reading - The PowderLine [preprint](https://doi.org/10.48550/arXiv.2608.17009) describes the design and its motivation. - [Comparing engines](comparing-engines.md) fits the same data in every engine and explains why the numbers differ.