From 485b2ef0de1af54ac7d93c9397d1c3b9b4227492 Mon Sep 17 00:00:00 2001 From: Paullo9 Date: Sat, 31 Oct 2026 01:03:14 +0100 Subject: [PATCH 1/3] test: name what the nine review regression CIFs hold and test Fixture move. The first comment line of the nine origin-shift, supercell, co-sited and rhombohedral CIFs named a past review session. Each header now says which structure the file holds and what RefuterRegressionTests (and, for the 7x supercell, the conventional-cell pins) checks with it. Only comment lines change. No pin file locks these CIFs by sha256: they are not in pymatgen-cells-pins.json or numpy-structure-factor-pins.json. Regenerating cells/pins.py, structure-factors/pins.py and conventional-cell/pins.py with the pinned pymatgen environment on the edited files gives byte-identical pins apart from the generated_at stamp, so no pin file is committed. Tests/Fixtures/README.md carries the new file sizes. Part of #40. Co-Authored-By: Claude Opus 5.5 --- .../alpha_quartz_P1_origin_shifted.cif | 3 ++- .../Fixtures/corundum_P1_rhombohedral.cif | 3 ++- .../gold_P1_origin_shifted_exact_decimals.cif | 3 ++- .../Fixtures/iron_P1_general_position.cif | 3 ++- .../Fixtures/magnesium_P1_origin_shifted.cif | 3 ++- .../magnesium_P63mmc_origin_shifted_loop.cif | 3 ++- .../Fixtures/mixed_centring.cif | 3 ++- .../Fixtures/polonium_P1_supercell_7x.cif | 3 ++- .../Fixtures/rocksalt_P1_origin_shifted.cif | 3 ++- Tests/Fixtures/README.md | 18 +++++++++--------- 10 files changed, 27 insertions(+), 18 deletions(-) diff --git a/Tests/DiffractoCoreTests/Fixtures/alpha_quartz_P1_origin_shifted.cif b/Tests/DiffractoCoreTests/Fixtures/alpha_quartz_P1_origin_shifted.cif index 7d00653..915d3e4 100644 --- a/Tests/DiffractoCoreTests/Fixtures/alpha_quartz_P1_origin_shifted.cif +++ b/Tests/DiffractoCoreTests/Fixtures/alpha_quartz_P1_origin_shifted.cif @@ -1,4 +1,5 @@ -# provenance: DiffractoKit slice-1 Gate B refuter, 2026-10-14 night (F2e_quartz_shifted.cif); a regression fixture +# provenance: alpha quartz SiO2 (P3_221) as a P1 list of its 9 sites at a shifted origin, 4 decimals. Regression fixture +# (RefuterRegressionTests): the 6 operations of P3_221 and Laue class -3m. data_F2e_quartz _symmetry_space_group_name_H-M 'P 1' _symmetry_Int_Tables_number 1 diff --git a/Tests/DiffractoCoreTests/Fixtures/corundum_P1_rhombohedral.cif b/Tests/DiffractoCoreTests/Fixtures/corundum_P1_rhombohedral.cif index 5693ac1..ce88086 100644 --- a/Tests/DiffractoCoreTests/Fixtures/corundum_P1_rhombohedral.cif +++ b/Tests/DiffractoCoreTests/Fixtures/corundum_P1_rhombohedral.cif @@ -1,4 +1,5 @@ -# provenance: DiffractoKit slice-1 Gate B refuter, 2026-10-14 night (corundum_rh_p1.cif); a regression fixture +# provenance: corundum Al2O3 (R-3c) in rhombohedral axes as a P1 list of its 10 sites. Regression fixture +# (RefuterRegressionTests): Laue class -3m with 12 operations, ring names in three indices, not Miller-Bravais. # generated using pymatgen data_Al2O3 _symmetry_space_group_name_H-M 'P 1' diff --git a/Tests/DiffractoCoreTests/Fixtures/gold_P1_origin_shifted_exact_decimals.cif b/Tests/DiffractoCoreTests/Fixtures/gold_P1_origin_shifted_exact_decimals.cif index 7abd466..012de0e 100644 --- a/Tests/DiffractoCoreTests/Fixtures/gold_P1_origin_shifted_exact_decimals.cif +++ b/Tests/DiffractoCoreTests/Fixtures/gold_P1_origin_shifted_exact_decimals.cif @@ -1,4 +1,5 @@ -# provenance: DiffractoKit slice-1 Gate B refuter, 2026-10-14 night (F2c_Au_shifted_exactdecimals.cif); a regression fixture +# provenance: fcc gold (Fm-3m) as a P1 list shifted by (0.2484, 0.3, 0.07), exact 4-decimal coordinates. Regression fixture +# (RefuterRegressionTests): 192 operations with off-grid translations; the inversion carries 0.4968, not 0.5. data_F2c _symmetry_space_group_name_H-M 'P 1' _symmetry_Int_Tables_number 1 diff --git a/Tests/DiffractoCoreTests/Fixtures/iron_P1_general_position.cif b/Tests/DiffractoCoreTests/Fixtures/iron_P1_general_position.cif index 6f58ec5..1b7ec4a 100644 --- a/Tests/DiffractoCoreTests/Fixtures/iron_P1_general_position.cif +++ b/Tests/DiffractoCoreTests/Fixtures/iron_P1_general_position.cif @@ -1,4 +1,5 @@ -# provenance: DiffractoKit slice-1 Gate B refuter, 2026-10-14 night (P1_cubic_1fe.cif); a regression fixture +# provenance: one Fe atom at the general position (0.11, 0.23, 0.31) of a 5 A cube, an origin shift of Pm-3m. Regression fixture +# (RefuterRegressionTests): 48 operations, every reflection allowed, ring {1 1 0} of 12; a fixed k/24 snap had given 43 operations. data_p1test _symmetry_space_group_name_H-M 'P 1' _symmetry_Int_Tables_number 1 diff --git a/Tests/DiffractoCoreTests/Fixtures/magnesium_P1_origin_shifted.cif b/Tests/DiffractoCoreTests/Fixtures/magnesium_P1_origin_shifted.cif index ec7fbfc..28c73e2 100644 --- a/Tests/DiffractoCoreTests/Fixtures/magnesium_P1_origin_shifted.cif +++ b/Tests/DiffractoCoreTests/Fixtures/magnesium_P1_origin_shifted.cif @@ -1,4 +1,5 @@ -# provenance: DiffractoKit slice-1 Gate B refuter, 2026-10-14 night (F2a_Mg_shifted.cif); a regression fixture +# provenance: hcp magnesium (P6_3/mmc) as a P1 list shifted by (0.1, 0.2, 0.3), written to 8 decimals. Regression fixture +# (RefuterRegressionTests): a detected translation of 0.20000001 must still pass the absence test, 282 of 324 allowed at 1.2 1/A. data_F2a _symmetry_space_group_name_H-M 'P 1' _symmetry_Int_Tables_number 1 diff --git a/Tests/DiffractoCoreTests/Fixtures/magnesium_P63mmc_origin_shifted_loop.cif b/Tests/DiffractoCoreTests/Fixtures/magnesium_P63mmc_origin_shifted_loop.cif index 59c7dcf..86bf3bc 100644 --- a/Tests/DiffractoCoreTests/Fixtures/magnesium_P63mmc_origin_shifted_loop.cif +++ b/Tests/DiffractoCoreTests/Fixtures/magnesium_P63mmc_origin_shifted_loop.cif @@ -1,4 +1,5 @@ -# provenance: DiffractoKit slice-1 Gate B refuter, 2026-10-14 night (F2f_Mg_loop_0.37.cif); a regression fixture +# provenance: hcp magnesium (P6_3/mmc) at a shifted origin with its 24 operations as exact rational translations in the loop. +# Regression fixture (RefuterRegressionTests): the file operations are symmetries and give 282 of 324 allowed at 1.2 1/A. # generated using pymatgen data_Mg _symmetry_space_group_name_H-M P6_3/mmc diff --git a/Tests/DiffractoCoreTests/Fixtures/mixed_centring.cif b/Tests/DiffractoCoreTests/Fixtures/mixed_centring.cif index d09381f..a59f9d6 100644 --- a/Tests/DiffractoCoreTests/Fixtures/mixed_centring.cif +++ b/Tests/DiffractoCoreTests/Fixtures/mixed_centring.cif @@ -1,4 +1,5 @@ -# provenance: DiffractoKit slice-1 Gate B refuter, 2026-10-14 night (mixed_centred.cif); a regression fixture +# provenance: an F-centred cubic oxide with Fe2+ 0.5 and Fe3+ 0.5 sharing the origin, centring written as a P1 operation loop. +# Regression fixture (RefuterRegressionTests): co-sited species keep their own rows, 8 Fe sites of 0.5 and 4 O after expansion. data_mixed_centred _cell_length_a 8.39 _cell_length_b 8.39 diff --git a/Tests/DiffractoCoreTests/Fixtures/polonium_P1_supercell_7x.cif b/Tests/DiffractoCoreTests/Fixtures/polonium_P1_supercell_7x.cif index 52c8768..9950d0d 100644 --- a/Tests/DiffractoCoreTests/Fixtures/polonium_P1_supercell_7x.cif +++ b/Tests/DiffractoCoreTests/Fixtures/polonium_P1_supercell_7x.cif @@ -1,4 +1,5 @@ -# provenance: DiffractoKit slice-1 Gate B refuter, 2026-10-14 night (F3_super7.cif); a regression fixture +# provenance: simple cubic polonium (a 3.35) as a 1 x 1 x 7 supercell in P1, z written to 8 decimals. Regression fixture +# (RefuterRegressionTests, conventional-cell pins): translations of k/7 lie off the 1/24 grid; 112 operations, 122 of 782 allowed at 0.9 1/A. data_F3 _symmetry_space_group_name_H-M 'P 1' _symmetry_Int_Tables_number 1 diff --git a/Tests/DiffractoCoreTests/Fixtures/rocksalt_P1_origin_shifted.cif b/Tests/DiffractoCoreTests/Fixtures/rocksalt_P1_origin_shifted.cif index a78044e..027da0e 100644 --- a/Tests/DiffractoCoreTests/Fixtures/rocksalt_P1_origin_shifted.cif +++ b/Tests/DiffractoCoreTests/Fixtures/rocksalt_P1_origin_shifted.cif @@ -1,4 +1,5 @@ -# provenance: DiffractoKit slice-1 Gate B refuter, 2026-10-14 night (F2d_NaCl_shifted.cif); a regression fixture +# provenance: rock salt NaCl (Fm-3m) as a P1 list shifted by (0.9172, 0.6292, 0.5141), exact 4-decimal coordinates. Regression +# fixture (RefuterRegressionTests): 192 operations and the absences and rings of the unshifted structure. data_F2d_NaCl _symmetry_space_group_name_H-M 'P 1' _symmetry_Int_Tables_number 1 diff --git a/Tests/Fixtures/README.md b/Tests/Fixtures/README.md index 9fe62b2..e8ce501 100644 --- a/Tests/Fixtures/README.md +++ b/Tests/Fixtures/README.md @@ -13,35 +13,35 @@ Licence: every fixture is generated from this kit's own code and the pinned Pyth | File | Size | Generated by | Note | |---|---|---|---| -| `DiffractoCoreTests/Fixtures/alpha_quartz_P1_origin_shifted.cif` | 747 B | review regression CIF, read by `RefuterRegressionTests` | | +| `DiffractoCoreTests/Fixtures/alpha_quartz_P1_origin_shifted.cif` | 828 B | review regression CIF, read by `RefuterRegressionTests` | | | `DiffractoCoreTests/Fixtures/alpha_quartz_P3221.cif` | 924 B | `tools/pins/cells/pins.py`, `tools/pins/structure-factors/pins.py` | | | `DiffractoCoreTests/Fixtures/alpha_quartz_P3221_as_seeded.cif` | 712 B | `tools/pins/cells/pins.py` | | | `DiffractoCoreTests/Fixtures/calcite_R-3c_hex.cif` | 1.4 KB | `tools/pins/cells/pins.py`, `tools/pins/structure-factors/pins.py` | | | `DiffractoCoreTests/Fixtures/cdi2_P-3m1.cif` | 462 B | `tools/pins/cells/pins.py`, `tools/pins/structure-factors/pins.py` | | | `DiffractoCoreTests/Fixtures/cif-symmetry-pins.json` | 19.4 KB | `tools/pins/cif-symmetry/pins.py` | | -| `DiffractoCoreTests/Fixtures/corundum_P1_rhombohedral.cif` | 1.3 KB | review regression CIF, read by `RefuterRegressionTests` | | +| `DiffractoCoreTests/Fixtures/corundum_P1_rhombohedral.cif` | 1.4 KB | review regression CIF, read by `RefuterRegressionTests` | | | `DiffractoCoreTests/Fixtures/fluorapatite_P63m.cif` | 781 B | `tools/pins/cells/pins.py`, `tools/pins/structure-factors/pins.py` | | | `DiffractoCoreTests/Fixtures/gold_P1.cif` | 396 B | `tools/pins/cells/pins.py`, `tools/pins/structure-factors/pins.py` | | -| `DiffractoCoreTests/Fixtures/gold_P1_origin_shifted_exact_decimals.cif` | 588 B | review regression CIF, read by `RefuterRegressionTests` | | +| `DiffractoCoreTests/Fixtures/gold_P1_origin_shifted_exact_decimals.cif` | 697 B | review regression CIF, read by `RefuterRegressionTests` | | | `DiffractoCoreTests/Fixtures/gold_centring_ops.cif` | 440 B | `tools/pins/cells/pins.py` | | | `DiffractoCoreTests/Fixtures/ideal_hcp_P1.cif` | 504 B | `tools/pins/cells/pins.py` | | -| `DiffractoCoreTests/Fixtures/iron_P1_general_position.cif` | 470 B | review regression CIF, read by `RefuterRegressionTests` | | -| `DiffractoCoreTests/Fixtures/magnesium_P1_origin_shifted.cif` | 529 B | review regression CIF, read by `RefuterRegressionTests` | | +| `DiffractoCoreTests/Fixtures/iron_P1_general_position.cif` | 622 B | review regression CIF, read by `RefuterRegressionTests` | | +| `DiffractoCoreTests/Fixtures/magnesium_P1_origin_shifted.cif` | 670 B | review regression CIF, read by `RefuterRegressionTests` | | | `DiffractoCoreTests/Fixtures/magnesium_P63mmc.cif` | 401 B | `tools/pins/phase-vectors/pins.py` | | | `DiffractoCoreTests/Fixtures/magnesium_P63mmc_24ops.cif` | 896 B | `tools/pins/cells/pins.py` | | -| `DiffractoCoreTests/Fixtures/magnesium_P63mmc_origin_shifted_loop.cif` | 1.5 KB | review regression CIF, read by `RefuterRegressionTests` | | -| `DiffractoCoreTests/Fixtures/mixed_centring.cif` | 561 B | review regression CIF, read by `RefuterRegressionTests` | | +| `DiffractoCoreTests/Fixtures/magnesium_P63mmc_origin_shifted_loop.cif` | 1.7 KB | review regression CIF, read by `RefuterRegressionTests` | | +| `DiffractoCoreTests/Fixtures/mixed_centring.cif` | 706 B | review regression CIF, read by `RefuterRegressionTests` | | | `DiffractoCoreTests/Fixtures/mp-import/input-aluminium-v3.json` | 1.6 KB | `tools/pins/mp-import/pins.py` | third party: Materials Project data, CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/), source https://materialsproject.org; credited in `NOTICE` | | `DiffractoCoreTests/Fixtures/mp-import/input-magnesiumJSON.json` | 1.1 KB | `tools/pins/mp-import/pins.py` | third party: Materials Project data, CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/), source https://materialsproject.org; credited in `NOTICE` | | `DiffractoCoreTests/Fixtures/mp-import/input-siliconJSON.json` | 2.9 KB | `tools/pins/mp-import/pins.py` | third party: Materials Project data, CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/), source https://materialsproject.org; credited in `NOTICE` | | `DiffractoCoreTests/Fixtures/mp-import/pymatgen-pins.json` | 6.3 KB | `tools/pins/mp-import/pins.py` | | -| `DiffractoCoreTests/Fixtures/polonium_P1_supercell_7x.cif` | 737 B | `tools/pins/conventional-cell/pins.py` | | +| `DiffractoCoreTests/Fixtures/polonium_P1_supercell_7x.cif` | 894 B | `tools/pins/conventional-cell/pins.py` | | | `DiffractoCoreTests/Fixtures/presets-pins.json` | 21.1 KB | `tools/pins/presets/pins.py` | | | `DiffractoCoreTests/Fixtures/pymatgen-cells-pins.json` | 2.7 MB | `tools/pins/cells/pins.py`, `tools/pins/conventional-cell/pins.py` | | | `DiffractoCoreTests/Fixtures/pyrite_Pa-3.cif` | 1.1 KB | `tools/pins/cells/pins.py`, `tools/pins/structure-factors/pins.py` | | | `DiffractoCoreTests/Fixtures/pyrite_Pa-3_as_seeded.cif` | 787 B | `tools/pins/cells/pins.py` | | | `DiffractoCoreTests/Fixtures/rocksalt_Fm-3m_192ops.cif` | 3.5 KB | `tools/pins/cells/pins.py`, `tools/pins/structure-factors/pins.py` | | -| `DiffractoCoreTests/Fixtures/rocksalt_P1_origin_shifted.cif` | 703 B | review regression CIF, read by `RefuterRegressionTests` | | +| `DiffractoCoreTests/Fixtures/rocksalt_P1_origin_shifted.cif` | 823 B | review regression CIF, read by `RefuterRegressionTests` | | | `DiffractoCoreTests/Fixtures/silicon_Fd-3m.cif` | 498 B | `tools/pins/cells/pins.py`, `tools/pins/structure-factors/pins.py` | | | `DiffractoCoreTests/Fixtures/spglib-conventional-cell-pins.json` | 2.2 MB | `tools/pins/conventional-cell/pins.py` | | | `DiffractoCoreTests/Fixtures/tetragonal-pins.json` | 14.3 KB | `tools/pins/cells/tetragonal-pins.py` | | From 383165d303c91e90c897dcf51537edc29420c0be Mon Sep 17 00:00:00 2001 From: Paullo9 Date: Sat, 31 Oct 2026 01:06:37 +0100 Subject: [PATCH 2/3] docs: name the pins and rules the pin scripts mean, not work sessions The comments, docstrings, READMEs and check messages of the pin scripts and the consumer check said slice 1, slice 2, slice 3a, slice 4. They now name the pin file, the generator or the rule they mean (the cells pins and tools/pins/cells/pins.py, the structure-factor pins, the ring-calibration pins). In conventional-cell/pins.py and phase-vectors/pins.py the variables SLICE1_PINS, slice1, SLICE2_PINS and slice2 are now CELLS_PINS, cells_pins, STRUCTURE_FACTOR_PINS and sf_pins. Strings written into fixtures (the source label slice-1, purpose, the families and rings descriptions, the vs_slice2 keys) stay: no fixture is regenerated here. The first docstring lines of seven scripts are left to PR #41. Rerunning structure-factors/pins.py and conventional-cell/pins.py with the pinned environment gives byte-identical pins apart from generated_at; every edited script compiles. Part of #40. Co-Authored-By: Claude Opus 5.5 --- tools/consumer-check/README.md | 2 +- .../consumer-check/ConventionalChecks.swift | 4 +-- tools/pins/cells/tetragonal-pins.py | 4 +-- tools/pins/conventional-cell/pins.py | 30 ++++++++--------- tools/pins/phase-vectors/README.md | 12 +++---- tools/pins/phase-vectors/pins.py | 32 +++++++++---------- tools/pins/ring-calibration/q-pins.py | 8 ++--- tools/pins/ring-calibration/ring-pins.py | 2 +- tools/pins/ring-indexing/pins.py | 2 +- tools/pins/structure-factors/README.md | 6 ++-- tools/pins/structure-factors/pins.py | 24 +++++++------- 11 files changed, 63 insertions(+), 63 deletions(-) diff --git a/tools/consumer-check/README.md b/tools/consumer-check/README.md index 8b7df45..f11a248 100644 --- a/tools/consumer-check/README.md +++ b/tools/consumer-check/README.md @@ -20,7 +20,7 @@ DiffractoKit 0.1.0 adds `RingFitChecks.swift` and two sections of `main.swift`: `FitReport`'s new fields, `progress:` / `isCancelled:` on every fit, `LocalizedError` on the three error types, and the tetragonal settings. 397 checks at 0.0.4. -Slice 1b adds `ConventionalChecks.swift`: `UnitCell.niggliReduced` / `ReducedCell` (fcc gold's primitive cell, a skewed basis of +The conventional setting and Niggli reduction add `ConventionalChecks.swift`: `UnitCell.niggliReduced` / `ReducedCell` (fcc gold's primitive cell, a skewed basis of it, an invalid cell), `UnitCell.conventionalCell` / `ConventionalCell` / `BravaisLattice` (gold primitive -> cF with 4 centring translations, bcc -> cI, a C-centred monoclinic primitive cell -> mC with b the unique axis), `CrystalStructure.setting` / `conventionalSetting` / `ConventionalSetting` and its four conversions (gold: 192 operations in the conventional cell and 48 in diff --git a/tools/consumer-check/Sources/consumer-check/ConventionalChecks.swift b/tools/consumer-check/Sources/consumer-check/ConventionalChecks.swift index ae1d957..4f895ea 100644 --- a/tools/consumer-check/Sources/consumer-check/ConventionalChecks.swift +++ b/tools/consumer-check/Sources/consumer-check/ConventionalChecks.swift @@ -1,6 +1,6 @@ // // ConventionalChecks.swift -// Role: the public API DiffractoKit slice 1b adds to DiffractoCore, used from outside the package with a real call and a +// Role: the conventional-setting and Niggli-reduction API of DiffractoCore, used from outside the package with a real call and a // result a reader verifies by hand: the Niggli reduction and its change of basis (`niggliReduced`, `ReducedCell`), // the conventional cell and its Bravais lattice (`conventionalCell`, `ConventionalCell`, `BravaisLattice`), the // symmetry of a structure in any setting (`detectingSymmetry(conventional:tolerance:search:)`, @@ -286,7 +286,7 @@ func runConventionalChecks(_ report: inout Report) { r.equal("and is not at the Cartesian 1e-3 A", tight.operations.count < 96, true) r.equal("the proposed Cartesian tolerance is 1e-3 A", SymmetryDetection.Tolerance.proposedCartesian, .cartesian(1e-3)) r.equal("it differs from the fractional default", SymmetryDetection.Tolerance.proposedCartesian != .default, true) - r.equal("the default is slice 1's fractional 5e-3", SymmetryDetection.Tolerance.default, .fractional(SymmetryDetection.positionTolerance)) + r.equal("the default is the fractional 5e-3 position tolerance", SymmetryDetection.Tolerance.default, .fractional(SymmetryDetection.positionTolerance)) r.equal("(its value)", SymmetryDetection.Tolerance.default, .fractional(5e-3)) let proposed = CrystalStructure.detectingSymmetry(cell: big, sites: displaced, tolerance: .proposedCartesian, conventional: false) r.equal("detecting with the proposed tolerance equals the .cartesian(1e-3) call", proposed.operations, tight.operations) diff --git a/tools/pins/cells/tetragonal-pins.py b/tools/pins/cells/tetragonal-pins.py index 09fdc7f..2f6c8a8 100644 --- a/tools/pins/cells/tetragonal-pins.py +++ b/tools/pins/cells/tetragonal-pins.py @@ -7,7 +7,7 @@ It writes Tests/DiffractoCoreTests/Fixtures/tetragonal-pins.json BEFORE the Swift fix exists. Oracle: pymatgen 2026.9.24 (pymatgen-core 2026.10.16) SpacegroupAnalyzer on spglib 2.8.0, NumPy 2.5.4. -Tolerance: symprec = 1e-4 (Angstrom, spglib's), as slice 1's pins (tools/pins/cells/pins.py SYMPREC). The inputs are +Tolerance: symprec = 1e-4 (Angstrom, spglib's), as the cells pins (tools/pins/cells/pins.py SYMPREC). The inputs are exact (coordinates written to 4 decimals and expanded by the space group's own operations, so every equivalent site is exact to 1e-15): the answer does not depend on symprec between 1e-6 and 1e-2, which the script checks (symprec 1e-2 gives the same space group number). The kit's own position tolerance is a fixed 5e-3 fractional (SymmetryDetection.positionTolerance); @@ -33,7 +33,7 @@ any), and the cubic one is invariant. Per entry the fixture carries: the cell, the sites, pymatgen's space-group number, point-group symbol, the Laue class -(the 32 -> 11 table of slice 1), the number of point operations of the point group (get_point_group_operations), the number of +(the 32 -> 11 table of tools/pins/cells/pins.py), the number of point operations of the point group (get_point_group_operations), the number of space-group operations on the cell as given (get_symmetry_operations; centring translations counted, lattice translations not), and the order of the Laue group (2 x the point-group's proper part: the count of {+-R}). """ diff --git a/tools/pins/conventional-cell/pins.py b/tools/pins/conventional-cell/pins.py index 3d0d1e5..fbca6b7 100644 --- a/tools/pins/conventional-cell/pins.py +++ b/tools/pins/conventional-cell/pins.py @@ -9,11 +9,11 @@ Oracles (all from the pinned venv): spglib 2.8.0 `niggli_reduce` and `get_symmetry_dataset` / `standardize_cell`; pymatgen-core 2026.10.16 `Lattice.get_niggli_reduced_lattice` as the second, independent Niggli implementation. The ring -list in the conventional setting uses slice 1's own definitions (tools/pins/cells/pins.py, imported, not copied) on spglib's +list in the conventional setting uses the definitions of tools/pins/cells/pins.py (imported, not copied) on spglib's standardised cell. Agreement is agreement, not truth: truth (the Niggli conditions, P unimodular, the group-theory counts) is checked in the Swift tests directly. -Inputs: pymatgen-cells-pins.json (cell and expanded sites of every slice-1 structure), the named non-conventional cells built +Inputs: pymatgen-cells-pins.json (cell and expanded sites of every CIF fixture structure there), the named non-conventional cells built below, and RANDOM_PER_LATTICE random cells of each of the 14 Bravais lattices under a random unimodular transformation (|entries| <= 2), seed SEED. @@ -41,7 +41,7 @@ HERE = os.path.dirname(os.path.abspath(__file__)) KIT = os.path.abspath(os.path.join(HERE, "..", "..", "..")) FIXTURES = os.path.join(KIT, "Tests", "DiffractoCoreTests", "Fixtures") -SLICE1_PINS = os.path.join(FIXTURES, "pymatgen-cells-pins.json") +CELLS_PINS = os.path.join(FIXTURES, "pymatgen-cells-pins.json") OUT = os.path.join(FIXTURES, "spglib-conventional-cell-pins.json") SEED = 20261015 @@ -51,8 +51,8 @@ K_MAX = 1.2 NIGGLI_AGREE = 1e-8 # the two oracles must agree on the reduced parameters (relative lengths, degrees) -# slice 1's generator, imported as a module (its main() is guarded) -_spec = importlib.util.spec_from_file_location("slice1_pins", os.path.join(HERE, "..", "cells", "pins.py")) +# the cells pin generator, imported as a module (its main() is guarded) +_spec = importlib.util.spec_from_file_location("cells_pins", os.path.join(HERE, "..", "cells", "pins.py")) s1 = importlib.util.module_from_spec(_spec) _spec.loader.exec_module(s1) @@ -162,7 +162,7 @@ def structure_entry(name, source, params, Z, occ, frac): ratio = lat["conv_volume"] / V check(f"{name}: conventional-cell volume ratio {ratio} is an integer", abs(ratio - round(ratio)) < 1e-6) conv_ops = n_rot * int(round(ratio)) - # the standardised (conventional, not primitive) cell and its rings: slice 1's own definitions on it + # the standardised (conventional, not primitive) cell and its rings: the definitions of tools/pins/cells/pins.py on it std = spglib.standardize_cell((A, frac, Zs), to_primitive=False, no_idealize=False, symprec=SYMPREC) check(f"{name}: spglib standardises the cell", std is not None) sL, sP, sZ = std @@ -208,7 +208,7 @@ def transformed(A, frac, P): return params_of(A2), f2 -def named_cells(slice1): +def named_cells(cells_pins): out = [] a = 4.0782 A = a / 2 * np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]], dtype=float) @@ -217,7 +217,7 @@ def named_cells(slice1): A = a / 2 * np.array([[-1, 1, 1], [1, -1, 1], [1, 1, -1]], dtype=float) out.append(("fe_bcc_primitive", params_of(A), [26], [1.0], [[0, 0, 0]])) # beta'' C-centred primitive cell: spglib's primitive of the 22-atom cell - b = next(s for s in slice1["structures"] if s["name"] == "beta_double_prime") + b = next(s for s in cells_pins["structures"] if s["name"] == "beta_double_prime") cellb = [b["cell"][k] for k in ("a", "b", "c", "alpha", "beta", "gamma")] stb = Structure(Lattice.from_parameters(*cellb, vesta=True), [s["z"] for s in b["sites"]], [s["frac"] for s in b["sites"]]) @@ -340,23 +340,23 @@ def main(): "spglib": spglib.__version__, "numpy": np.__version__, } print("oracle:", json.dumps(versions)) - with open(SLICE1_PINS, encoding="utf-8") as f: - slice1 = json.load(f) + with open(CELLS_PINS, encoding="utf-8") as f: + cells_pins = json.load(f) structures = [] - print("\n--- slice 1's fixtures (P1 sites, as the Swift tests build them) ---") - for s in slice1["structures"]: + print("\n--- the CIF fixture structures of pymatgen-cells-pins.json (P1 sites, as the Swift tests build them) ---") + for s in cells_pins["structures"]: sites = s.get("sites_as_written") or s["sites"] params = [s["cell"][k] for k in ("a", "b", "c", "alpha", "beta", "gamma")] e = structure_entry(s["name"], "slice-1", params, [x["z"] for x in sites], [x["occupancy"] for x in sites], [x["frac"] for x in sites]) - # slice 1's own pin for the same crystal agrees where the setting is unchanged + # the cells pin for the same crystal agrees where the setting is unchanged if abs(e["lattice"]["conv_volume"] / abs(np.linalg.det(lattice_matrix(params))) - 1) < 1e-9: - check(f"{s['name']}: conventional operation count equals slice 1's pin", + check(f"{s['name']}: conventional operation count equals the cells pin", e["conventional_operations_count"] == len(s["operations"]), f"{e['conventional_operations_count']} vs {len(s['operations'])}") structures.append(e) print("\n--- named non-conventional cells ---") - for name, params, Z, occ, frac in named_cells(slice1): + for name, params, Z, occ, frac in named_cells(cells_pins): structures.append(structure_entry(name, "named", params, Z, occ, frac)) # the counts stated before the run diff --git a/tools/pins/phase-vectors/README.md b/tools/pins/phase-vectors/README.md index 6230803..fda2577 100644 --- a/tools/pins/phase-vectors/README.md +++ b/tools/pins/phase-vectors/README.md @@ -1,6 +1,6 @@ # tools/pins/phase-vectors: the pin generators -Written before the Swift of slice 4 (DiffractoOrientation) was trusted; the Swift +Written before the Swift of DiffractoOrientation vector matching was trusted; the Swift tests read the file these write. A difference is a failure of the port, never a tolerance to widen (except the changes named under "Measured floors and open points"). @@ -37,13 +37,13 @@ matches by hkl). - `phases`: Al 4.0495 (fcc, Z 13), Au 4.0782, Cu 3.6149 (the apps' presets of mac4DSTEM 7d6fdbd1, the sites as `Crystal.fcc` writes them), theta'-Al2Cu, beta''-Mg5Si6 (the apps' `Crystal.betaDoublePrime`, 22 sites in the apps' order and unwrapped - expressions; `cif_check` shows each site, modulo 1, is a site of the CIF slice 2 pinned, worst difference recorded), Mg hcp + expressions; `cif_check` shows each site, modulo 1, is a site of the CIF the structure-factor pins hold, worst difference recorded), Mg hcp (3.2094 / 5.2108, sites (0,0,0), (2/3,1/3,1/2)), one triclinic cell. Each carries the cell, the sites with occupancy, `lat_real`, `lat_inv`, and per zone axis: `n`, `e1`, `e2`, py4DSTEM's x', y', z', `frame_map` (the 2x2 `M` with `(g.x', g.y') = M (g.e1, g.e2)`, `phi_py_rad`, `phi_py_deg`, `det`, `handedness`), `counts`, `flags`, `entries`, `library`, `kv200`. - Zone axes: cubic Al, Au, Cu [001] [011] [111] [112] [123]; theta' the same plus [100] (edge-on); beta'' the five plus [010] (the needle); Mg [001] [100] [110] [210] [111]; triclinic [001] [010] [100] [011] [111]. 37 in all. -- `structure_factor_check` (per phase): py4DSTEM's hkl set and float32 `|F|^2` against V2's, and against slice 2's pins at kMax +- `structure_factor_check` (per phase): py4DSTEM's hkl set and float32 `|F|^2` against V2's, and against the structure-factor pins at kMax 1.2 for Al, Au, Mg, beta''. - `euler`: 1000 seeded random rotations (SciPy `Rotation.random`, `default_rng(20261015)`), the gimbal and near-gimbal cases (Phi = 0, pi, 1e-9, 1e-10, 5e-9, 2e-8, 5e-8, 9e-8, 1e-7, 1e-6, 1e-5, 1e-3, pi - 1e-5, pi - 1e-9, pi - 1e-10, pi - 5e-9, each with three @@ -68,8 +68,8 @@ matches by hkl). py4DSTEM**; adopted instead: hkl sets equal and `|q|` within 1e-12 against py4DSTEM (measured 8.9e-16), relative intensity within 5e-5 against py4DSTEM (the float32 floor, measured 1.2e-5), within 1e-12 against the float64 reference V2 (the `entries` / `library` columns); the kit's q rotated by phi_py equals py4DSTEM's x', y' within 1e-12 (measured 8.9e-16). -3. **|F|^2 against slice 2** (kit float64, Al, Au, Mg, beta'' at kMax 1.2, kept sets equal): max relative difference 1.6e-15 (Al), - 4.3e-16 (Au), 7.3e-16 (Mg), 5.5e-13 (beta'', slice 2 pinned the CIF's wrapped sites). py4DSTEM's float32: 1.7e-7, 1.5e-7, 2.7e-7, 1.1e-5. +3. **|F|^2 against the structure-factor pins** (kit float64, Al, Au, Mg, beta'' at kMax 1.2, kept sets equal): max relative difference 1.6e-15 (Al), + 4.3e-16 (Au), 7.3e-16 (Mg), 5.5e-13 (beta'', the structure-factor pins hold the wrapped sites of the CIF). py4DSTEM's float32: 1.7e-7, 1.5e-7, 2.7e-7, 1.1e-5. Same convention (Lobato table, Debye-Waller off, occupancy, 1/V, no 2 pi). 4. **The hkl sets**: py4DSTEM's tiling misses no kept hkl on any of the seven phases at kMax 1.6 (beta'' included), and keeps none the float64 cut drops; the sets at the zone axes equal V2's in all 37 cases. @@ -94,4 +94,4 @@ matches by hkl). definitions do not cover them and the oracle may not read the Swift. 10. theta'-Al2Cu has no definition in the apps or the kit. Pinned: body-centred tetragonal 4.04 / 5.80 A, Cu 2a (0,0,0), (1/2,1/2,1/2), Al 4d (0,1/2,1/4), (1/2,0,1/4), (0,1/2,3/4), (1/2,0,3/4) (the fluorite-derived arrangement), sites written out. The triclinic cell is - slice 1's (4, 5, 6, 80, 85, 95) with two invented sites: the kit has no triclinic CIF fixture. + the triclinic parameter cell of pymatgen-cells-pins.json (4, 5, 6, 80, 85, 95) with two invented sites: the kit has no triclinic CIF fixture. diff --git a/tools/pins/phase-vectors/pins.py b/tools/pins/phase-vectors/pins.py index dbbdf05..7be7ae0 100644 --- a/tools/pins/phase-vectors/pins.py +++ b/tools/pins/phase-vectors/pins.py @@ -4,7 +4,7 @@ References/python/pymatgen/bin/python -I -B DiffractoKit/tools/pins/phase-vectors/pins.py -It writes Tests/DiffractoOrientationTests/Fixtures/py4dstem-phase-vector-pins.json BEFORE any Swift of slice 4 is trusted. Deterministic: no +It writes Tests/DiffractoOrientationTests/Fixtures/py4dstem-phase-vector-pins.json BEFORE any Swift of DiffractoOrientation vector matching is trusted. Deterministic: no clock, seeded random numbers (np.random.default_rng), sorted keys, so two runs give the same bytes. Two interpreters, one memory cap: this script runs the py4DSTEM half (tools/pins/phase-vectors/pins-py4dstem.py) as a subprocess with @@ -14,8 +14,8 @@ What is an oracle for what -------------------------- V2 "reference": NumPy float64 written from the DEFINITIONS in this file, the structure factors from the - py4DSTEM electron-scattering table (slice 2's sha-locked file) with the cell/g conventions py4DSTEM uses, every hkl with - |F| > 1e-4 (strict, slice 2) and |g| <= kMax enumerated by a bound that cannot miss a lattice point, the FLAT excitation + py4DSTEM electron-scattering table (the sha-locked file of the structure-factor pins) with the cell/g conventions py4DSTEM uses, every hkl with + |F| > 1e-4 (strict, as the structure-factor pins) and |g| <= kMax enumerated by a bound that cannot miss a lattice point, the FLAT excitation s = g.n, I = |F|^2 exp(-(s/w)^2), the in-plane frame (e1, e2) of `ZoneAxisFrame`. This is what the Swift library must equal (G4, and the pre-cap entries of G2). V0 py4DSTEM f050d207 as it is called (Crystal.generate_diffraction_pattern at a huge accelerating voltage, sigma = w/sqrt(2), @@ -53,7 +53,7 @@ PY4D_PYTHON = os.path.join(SUITE, "References", "python", "py4dstem", "bin", "python") PY4D_HELPER = os.path.join(HERE, "pins-py4dstem.py") BETA_CIF = os.path.join(KIT, "References", "crystal-structures", "beta_double_prime_Mg5Si6.cif") -SLICE2_PINS = os.path.join(KIT, "Tests", "DiffractoScatteringTests", "Fixtures", "numpy-structure-factor-pins.json") +STRUCTURE_FACTOR_PINS = os.path.join(KIT, "Tests", "DiffractoScatteringTests", "Fixtures", "numpy-structure-factor-pins.json") OUT_DIR = os.path.join(KIT, "Tests", "DiffractoOrientationTests", "Fixtures") OUT = os.path.join(OUT_DIR, "py4dstem-phase-vector-pins.json") @@ -67,8 +67,8 @@ FLOOR = 0.05 CAP = 48 IN_PLANE_STEP = 2.0 -CUT = 1e-4 # |F| cut, strict (slice 2) -TIE_TOL = 1e-12 # slice 2 / slice 1 3.8: the kit's reflection order +CUT = 1e-4 # |F| cut, strict (as the structure-factor pins) +TIE_TOL = 1e-12 # the reflection order of the structure-factor and cells pins Q_FLOOR = 1e-9 # the length floor of a projected vector BASIS_SWITCH = 0.9 # |n_x| < 0.9: reference axis x, else y SEED = 20261015 @@ -89,7 +89,7 @@ def predict(name, predicted, measured): # ---------------------------------------------------------------------------------------------------------------- -# f_e, lattice, reflections (slice 2's definitions, copied, not imported) +# f_e, lattice, reflections (the definitions of tools/pins/structure-factors/pins.py, copied, not imported) # ---------------------------------------------------------------------------------------------------------------- def load_table(): data = open(TABLE_PATH, "rb").read() @@ -188,7 +188,7 @@ def four_i(z, x, zc): def beta_cif_check(sites): - """The apps' 22 sites equal, modulo 1 and as a set, what pymatgen expands from the beta'' CIF the kit's slice 2 pinned.""" + """The apps' 22 sites equal, modulo 1 and as a set, what pymatgen expands from the beta'' CIF of the structure-factor pins.""" if not os.path.isfile(BETA_CIF): return {"checked": False, "reason": "References/crystal-structures/beta_double_prime_Mg5Si6.cif absent"} with warnings.catch_warnings(): @@ -597,7 +597,7 @@ def rel(a, b): return float(abs(a - b) / max(abs(b), 1e-300)) -def phase_block(ph, table, py, slice2): +def phase_block(ph, table, py, sf_pins): name, cell, sites, axes = ph["name"], ph["cell"], ph["sites"], ph["axes"] A, B, vol = lattice(cell) H, g = enumerate_box(A, B, K_MAX) @@ -636,8 +636,8 @@ def phase_block(ph, table, py, slice2): "py4dstem_F_dtype": p["F_dtype"], "py4dstem_F2_dtype": p["F2_dtype"], "note": "py4DSTEM accumulates the structure factor in complex64, so its |F|^2 carries a float32 floor"} check(f"{name}: py4DSTEM's |F|^2 equals the float64 one to 1e-4 relative (the float32 floor)", sfcmp["max_relative_F2_difference"] <= 1e-4) - # slice 2 - s2 = slice2.get(name) + # against the structure-factor pins + s2 = sf_pins.get(name) if s2 is not None: items = {(it[0], it[1], it[2]): it for it in s2["reflections"]["items"] if it[6] > CUT and it[3] <= 1.2} mine = {tuple(int(x) for x in h): i for i, h in enumerate(Hk) if gk[i] <= 1.2} @@ -647,8 +647,8 @@ def phase_block(ph, table, py, slice2): "slice2_kept": len(items), "this_kept": len(mine), "hkl_only_in_slice2": sorted(map(list, set(items) - set(mine)))[:10], "hkl_only_here": sorted(map(list, set(mine) - set(items)))[:10], "max_relative_F2_difference_float64": max(cmp_f64, default=None), "max_relative_F2_difference_py4dstem_float32": max(cmp_p, default=None)} - check(f"{name}: float64 |F|^2 equals slice 2's to 1e-11 relative (its sites are wrapped and CIF-rounded)", max(cmp_f64) <= 1e-11, str(max(cmp_f64))) - check(f"{name}: the kept hkl sets at 1.2 equal slice 2's", set(items) == set(mine)) + check(f"{name}: float64 |F|^2 equals the structure-factor pins to 1e-11 relative (its sites are wrapped and CIF-rounded)", max(cmp_f64) <= 1e-11, str(max(cmp_f64))) + check(f"{name}: the kept hkl sets at 1.2 equal the structure-factor pins", set(items) == set(mine)) zones = [] obs_matrices = [] @@ -789,12 +789,12 @@ def main(): request = {"k_max": K_MAX, "slab": SLAB, "width": WIDTH, "phases": [{"name": ph["name"], "cell": list(ph["cell"]), "sites": ph["_sites_json"], "axes": ph["axes"]} for ph in PH]} py = run_py4dstem(request) - s2 = {s["name"]: s for s in json.load(open(SLICE2_PINS, encoding="utf-8"))["structures"]} - slice2 = {"Al": s2["Al"], "Au": s2["Au"], "Mg": s2["Mg"], "beta_double_prime_Mg5Si6": s2["beta_double_prime"]} + s2 = {s["name"]: s for s in json.load(open(STRUCTURE_FACTOR_PINS, encoding="utf-8"))["structures"]} + sf_pins = {"Al": s2["Al"], "Au": s2["Au"], "Mg": s2["Mg"], "beta_double_prime_Mg5Si6": s2["beta_double_prime"]} blocks, frames, mats = [], [], [] for ph in PH: - b, fr, mt = phase_block(ph, table, py, slice2) + b, fr, mt = phase_block(ph, table, py, sf_pins) blocks.append(b) frames += fr mats += mt diff --git a/tools/pins/ring-calibration/q-pins.py b/tools/pins/ring-calibration/q-pins.py index 4337234..80f25ba 100644 --- a/tools/pins/ring-calibration/q-pins.py +++ b/tools/pins/ring-calibration/q-pins.py @@ -5,14 +5,14 @@ ../References/python/pymatgen/bin/python -I -B tools/pins/ring-calibration/q-pins.py [out.json] It writes Tests/DiffractoGeometryTests/Fixtures/q-pins.json BEFORE the Swift tests read it. Deterministic: no clock, no random -numbers; two runs give the same bytes. Reads slice 3a's Fixtures/numpy-ring-calibration-pins.json (320 cases) for the 1/d identity. +numbers; two runs give the same bytes. Reads the ring-calibration pins Fixtures/numpy-ring-calibration-pins.json (320 cases) for the 1/d identity. The two closed forms (q in 1/Angstrom, no 2 pi; R and L in pixels; lambda in Angstrom): q(R; L, lambda) = 2 sin(atan(R/L) / 2) / lambda the Bragg angle theta = atan(R/L)/2, q = 2 sin(theta)/lambda = 1/d R(q; L, lambda) = L tan(2 asin(q lambda / 2)) its inverse, needs q lambda / 2 < 1/sqrt(2) (2 theta < 90 degrees) TRUTH. Python `decimal` at 60 digits from the exact float64 inputs (lambda is the float64 nearest to the CODATA-2018 relativistic -formula in decimal, exactly as slice 3a's tools/pins/ring-calibration/ring-pins.py computes it: `dec_wavelength_a`, imported from that file's +formula in decimal, exactly as tools/pins/ring-calibration/ring-pins.py computes it: `dec_wavelength_a`, imported from that file's formulas, copied here verbatim), then rounded ONCE to float64. Two independent decimal evaluations, which must agree to 1e-50 relative before anything is written: (a) ALGEBRAIC, no trigonometric library and no series: with x = R/L and c = cos(atan x) = 1/sqrt(1 + x^2), @@ -24,7 +24,7 @@ THE GRID: V in {60, 80, 120, 200, 300} kV, L_px in {500, 2000, 14082.307097295722, 1e5}, R in {1, 40, 150, 600, 2000} px (100 forward cases); the inverse on the q those give (100) plus q at 0.99 of the Bragg limit, q lambda / 2 = -0.99 / sqrt(2) (20: every V and L); THE IDENTITY: slice 3a's 320 cases, q(R, L_px(R, 2 theta), lambda) = 1/d (stored with the +0.99 / sqrt(2) (20: every V and L); THE IDENTITY: the 320 cases of the ring-calibration pins, q(R, L_px(R, 2 theta), lambda) = 1/d (stored with the decimal 1/d and the decimal q of the same float64 inputs, which must agree to 1e-12: L_px is rounded to float64 there); THE NaN PLANTS: zero, negative, NaN, infinite R / L / lambda / q; q lambda / 2 at the limit (the float64 nearest 1/sqrt(2), as `maximumLambdaOver2d`), one ulp above it, 0.8, 1, 10 (beyond); an overflow (q = 2 sin(...)/lambda past Double.greatestFiniteMagnitude with @@ -259,7 +259,7 @@ def main(): if len(forward) != 100 or len(inverse) != 120: fail("%d forward, %d inverse cases" % (len(forward), len(inverse))) - # ---- the identity on slice 3a's 320 cases ---- + # ---- the identity on the 320 ring-calibration cases ---- with open(RING_PINS) as f: ring = json.load(f) if len(ring["cases"]) != 320: diff --git a/tools/pins/ring-calibration/ring-pins.py b/tools/pins/ring-calibration/ring-pins.py index 0e67132..50ed9d2 100644 --- a/tools/pins/ring-calibration/ring-pins.py +++ b/tools/pins/ring-calibration/ring-pins.py @@ -37,7 +37,7 @@ decided in decimal arithmetic. Radius (10 plants): FitReport.radius = (a + b)/2 on axes planted with 3 % ellipticity, the major axis either way; the expected radius is numpy's mean, checked against the decimal sum of the two float64 axes. -Slice 3a additions to the seed: `truth` (the literals of RingCalibrationTests recomputed in decimal and asserted equal to +Ring-calibration additions to the seed: `truth` (the literals of RingCalibrationTests recomputed in decimal and asserted equal to the typed ones: lambda at 80/120/200/300 kV, Au {111} at R 150 px, 200 kV, p 0.014 mm; the small-angle excess; the ellipticity-bound gap), and the mutation list, written into the fixture beside the quantity each moves. diff --git a/tools/pins/ring-indexing/pins.py b/tools/pins/ring-indexing/pins.py index a18d1d9..187917c 100644 --- a/tools/pins/ring-indexing/pins.py +++ b/tools/pins/ring-indexing/pins.py @@ -195,7 +195,7 @@ def add_list(name, source, kmax, fams): ("beta''", "beta_double_prime")] for name, src in base: s = by_name[src] - check(s["reflections"]["k_max"] == KMAX, f"{name}: slice-2 pins k_max {s['reflections']['k_max']}") + check(s["reflections"]["k_max"] == KMAX, f"{name}: structure-factor pins k_max {s['reflections']['k_max']}") add_list(name, src, KMAX, s["families"]) gs = [f["g"] for f in s["families"]] check(all(a < b for a, b in zip(gs, gs[1:])), f"{name}: family g not strictly increasing") diff --git a/tools/pins/structure-factors/README.md b/tools/pins/structure-factors/README.md index 5afabd2..f7e1634 100644 --- a/tools/pins/structure-factors/README.md +++ b/tools/pins/structure-factors/README.md @@ -1,6 +1,6 @@ # tools/pins/structure-factors: the pin generators -Written before any Swift of slice 2 (DiffractoScattering) existed; the Swift tests +Written before any Swift of DiffractoScattering existed; the Swift tests read the files these write. A difference is a failure of the port, never a tolerance to widen. ``` @@ -11,12 +11,12 @@ tools/pins/structure-factors/fetch-table.sh - `pins.py` writes `Tests/DiffractoScatteringTests/Fixtures/numpy-structure-factor-pins.json` (about 1.7 MB): the versions, the table's sha256 and the 103 x 10 table as `np.loadtxt` reads it, `f_e` at g2 in {0, 0.25, 1, 4} for Z = 1..103, 20 structures (the apps' presets Au, Al, Si, Mg, WS2; Cu3Au-occ and Weak-cut from macTEM before v1.0.0 (history squashed); ideal hcp; the extra non-centrosymmetric - zincblende GaAs; beta'' and ten slice-1 CIF fixtures), each with every reflection above 1e-6 in |F|, the kept count, the + zincblende GaAs; beta'' and ten CIF fixtures of pymatgen-cells-pins.json), each with every reflection above 1e-6 in |F|, the kept count, the rings after the cut, the ring counts on the operation-allowed and on all reflections, the operation-absent check; the mutation-moves; pymatgen's X-ray pattern per peak with the explicit half-space members; the `cut_edge` cell whose every |F| is exactly 1e-4; the registered predictions with the measured values. - The oracle is NumPy written from the definitions (not from any Swift), the Laue group comes from pymatgen - `SpacegroupAnalyzer` (symprec 1e-4), the ring rule and canonical forms are slice 1's (copied, not imported), and + `SpacegroupAnalyzer` (symprec 1e-4), the ring rule and canonical forms are those of tools/pins/cells/pins.py (copied, not imported), and pymatgen's `XRDCalculator` is the independent phase sum. CIF structures are pinned on the sites AS WRITTEN (`frac_tolerance = 0`, what `CIFImport` keeps); `sites_snapped` and `snap_effect` carry pymatgen's default. - Exit 0: every self-check and every registered predicted count holds. 1: a self-check failed. 3: the file was written but a diff --git a/tools/pins/structure-factors/pins.py b/tools/pins/structure-factors/pins.py index c57c239..8dda54f 100644 --- a/tools/pins/structure-factors/pins.py +++ b/tools/pins/structure-factors/pins.py @@ -10,14 +10,14 @@ Oracle 1 is NumPy, written from the definitions in the comments below (not from any Swift): the table is np.loadtxt of the fetched py4DSTEM file, F(hkl) = (1/V) sum_sites f_e(Z, |g|^2) occ exp(2 pi i (h x + k y + l z)). The Laue group (for orbits and names) comes from pymatgen's SpacegroupAnalyzer (symprec 1e-4), the family rule and the -canonical form are slice 1's (copied from tools/pins/cells/pins.py, imported +canonical form are those of the cells pins (copied from tools/pins/cells/pins.py, imported from nowhere). Oracle 2 is pymatgen's XRDCalculator (an independent implementation of the phase sum with X-ray form factors). Agreement is agreement, not truth: section 5's closed forms and counts are asserted below too. The sites the Swift reads ------------------------- A structure built from a CIF is pinned on the sites AS WRITTEN (pymatgen's CifParser with frac_tolerance = 0, the -expansion slice 1 pinned as `sites_as_written`): the kit's CIFImport keeps the written value (0.333333), pymatgen's +expansion the cells pins hold as `sites_as_written`): the kit's CIFImport keeps the written value (0.333333), pymatgen's default snaps a coordinate within 1e-4 of 1/3 or 2/3 to the exact fraction. The snapped sites are pinned beside them and the effect of the snap on F is recorded (`snap_effect`). A presets' sites are exact doubles (1.0/3.0 ...), as the apps'. @@ -64,10 +64,10 @@ CUT = 1e-4 # |F| cut, Angstrom (the apps') LISTED = 1e-6 # everything above this is listed so the edge band of the cut is visible LIST_RADIUS = K_MAX * (1 + 1e-6) -FAMILY_TOL = 1e-9 # slice 1's ring rule -TIE_TOL = 1e-12 # slice 1 3.8: equal |g| within 1e-12 relative are ordered by descending (h, k, l) +FAMILY_TOL = 1e-9 # the ring rule of tools/pins/cells/pins.py +TIE_TOL = 1e-12 # the reflection order of tools/pins/cells/pins.py: equal |g| within 1e-12 relative are ordered by descending (h, k, l) SYMPREC = 1e-4 -ABSENCE_TOL = 1e-9 # slice 1 3.8 +ABSENCE_TOL = 1e-9 # the absence rule of tools/pins/cells/pins.py WAVELENGTH = 0.5 # Angstrom, the X-ray pin FE_G2 = (0.0, 0.25, 1.0, 4.0) HEX_AXES_CLASSES = ("-3", "-3m", "6/m", "6/mmm") @@ -125,7 +125,7 @@ def f_e(table, z, g2): # ---------------------------------------------------------------------------------------------------------------- -# Lattice (slice 1's; the apps' Crystal.latReal), written from the definitions +# Lattice (as in tools/pins/cells/pins.py; the apps' Crystal.latReal), written from the definitions # ---------------------------------------------------------------------------------------------------------------- def lattice(a, b, c, al, be, ga): ca, cb, cg = (math.cos(math.radians(x)) for x in (al, be, ga)) @@ -156,7 +156,7 @@ def metric_is_hexagonal(cell, eps=1e-9): # ---------------------------------------------------------------------------------------------------------------- -# Slice 1's reflection order, orbits, names, ring rule (copied from tools/pins/cells/pins.py) +# Reflection order, orbits, names and ring rule, copied from tools/pins/cells/pins.py # ---------------------------------------------------------------------------------------------------------------- I3 = np.eye(3, dtype=np.int64) @@ -208,7 +208,7 @@ def check_group_closed(name, Rs): def laue_class_from_group(Rs): - """Slice 1's Laue class rule, with 'a 4-fold / a 6-fold' read as a proper rotation (det +1) of that order.""" + """The Laue class rule of tools/pins/cells/pins.py, with 'a 4-fold / a 6-fold' read as a proper rotation (det +1) of that order.""" n = len(Rs) dets = [int(round(np.linalg.det(R))) for R in Rs] orders = [matrix_order(R) for R in Rs] @@ -272,7 +272,7 @@ def canonical(self, hkl): def allowed_flags(H, Rs, ts): - """Slice 1, 3.8: absent iff some operation has (hkl) R == (hkl) and (hkl) . t not an integer within 1e-9.""" + """The absence rule of tools/pins/cells/pins.py: absent iff some operation has (hkl) R == (hkl) and (hkl) . t not an integer within 1e-9.""" HR = np.einsum("mi,nij->mnj", H, Rs) fixed = np.all(HR == H[:, None, :], axis=2) dot = H.astype(float) @ np.asarray(ts, dtype=float).T @@ -288,7 +288,7 @@ def orbit_name(c, hex_axes): def build_families(seq, cache, hex_axes): - """seq: [(hkl tuple, g, |F|^2)] in sweep order (kept reflections). Slice 1's ring rule, plus sumF2.""" + """seq: [(hkl tuple, g, |F|^2)] in sweep order (kept reflections). The ring rule of tools/pins/cells/pins.py, plus sumF2.""" fams = [] cur = [] g0 = None @@ -485,7 +485,7 @@ def pmg_structure_cu3au(cell, sites): # ---------------------------------------------------------------------------------------------------------------- def analyze(S, kmax, detail): """S: dict with A, B, vol, sites, table, rot/trans (operations), cache, hex_axes. Returns the listing and families. - Reflections: every |g| <= kmax (+1e-6 relative when detail) in slice 1's order.""" + Reflections: every |g| <= kmax (+1e-6 relative when detail) in the order of tools/pins/cells/pins.py.""" radius = LIST_RADIUS if (detail and kmax == K_MAX) else kmax H, g = enumerate_box(S["A"], S["B"], radius) order = order_reflections(H, g) @@ -502,7 +502,7 @@ def analyze(S, kmax, detail): seq_all = [(tuple(int(x) for x in H[i]), float(g[i]), 0.0) for i in range(len(H)) if g[i] <= kmax] seq_allowed = [(tuple(int(x) for x in H[i]), float(g[i]), 0.0) for i in range(len(H)) if g[i] <= kmax and allowed[i]] res["families_all_n"] = len(build_families(seq_all, S["cache"], S["hex_axes"])) # every reflection: the lattice rings - res["families_allowed_n"] = len(build_families(seq_allowed, S["cache"], S["hex_axes"])) # slice 1: rings of the operation-allowed + res["families_allowed_n"] = len(build_families(seq_allowed, S["cache"], S["hex_axes"])) # as the cells pins: rings of the operation-allowed res["allowed_count"] = int((allowed & (g <= kmax)).sum()) return res From 114ed88bcb59bc2e8a60e634073a08a4822e2c80 Mon Sep 17 00:00:00 2001 From: Paullo9 Date: Sat, 31 Oct 2026 01:07:41 +0100 Subject: [PATCH 3/3] ci: show test entries in the CHANGELOG as Fixtures and tests release-please hides test: commits by default, so a fixture move had no CHANGELOG line. The config now lists the default sections and shows test: under Fixtures and tests; the hidden sections stay hidden. Part of #40. Co-Authored-By: Claude Opus 5.5 --- release-please-config.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/release-please-config.json b/release-please-config.json index a12a8ef..01d5a3f 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -4,6 +4,20 @@ "include-component-in-tag": false, "include-v-in-tag": false, "bump-minor-pre-major": true, + "changelog-sections": [ + {"type": "feat", "section": "Features"}, + {"type": "fix", "section": "Bug Fixes"}, + {"type": "perf", "section": "Performance Improvements"}, + {"type": "revert", "section": "Reverts"}, + {"type": "deps", "section": "Dependencies"}, + {"type": "test", "section": "Fixtures and tests"}, + {"type": "docs", "section": "Documentation", "hidden": true}, + {"type": "style", "section": "Styles", "hidden": true}, + {"type": "chore", "section": "Miscellaneous Chores", "hidden": true}, + {"type": "refactor", "section": "Code Refactoring", "hidden": true}, + {"type": "build", "section": "Build System", "hidden": true}, + {"type": "ci", "section": "Continuous Integration", "hidden": true} + ], "packages": { ".": { "package-name": "DiffractoKit",