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 b70d9b7..e548677 100644 --- a/Tests/Fixtures/README.md +++ b/Tests/Fixtures/README.md @@ -30,35 +30,35 @@ No fixture is copied from the test data of pymatgen, orix or py4DSTEM. | File | Size | Generated by | Note | |---|---|---|---| -| `DiffractoCoreTests/Fixtures/alpha_quartz_P1_origin_shifted.cif` | 747 B | review regression CIF, read by `RefuterRegressionTests` | written for this kit (GPL-3.0-only) | +| `DiffractoCoreTests/Fixtures/alpha_quartz_P1_origin_shifted.cif` | 828 B | review regression CIF, read by `RefuterRegressionTests` | written for this kit (GPL-3.0-only) | | `DiffractoCoreTests/Fixtures/alpha_quartz_P3221.cif` | 924 B | `tools/pins/cells/pins.py`, `tools/pins/structure-factors/pins.py` | mac4DSTEM tests: from mac4DSTEM 7448401b (GPL-3.0, same author), named in its first comment line | | `DiffractoCoreTests/Fixtures/alpha_quartz_P3221_as_seeded.cif` | 712 B | `tools/pins/cells/pins.py` | mac4DSTEM tests: from mac4DSTEM 7448401b (GPL-3.0, same author), named in its first comment line | | `DiffractoCoreTests/Fixtures/calcite_R-3c_hex.cif` | 1.4 KB | `tools/pins/cells/pins.py`, `tools/pins/structure-factors/pins.py` | mac4DSTEM tests: from mac4DSTEM 7448401b (GPL-3.0, same author), named in its first comment line | | `DiffractoCoreTests/Fixtures/cdi2_P-3m1.cif` | 462 B | `tools/pins/cells/pins.py`, `tools/pins/structure-factors/pins.py` | mac4DSTEM tests: from mac4DSTEM 7448401b (GPL-3.0, same author), named in its first comment line | | `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` | written for this kit (GPL-3.0-only), with pymatgen `CifWriter` | +| `DiffractoCoreTests/Fixtures/corundum_P1_rhombohedral.cif` | 1.4 KB | review regression CIF, read by `RefuterRegressionTests` | written for this kit (GPL-3.0-only), with pymatgen `CifWriter` | | `DiffractoCoreTests/Fixtures/fluorapatite_P63m.cif` | 781 B | `tools/pins/cells/pins.py`, `tools/pins/structure-factors/pins.py` | mac4DSTEM tests: from mac4DSTEM 7448401b (GPL-3.0, same author), named in its first comment line | | `DiffractoCoreTests/Fixtures/gold_P1.cif` | 396 B | `tools/pins/cells/pins.py`, `tools/pins/structure-factors/pins.py` | mac4DSTEM tests: from mac4DSTEM 7448401b (GPL-3.0, same author), named in its first comment line | -| `DiffractoCoreTests/Fixtures/gold_P1_origin_shifted_exact_decimals.cif` | 588 B | review regression CIF, read by `RefuterRegressionTests` | written for this kit (GPL-3.0-only) | +| `DiffractoCoreTests/Fixtures/gold_P1_origin_shifted_exact_decimals.cif` | 697 B | review regression CIF, read by `RefuterRegressionTests` | written for this kit (GPL-3.0-only) | | `DiffractoCoreTests/Fixtures/gold_centring_ops.cif` | 440 B | `tools/pins/cells/pins.py` | mac4DSTEM tests: from mac4DSTEM 7448401b (GPL-3.0, same author), named in its first comment line | | `DiffractoCoreTests/Fixtures/ideal_hcp_P1.cif` | 504 B | `tools/pins/cells/pins.py` | written for this kit (GPL-3.0-only) | -| `DiffractoCoreTests/Fixtures/iron_P1_general_position.cif` | 470 B | review regression CIF, read by `RefuterRegressionTests` | written for this kit (GPL-3.0-only) | -| `DiffractoCoreTests/Fixtures/magnesium_P1_origin_shifted.cif` | 529 B | review regression CIF, read by `RefuterRegressionTests` | written for this kit (GPL-3.0-only) | +| `DiffractoCoreTests/Fixtures/iron_P1_general_position.cif` | 622 B | review regression CIF, read by `RefuterRegressionTests` | written for this kit (GPL-3.0-only) | +| `DiffractoCoreTests/Fixtures/magnesium_P1_origin_shifted.cif` | 670 B | review regression CIF, read by `RefuterRegressionTests` | written for this kit (GPL-3.0-only) | | `DiffractoCoreTests/Fixtures/magnesium_P63mmc.cif` | 401 B | `tools/pins/phase-vectors/pins.py` | mac4DSTEM tests: from mac4DSTEM 7448401b (GPL-3.0, same author), named in its first comment line | | `DiffractoCoreTests/Fixtures/magnesium_P63mmc_24ops.cif` | 896 B | `tools/pins/cells/pins.py` | mac4DSTEM tests: from mac4DSTEM 7448401b (GPL-3.0, same author), named in its first comment line | -| `DiffractoCoreTests/Fixtures/magnesium_P63mmc_origin_shifted_loop.cif` | 1.5 KB | review regression CIF, read by `RefuterRegressionTests` | written for this kit (GPL-3.0-only), with pymatgen `CifWriter` | -| `DiffractoCoreTests/Fixtures/mixed_centring.cif` | 561 B | review regression CIF, read by `RefuterRegressionTests` | written for this kit (GPL-3.0-only) | +| `DiffractoCoreTests/Fixtures/magnesium_P63mmc_origin_shifted_loop.cif` | 1.7 KB | review regression CIF, read by `RefuterRegressionTests` | written for this kit (GPL-3.0-only), with pymatgen `CifWriter` | +| `DiffractoCoreTests/Fixtures/mixed_centring.cif` | 706 B | review regression CIF, read by `RefuterRegressionTests` | written for this kit (GPL-3.0-only) | | `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` | written for this kit (GPL-3.0-only) | +| `DiffractoCoreTests/Fixtures/polonium_P1_supercell_7x.cif` | 894 B | `tools/pins/conventional-cell/pins.py` | written for this kit (GPL-3.0-only) | | `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` | mac4DSTEM tests: from mac4DSTEM 7448401b (GPL-3.0, same author), named in its first comment line | | `DiffractoCoreTests/Fixtures/pyrite_Pa-3_as_seeded.cif` | 787 B | `tools/pins/cells/pins.py` | mac4DSTEM tests: from mac4DSTEM 7448401b (GPL-3.0, same author), named in its first comment line | | `DiffractoCoreTests/Fixtures/rocksalt_Fm-3m_192ops.cif` | 3.5 KB | `tools/pins/cells/pins.py`, `tools/pins/structure-factors/pins.py` | mac4DSTEM tests: from mac4DSTEM 7448401b (GPL-3.0, same author), named in its first comment line | -| `DiffractoCoreTests/Fixtures/rocksalt_P1_origin_shifted.cif` | 703 B | review regression CIF, read by `RefuterRegressionTests` | written for this kit (GPL-3.0-only) | +| `DiffractoCoreTests/Fixtures/rocksalt_P1_origin_shifted.cif` | 823 B | review regression CIF, read by `RefuterRegressionTests` | written for this kit (GPL-3.0-only) | | `DiffractoCoreTests/Fixtures/silicon_Fd-3m.cif` | 498 B | `tools/pins/cells/pins.py`, `tools/pins/structure-factors/pins.py` | mac4DSTEM tests: from mac4DSTEM 7448401b (GPL-3.0, same author), named in its first comment line | | `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` | | 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", 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 1e2761c..371b05b 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 cfe1fd0..d50a4d6 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 f29bd9b..407b6a1 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 26c7268..477cfc1 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