Skip to content

Re/05 topas schema - #6

Merged
AdamCorrao merged 16 commits into
refactor/multi-enginefrom
re/05-topas-schema
Oct 9, 2026
Merged

AdamCorrao merged 16 commits into
refactor/multi-enginefrom
re/05-topas-schema

Conversation

@AdamCorrao

@AdamCorrao AdamCorrao commented Oct 9, 2026 •

Copy link
Copy Markdown
Owner

re/05: native topas.* schemas, writer and runner (TOPAS 6)

Merges re/05-topas-schema into refactor/multi-engine (base 139e72d, re/04
merged; tip 240147e, 16 commits: 11 for the branch, 5 from its review).

Native TOPAS recipes (topas.rietveld, topas.spf) can now be validated,
written to an INP, run on TOPAS, and turned into the same standard result dict
and report files as the GSAS-II gateway. The branch declares TOPAS 6 only
(A143): the only TOPAS available is TOPAS-64 Version 6. Everything that needs
TOPAS was checked on it through five probe packs (devkit
probes/re05_topas/pack{1..5}).

The 0.26.0 GSASII_* → TOPAS path is unchanged; it keeps its >=7,<8 gate
and its v7.25 references until re/07 (A146).

What changes

Package move (A136).

  • powderline.topas is now powderline.gateways.topas, with no shim.
  • The topas-kicker entry point and pixi task follow.
  • An existing checkout needs pixi reinstall powderline.

Native schemas (gateways/topas/schema.py; draft rev 3 signed as a set,
A147). The recipe uses TOPAS's own keywords, macro parameters and units (A142),
and PowderLine fills in no defaults: a block left out is not modelled.

  • engine_version is a required field; only "6" is supported (A144). A run
    needs the installed version declared (topas_version or config) and equal
    to it (A145).
  • Bounds: a stated min/max replaces TOPAS's default limit on that side
    (A138). A refined start value outside a default limit on an open side is a
    validation error, because TOPAS would move it silently.
  • Instrument:
    • radiation: lam with lh required; lg only together with lh.
    • geometry: Rp/Rs.
    • corrections, keyed by macro: Zero_Error, LP_Factor /
      LP_Factor_Synchrotron, Simple/Full axial, capillary (parallel or
      divergent beam).
    • broadening: TCHZ / PV / PVII peak types.
  • Background: Chebyshev, One_on_X, and pv/spv/spvii background peaks.
  • TopasPhase:
    • scale;
    • CS_L/CS_G and Strain_L/Strain_G, reported with TOPAS's LVol-IB
      and e0;
    • Uiso written as beq, Uaniso as u_ij.
  • Space groups: 541 accepted and 23 refused, checked against TOPAS 6's own
    operator output.
  • SPF: each peak is the instrument peak type convolved with its own
    Gaussian/Lorentzian width.

