Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Tests/DiffractoCoreTests/ConventionalSymmetryTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,9 @@ struct ConventionalSymmetryTests {
}

/// A file in its conventional setting keeps its numbers and its identity transformation; `conventional: false` is
/// the plain detection in the file's cell.
@Test func testConventionalFalseIsSliceOnesDetectionAndConventionalFilesAreUnchanged() throws {
/// the plain detection in the file's cell. Runs on the CIF fixture structures, which the conventional-cell pins label
/// `source == "slice-1"` (a label written into the fixture; it stays until the pins are regenerated).
@Test func testConventionalFalseIsThePlainDetectionAndConventionalFilesAreUnchanged() throws {
for (pin, s) in Self.detected where pin.source == "slice-1" {
let plain = CrystalStructure.detectingSymmetry(cell: pin.cell.unitCell, sites: pin.atomSites, conventional: false)
#expect(plain.conventionalSetting == nil && plain.setting == .file, "\(pin.name)")
Expand Down
10 changes: 5 additions & 5 deletions Tests/DiffractoCoreTests/FastDetectionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@ import DiffractoCore

struct FastDetectionTests {

/// numpy-structure-factor-pins.json's preset structures (cell and sites), the slice-2 fixtures.
private struct Slice2: Decodable {
/// The structures of numpy-structure-factor-pins.json (cell and sites); only the presets are used.
private struct StructureFactorPins: Decodable {
struct Build: Decodable { let how: String }
struct S: Decodable { let name: String; let build: Build; let cell: CellParams; let sites: [SitePin] }
let structures: [S]
}

private static let slice2Presets: [(String, UnitCell, [AtomSite])] = {
private static let scatteringPresets: [(String, UnitCell, [AtomSite])] = {
let url = Fixtures.directory.deletingLastPathComponent().deletingLastPathComponent()
.appendingPathComponent("DiffractoScatteringTests/Fixtures/numpy-structure-factor-pins.json")
guard let data = try? Data(contentsOf: url) else { fatalError("numpy-structure-factor-pins.json not found at \(url.path)") }
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
guard let parsed = try? decoder.decode(Slice2.self, from: data) else { fatalError("numpy-structure-factor-pins.json does not decode") }
guard let parsed = try? decoder.decode(StructureFactorPins.self, from: data) else { fatalError("numpy-structure-factor-pins.json does not decode") }
return parsed.structures.filter { $0.build.how == "preset" }.map { s in
(s.name, s.cell.unitCell, s.sites.map {
AtomSite(z: $0.z, fractional: SIMD3($0.frac[0], $0.frac[1], $0.frac[2]), occupancy: $0.occupancy)
Expand All @@ -38,7 +38,7 @@ struct FastDetectionTests {
/// scattering presets, and the CIFs of the fixtures directory.
private static var everyFixture: [(String, UnitCell, [AtomSite])] {
var out = ConventionalCellPins.shared.structures.map { ($0.name, $0.cell.unitCell, $0.atomSites) }
out += slice2Presets
out += scatteringPresets
// near-coincident rows (1.3e-3 apart, both inside the tolerance) and their inversion about a general point: the
// target of an image is ambiguous, so the plain scan's choice (the lowest unclaimed index) decides the translation
let u: [SIMD3<Double>] = [[0.10, 0.20, 0.30], [0.1013, 0.20, 0.30], [0.62, 0.31, 0.12], [0.62, 0.3113, 0.12]]
Expand Down
8 changes: 4 additions & 4 deletions docs/parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ and are counted apart.
| P9 | NumPy reading of the registered indexing rule on 7 ring lists, the T1 counts and the T4 refusal plants | `tools/pins/ring-indexing/pins.py` -> `numpy-ring-indexing-pins.json` | `RingIndexingPinsTests` | decision and counts exact |
| P10 | retired (#18): the bit-identity dump of the apps' compiled ellipse fit (mac4DSTEM 7d6fdbd1, -O and -Onone); P11 holds `fit1D` against SciPy, and the noisy, stop-rule and policy tests hold the fit against planted truth | — | — | — |
| P11 | SciPy 1.18.1 `least_squares` optimum of `fit1D`'s objective, 20 clean rings | `tools/pins/ellipse/sweep.py` -> `scipy-ellipse-sweep-pins.json` | `EllipseCalibrationTests.testCleanSweepEqualsSciPysOptimum` | centre 1e-5 px, axes 1e-7 relative, theta 1e-6 rad (measured 7.1e-8, 1.2e-9, 5.0e-8) |
| P12 | spglib 2.8.0 `niggli_reduce` and pymatgen `Lattice.get_niggli_reduced_lattice` (two independent implementations; tol 1e-5): reduced cell parameters of 2827 cells (27 structures: slice 1's fixtures plus the non-conventional cells, and 200 random cells of each of the 14 Bravais lattices under a random unimodular transformation, seed 20261015) | `tools/pins/conventional-cell/pins.py` -> `Tests/DiffractoCoreTests/Fixtures/spglib-conventional-cell-pins.json` (pymatgen 2026.9.24, pymatgen-core 2026.10.16, spglib 2.8.0, numpy 2.5.4) | `ReductionTests.testNiggliReducedCellAgreesWithSpglibAndPymatgen`; the Niggli conditions and P unimodular (truth, not the oracle): `testReducedBasisSatisfiesTheNiggliConditionsAndPIsUnimodular` | 1e-5 (the functions' own `tol`; was 1e-10; relative on lengths, degrees on angles); measured worst 3.5e-11, the two oracles agree to 2.8e-13 |
| P13 | spglib 2.8.0 `get_symmetry_dataset` and `standardize_cell` (symprec 1e-4) on the same file: the Bravais lattice name (spglib's lattice-only dataset, 2800 random cells), the conventional cell's volume, metric and parameters, operation counts and Laue class in the file's and the conventional setting (27 structures), and the rings after slice 1's definitions on the standardised cell (4332 rings, k_max 1.2) | same generator and file as P12 | `ConventionalCellTests.testBravaisNameOfRandomCellsAgreesWithConstructionAndSpglib`, `testConventionalCellAgreesWithSpglibAndTheTransformationReproducesIt`; `ConventionalSymmetryTests.testOperationsLaueClassAndBravaisInBothSettingsAgreeWithSpglib`, `testRingsInBothSettingsAgreeWithSpglibsStandardisedCell`, `testIndicesAndOperationsTransformBothWays` | names, counts, Laue class exact; volume 1e-7; metric 1e-7; ring g 1e-7 (was 1e-10, 1e-9, 1e-8; ring names only where the conventional cell has spglib's parameters: 1294 compared, 872 skipped) |
| P12 | spglib 2.8.0 `niggli_reduce` and pymatgen `Lattice.get_niggli_reduced_lattice` (two independent implementations; tol 1e-5): reduced cell parameters of 2827 cells (27 structures: the CIF fixture structures of `pymatgen-cells-pins.json` plus the named non-conventional cells, and 200 random cells of each of the 14 Bravais lattices under a random unimodular transformation, seed 20261015) | `tools/pins/conventional-cell/pins.py` -> `Tests/DiffractoCoreTests/Fixtures/spglib-conventional-cell-pins.json` (pymatgen 2026.9.24, pymatgen-core 2026.10.16, spglib 2.8.0, numpy 2.5.4) | `ReductionTests.testNiggliReducedCellAgreesWithSpglibAndPymatgen`; the Niggli conditions and P unimodular (truth, not the oracle): `testReducedBasisSatisfiesTheNiggliConditionsAndPIsUnimodular` | 1e-5 (the functions' own `tol`; was 1e-10; relative on lengths, degrees on angles); measured worst 3.5e-11, the two oracles agree to 2.8e-13 |
| P13 | spglib 2.8.0 `get_symmetry_dataset` and `standardize_cell` (symprec 1e-4) on the same file: the Bravais lattice name (spglib's lattice-only dataset, 2800 random cells), the conventional cell's volume, metric and parameters, operation counts and Laue class in the file's and the conventional setting (27 structures), and the rings by the reflection-list and ring rule of `tools/pins/cells/pins.py` on the standardised cell (4332 rings, k_max 1.2) | same generator and file as P12 | `ConventionalCellTests.testBravaisNameOfRandomCellsAgreesWithConstructionAndSpglib`, `testConventionalCellAgreesWithSpglibAndTheTransformationReproducesIt`; `ConventionalSymmetryTests.testOperationsLaueClassAndBravaisInBothSettingsAgreeWithSpglib`, `testRingsInBothSettingsAgreeWithSpglibsStandardisedCell`, `testIndicesAndOperationsTransformBothWays` | names, counts, Laue class exact; volume 1e-7; metric 1e-7; ring g 1e-7 (was 1e-10, 1e-9, 1e-8; ring names only where the conventional cell has spglib's parameters: 1294 compared, 872 skipped) |
| P20 | pymatgen 2026.9.24 `SpacegroupAnalyzer(symprec=1e-4, angle_tolerance=5)` `get_conventional_standard_structure` and `get_primitive_standard_structure` of three committed Materials Project JSON structures (Al Fm-3m, Si Fd-3m, Mg P6_3/mmc; the app route is fed pymatgen's own primitive cells, so the F conversion really runs on Al and Si): space group, lattice, sites | `tools/pins/mp-import/pins.py` -> `Tests/DiffractoCoreTests/Fixtures/mp-import/pymatgen-pins.json` (pymatgen-core 2026.10.16, spglib 2.8.0, numpy 2.5.4) | `CellStandardisationTests.appRouteMatchesPymatgenConventional` (the app's fixed-centring route), `detectedConventionalCellMatchesPymatgen` (the kit's detection, the reference route), `bodyCentredPrimitiveGivesTheCube`, `guardsRefuseBadInput`, `moreGuardsRefuse` | cell parameters 1e-9 (app route) and 1e-6 (detected route, metric tolerance 1e-6), relative above 1 Å or degrees and absolute below; sites 1e-9 fractional modulo one rigid origin shift (pymatgen moves the origin to its standard one, the app keeps the served origin, a named difference); only F is pinned against pymatgen (Al, Si); the I matrix is checked against a closed form (bcc primitive gives the cube) and the A, B, C and R matrices are not tested |
| P14 | py4DSTEM f050d207 run (a pinned environment outside the repository, its files checked against the f050d207 tree by `pins-py4dstem.py`): `Crystal.generate_diffraction_pattern` at lambda -> 0 (`setup_diffraction(1e30)`), sigma = w/sqrt(2), on 7 phases x 37 zone axes (hkl, gx, gy, intensity), and `parse_orientation` (the in-plane frame per axis); NumPy float64 with py4DSTEM's convention beside it | `tools/pins/phase-vectors/pins.py` (+ `pins-py4dstem.py`) -> `Tests/DiffractoOrientationTests/Fixtures/py4dstem-phase-vector-pins.json` | `PhaseVectorPinsTests` (G2) | hkl sets equal; \|q\| 1e-7 (was 1e-12; measured 1.1e-15); rotated q 1e-7; frame vectors and map 1e-7 (was 1e-15); intensity 5e-5 vs py4DSTEM (its complex64: 1.15e-5 measured), 1e-12 vs NumPy (one sub-floor reflection at 2.7e-12, a known issue) |
| P15 | SciPy 1.18.1 `Rotation.from_matrix(M.T).as_euler("zxz")` and `from_euler` on 1000 seeded rotations and the gimbal cases; NumPy closed forms of the matcher (chance fraction, score, nearest, Friedel pair, in-plane steps, the 49 low-index axes) | same file | `PhaseVectorPinsTests` (G3, G4) | angles rtol 1e-7 + atol 1e-12 (was one Float ulp + 1e-12; Phi within 1.5e-8 near 0 and pi, a known issue); closed forms 1e-15 |
Expand All @@ -95,8 +95,8 @@ and are counted apart.
| P34 | py4DSTEM f050d207 `Crystal.symmetry_reduce_directions` on real `Crystal`s: 57 centrosymmetric space groups across the 11 Laue classes and both -3m settings, pymatgen 2025.1.9 (the py4DSTEM venv's) operator order, `orientation_plan('auto')` sectors, 151 seeded and designed matrices each | `tools/pins/orientation-symmetry/family-real-pins.py` (py4DSTEM venv) -> `py4dstem-family-real-pins.json`, `PymatgenOperatorTable.swift` | `Py4DSTEMRealFamilyTests` | family entries 1e-7 absolute (was 1e-12; measured 4.4e-16) on 8 607 matrices; `.sortedFractional` (the P28 harness) differs on 139 and 136 of 151 for 2/m and -31m (recorded); wall columns not compared |
| P35 | spglib 2.8.0 `get_spacegroup_type` over all 530 Hall settings: every Hermann-Mauguin spelling (short, full, unspaced, alternative settings; 1 048 symbols); pymatgen's primitive C2/m cell with beta = 90 (the fixture records `pymatgen` 2026.9.24, the metapackage; the code that built it is pymatgen-core 2026.10.16, the one on disk in `References/python/orix` and `py4dstem`; regenerated on 2026-10-21, the file is byte-identical) and spglib's operations in that cell | `tools/pins/cif-symmetry/pins.py` -> `Tests/DiffractoCoreTests/Fixtures/cif-symmetry-pins.json` | `CIFSymmetryCompletenessTests` | point-group order x centring equals `CIFImport.spaceGroupOrder` exactly for all 1 048; the primitive cell imports with its atom count (the rows it feeds: sections 1.6 and 1.3; the 0.9.0 numerics are in 2.3 and 2.4) |

pyxem, diffsims and orix are **not pinned clones** here; py4DSTEM is held as its scattering table file (P5) and, for slice 4,
as a pinned f050d207 environment the pins run (P14, P18). The remaining unexecuted Python APIs below are a checklist, not an additional parity claim. P18 byte-checks the installed crystal.py, crystal_ACOM.py, crystal_viz.py and utils.py against the pinned clone before execution.
pyxem, diffsims and orix are **not pinned clones** here; py4DSTEM is held as its scattering table file (P5) and, for the vector
matching and phase mapping of DiffractoOrientation, as a pinned f050d207 environment the pins run (P14, P18). The remaining unexecuted Python APIs below are a checklist, not an additional parity claim. P18 byte-checks the installed crystal.py, crystal_ACOM.py, crystal_viz.py and utils.py against the pinned clone before execution.

## 1. pymatgen 2026.9.24 / pymatgen-core 2026.10.16, in the fixtures and on disk (source walked from `References/python/orix/lib/python3.12/site-packages/pymatgen`; pinned clones: `References/pymatgen-8461938`, `References/pymatgen-core-ae9a834`)

Expand Down
2 changes: 1 addition & 1 deletion tools/pins/cells/pins.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""DiffractoKit slice 1: the pin generator for cells, symmetry and reflections.
"""DiffractoKit: the DiffractoCore pin generator for cells, symmetry and reflections.

Run, from any directory, with the pinned oracle:

Expand Down
2 changes: 1 addition & 1 deletion tools/pins/conventional-cell/pins.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""DiffractoKit slice 1b: the pin generator for the conventional setting.
"""DiffractoKit: the pin generator for the conventional setting and Niggli reduction (DiffractoCore).

Run, from any directory, with the pinned oracle, UNDER A MEMORY CAP (setrlimit and
`ulimit -v` fail on macOS 27, so the cap is an external watchdog's, 8 GB; one such worker at a time):
Expand Down
2 changes: 1 addition & 1 deletion tools/pins/ellipse/sweep.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python
"""DiffractoKit slice 3b, optional check: the clean sweep against SciPy.
"""DiffractoKit, optional check of the ellipse fit: the clean sweep against SciPy.

Renders the 20 clean sweep rings itself (the planted ring of Tests/DiffractoKitTestSupport/EllipseFixtures.swift with no
noise and no tail, ported line for line to Python floats and stored as Float32; the FNV-1a hash of every ring equals the Swift
Expand Down
2 changes: 1 addition & 1 deletion tools/pins/phase-vectors/pins-py4dstem.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""DiffractoKit slice 4: the py4DSTEM half of the pin generator.
"""DiffractoKit: the py4DSTEM half of the pin generator.

Not run by hand: tools/pins/phase-vectors/pins.py starts it with the py4DSTEM venv's interpreter, sends the phases as JSON on stdin and
reads the JSON on stdout. Run through pins.py the whole chain sits under ONE memory cap.
Expand Down
2 changes: 1 addition & 1 deletion tools/pins/phase-vectors/pins.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""DiffractoKit slice 4: the pin generator for the orientation and phase-matching module.
"""DiffractoKit: the pin generator for vector matching and phase mapping (DiffractoOrientation).

Run, from any directory, under the memory cap, with the pymatgen venv (Python 3.14, NumPy 2.5.4, SciPy 1.18.1):

Expand Down
2 changes: 1 addition & 1 deletion tools/pins/ring-calibration/ring-pins.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""DiffractoKit slice 3a: the ring-calibration pin generator.
"""DiffractoKit: the DiffractoGeometry ring-calibration pin generator.

Run, from any directory, with the pinned oracle:

Expand Down
2 changes: 1 addition & 1 deletion tools/pins/structure-factors/pins.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""DiffractoKit slice 2: the pin generator for scattering factors and structure factors.
"""DiffractoKit: the DiffractoScattering pin generator for scattering factors and structure factors.

Run, from any directory, with the pinned oracle:

Expand Down
Loading