Repository navigation
Re/03b phase block - #4
Merged
Merged
Conversation
…ror normal (A102) `_monoclinic_unique_axis` looked only for a proper 2-fold, so `cell_constraints` raised "could not determine the monoclinic unique axis" for every setting of the mirror-only groups Pm, Pc, Cm and Cc (space groups 6-9, 41 gemmi settings) since v0.1.1. The unique axis is now the invariant direction of det(R)*R for any operation with |trace| = 1: the 2-fold axis or the mirror/glide normal. One shared helper for every cell rule (the re/03b cell tie groups, re/04's coarse groups, re/05/re/06). Deliberate change (devkit tasks/deliberate-changes.md #4): the legacy TOPAS writer and the easydiffraction policy/builder no longer refuse these groups early. Tests: Pm/Pc/Cm/Cc and P2, P2_1/c, C2/c in a-, b- and c-unique settings and other cell choices, plus every monoclinic setting in gemmi's table (115). Assisted-by: claude-code:claude-opus-5-5
…ical space groups (A93-A96, A100, A105) The core phase model is now one block: `Phase[P]`, `UnitCell[P]` and `Atom[P]`, generic over the parameter type P (RefinableParameter, or BoundedRefinableParameter for engines that honor bounds; one P for cell and atoms, review N6). Every structural quantity (cell a..gamma; atom x, y, z, occupancy, Uiso, U11..U23) is `[value, refine_flag]`; element, ADP, a stated Multiplicity and the space group stay plain values. Engine schemas subclass `Phase[TheirType]` and add their own fields flat (gsasii: scale, peak_broadening). - Value rules unchanged in substance, now on `.value` (A63, A69, A73, A76-A78); canonicalization replaces `.value` and keeps the flag and bounds. - No null and no defaults (A96): occupancy is required; Multiplicity, Uiso and Uaniso may be left out but never null; a dump leaves them out, so it validates again. - No phase_name: the phases dict key is the name (A100). - `space_group` must be gemmi's canonical xhm() name exactly (A105); any other spelling is an error naming the canonical form (both settings / origins for a bare rhombohedral / two-origin symbol). New engine-free `symmetry.canonical_space_group`; the lenient resolve_space_group stays for the legacy paths. - Reserved names: an engine subclass that redeclares space_group, unit_cell or atoms raises TypeError at class definition. - `CoreModel` base + `CoreJsonSchema`: readable $defs names (`Atom`, not `Atom_RefinableParameter_`); qualified fallback if two parameter types ever share a document (devkit tasks/re03b-spike-generics.md). - Removed: PhaseStructure, StructureRefinableParameter, StructureBoundedRefinableParameter and the null-value schema note (A71's structure part is superseded by A93/A96). Flag and bound rules (F1-F4) follow in a later commit. Nothing at runtime consumes the phase model yet; the legacy 0.26.0 schema is unchanged. Tests: test_schema_core reworked by ID (17 Structure*-only tests and 14 Structure* numeric parametrizations removed, 22 test_phase_structure_* renamed test_phase_*), plus new phase-block tests. Assisted-by: claude-code:claude-opus-5-5 Assisted-by: claude-code:claude-sonnet-5-5
…ith affine relations (A94, A95, A98)
- `cell_tie_groups(space_group)`: which cell parameters are one parameter
(cubic {a,b,c}; tetragonal/hexagonal {a,b},{c}; `:R` {a,b,c} and
{alpha,beta,gamma}; monoclinic unique-axis angle via the A102 helper) and
which angles are fixed. `cell_constraints` is unchanged and keeps its `:R`
refusal for the legacy TOPAS/easydiffraction callers (deliberate change #2).
- `coupling_groups(space_group, xyz)`: coordinate and Uij tie groups at the
canonical site, as connected components of the orthogonal projector onto
the allowed subspace (the oblique Reynolds average gives wrong Uij groups on
hexagonal axes). Each group exposes its exact relation `member = k*rep + c`:
affine for coordinates, with `c` from the canonical values (P-42_1m 4e
y = x + 1/2; P4_2/mnm y = -x + 1), linear for Uij (U12 = U22/2,
U23 = -U13). Review B1.
Proof:
- engine-free, all 564 gemmi settings, every distinct special site on the
1/24 grid (3,451): fixed sets match analyze_site; moving along any
coordinate group's relation stays fixed by the whole stabilizer; a Uij
tensor along any group's relation is invariant (2,792 coordinate groups);
- vs GSAS-II GetCSxinel/GetCSuinel, the 522 settings GSAS-II reads (2,928
sites): partition, fixed set, one DOF per group and relation coefficients
agree everywhere GSAS-II names the site; its 17 known defects (9 R-centred
2-fold sites read as general, EB-40; 8 'sp' sites, EB-05) are listed and
asserted to still differ (review S2);
- every gemmi setting: cell groups agree with the metric (a group moved
together stays symmetric, one member alone or a fixed angle breaks it) and,
off `:R`, with cell_constraints.
Assisted-by: claude-code:claude-opus-5-5
…A104, A106, A108)
The phase validator now checks refine flags and bounds against the symmetry
ties (cell_tie_groups, coupling_groups), after the values are valid and
canonical:
- F1: the members of one tie group carry the same flag. Reported at every
member whose flag differs from the group's first member, naming the whole
group (review N1), e.g. "cubic cell ('P m -3 m'): a, b, c are one
parameter; set the same refine flag on all three".
- F2: a symmetry-fixed parameter has flag false; A106: and no bounds.
- F3: different groups are independent. A104: co-located atoms are never
compared.
- F4 (with bounds): member bounds follow the tie from the first member's,
affine for coordinates ([k*min + c, k*max + c], swapped for k < 0, open
sides map to the matching side). A108: within the family's value tolerance
(coordinates 5e-6, Uij 1e-6 A^2, cell CELL_TOL) the bounds are set exactly,
with a `tied_bounds_adjusted` warning; beyond, an error at the member.
- Review N4: a value moved by canonicalization (A73/A78) is re-checked against
its final bounds.
All problems are reported together in one ValidationError (A70), each at its
parameter (`unit_cell.b`, `atoms.<label>.y`, `atoms.<label>.Uaniso.U12`).
Tests: every crystal system incl. `:R` and Pm/Pc/Cm/Cc cells, special
positions, multi-group and negative Uij ties, affine ties at P-42_1m 4e and
P4_2/mnm 4f, open bounds, snapping vs errors, A106, N4, exact messages and
locations. The engine-import-block test now runs the new symmetry code and a
full phase validation with GSAS-II and easydiffraction blocked.
Assisted-by: claude-code:claude-opus-5-5
SCHEMA_HISTORY's core 1.0.0 entry now describes the phase block that replaced the structure-only draft before release (noted as a pre-release revision, not a new version): one block keyed by the phase name, every structural quantity a parameter, no null and no defaults, gemmi's canonical space-group name (A105), and refinement intent that follows the symmetry (tied members share a flag; fixed parameters have flag false and no bounds; bounds follow affine ties, snapped within tolerance; co-located atoms independent). Assisted-by: claude-code:claude-opus-5-5
…hema and errors at the field Review follow-up to item 4 of the re/03b record. `Uiso`/`Uaniso` were never optional in effect (ADP selects exactly one), but the JSON Schema listed neither as required and the pairing errors were reported at the atom, not at the field. - `Uaniso` is now a `UanisoTensor[P]` model with all six of U11..U23 required: a missing or unknown component is reported at it (`atoms.<label>.Uaniso.U23`). Same JSON shape as before. - The ADP check reports a missing selected parameter as `missing` and the other one, if given, as `extra_forbidden`, each at its own field (`atoms.<label>.Uiso`), with a message naming the ADP choice. - The Atom JSON Schema states the rule: if ADP is "Uiso", Uiso is required and Uaniso forbidden; if "Uaniso", the reverse. - Multiplicity is unchanged: derived, so optional; never null. Tests: the six test_atom_adp_* tests keep their IDs and now pin location, error type and message; new tests for the location inside a phase and the schema rule. SCHEMA_HISTORY updated. Assisted-by: claude-code:claude-opus-5-5
Core's space-group contract is gemmi's canonical names (A105), and the symmetry rules (special positions, tie groups, unique axis) are characterized against this gemmi version. A gemmi release that renames a symbol or changes an operation would change which recipes are valid, so gemmi is widened only deliberately, after the symmetry tests and the committed name list (tests/data/canonical_space_groups.txt, next commit) are re-checked. pixi.lock: only powderline's own requires_dist copy changes (gemmi 0.7.5 was already the locked build); `pixi lock --check` reports it up to date. Re/03b review B4. Assisted-by: claude-code:claude-opus-5-5
…n; monoclinic symbols
Follow-ups from the independent re/03b review (maintainer answers 2026-10-07).
- Engine subclasses of Phase may not reuse a core validator, private
attribute or method name (_space_group, _sites_check, _sites, _warnings,
site, warnings). pydantic collects validators by name, so a same-named
engine validator silently replaced core's: e.g. a gsasii `_space_group`
check let "Pm-3m" through, and overriding `_sites_check` dropped every
symmetry, flag and bound rule. A subclass must also keep extra='forbid'
(A19). Both raise TypeError when the subclass is defined. (Review A1.)
- Atom labels (and, through the exported CoreName, engine phase names) follow
one rule every engine can use: an ASCII letter, then ASCII letters, digits
or '_'. Evidence: GSAS-II renames a phase with surrounding spaces or
non-ASCII characters and the parameterization then skips it silently;
easydiffraction rejects any other atom label; TOPAS writes the names into
its input; phase names become report file names; field paths are
'.'-joined. check_names_unique_ignoring_case() is for engine payloads'
phase names (case-insensitive file systems). JSON Schema states the rule
as propertyNames. (Review C6.)
- In engines with bounds, a stated cell bound must lie where the parameter
exists: a length's min > 0, an angle's min/max strictly between 0 and 180;
null still means no bound. (Review C5.)
- A short monoclinic symbol ("P21/c", "C2/m") states neither the unique axis
nor the cell choice; the error now lists every setting of that space group
by unique axis instead of suggesting gemmi's silent pick. (Review B3.)
- tests/data/canonical_space_groups.txt pins gemmi's 564 canonical names; a
test fails if the installed gemmi differs. (Review B4.)
- SCHEMA_HISTORY: the name rule, the bound domain, the monoclinic message and
the pinned name list.
Suite (default env): see the devkit record.
Assisted-by: claude-code:claude-opus-5-5
… parameter type
Second round of the re/03b review follow-ups (maintainer answers 2026-10-07).
- A115, validation never changes a recipe. The recipe is the record of the
refinement intent, so validation only accepts or rejects. A value that is
near the exact one but not equal to it (to floating-point precision) is an
error that gives the value to write:
- a special position within 5e-6 (was: replaced by the exact values,
A73); the 5e-6..2e-3 band error is unchanged;
- a Uaniso within 1e-6 A^2 of symmetric (was: adjusted, A78);
- tied bounds near the mapped ones (was: snapped, A108).
The suggestion keeps each tie group's first member as stated and derives
the others ("write y = 0.2468", "write U12 = 0.0061735"); fixed values are
exact. Every value is the repr of the float, so "0.3333333333333333" for
1/3. N4 is moot. Phase.warnings() and the codes special_position_adjusted,
adp_symmetry_adjusted and tied_bounds_adjusted are removed. None of the
67 atoms in the committed examples is affected.
- A114, a phase model needs its parameter type. An engine subclass that
forgets [P] is a TypeError when it is defined, unless it declares
Generic[P]. Validating with an open type is a TypeError. Unparametrized,
one phase could mix [value, flag] and [value, flag, min, max], and F4 then
crashed.
- A112 amended: names are also not Python keywords and are at most 64
characters (phase names become file names).
- Tests: removed test_bounded_phase_canonicalization_keeps_flag_and_bounds
and the two N4 tests. Renamed the adjust/snap tests to *_not_exact_rejected,
test_tied_cell_bounds_equal_exactly and
test_phase_holds_the_callers_atoms_unchanged. Added tests for the A114
guards and the keyword and length rules.
- SCHEMA_HISTORY: exactness, suggested values, the 1/3 spelling, names.
Assisted-by: claude-code:claude-opus-5-5
… (A118) A Uaniso whose symmetric matrix has a principal value <= 0 describes no thermal ellipsoid. Like a negative Uiso (A111), it can show that the parameter compensates for something the model lacks, so it is accepted as stated. It gets a structured warning, uaniso_not_positive_definite, at atoms.<label>.Uaniso. The sign test needs no cell: the Cartesian tensor is a congruence transform of the Uij matrix (Sylvester's law of inertia). Phase.warnings() returns, for informative findings only. The warning never comes with a changed value (A115), so validating the same recipe again gives the same warning. GSAS-II 5.7.9 has no such check (devkit EB-50). Assisted-by: claude-code:claude-opus-5-5
…tly (A120)
This refines A115, following the maintainer's rule. A value that has an
exact decimal form is written exactly: 0.5, 0.25, 0.125, coupled
coordinates such as y = 2x, every Uij relation and every cell tie.
0.4999999 is an error ("write 0.5").
A value with no exact decimal form cannot be written exactly. It is stated
to at least 6 decimals (within A73's 5e-6) and read as the exact value. In
crystallographic symmetry this always means a third: 0.333333 = 1/3,
0.166667 = 1/6, or a coupled member y = x + 1/3 and its bounds. This was
checked over all 564 settings and every distinct special site: fixed
denominators are 1, 2, 3, 4, 6, 8 and 12; tie factors are +-1, +-2 and
+-1/2; offset denominators are 1 to 4; Uij offsets are 0.
- The rule is applied per coordinate, from the stated tie-group
representative, which is never changed.
- The reading carries no warning: it is the documented meaning of the
recipe. A dump (0.3333333333333333) means the same thing and validates the
same way.
- The caller's atom is untouched; the phase holds a copy.
- The 5e-6..2e-3 band error is unchanged.
- Errors give the values to write: exact values with repr; thirds to 6
decimals.
Assisted-by: claude-code:claude-opus-5-5
…/3 in full (A120) Option A of A120, chosen by the maintainer. The validated model holds the float nearest 1/3, so tie relations hold exactly and every engine starts from the same structure. The user's file is never rewritten. Only a Python dump of the model shows 0.3333333333333333, which is the same value. Assisted-by: claude-code:claude-opus-5-5
…band message to 6 decimals re/03b PR review S5 and N3. - SiteAnalysis.canonical is the stated position moved onto the special position by the least (orthogonal) change. It is used to find the stabilizer and the tie offsets. It is not what the engines receive: the validated core Phase holds those coordinates (A120), and keeps a tie group's first member as stated where canonical may move it (by ~1e-7 at a y = x + 1/3 tie). The docstring said gateways use it. - The ambiguous-band message printed the nearby special position's free coordinates with .6g, which gives fewer than 6 decimals once |x| >= 1. It now prints 6 decimals. Assisted-by: claude-code:claude-opus-5-5
…rs (A121-A123)
Fixes from the re/03b PR review, as decided by the maintainer. They share
the functions of Phase's validation, so they are one commit.
One rule for symmetry-determined values (A121, option B; S1, S3, Q1, N1-N3,
N7). The first member of each tie group is the independent parameter, kept
exactly as stated. Every other value is derived from the space group and
those parameters in exact rational arithmetic (Fraction), rounded once, and
the validated model holds the derived value, so every tie holds exactly.
How closely the recipe must state a derived value:
- a constant of the site (a fixed coordinate, a Uij fixed at 0) with a
decimal form exactly (0.5, not 0.4999999; 0, not 1e-9); a third to at
least 6 decimals (A120, unchanged);
- a value derived from another stated value (a coupled coordinate or Uij,
a tied member's bounds) within its family's tolerance, coordinates 5e-6
(A73) and Uij 1e-6 A^2 (A78), read as the derived value.
The ideal C14 6h position (0.833333, 0.666667, 1/4), y = 2x - 1, was an
error ("write y = 0.666666"); it is now read. A third written to 13 digits
is held as 1/3. A value and its bounds are derived together, so a value at
its bound stays within it. One message per parameter set gives the values
derived from the first member, and names equal ties (z = y, U22 = U11).
docs/SCHEMA_HISTORY.md shows users the rule as a table of cases.
Cell (A123; S4). Checked from its tie groups within A77's CELL_TOL (1e-9,
kept), with the metric check behind it. The model holds the first member's
values and exact fixed angles (b = a exactly). The error states the stated
values and the values to write (repr, not %g).
Engine phase guard (A122; S2, N5). A mixin placed before Phase[P] could
replace _space_group or _sites_check silently, and a 'wrap' validator could
skip core. Now:
1. no class in the MRO besides core's own, mixins included, may redeclare a
core field, reuse a core name, or define a pydantic entry point;
2. every core validator must still be core's own function;
3. an engine's own validators are 'after' validators on core fields and the
model (a 'before' one could rewrite the recipe before core reads it);
4. tests/test_phase_canary.py runs core's must-fail recipes and a valid
recipe through every engine phase model (re/04-06 register theirs) and
re-runs the structural guard on each.
The parameter type must be RefinableParameter or BoundedRefinableParameter
(Phase[float] is a TypeError).
Also:
- the uaniso_not_positive_definite warning gives the Cartesian principal
mean-square displacements; a near-zero one counts as 0 (A118 amended;
N4);
- atom labels must differ by more than case (A112 amended; N8).
Tests: 3 renamed (behaviour changed), 6 updated, 38 new IDs; none removed.
Default suite 1518 passed, 4 skipped; easydiff 55 passed. The fuzz over all
564 settings and translates (10,353 sites) gives 0 rejections and the exact
derived float in every case.
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/03b-phase-block: one phase block, symmetry-aware refinement intent, strict core validation
Base:
refactor/multi-engine(4b61201) ← head:re/03b-phase-block(b446577), 14 commits.Core only: no gateway runtime changes, no frozen GSAS-II code touched,
examples/untouched.Suite: 1311 → 1518 passed, 4 skipped (default env); easydiffraction env 55 passed, unchanged.
Overview
re/03b replaces core schema 1.0.0's structure-only phase model before its
first release. A phase is now one block: the space group, the unit cell
and the atoms, where every structural quantity is a refinable parameter
(
[value, flag], or[value, flag, min, max]in engines with bounds).Engine schemas (re/04–06) subclass it and add their own phase fields flat
next to core's.
The most important change is what a recipe means and what validation is
allowed to do with it:
symmetry are one parameter. Every member is stated, and all members
carry the same flag and consistent bounds. A parameter fixed by symmetry
is never refined and has no bounds. Inconsistent intent is an error at
the offending field. Before, it was silently resolved ("group-OR",
KI-02) or ignored.
record of the refinement intent:
exactly as written;
model holds the derived value, so every tie holds exactly and every
engine receives the same structure;
0.5, not0.4999999;0, not1e-9;0.333333;y = 2x,U12 = U22/2,a tied member's bounds) is read from the precision of its kind;
Everything downstream reads the validated model, never the raw JSON. That
model holds exactly one structure, and it is the one every engine receives.
Change of intent and logic (what reviewers should check first)
1. One phase block with refinable values (A93, A94, A96, A100)
Phase[P],UnitCell[P],Atom[P],UanisoTensor[P]are generic overthe parameter type
P(src/powderline/schema_core.py:593).src/powderline/schema_core.py:619; ADP pairing,:670; atom,:677; phase,:763.nulland nothing has a default;occupancymust be stated;ADPselects which ofUiso/Uanisois required, stated in theJSON Schema too (
:670);phasesdict, so there isno
phase_namefield.PhaseStructureand theStructure*RefinableParametervariants with null values (A71's structure part). Nothing at runtime used
them; core 1.0.0 is unreleased.
CoreJsonSchemaandCoreModel(:134,:152)name parametrized models
Atom/UnitCellinstead ofAtom_RefinableParameter_.2. Refinement intent follows the symmetry (A94, A95, A98, A104, A106)
_tie_rules,src/powderline/schema_core.py:1256):member's. The tie is affine:
member = k·rep + cmaps[min, max]to[k·min + c, k·max + c], swapped for k < 0. An open side stays open.unit_cell.b,atoms.O1.y,atoms.O1.Uaniso.U12). For F1 there is one error per memberthat differs from the group's first member, and each message names the
whole group and the fix.
src/powderline/symmetry.py):cell_tie_groups(:756) covers every crystal system, includingrhombohedral axes
:R(:772). The legacycell_constraintskeeps its:Rrefusal.coupling_groups(:838,_tie_groups:796) groups coordinate andUij ties by connectivity of the orthogonal projector. Coordinate
relations are affine, with the offset taken from the stated position;
Uij relations are linear.
TieGroup.relationis at:693.tests/test_symmetry_core.py:272; cell ties against the metric::387.GetCSxinel/GetCSuinel, 2,928 sites:tests/test_symmetry_characterization.py:299. 17 known GSAS-II misreadsare pinned as still differing (
:308).3. One rule for symmetry-determined values (A115, A120, A121, A123)
Before (draft and re/03):
validated recipe was validated again.
Now (
Phase._sites_check,src/powderline/schema_core.py:940; therule is
_derive,:1178, with one tolerance family per kind,:1159-1170):before z; U11 before U22…) is kept exactly as written.
and those parameters in exact rational arithmetic (
Fraction) androunded once. The validated model holds it (A120 option A, extended by
A121), so every tie holds exactly in the model.
an exact decimal form: exactly, to floating-point precision
(
0.4999999→ error "write x = 0.5";U13 = 1e-9→ error"write U13 = 0.0"). The maintainer's rule: a value that can be written
exactly is never snapped, although most crystallographic programs do;
_has_decimal_form,:1131): toat least 6 decimals (within 5e-6, A73), read as the exact value;
coupled Uij, a tied member's bounds): within 5e-6 for coordinates (A73)
or 1e-6 Ų for Uij (A78), read as the derived value, whatever the
relation (
y = 2x,y = x + 1/2,y = x + 1/3);:968): tied lengths and angles equal the firstmember's and fixed angles are exactly 90 or 120, within A77's 1e-9
(relative on lengths). The model holds
b = aexactly. The errorstates each stated value and the value to write:
stated b = 4.2, but by symmetry b = a; write b = 4.15692.that meaning. A dump of the model validates as itself.
Why derived values are read but constants are not: a constant's
decimal form is fixed by the space group. A derived value's depends on how
the first member was typed. The ideal C14 (MgZn₂-type) 6h position
x, y = 0.833333, 0.666667hasy = 2x − 1 = 0.666666. Requiring thatexactly would reject a correct 6-decimal statement.
How PowderLine reads typical statements (the table users get in
docs/SCHEMA_HISTORY.md:143, pinned bytests/test_schema_core.py:2318-2336):x = 0.4999999at (1/2, 0, 0)x = 0.50.333333, 0.666667at (1/3, 2/3, 1/4)0.3333333333333, 0.66666666666670.833333, 0.666667at 6h (x, 2x, 1/4)0.1, 0.2000001at (x, 2x, 0)0.066667, 0.4at (x, x + 1/3, 1/6)U13 = 1e-9at 6hU13 = 0.0U12 = 0.006174,U22 = 0.012347at 6hWhy "no decimal form" means a third: across all 564 settings, every
distinct special site and lattice translates:
Representation:
0.333333);
:985,:1037);Phase.site(label)returns the site's symmetry (multiplicity, ties). Itscanonicalposition is not the engines' structure; the model is(
src/powderline/symmetry.py:426).Warnings:
special_position_adjusted,adp_symmetry_adjustedortied_bounds_adjustedcodes;Phase.warnings()(:1047) holds only informative findings that changenothing. Today that is one: a non-positive-definite
Uaniso(A118,:1023). It is accepted, like a negativeUiso, because it maycompensate for an unmodeled effect;
(
_principal_msd,:1106). The raw Uij matrix only has the right signs:an isotropic hexagonal −0.01 read −0.015, −0.01, −0.005. A near-zero
value counts as 0, so a semidefinite tensor always warns.
Effect on the committed examples: none of their 67 atoms is affected.
4. One spelling of the space group (A105, A110)
space_groupmust equal gemmi's canonical extended H-M nameexactly (
"P m -3 m","C 1 2/m 1","R -3 m:H","F d -3 m:2").Implemented in
canonical_space_group,src/powderline/symmetry.py:152.:H/:Ror:1/:2;"P21/c") lists every setting by uniqueaxis (
:191) instead of gemmi's silent pick.==0.7.5(pixi.toml,pyproject.toml). The 564names are committed in
tests/data/canonical_space_groups.txtand comparedwith the installed gemmi (
tests/test_schema_core.py:1488).resolve_space_groupuntil re/07.5. Engine subclasses cannot replace, skip or rewrite core (A93, A109, A114, A122)
Checked in
Phase.__pydantic_init_subclass__(
src/powderline/schema_core.py:837), as aTypeErrorwhen the class isdefined:
Names: no class in the MRO besides core's own, mixins included,
may:
space_group,unit_celloratoms;(computed from
Phase,_reserved_phase_members,:1056);__init__,model_post_init,model_validate, …;:1070,:887).pydantic collects validators by name, so a gsasii
_space_groupsilentlyreplaced the canonical-name check, and a mixin placed before
Phase[P]could do the same.
Identity: every core validator must still be core's own function
(
_core_decorators,:1088,:895).Check, never rewrite: an engine's own validators on core fields and on
the model are
'after'validators (:900). A'before','wrap'or'plain'one could rewrite the recipe before core reads it, or skip core.An engine
'before'validator on its own field is allowed.Canary (
tests/test_phase_canary.py): core's must-fail recipes and avalid recipe run through every engine phase model, and the structural
guard is re-run on each, so even a mixin that overrides the hook is
caught. re/04–06 register their models in
ENGINE_PHASES; a completenesstest fails until they do.
Also:
extra='forbid'cannot be relaxed (tests/test_schema_core.py:1579).:921): a subclass thatforgets
[P]fails when defined, unless it declaresGeneric[P](
tests/test_schema_core.py:1557-1564). A parametrization with any othertype (
Phase[float]) is aTypeError(:863).(
tests/test_schema_core.py:1586). Tests for the guard layers::2438-2532.6. Names and value domains (A111, A112)
CoreName, engine phasenames) match
^[A-Za-z][A-Za-z0-9_]*$, are not Python keywords, and are atmost 64 characters. Phase names, and the atom labels of a phase, must also
differ by more than case (
:956).Code:
src/powderline/schema_core.py:190-262.parameterization then silently skips it;
macOS;
.-joined.lengths > 0, angles strictly between 0 and 180 (
:633,:643).nullstill means no bound. Negative ADPs are not blocked (A111).
7. Monoclinic unique axis fix (A102), the one legacy-visible change
_monoclinic_unique_axis(src/powderline/symmetry.py:218; the|trace| = 1check is at:228) now uses the 2-fold axis or themirror/glide normal. Before, it raised for every Pm, Pc, Cm and Cc
setting (space groups 6–9) since v0.1.1.
policy/builder no longer refuse these groups. easydiffraction gives a
clear build-time error for non-short spellings. Whether TOPAS accepts them
is checked in re/05.
tests/test_topas_symmetry.py:63,:73.Commits
6c29497a0c1a15Phase[P], canonical space groups (A93–A96, A100, A105)af9d2ec:R) and coupling groups with affine relations8f080c2ec261a80eb3d318bd674a6afa7e2abb6b640b06b09Uanisoaccepted with a warning (A118)5994fd04f41bbf6af30c9SiteAnalysis.canonicalis not the engines' structure; band message to 6 decimalsb446577Tests and verification
4b61201→ 1518 passed, 4skipped at
b446577.tests/test_schema_core.pywas reworked. The tests of the removedStructure*classes are gone.test_phase_structure_*is renamedtest_phase_*. The adjust/snap tests became*_not_exact_rejected, andthe N4 re-check tests were removed.
The PR review renamed three tests whose behaviour A121/A123 changed
(tied bounds and coupled Uij are now read):
test_phase_uaniso_hexagonal_6h_not_exact_rejected→…_6h_coupled_member_read_within_1e_6;test_tied_coordinate_bounds_not_exact_rejected→test_tied_coordinate_bounds_read_within_6_decimals;test_tied_cell_bounds_equal_exactly→test_tied_cell_bounds_follow_the_first_member.No ID was removed.
The full list of removed and renamed IDs, with reasons, is in the devkit
records.
:Rand Pm/Pc/Cm/Cc(
tests/test_schema_core.py:1884-2130);:2040,:2048);:2318-2336); thirds (:1095,:1143); fixed Uijexactly 0 (
:1294); cell values (:2365); fixed angles in all 564settings (
:2382);:1499-1586,:2438-2532) and the canary(
tests/test_phase_canary.py);:2166-2210, case:2427); cell bound domain (:2230);Uanisoand its Cartesian values (:2260,:2292).settings, with decimals written exactly and thirds and derived members to
6 decimals. 0 rejections, and the exact derived float in every case.
tests/test_easydiff_*): 55 passed, identicalIDs.
and a full
Phasevalidation with GSAS-II and easydiffraction blocked(
tests/test_gateways.py:479-490).kicker.py,gateways/gsasii/,_code_hash.jsonorexamples/change.
pixi.lockchanges only powderline's ownrequires_distcopy ofthe gemmi pin.
pixi lock --checkreports it up to date.Deliberate behavior changes
Pm/Pc/Cm/Cc in every setting (A102).
>=0.7,<0.8. A pipinstall next to another gemmi 0.7.x now conflicts.
changes.
docs/SCHEMA_HISTORY.mdrecords them as a pre-release revision(
:25). The user-facing rules are documented at::65names;:91space group;:104unit cell;:120symmetry-determined values, with the table at:143;:173refinement intent.What re/04–06 build on
Phase[RefinableParameter]for gsasii (no bounds, A84),
Phase[BoundedRefinableParameter]for TOPASand easydiffraction.
CoreName+name_keyed+check_names_unique_ignoring_caseforits
phases;'after'mode (A109, A122);tests/test_phase_canary.pyENGINE_PHASES;Phase.site().canonical.= 1/3;andx + 1/3from core's exact fractions;Assisted-by: claude-code:claude-opus-5-5
Assisted-by: claude-code:claude-sonnet-5-5