Writer and runner (native_writer.py, native_run.py, gateway routing).

  • One TOPAS parameter per symmetry tie group, with exact tie equations.
  • Index-based names, and macro limits written explicitly.
  • TOPAS 6's exit code says nothing, so success is judged from the output
    files the INP asks for. They are removed before the run, so an earlier
    run's files can never pass; timestamps are not used.
  • TOPAS's files are read strictly. Any non-finite number in the results, the
    profile or a reflection list is a divergence error, in Python or MSVC
    spelling (nan, 1.#QNAN, -nan(ind)). An undetermined ESD is left
    empty.
  • Structured warnings:
    • topas_adp_converted;
    • topas_parameter_at_limit, which names the stated bound or TOPAS's default;
    • topas_parameters_not_varied;
    • topas_phase_contributes_nothing (informative only: a phase absent from
      the sample legitimately contributes nothing);
    • topas_background_not_calculated.

Fit window on data points, every engine (A149, A150).

  • Core fit_limits_on_data hands each engine the first and last weighted data
    points inside fit_range as its limits. An open end, or no fit_range,
    becomes the data's first or last weighted point, so zero-weight edge points
    never widen an engine's window. The data themselves are not cut.

  • The two engines round a limit that falls between data points differently:

    • GSAS-II fits one point past such a maximum (EB-54);
    • TOPAS takes the data point nearest each limit (EB-59).

    With limits on data points, both fit exactly min ≤ 2θ ≤ max, the same points
    core scores.

  • TOPAS's own r_wp now equals core Rwp in all six fixtures. GSAS-II's Rwp
    equals core Rwp on LaB6, for gsasii.rietveld and gsasii.spf. With the
    open ends on weighted points, every engine's Chebyshev basis spans the same
    first and last points.

  • The GSAS-II side is frozen-code ledger row R17 (signed, and annotated
    for the open ends). It changes only the fit-range read in
    executors.run_refinement; the 0.26.0 path is untouched.

  • A fit_range with no positively weighted point is now a validation error.

Background column from a second TOPAS run (A152).

  • TOPAS 6 cannot output the background: Get(bkg) is 0, and phase_out_X is
    TOPAS 7 only.
  • After the fit, PowderLine runs a background-only INP: refined values fixed,
    no phases, iters 0. Its Ycalc becomes y_bkg, with every convolution
    TOPAS applies.

Goldens.

  • tests/data/topas/native/golden/ holds the TOPAS 6 outputs for the six
    hand-written fixtures (A137).
  • tests/test_topas_native_golden.py checks that the writer still emits
    exactly the INPs TOPAS ran, and that the result built from TOPAS's files is
    right.

Docs: docs/SCHEMA_HISTORY.md gains the topas 1.0.0 entry and fit-window
notes for core and gsasii.

Commits

  • 5cde3e7 — move the TOPAS package into gateways/topas (A136).
  • e448e16 — parameters_requested moves into schema_core, re-exported
    from the gsasii fit_report (D12).
  • a357bda — the native schemas.
  • 68f7926 — native writer, runner and gateway routing; six fixtures.
  • 5062d3c — two bugs found by the pack 2 runs:
    • TOPAS's printed 2θ rounded above the recipe value missed its own point;
    • the cell volume was not exported.
  • 720d883 — A149: core rule plus the TOPAS writer.
  • 4347ae0 — A149 for GSAS-II (ledger R17, signed; code hash refreshed).
  • c677b0d — the background-run INP (A152).
  • 8cd5c86 — runner integration of the background run; the analytic
    background code is removed.
  • 450c803 — TOPAS 6 goldens and roundtrip tests.
  • ff17579 — SCHEMA_HISTORY.

From the PR review (record: devkit tasks/re05-pr-review-record.md; decisions
A154–A157):

  • 1a35898 — TOPAS's files read strictly: any non-finite number is a
    divergence (S1).
  • e8b2bf2 — outputs removed before the run; no timestamps (S3).
  • 06cb9a4 — nits: limit messages in recipe units and with a limit that
    validates; CS's 10000 nm ceiling known for limit-hit warnings; SPF window
    test.
  • b5342c8 — open fit-range ends on the first/last weighted point (S2;
    ledger R17 annotation).
  • 240147e — SCHEMA_HISTORY and docstrings.

Most of the +128k lines are test data: six fixtures carrying the full 4096-point
patterns, the space-group list, and the goldens. src/ changes are +2262/−123
lines.

Verification

  • Default suite 2954 passed, 9 skipped. Five of the skips are the DRX_33-only
    golden test on the other fixtures.

  • Every 139e72d baseline ID passes, except the six whose IDs the package move
    renamed; every ff17579 ID passes (one test is now parametrized in two)
    (devkit tasks/re05-prreview-240147e.ids).

  • easydiff suite 55 passed, with IDs identical to the baseline.

  • Undefined-name probe 0; no CRLF; examples/ untouched; code hash current.

  • TOPAS 6 evidence (maintainer's machine):

    • all six fixtures run;
    • TOPAS r_wp = core Rwp over 3767 points;
    • the background run reproduces core's Chebyshev to ≤ 3e-7 where only a
      Chebyshev is modelled;
    • TOPAS's Ycalc is never below the background.
  • DRX_33 cubic a, all fits over the same 3767 points:

    Run cubic a (Å)
    TOPAS 6 4.18244(39)
    TOPAS 7.25, 0.26.0 path 4.18266(39)
    GSAS-II 4.17153(330)

    The two TOPAS versions agree. The GSAS-II difference comes from the engines'
    different profile models; the 0.26.0 paths show the same difference.

Behaviour changes (devkit tasks/deliberate-changes.md rows 22–24)

  • Imports: powderline.topas.* imports break, since the package moved
    without a shim.
  • Native gsasii fits exactly min ≤ 2θ ≤ max. With a maximum that falls
    between data points, Nobs drops by one and refined values move slightly
    (LaB6: scale 5e-6 relative, U 7e-4). Without a fit_range, zero-weight
    points at the data's edges are no longer inside GSAS-II's window.
  • Validation: a fit range with no positively weighted point is an error.

Known, documented, not blocking

  • EB-66: without an axial convolution, TOPAS 6 calculates a wrong value at
    the last fitted point of a background or SPF peak (TOPAS xo_Is peaks).
    • No workaround exists: an exclude also drops the in-window neighbour.
    • y_bkg shows the same value, so it stays consistent with Ycalc.
    • TOPAS 7 is unverified.
  • EB-69: a fixed peak-type term that makes a width negative (e.g. TCHZ
    X < 0 with Y = 0) validates; TOPAS 6 stops on a negative convolution width,
    but whether it does for the peak types is unverified. Documented for now; if
    it does, any negative value becomes a validation error.
  • TOPAS 7 support needs a TOPAS 7 machine; the version set is widened
    explicitly (A143).
  • The native runner end to end on Windows (powderline.run(..., engine="topas"), not only the INPs it writes) is checked by the G4 smoke.
  • Until re/07 removes engine=, a topas.* recipe needs
    engine="topas".
  • For re/10, the user docs:
    • the fit-window rule;
    • the background run;
    • that TOPAS's size/strain terms are not interpretable sizes or strains;
    • that TOPAS's TCHZ letters are not GSAS-II's U..Z.

Decision log: master plan §3, A134–A157. TOPAS 6 findings: devkit
tasks/re05-topas6-compat.md (C1–C36). Engine behaviour: EB-55..69. PR review:
devkit tasks/re05-pr-review-record.md. Execution log entries of 2026-10-09.

Assisted-by: claude-code:claude-opus-5-5
Assisted-by: claude-code:claude-sonnet-5-5

src/powderline/topas/ -> src/powderline/gateways/topas/, verbatim (git
rename, no content change beyond the module path). The entry point
(powderline.gateways:topas), the topas-kicker pixi task and every src/tests
import use the new path; no powderline.topas shim (A136). Prose docs citing
the old path are left for re/10.

An existing checkout needs `pixi reinstall powderline` so the entry-point
metadata points at the new module.

Suite: 2781 passed, 4 skipped (baseline 139e72d identical except six
parametrized IDs that contain the module path).

Assisted-by: claude-code:claude-opus-5-5
The count of independently refined parameters (one per refined symmetry
tie group, A94) now lives in core and accepts both parameter shapes, so
the TOPAS and easydiffraction gateways report it the same way as gsasii.
gateways.gsasii.fit_report re-exports it; behaviour unchanged.

Assisted-by: claude-code:claude-opus-5-5
Engine schema 1.0.0 over the core phase block (Phase[BoundedRefinableParameter]):
native TOPAS terms and units, no defaults (A142); required engine_version,
TOPAS ==6 (A143, A144); instrument radiation (lh required, lg only with lh,
C30), geometry radii as the axial model needs them (C26), corrections
keyed by macro (A140), peak types TCHZ/PV/PVII with exact parameter names
(A141); background Chebyshev / One_on_X / peaks; isotropic size and strain
(CS_L/CS_G, Strain_L/Strain_G); SPF single peaks.

Rules: a refined start outside TOPAS's fixed default limit on a side the
recipe leaves null is an error (A138, C20); widths >= 0 (C24); wavelength,
size, scale > 0 (A126); peaks inside the fit window; LP_Factor_Synchrotron
never refines pp and mono together (A147); simulation (iters 0) refines
nothing. Space groups: 541 canonical settings TOPAS 6 reads as themselves,
23 refused (C16), committed as tests/data/topas_space_groups.txt.
TopasPhase registered in the phase canary (A122). 62 schema tests.

Assisted-by: claude-code:claude-opus-5-5
…e/05)

- native_writer: INP from the validated model only; one TOPAS parameter per
  symmetry tie group with exact equations (C25); index-based names (D9);
  macro parameters as prm with stated or TOPAS.INC limits per side (A138),
  passed to the macros by name; geometry corrections at xdd level, LP and
  the peak type per str (A148); outputs: results CSV (incl.
  number_independent_parameters, derived LVol-IB/e0/volume), profile,
  phase_out reflection lists.
- native_run: success judged from fresh output files, not the exit code
  (C13); standard statistics over exactly the stated window (A131);
  divergence and missing outputs are EngineExecutionError (A130);
  warnings topas_parameters_not_varied (A128, C22),
  topas_parameter_at_limit (Q-L4, C21), topas_phase_contributes_nothing
  (Q-Z1, C23), topas_adp_converted (D8); background column computed from
  the refined terms (C17, C18, Q-B3); SPF columns per D13.
- gateway: topas.* recipes validate and run natively; a run that executes
  TOPAS needs a declared TOPAS version equal to the recipe's (A145);
  EngineVersionError takes an explicit message. The 0.26.0 path is
  unchanged (A146).
- Six hand-written native fixtures with their derivations (A137); 30
  native tests; the import-block test covers the native modules.

Suite: 2875 passed, 4 skipped.

Assisted-by: claude-code:claude-opus-5-5
Assisted-by: claude-code:claude-sonnet-5-5
- build_result matched TOPAS's profile X to the recipe's 2theta with
  searchsorted, so an X that TOPAS printed rounded above the recipe value
  (10 significant digits) missed its own point. Match the nearest point,
  for Ycalc and for the profile's weights.
- The cell volume (category cell_volume) is not refined, so the out block
  skipped it and the unit-cell report had no volume. Export it like the
  derived values.
- The fixtures README said lab6_spf has 38 peaks; it has 36.

Assisted-by: claude-code:claude-opus-5-5
TOPAS fits the data points nearest start_X/finish_X (EB-59) and GSAS-II
one point past a max between points (EB-54), so the same recipe range
fitted different points per engine. Core fit_limits_on_data moves each
stated end onto the first/last weighted data point inside the window;
the TOPAS writer writes those as start_X/finish_X with the .xye's digits.
TOPAS 6 pack 3: r_wp then equals core Rwp in all six fixtures. A window
with no weighted point is now a validation error.

Assisted-by: claude-code:claude-opus-5-5
run_refinement's fit-range read hands GSAS-II the first/last weighted data
points inside fit_range (core fit_limits_on_data). GSAS-II's slice
x[searchsorted(lo) : searchsorted(hi) + 1] is then exactly
min <= 2theta <= max (EB-54): LaB6 n_obs 3767, GSAS-II Rwp = core Rwp.
set_fit_range_hist and the call order are unchanged; the 0.26.0 path
(legacy copy) is untouched. Code hash refreshed.

Assisted-by: claude-code:claude-opus-5-5
TOPAS 6 cannot output the background (Get(bkg) = 0, C17; phase_out_X and
Get(phase_ycalc) are TOPAS 7 only, C34), and the computed y_bkg misses
TOPAS's convolutions of background peaks. render_background writes a
second INP: the main run's .xye, window, corrections, emission and
background with every parameter fixed at its refined value, no str phases
or SPF peaks, iters 0; its Ycalc is the background. Runner integration
follows the TOPAS 6 check (probe pack 5).

Assisted-by: claude-code:claude-opus-5-5
After the fit, run_native runs the background-only INP (render_background:
refined values fixed, no phases, iters 0) and build_result reads its
Ycalc as y_bkg, so the column carries every convolution TOPAS applies.
Without that profile the column is empty with a
topas_background_not_calculated warning; a stale profile is removed
first. The computed background (Chebyshev + analytic peak shapes, which
missed the convolutions) is gone. TOPAS 6 pack 5: the background run
matches the analytic Chebyshev to 1e-7 where no peaks or convolutions
apply, and Ycalc >= background everywhere.

Assisted-by: claude-code:claude-opus-5-5
TOPAS-64 Version 6 outputs (devkit packs 3 and 5): the writer's INP, the
results CSV, profile, reflection lists and the background run. The tests
check the writer still emits those INPs (main and background) and that
the result built from TOPAS's files has core Rwp = TOPAS r_wp (A149),
3767 points, requested = varied, y_bkg = the background run, TOPAS's
refined values and, for DRX_33, the cells.

Assisted-by: claude-code:claude-opus-5-5
…sasii, core)

