diff --git a/Tests/DiffractoCoreTests/ConventionalSymmetryTests.swift b/Tests/DiffractoCoreTests/ConventionalSymmetryTests.swift index 95fa1a4..43b2fff 100644 --- a/Tests/DiffractoCoreTests/ConventionalSymmetryTests.swift +++ b/Tests/DiffractoCoreTests/ConventionalSymmetryTests.swift @@ -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)") diff --git a/Tests/DiffractoCoreTests/FastDetectionTests.swift b/Tests/DiffractoCoreTests/FastDetectionTests.swift index a49d89d..740532b 100644 --- a/Tests/DiffractoCoreTests/FastDetectionTests.swift +++ b/Tests/DiffractoCoreTests/FastDetectionTests.swift @@ -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) @@ -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] = [[0.10, 0.20, 0.30], [0.1013, 0.20, 0.30], [0.62, 0.31, 0.12], [0.62, 0.3113, 0.12]] diff --git a/docs/parity.md b/docs/parity.md index 96cb38a..12286bd 100644 --- a/docs/parity.md +++ b/docs/parity.md @@ -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 | @@ -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`) diff --git a/tools/pins/cells/pins.py b/tools/pins/cells/pins.py index 104ffab..f0336c9 100644 --- a/tools/pins/cells/pins.py +++ b/tools/pins/cells/pins.py @@ -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: diff --git a/tools/pins/conventional-cell/pins.py b/tools/pins/conventional-cell/pins.py index 3d0d1e5..1e2761c 100644 --- a/tools/pins/conventional-cell/pins.py +++ b/tools/pins/conventional-cell/pins.py @@ -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): diff --git a/tools/pins/ellipse/sweep.py b/tools/pins/ellipse/sweep.py index 44ed39d..2c0631c 100644 --- a/tools/pins/ellipse/sweep.py +++ b/tools/pins/ellipse/sweep.py @@ -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 diff --git a/tools/pins/phase-vectors/pins-py4dstem.py b/tools/pins/phase-vectors/pins-py4dstem.py index 3783e7f..611b3ff 100644 --- a/tools/pins/phase-vectors/pins-py4dstem.py +++ b/tools/pins/phase-vectors/pins-py4dstem.py @@ -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. diff --git a/tools/pins/phase-vectors/pins.py b/tools/pins/phase-vectors/pins.py index dbbdf05..cfe1fd0 100644 --- a/tools/pins/phase-vectors/pins.py +++ b/tools/pins/phase-vectors/pins.py @@ -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): diff --git a/tools/pins/ring-calibration/ring-pins.py b/tools/pins/ring-calibration/ring-pins.py index 0e67132..f29bd9b 100644 --- a/tools/pins/ring-calibration/ring-pins.py +++ b/tools/pins/ring-calibration/ring-pins.py @@ -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: diff --git a/tools/pins/structure-factors/pins.py b/tools/pins/structure-factors/pins.py index c57c239..26c7268 100644 --- a/tools/pins/structure-factors/pins.py +++ b/tools/pins/structure-factors/pins.py @@ -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: