From 1fb20a7640fed2dff565c5b546b98f5acb12b2ab Mon Sep 17 00:00:00 2001 From: Paullo9 Date: Sat, 31 Oct 2026 16:04:00 +0100 Subject: [PATCH] docs: parity, open-items and tools rows for image interpolation and the mixed-radix FFT parity.md: Images.interpolate gets its own row, partial (method fft, both normalizations, pinned at rtol 1e-7 by tools/pins/image-interpolate); DiffractionPatterns.interpolate stays missing. integrate_disc names interpolate:. The Grid row says lengths off vDSP that factor into 2, 3, 5 and 7 run the mixed-radix FFT (#42). Totals recounted from the table: 129 rows, 38 present, 40 partial, 51 missing; the study split was stale (now 12, 7, 8). open-items.md: the FFT item names what is left after #42 (a prime factor above 7, 506 squared); the #9 item drops integrate_disc(interpolate:) and adds the spline method. tools/README.md: the image-interpolate generator row. The azimuthal_average (#39) and LineScan defaults (#40) rows wait for those PRs to merge. Done: rows for #41 and #42. Next: rows for #39 and #40 when they merge. Co-Authored-By: Claude Opus 5.5 --- docs/open-items.md | 12 +++++++----- docs/parity.md | 11 ++++++----- tools/README.md | 1 + 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/open-items.md b/docs/open-items.md index 0e41b28..9fc144c 100644 --- a/docs/open-items.md +++ b/docs/open-items.md @@ -5,17 +5,19 @@ coverage: `docs/parity.md`. ## Slower than Python at a typical size -- **Any-length FFT off the vDSP lengths (Bluestein)**: 2x to 7x slower than NumPy at 250² and 506²; numbers are right - (rtol 1e−7), only speed is open. Fix candidate: a mixed-radix plan for lengths that factor into 2, 3, 5 and 7, Bluestein - for the rest (#4). +- **Any-length FFT with a prime factor above 7 (Bluestein)**: lengths off vDSP that factor into 2, 3, 5 and 7 run a + mixed-radix FFT since #42 (250² 0.36 ms against NumPy 0.24 ms, 500² 1.58 ms against 1.52 ms); 506² (2 × 11 × 23) stays on + Bluestein, 1.6x slower than NumPy. Numbers are right (rtol 1e−7), only speed is open. Fix candidate: generic odd-radix + passes (#4). - **Rows measured on a loaded machine**: `fft2+ifft2` at 192² to 512², multislice and frozen phonons at 192², the probe build at 192² to 512² against abTEM float32. Re-measure in a quiet run before changing anything (#5). ## Work items - **Detector and noise follow-ups from 0.4.0**: line profiles for segmented and - pixelated line scans, the azimuthal average, `LineScan` defaults, `integrate_disc(interpolate:)`, the DPC measurement of a - `SegmentedDetector`, tilt distributions (#9). + pixelated line scans, the azimuthal average, `LineScan` defaults, the "spline" method of `Images.interpolate`, tilt + distributions (#9). `integrate_disc(interpolate:)` and `Images.interpolate` (method "fft") are in; the DPC measurement of a + `SegmentedDetector` is under Polar measurements. - **Potential API**: `CrystalPotential` (a unit potential repeated, frozen phonons per cell), its API undecided (#7). - **Cells**: non-orthogonal and rotated cells, `periodic=False`, `orthogonalize_cell` in `AtomicStructure` (the kit keeps its own structure type: `docs/adr/0001-standalone-structure-types.md`). Finite projection, the Kirkland, Peng and Ewald diff --git a/docs/parity.md b/docs/parity.md index da4f1d1..5893415 100644 --- a/docs/parity.md +++ b/docs/parity.md @@ -4,8 +4,8 @@ The coverage table of abTEM 1.0.10 (`References/python/abtem`, git `164e644`): i the source in `site-packages/abtem` against SimulatoKit. Rewritten for 0.3.0 (the growing phase's kit audit); every row has one state and one priority. -**Totals (128 rows): 37 present, 37 partial, 54 missing.** By priority: **study** 27 rows (11 present, 5 partial, 11 missing); -**useful** 79 (26, 32, 21); **not planned** 22 (all missing, each with its reason). +**Totals (129 rows): 38 present, 40 partial, 51 missing.** By priority: **study** 27 rows (12 present, 7 partial, 8 missing); +**useful** 80 (26, 33, 21); **not planned** 22 (all missing, each with its reason). States: @@ -40,7 +40,7 @@ detector, the Gaussian source size and Poisson noise on a scan. | `core.energy.energy2mass` | | **missing**: planned: SimulatoCore (`ElectronBeam.mass` = the factor × mₑ) | useful | | `Accelerator`, `HasAcceleratorMixin` (`energy`, `wavelength`, `sigma`, `lock_energy`, `match`, `check_match`) | `ElectronBeam(energy:)` | **partial**: energy, wavelength and σ; an undefined energy (None), `lock_energy` and `match` not offered; the energy must be finite and positive | useful | | `core.constants.kappa` | `PhysicalConstants.kappa` | **present** (the same double) | useful | -| `Grid`, `HasGrid2DMixin` (`extent`, `gpts`, `sampling`, `reciprocal_space_sampling`, `endpoint`) | `Grid(nx:ny:extentX:extentY:)`, `Grid(extentX:extentY:samplingX:samplingY:)`, `.samplingX`/`Y`, `.reciprocalSamplingX`/`Y`, `.matches(_:)` | **partial**: any gpts ≥ 2 on both backends (0.3.0; vDSP where it has a length, Bluestein otherwise, pinned 90 x 75 against abTEM); `sampling=` gives ⌈extent / sampling⌉ points as abTEM; two dimensions, endpoint False only; the reciprocal sampling is abTEM's expression (bit-equal) | study | +| `Grid`, `HasGrid2DMixin` (`extent`, `gpts`, `sampling`, `reciprocal_space_sampling`, `endpoint`) | `Grid(nx:ny:extentX:extentY:)`, `Grid(extentX:extentY:samplingX:samplingY:)`, `.samplingX`/`Y`, `.reciprocalSamplingX`/`Y`, `.matches(_:)` | **partial**: any gpts ≥ 2 on both backends (0.3.0; vDSP where it has a length, a Stockham mixed-radix FFT for other lengths whose prime factors are 2, 3, 5 and 7, Bluestein otherwise; pinned 90 x 75 against abTEM; the mixed-radix path within 4e−16 of the largest term of NumPy `fft2` at 250² and 500², checked against a direct DFT); `sampling=` gives ⌈extent / sampling⌉ points as abTEM; two dimensions, endpoint False only; the reciprocal sampling is abTEM's expression (bit-equal) | study | | `core.grid.spatial_frequencies`, NumPy `fftfreq` | `Grid.fftfreq`, `.frequenciesX`/`Y` | **present** (exact today, `SimulatoCoreTests`) | useful | | `BaseWaves.angular_sampling`, `.cutoff_angles`, `.antialias_cutoff_gpts`, `.antialias_valid_gpts`, `.rectangle_cutoff_angles`, `.full_cutoff_angles`, `.cutoff_frequencies` | `DiffractionIntensity.angularSampling`, `.cutoffAngles`, `Antialias.cutoffPatternSize(on:)` | **partial**: angular sampling, cutoff angles and the odd cutoff size; the valid size only inside `PixelatedDetector(.valid)`; the rectangle and full cutoff angles and the cutoff frequencies not offered | useful | | `abtem.config` (dask, device, precision, antialias settings), `abtem.axes` (axis metadata) | | **missing**: not planned: global configuration and ensemble-axis metadata belong to abTEM's lazy array objects; the kit takes explicit arguments | not planned | @@ -184,10 +184,11 @@ detector, the Gaussian source size and Poisson noise on a scan. | `DiffractionPatterns.center_of_mass(units)` | `CenterOfMassDetector(maxAngle:units:)` | **partial**: Σ P·α over the pixelated detector's crop in mrad or 1/Å, not divided by Σ P (as abTEM), pinned within 1e−5 of abTEM's complex64 result (0.3.0); a detector on the exit wave, not a method of a stored pattern; `integrated_center_of_mass` is `ScanMeasurement.integratedCenterOfMass` (next row) | study | | `DiffractionPatterns.integrated_center_of_mass()` | `ScanMeasurement.integratedCenterOfMass(samplingX:samplingY:)` of a `CenterOfMassDetector` scan | **present**: abTEM's centre of mass through the kit 2e−16 (rtol 1e−7 default); the kit's own centre of mass 5e−8 (its COM is registered at 1e−5); no scaling beyond abTEM's (units of the COM times Å, minimum 0) | study | | `Images.integrate_gradient` | `RealField.integratingGradient(x:y:)`, `ComplexField.integratedGradient(samplingX:samplingY:)` | **present** within assert_allclose float64 defaults (rtol 1e−7, atol 1e−12 of the max); measured 8e−16 of the maximum on even, odd, mixed, 2×3 and random (Nyquist) fields; planted potentials recovered to 1e−9 of their range | study | -| `integrate_disc` (`Images.integrate_disc`) | `RealField.integrateDisc(x:y:radius:mean:border:)` | **present** same tolerance; measured 2e−15 relative; `interpolate` not offered | study | +| `integrate_disc` (`Images.integrate_disc`) | `RealField.integrateDisc(x:y:radius:mean:border:interpolate:)` | **present** same tolerance; measured 2e−15 relative; `interpolate` resamples the image to that sampling first (`Images.interpolate`, method "fft"), pinned at rtol 1e−7 on three discs including one that wraps (`tools/pins/image-interpolate/pins.py`, held inline) | study | | `Images.tile` | `RealField.tiled(repetitionsX:repetitionsY:)` | **present** exact | useful | | `DiffractionPatterns.crop(max_angle, max_frequency, gpts)` | `Waves.diffractionPattern(maxAngle: .cutoff / .full)`, `PixelatedDetector(maxAngle:)` | **partial**: the crop is chosen when the pattern is made; not a method of a stored pattern | useful | -| `DiffractionPatterns.interpolate(sampling, gpts)`, `Images.interpolate(sampling, gpts, method, boundary, order, normalization, cval)` | | **missing**: planned: SimulatoCore | useful | +| `Images.interpolate(sampling, gpts, method, boundary, order, normalization, cval)` | `RealField.interpolated(rows:columns:normalization:)`, `.interpolated(samplingX:samplingY:normalization:)` | **partial**: method "fft" (crop or zero-pad in Fourier space, periodic), `normalization` "values" (scaled by new / old points) or "intensity"; a sampling gives ⌈extent / sampling⌉ points; pinned at rtol 1e−7 against abTEM in float64 on up, down and mixed resamplings of a planted 5 × 6 image (`tools/pins/image-interpolate/pins.py`, held inline); method "spline" (with `boundary`, `order`, `cval`) not offered; an axis of one point is refused (abTEM keeps its zero frequency) | useful | +| `DiffractionPatterns.interpolate(sampling, gpts)` | | **missing**: planned: SimulatoCore | useful | | `DiffractionPatterns.gaussian_source_size(sigma)` | `ScanMeasurement.gaussianSourceSize(sigma:samplingX:samplingY:)` | **present**: scipy `gaussian_filter` over the two scan axes, mode wrap, radius ⌊4σ + ½⌋; pinned at rtol 1e−7 (float64) incl. a σ that wraps the scan (0.3.0) | study | | `DiffractionPatterns.bandlimit`, `.block_direct`, `.tile_scan` | | **missing**: planned: SimulatoDetector | useful | | `Images.crop(extent, offset, centered)` | | **missing**: planned: SimulatoCore | useful | diff --git a/tools/README.md b/tools/README.md index 0ce85b5..9691bbb 100644 --- a/tools/README.md +++ b/tools/README.md @@ -42,6 +42,7 @@ git status --short Tests/ # no change to an *-arrays.bin: the fixtures reprodu | `pins/potential-project-tile/pins.py` | none: prints the numbers `SimulatoMultisliceTests/PotentialProjectTileTests.swift` holds inline | | `pins/polar/pins.py` | none: prints the numbers `SimulatoDetectorTests/PolarMeasurementTests.swift` holds inline | | `pins/crystal-slices/pins.py` | none: prints the numbers `SimulatoMultisliceTests/CrystalSliceThicknessTests.swift` holds inline | +| `pins/image-interpolate/pins.py` | none: prints the numbers `SimulatoCoreTests/ImageInterpolationTests.swift` holds inline | ## Other tools