Assisted-by: claude-code:claude-opus-5-5
…gence (A130, A154)

The native runner read the results CSV with the 0.26.0 round-trip parser,
which drops a row whose value is not finite, so a NaN refined value gave a
success with the parameter missing; a NaN in a reflection list passed; MSVC
spellings in the profile (1.#QNAN, -1.#IND, -nan(ind)) raised a raw
ValueError. native_run now reads the results CSV, the profile and the
reflection lists itself: a non-finite value (Python or MSVC spelling) raises
"the refinement diverged"; a non-finite ESD stays empty (undetermined ESDs
of degenerate parameters are normal); an unreadable token is an
EngineExecutionError. The 0.26.0 parser is unchanged (A146).

re/05 PR review S1.

Assisted-by: claude-code:claude-opus-5-5
…stamps (A154)

Success was judged by modification time (written within a second of the
run's start). On a network share whose clock is behind the PC's a
successful run read as failed; a clock ahead of it, or a failed re-run soon
after a successful one, let an earlier run's files pass for this run's. The
runner now removes the expected results, profile and reflection lists
before it starts TOPAS and checks only that they exist (the background
profile was already handled so).

re/05 PR review S3.

Assisted-by: claude-code:claude-opus-5-5
- topas_parameter_at_limit gives the limit in recipe units (a Uiso stopped
  at beq 20 reads "max limit 0.253303 (..., TOPAS value 20)", not 20).
- A start-outside-limit message prints the limit to 6 digits only when that
  value is itself allowed, else exactly (the Uiso maximum printed as
  0.253303 failed when written back).
- The writer records the fixed part of a Min/Max limit in either argument,
  so CS's ceiling 10000 nm is a known limit for limit-hit warnings.
- test_spf_runs_with_standard_statistics asserts core Rwp = GSAS-II's Rwp
  (A149/R17 also governs gsasii.spf).

re/05 PR review N1, N2, N3, N7.

Assisted-by: claude-code:claude-opus-5-5
…154; ledger R17 note)

fit_limits_on_data left an open end (and a recipe without fit_range) as
None. GSAS-II then kept its default limit, the first/last data point even
when its weight is 0, counted those points in Nobs and mapped its Chebyshev
basis over them, while the TOPAS .xye has no zero-weight points, so TOPAS
mapped from the first weighted point: one recipe, two background bases.
An open end now becomes the first or last weighted data point, so every
engine gets explicit limits on the same points. Unchanged wherever the data's
edge points are weighted (every committed example and golden).

GSAS-II: the R17 line in run_refinement is unchanged; the values it receives
change as above (ledger R17 annotation, signed by the maintainer with the
review answers). TOPAS: start_X/finish_X are always written.

re/05 PR review S2.

Assisted-by: claude-code:claude-opus-5-5
- Fit window (gsasii, topas): the first/last weighted points; open ends the
  data's first/last weighted point.
- topas: divergence = any non-finite number TOPAS writes; outputs removed
  before the run; INP numbers to 10 significant digits.
- topas: why la, lo, lh, lg and the capillary diameter must be > 0; fixed
  peak-type terms are not checked (a negative width stops TOPAS 6 for
  convolutions, C24; unverified for the peak types): documented only.
- Writer: the kept del / omitted val_on_continue noted.

re/05 PR review N5, N6, N8, Q2, Q6.

Assisted-by: claude-code:claude-opus-5-5
@AdamCorrao
AdamCorrao merged commit 494a802 into refactor/multi-engine Oct 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant