Repository navigation
Re/06 easydiff schema - #7
Merged
Merged
Conversation
…sydiffraction (A24, A136) src/powderline/easydiff/ -> src/powderline/gateways/easydiffraction/, verbatim (git rename, no content change beyond the module path). The entry point (powderline.gateways:easydiffraction) and every src/tests import use the new path; no powderline.easydiff shim (A136). No pixi task names the module. Prose docs citing the old path are left for re/10; the pixi env keeps its name `easydiff` until re/08 (A42). An existing checkout needs `pixi reinstall powderline` so the entry-point metadata points at the new module. Suites: default 2954 passed, 9 skipped (baseline 494a802 identical except four parametrized test_gateways IDs that contain the module path); easydiff env tests/test_easydiff_* 55 passed, IDs identical. Assisted-by: claude-code:claude-opus-5-5
Engine schema 1.0.0 on the core schema: EasydiffractionPhase = Phase[BoundedRefinableParameter] + scale; easydiffraction's own names and units (A4); the instrument in gsasii's A103 layout with every correction easydiffraction 0.21.1 models for CW X-ray powder data as a slot (A160): Kα2 doublet, polarization + monochromator angle, zero, displacement, transparency, cylinder absorption, and the three peak types (pseudo-Voigt, Bérar-Baldinozzi, TCH + FCJ) as a discriminated union. refinement_controls carries the calculator (cryspy | crysfml), the minimizer (lmfit leastsq only) and its stopping criteria (A162). Rules: a setting easydiffraction has (EB-42; committed table generated from the library by scripts/generate_easydiffraction_space_groups.py and checked by a test where that version is installed); values inside easydiffraction's physical limits (EB-71); wavelength > 0; scale >= 0 (A161); weights <= 1e8 on the points handed to the engine (A159, EB-73); peak type and cutoff_fwhm per calculator; Kα2 pair stated together. The gateway routes easydiffraction.* recipes to the native schema in validate()/capabilities(); the native run comes with the builder. EasydiffractionPhase is registered in the A122 canary. Hand-derived fixtures with their derivations: tests/data/easydiffraction/native/. Assisted-by: claude-code:claude-opus-5-5 Assisted-by: claude-code:claude-sonnet-5-5
…tveld (re/06) native_builder: builds the easydiffraction project from the validated model (A39). The window's weighted points (fit_limits_on_data) go in as a CIF data loop at full precision (A158; the ASCII reader rounds 2theta to 4 decimals, EB-72). Calculator, then peak type, then profile (EB-76); every instrument slot from the model; exact model coordinates. One free parameter per refined tie group, the member easydiffraction leaves free (an error if its constraints disagree with core's); stated bounds become fit_min/fit_max, a null side keeps the physical limit (A157 B4). After the build: easydiffraction's multiplicity and coordinates must equal core's (EB-47), and with CrysPy an atom on a special position with a free coordinate must be written in the pattern of easydiffraction's template, since CrysPy computes wrong structure factors for other images (EB-77; the error names the equivalent position). native_run: lmfit with the recipe's stopping criteria, or a calculation when nothing is refined; strict reading (non-finite = divergence, A130, A154); core fitstats over the handed points (Rwp equals easydiffraction's); P = easydiffraction's n_free_parameters (lmfit's varied count); y_bkg = the engine's background; the shared report writers; warnings for not varied, at limit, phase contributes nothing, not converged, and no reflection list with CrysFML (EB-79). Schema: a space-group setting must be one the chosen calculator computes correctly (EB-80, proposed A163): committed space_group_support.py, written by scripts/verify_easydiffraction_settings.py (computed for all 531 settings: CrysPy 437, CrysFML 227); a test re-verifies a sample. The gateway routes easydiffraction.* runs to the native path. Tests: 46 builder/run tests and golden comparisons (tests/data/easydiffraction/ native/golden, written by the devkit's probes/re06/make_goldens.py); they run where easydiffraction 0.21.1 is installed. Assisted-by: claude-code:claude-opus-5-5 Assisted-by: claude-code:claude-sonnet-5-5
Follows the maintainer's pixi.toml/lock bump (508f39f). ENGINE_VERSION_SPEC moves from ">=0.20.1,<0.21" to "==0.21.1"; the version tests fake 0.20.1 (now outside the spec) and 0.21.1; the native tests no longer bypass the version check. The legacy LaB6 example's Rwp is unchanged (0.20.1 9.633040 %, 0.21.1 9.633031 %): prof_wr_factor was already 1/sigma^2-weighted; 0.21.0's #224 fixed the printed table's weighted R only (EB-81, devkit). easydiff env (0.21.1): test_easydiff_* test_easydiffraction_* test_gateways test_phase_canary 226 passed, 2 skipped. Assisted-by: claude-code:claude-opus-5-5
- Uaniso with calculator crysfml is a validation error: easydiffraction hands CrysFML only B = 8 pi^2 Ueq, so the tensor would have no effect. - The builder hands easydiffraction the anisotropic atoms first. In easydiffraction 0.21.1 the CrysPy update during a fit writes a refined tensor into the column of its place among the anisotropic atoms, not its own (_update_aniso_beta), so an anisotropic atom after an isotropic one kept its starting tensor and lmfit reported it had no effect (EB-86). Reports keep the recipe's atom order. - A127 test: with every refinable flagged, each free parameter changes the pattern computed on lmfit's own update path (both calculators); without the reordering it names B's U11 and U22. A CrysPy fit refines B's Uij with ESDs. Assisted-by: claude-code:claude-opus-5-5
…ses (A166) The run stays a success with the values reported; the warning now says so and points at too few iterations or poorly conditioned parameters. Assisted-by: claude-code:claude-opus-5-5
…170) The generator tested CrysFML only where CrysPy passed, so a setting CrysPy crashes on or computes as another hand was refused for CrysFML too. - CrysPy: per-reflection F^2 against the ITC reference, unchanged (strict: sensitive to the hand; P 31, which CrysPy computes with P 32's operators, stays refused). - CrysFML: its pattern against its own pattern of the orbit written out in P 1 (no symmetry handling involved); an engine error fails that calculator only. Regenerated support: CrysPy 437 (unchanged), CrysFML 227 -> 230 (+ P 31, P 6/m, F d -3 c:2). The sample test re-verifies those three. Passes R < 1e-4, failures >= 0.11 for CrysFML; R_PASS 0.03 unchanged. Assisted-by: claude-code:claude-opus-5-5
drx33_two_phase_cryspy.json: the example_DRX_33 data with its two phases, cubic DRX_33 (F m -3 m, mixed 4a site) and Li4MgWO6 (C 1 2/m 1), CrysPy pseudo-Voigt, 18 parameters (profile, background, scales, cells). Test: both structures linked, every parameter varied with an ESD, per-phase reports; golden for the built project and the run (Rwp 7.248 %). Assisted-by: claude-code:claude-opus-5-5
… after a run (A171)
Where the Gaussian FWHM^2 (U tan^2 + V tan + W) or the Lorentzian FWHM
(X tan + Y / cos) is negative, easydiffraction 0.21.1 computes a wrong
pattern without a message: CrysFML drops those peaks, CrysPy distorts them
(EB-87). A stated profile that makes a width negative at a point handed to
the engine is a validation error naming the 2theta range; a refined one
gets the warning easydiffraction_negative_width, values kept.
Also: pydantic's error templates take no format specs, so the weight error
printed "sigma {sigma:.3g}" literally; values are formatted before.
Assisted-by: claude-code:claude-opus-5-5
…re/06) Assisted-by: claude-code:claude-opus-5-5
easydiffraction 0.21.1's CIF data loop cannot read exponent notation (5e-05, 5E-05, 1e+16: an opaque TypeError), and repr() writes it for |v| < 1e-4 or >= 1e16, so normalized intensities (allowed with sigma >= 1e-4, A159) crashed the run. Numbers are now written positional at full precision (numpy format_float_positional, unique: exact round trip). Test: intensities ~1e-5 with sigma 1e-4 reach the engine exactly. Assisted-by: claude-code:claude-opus-5-5
…dth warning (A171) The refined CrysPy pseudo-Voigt profile has broad_lorentz_x = -0.098(4), so X tan + Y / cos is negative from 11.2 to 15 deg 2theta and the run now warns easydiffraction_negative_width. Values unchanged (regenerated with make_goldens.py lab6_cryspy_pv.json; only the warnings list differs). Assisted-by: claude-code:claude-opus-5-5
fit_report reads GSAS-II's Rvals['converged'] (the last cycle's chi^2 change below its tolerance) into engine_details['converged'] and warns gsasii_fit_not_converged when it is False, values kept (as A166 for easydiffraction; not in a simulation). Single peak fitting reports converged None: DoPeakFit keeps no convergence flag (EB-85). fit_report.py is neither frozen nor hashed. Tests: fit_report on a faked run (False warns, True and simulation do not); LaB6 with 2 cycles is a success with the warning; the 5-cycle LaB6 converges; SPF reports None. Default suite 3029 passed, 19 skipped. Assisted-by: claude-code:claude-opus-5-5
…x safeguards (re/06 PR review N1, N5) A non-finite value in easydiffraction's reflection list (d-spacing, 2theta, F^2) is now the divergence error, as the profile and refined values are (A154). New tests for safeguards that no test caught when disabled: the EB-47 site check, interior zero-weight points not handed, the RAISE reaction against ED_LOG_REACTION=WARN (EB-48), the not-varied warning (A128), y_bkg = easydiffraction's own Chebyshev (A157 B5), and the divergence error for a non-finite refined value or reflection. Assisted-by: claude-code:claude-opus-5-5
…efuses nine origin-1 settings (A174) CrysPy computes template-form special positions with a free coordinate wrongly in nine origin-choice-1 settings although their general position is right (re/06 PR review S1, EB-77). The settings verification now also compares, per calculator, every special Wyckoff site's pattern with the calculator's own P 1 expansion (raw R < 1e-3; CrysFML's general position by raw R too, N7), and the support table is regenerated: CrysPy 437 -> 428 (F d d d:1, F d -3 m:1, F d -3:1, P n -3 m:1, P 4/n n c:1, P 42/n n m:1, I 41/a:1, I 41/a m d:1, I 41/a c d:1), CrysFML 230 unchanged. The error names the origin-2 setting. Assisted-by: claude-code:claude-opus-5-5
…within 1 deg of the window (A177, A178) A177 (S4, EB-91): easydiffraction refuses a negative measured intensity, so a negative Itth on a point handed to it is now a validation error at xrd_data.Itth[i] instead of a raw TypeError at run time; points outside the window or with zero weight are not checked. A178 (S5): the stated (and, after the run, refined) peak widths must be >= 0 on the handed points and on a 0.01 deg grid over the window widened by 1 deg plus the size of the zero, displacement and transparency corrections: easydiffraction computes reflections just outside the window, and a negative width there moved the first window point by 12 sigma in the review's probe. Assisted-by: claude-code:claude-opus-5-5
…; CrysFML warns; golden tolerances (A175, A176, A179) A176 (Q3, EB-74): easydiffraction stores some template-pattern positions in its own writing, e.g. (0.137, 0.863, z) on (x, -x, z) as (0.137, -0.137, z): the same atom and pattern. The site check now accepts a lattice translation (multiplicity and position otherwise unchanged), records it, moves stated bounds of a free coordinate with it, reports refined coordinates in the recipe's writing, and warns easydiffraction_position_translated. Any other move, or another multiplicity, stays an error (EB-47). A175 (Q2, EB-90): every CrysFML refinement warns easydiffraction_crysfml_start_dependent: CrysFML computes in single precision, below lmfit's finite-difference step, so refined values, Rwp and ESDs can depend on the starting values. Its refinement golden now compares only stop-independent quantities (counts, warnings, cell within 1e-4); a new CrysFML calculation fixture carries CrysFML's numbers. A179 (S6): golden tolerances follow docs/regression-tolerance.md (values, ESDs, statistics rtol 1e-3, a value also within 1 % of its ESD; cell 1e-4); goldens record the easydiffraction, cryspy, crysfml, lmfit, numpy and scipy versions (regenerated: no value changed). Assisted-by: claude-code:claude-opus-5-5
…74-A178) Settings verified on special sites (CrysPy 428, CrysFML 230), widths checked within 1 deg of the window, negative intensities refused, positions held as a lattice translation accepted with a warning, CrysFML's start-dependence warning, reflection-list values read strictly. Assisted-by: claude-code:claude-opus-5-5
…ative to the starting value (A181) easydiffraction_parameter_at_limit used an absolute 1e-12 at a limit of 0, which has no physical scale: a phase scale is ~1e-6, a Uiso ~1e-2. A value now ends at a zero limit when it is within 1e-6 of the parameter's starting value (1e-12 absolute only if it also started at 0), as nonzero limits use 1e-6 of the limit (re/06 PR review N4). Assisted-by: claude-code:claude-opus-5-5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
re/06: native
easydiffraction.rietveldschema, builder and run (easydiffraction 0.21.1)Branch
re/06-easydiff-schema→refactor/multi-engine(base494a802, 20 commits, tip799938f).Not a gate branch. Decisions referenced as A-numbers are in the refactor's decision log (A116, A136, A157–A181; A174–A181 from the PR review).
Summary
powderline.gateways.easydiffraction(no shim).easydiffraction.rietveldrecipe (engine schema 1.0.0, on core 1.0.0). It uses easydiffraction's own names and units: Caglioti U/V/W as FWHM² in deg², the Lorentzian X/Y terms,calib_twotheta_offset, …508f39f, plus the gateway spec ine2408f3.What the native path does
refinement_controls.calculator:cryspy|crysfml, A162). Validation depends on it:cwl-pseudo-voigt, Bérar–Baldinozzi asymmetry for CrysPy, Thompson-Cox-Hastings with FCJ asymmetry for CrysFML);cutoff_fwhm, which only CrysPy uses;nullkeeps the physical limit;easydiffraction_parameter_at_limitreports parameters that ended at a limit.fit_limits_on_data);easydiffraction_position_translated, and bounds and reports stay in the recipe's writing (A176).Uanisois refused with CrysFML, which is handed only B = 8π²·Ueq (A169).rwp/r_exp/gof/chi2_redfrom corefitstatsover the window (Rwp equals easydiffraction's);parameters_requestedandparameters_variedunderengine_details;easydiffraction_parameters_not_varied;easydiffraction_parameter_at_limit(within 1e-6 relative of the limit; at a limit of 0, within 1e-6 of the starting value, A181);easydiffraction_phase_contributes_nothing(informative);easydiffraction_fit_not_converged(informative, values kept, A166);easydiffraction_negative_width, when the refined profile makes a width negative within 1° of the window (widened by the position corrections; A171, A178). A stated profile that does so is a validation error, because easydiffraction then computes a wrong pattern without a message, also from a reflection just outside the window.easydiffraction_reflections_not_available(CrysFML);easydiffraction_crysfml_start_dependenton every CrysFML refinement (A175): CrysFML computes in single precision, below lmfit's finite-difference step, so the refined values, Rwp and ESDs can depend on the starting values (in the LaB6 test, Rwp 6.15–6.79 % for starts differing by 1e-6–1e-4; the cell barely moves);easydiffraction_position_translated(A176).engine_details['converged']comes from GSAS-II'sRvals['converged']. When it is False, the warninggsasii_fit_not_convergedis given and the values are kept. Single peak fitting reportsNone: GSAS-II'sDoPeakFitkeeps no convergence flag.Engine findings worth knowing (easydiffraction 0.21.1)
CrysPy:
P 31is computed with P 32's operators (same powder pattern, the other hand per reflection);CrysPy also computes special positions wrongly in nine origin-choice-1 settings, even in the template writing.
CrysFML:
Data: easydiffraction refuses negative measured intensities.
Both calculators: a negative peak width gives a wrong pattern without a message.
Cell constraints: they reset the unique angle of c- and a-unique monoclinic cells.
Cross-engine LaB6 cell, refined in both (GSAS-II 4.157216(21) Å):
The offset is the missing axial asymmetry, whichever calculator runs the profile.
DRX_33, two phases: agrees with TOPAS 6 native: cubic a 4.18201 vs 4.18244 Å, Rwp 7.25 vs 7.31 %.
0.20.1 → 0.21.1: the legacy LaB6 example's Rwp is unchanged (9.633 %); some ESDs change.
Tests
test_easydiff_* test_easydiffraction_* test_gateways test_phase_canary): 265 passed, 2 skipped (the "easydiffraction not installed" tests).tests/test_easydiffraction_schema.py;tests/test_easydiffraction_native.py: the builder, the run, an A127 check that every free parameter changes the pattern on lmfit's own update path, divergence, warnings, and four fixtures (three LaB6, one two-phase DRX_33) with goldens;tests/test_easydiffraction_space_groups.py: the space-group table is regenerated and compared, and a sample of settings is re-verified by computation.tasks/re06-pr-review-record.mdin the devkit) checked every decision by disabling its code (each caught by a test) and led to the fixes in the last six commits (A174–A181).scripts/generate_easydiffraction_space_groups.pyandscripts/verify_easydiffraction_settings.py(about 1.5 h with--jobs 4; more workers need more than ~1.5 GB each).examples/is unchanged.For reviewers
After checkout, refresh the entry points (
pixi reinstall powderline, orpip install --no-deps --no-build-isolation --no-index -e .per env), because the package moved.The fixtures' derivations from GSAS-II's instrument terms are in
tests/data/easydiffraction/native/README.md. They cover:The v0.1.1 path got these wrong; it stays as is until re/07.
The CrysPy LaB6 fixture's fit refines X = −0.098, so its golden carries
easydiffraction_negative_width. This is intentional: the start is valid and the warning describes the output.Follow-ups (not in this PR)
Assisted-by: claude-code:claude-opus-5-5
Assisted-by: claude-code:claude-sonnet-5-5