Skip to content

Defer the eager scipy.sparse and scipy.spatial imports (~280 ms off import) - #477

Merged
Jammy2211 merged 2 commits into
mainfrom
claude/defer-scipy-sparse-import
Aug 22, 2026
Merged

Defer the eager scipy.sparse and scipy.spatial imports (~280 ms off import)#477
Jammy2211 merged 2 commits into
mainfrom
claude/defer-scipy-sparse-import

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

Summary

import autoarray drops from 464.4 ms to 183.7 ms — a 281 ms saving, ~60% of the import cost. Medians of 15 runs each, Python 3.13, dev extras, same interpreter and machine.

This is the follow-up filed off the pynufft removal (#475), whose measurement turned up the real import-time cost.

The filed premise was wrong, in the same way the pynufft one was

The task named derivative_util.py:30's module-scope from scipy.sparse import csr_matrix as the target, on the evidence that scipy.sparse costs ~0.11 s of every import. Deferring it changed nothingscipy.sparse was never being imported from there. Traced with a sys.meta_path hook:

autoarray/__init__.py:80
  -> inversion/mesh/mesh_geometry/delaunay.py:2   import scipy.spatial
    -> scipy/spatial/__init__.py:111              from ._kdtree import *
      -> scipy/spatial/_kdtree.py:4               from ._ckdtree import cKDTree

scipy.spatial (134 ms) pulls scipy.sparse (154 ms) in transitively. The csr_matrix import was riding on a subtree already paid for by something else — exactly the shape of the pynufft error, where a 0.19 s "cost" turned out to be 95% shared scipy.sparse. Deferring scipy.spatial too is what actually removes both, and is required to meet the task's own acceptance criterion.

That also means the win is ~280 ms rather than the ~100 ms the task predicted, because scipy.spatial's own cost comes off as well.

API Changes

None — internal changes only. No public symbol, signature, or behaviour changes; only the point at which two third-party modules are imported.

Test Plan

  • python -X importtime -c "import autoarray" | grep scipy.sparse — empty
  • python -X importtime -c "import autoarray" | grep scipy.spatial — empty
  • import autoarray: 464.4 ms → 183.7 ms (medians of 15; main min/max 445.1/511.9, branch 172.8/204.9)
  • pytest test_autoarray1179 passed
  • Downstream against this branch: PyAutoGalaxy 1103 passed, 1 skipped; PyAutoLens 532 passed, 1 skipped
Changes by file

inversion/mesh/mesh_geometry/delaunay.py

Drops the module-scope import scipy.spatial. Two of its three use sites (lines 19 and 179 on main) already had local imports — this deferral had been started and left half-done; only voronoi_neighbors_from needed one added. The "scipy.spatial.Voronoi" return annotation is already a string literal, so it needs no import at definition time.

operators/derivative_util.py, operators/coarse_interp_util.py

Move from scipy.sparse import csr_matrix into the four functions that use it — derivative_1st_operators_from, derivative_2nd_operators_from, forward_difference_operators_from, coarse_interp_matrix_from. Not load-bearing for import time on its own any more, but it keeps scipy.sparse off the import path independently of what scipy.spatial happens to pull in, so this does not silently regress if scipy reshuffles its internals.

On the deferral pattern

Every use site is inside a function, so a plain local import suffices. No module-level cache as in transformer.py:_load_nufftax() — that one needed a cache only because unpickled instances in multiprocessing workers never re-run __init__. Function-local imports run on every call and hit sys.modules after the first, so the cost is a dict lookup.


Generated by Claude Code

claude added 2 commits August 22, 2026 18:19
`import autoarray` drops from 464.4 ms to 183.7 ms (medians of 15 runs,
Python 3.13, dev extras) — a 281 ms saving.

The filed task named `derivative_util.py:30`'s module-scope
`from scipy.sparse import csr_matrix` as the target, on the evidence that
`scipy.sparse` costs ~0.11 s of every import. Deferring it changed nothing,
because `scipy.sparse` was never being imported from there:

    autoarray/__init__.py:80
      -> inversion/mesh/mesh_geometry/delaunay.py:2   import scipy.spatial
        -> scipy/spatial/__init__.py:111              from ._kdtree import *
          -> scipy/spatial/_kdtree.py:4               from ._ckdtree import cKDTree

`scipy.spatial` (134 ms) pulls `scipy.sparse` (154 ms) in transitively, so the
csr_matrix import was riding on a subtree that was already paid for. Deferring
`scipy.spatial` as well is what actually removes both, and is required to meet
the task's own acceptance criterion.

Changes:
- `inversion/mesh/mesh_geometry/delaunay.py` — drop the module-scope
  `import scipy.spatial`. Two of its three use sites already had local
  imports; only `voronoi_neighbors_from` needed one added, so this finishes a
  deferral that had been started and left half-done.
- `operators/derivative_util.py`, `operators/coarse_interp_util.py` — move
  `from scipy.sparse import csr_matrix` into the four functions that use it.
  No longer load-bearing for the import time on its own, but it keeps
  `scipy.sparse` off the import path independently of what `scipy.spatial`
  happens to pull in.

Every use site is inside a function, so a plain local import suffices — no
module-level cache as in `transformer.py:_load_nufftax()`, which needed one
only because unpickled instances in multiprocessing workers never re-run
`__init__`. Function-local imports run on every call and hit `sys.modules`
after the first.

Verified: `python -X importtime -c "import autoarray"` shows neither
`scipy.sparse` nor `scipy.spatial`. Suites green — autoarray 1179 passed,
autogalaxy 1103 passed / 1 skipped, autolens 532 passed / 1 skipped.
`test_autoarray/.../output_test/array.fits` is a tracked file that the test
suite rewrites when it runs. It is unrelated to the import deferral and was
picked up by `git add -A`; this restores it to its state on main.
@Jammy2211
Jammy2211 merged commit 6bbde1a into main Aug 22, 2026
3 checks passed
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.

2 participants