Skip to content

Release 3.2.0: structure-function API rename, kernel lengths, field and noise realizations - #41

Merged
richteague merged 4 commits into
masterfrom
add-noise-realizations
Sep 14, 2026
Merged

richteague merged 4 commits into
masterfrom
add-noise-realizations

Conversation

@richteague

Copy link
Copy Markdown
Collaborator

Cuts 3.2.0. Three commits: the structure-function API rename and kernel-length
convention, the beam-convolved noise realizations, and the release prep
(version bump, dated CHANGELOG, two missing doc entries).

Changed

  • Structure-function API renamed to the calculate_ / fit_ / plot_
    convention, and the result classes dropped their 2D suffix —
    StructureFunction and StructureFunctionStack. Every old spelling still
    works but emits a DeprecationWarning and goes away in 4.0. The class
    aliases are served through a module-level __getattr__ (PEP 562) rather
    than as subclasses, so isinstance checks against the old names are
    unaffected, and a plain import eddy does not warn.
  • calculate_heuristics now returns kernel correlation lengths, not
    half-power lags. The two differ by sqrt(2 ln 2) = 1.1774, so before this
    change calculate_heuristics and fit_GRF returned numbers that differed
    by that factor on identical data and nothing in the test suite noticed. The
    old behavior is available as length_scale='halfpower'.
  • grid= on every bare-array entry point, defaulting to 'polar'. The
    kernel is a generic regular-grid lag estimator and cannot infer the
    geometry, so it is now declared rather than assumed; the radius/azimuth
    analyses raise on a Cartesian result instead of reading its second axis as
    degrees.

Added

  • Field realizationsdraw_polar_field (exact Paciorek–Schervish
    factorization, or a spatially-varying process convolution that targets the
    same covariance without forming an N x N matrix), make_polar_grid,
    polar_covariance, and StructureFunction.draw_realization for
    Wiener–Khinchin synthesis from a measured S_2.
  • Noise realizationsgaussian_beam_realization (the realization
    counterpart of gaussian_beam_s2, and the naive-PSF null to
    draw_realization's empirical one) and imagecube.noise_realization as
    the cube-level entry point to both backends.
  • Module-level calculate_structure_function / calculate_structure_function_stack,
    so the bare-array and sky-map routes share one verb.

Fixed

  • S2_i is no longer returned in mixed units from the bare-array polar path.

See CHANGELOG.md for the full list.

Checks

  • pytest: 93 passed (58 at 3.1.0). New tests/test_length_convention.py
    pins the kernel-length convention end to end, including the slice-mode
    stack fit_GRF path that had no coverage before.
  • ruff check .: clean.
  • docs/user/structurefunction.rst builds with every new cross-reference
    resolving.

Known gaps, not blocking

  • fit_GRF(method='mcmc') is still untested; the lsq path is well covered.
  • The slice-based fit_GRF / calculate_heuristics see the axis-aligned
    chords of a pitched correlation ellipse, so ell0phi and the anisotropy
    are biased low when pitch != 0. Only the global-surface
    fit_GRF(pitch=True, r_axis=...) recovers the truth. The docs say pitch is
    only measurable in global mode but do not yet warn that the slice values
    are biased.
  • Tutorial 7's stored outputs come from a patched-up session rather than a
    clean restart-and-run-all.

🤖 Generated with Claude Code

richteague and others added 4 commits August 20, 2026 09:59
The accumulated structure-function work that had been sitting in the
working tree. Committed as one base so later changes are reviewable
against a known state rather than against an unversioned checkout.

- Rename to the calculate_/fit_/plot_ convention; StructureFunction2D*
  drop the 2D suffix. Old spellings alias through a PEP 562 module
  __getattr__ with a DeprecationWarning, removal in 4.0.
- calculate_heuristics defaults to length_scale='kernel', so heuristic
  and fit_GRF lengths are finally in one convention.
- Declare grid= ('polar'/'cartesian') on every bare-array entry point;
  radius/azimuth analyses refuse a Cartesian grid, and S2_i is None on
  a polar grid rather than mixing arcsec with degrees.
- plateau() and half_power_lag() exclude unpopulated lag bins.
- Field realizations: draw_polar_field, make_polar_grid, and
  StructureFunction.draw_realization (spectral synthesis from a
  measured S_2, defaulting sigma2 to plateau()/2 and warning when the
  PSD clip is large).
- Module-level calculate_structure_function[_stack] entry points.
Completes the {analytic, empirical} x {S_2 prediction, field
realization} set: gaussian_beam_s2 had no realization counterpart, and
draw_realization had no cube-level wrapper, so downstream projects were
carrying their own copies of both. The structure-function papers'
paper2/notebooks/noise.py was one such fork, and it had drifted -- it
took the injected variance from max(S2)/2 where draw_realization uses
plateau()/2, over-dispersing every empirical noise draw by 6-10% in
realized variance on four production ALMA cubes.

- gaussian_beam_realization(shape, dpix, bmaj, bmin, bpa, sigma, ...):
  white noise convolved with a unit-power Gaussian beam kernel, in the
  same beam frame as gaussian_beam_s2 (FITS PA east of north, axis 0 =
  +DEC / axis 1 = -RA). Unit power means the output sits at the
  requested per-pixel sigma whatever the beam size, matching the
  2*sigma2 plateau gaussian_beam_s2 predicts.
- imagecube.noise_realization(method, ...): fills shape, dpix and beam
  from the object and dispatches to gaussian_beam_realization or
  StructureFunction.draw_realization, mirroring how
  linecube.gaussian_beam_s2 already wraps the module-level function.
  On imagecube so momentmap and rotationmap get it too. The analytic
  sigma defaults to estimate_cube_RMS() on a linecube and is required
  elsewhere rather than invented.

Verified bit-identical to the fork it replaces on the analytic path,
and on the empirical path once the old sigma2 is passed explicitly, so
the only behaviour change is the normalization above.

Tested: the measured S_2 of the draws matches gaussian_beam_s2 to 0.8%
RMS of the plateau while missing a 90-degree-rotated beam by 16%, so an
axis swap or sign error in the beam frame cannot pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bump the version to 3.2.0 and date the CHANGELOG section that had been
accumulating under [Unreleased] since 3.1.1: the structure-function API
rename, the `grid=` declaration on the bare-array entry points, the
kernel-length convention, and the field/noise realization additions.

Also document the two realization entry points that had been left out of
docs/user/structurefunction.rst -- `gaussian_beam_realization` and the
cube-level `imagecube.noise_realization` -- and say in prose how the
parametric and empirical noise nulls relate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was no written release flow, and the history shows it: 3.0.1 and
3.1.0 were never tagged, and 3.1.0 never reached PyPI at all despite
having a CHANGELOG entry. Write down the six steps -- dual version
bump, local checks, annotated tag, scoped twine upload, GitHub release,
confirm -- so the easy-to-skip ones are on a list.

Notes the two gotchas hit while cutting 3.2.0: the docs build needs
pandoc on PATH (a system package) for nbsphinx, and `twine upload`
must be scoped to the new version's artifacts because a bare `dist/*`
re-submits every previous release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@richteague
richteague merged commit 2423600 into master Sep 14, 2026
3 checks passed
@richteague
richteague deleted the add-noise-realizations branch September 14, 2026 16:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant