Skip to main content

aobasis

Modal basis sets for adaptive-optics deformable mirrors: KL, Zernike, Fourier, Hadamard, zonal and zonal-fast modes, for any actuator geometry.

Every generator takes your actuator positions and returns a modal-to-command matrix (M2C), an (n_actuators, n_modes) NumPy array whose column k is the DM command for mode k. It's ready for a reconstructor, a calibration sequence or a real-time controller such as pyRTC.

Installation

pip install aobasis                 # numpy + scipy only
pip install "aobasis[plot]"         # + matplotlib, for .plot()
pip install "aobasis[fits]"         # + astropy, for save_fits / load_fits
pip install "aobasis[tutorials]"    # + matplotlib, astropy and Jupyter, for the tutorials

aobasis is pure Python (3.8–3.14) and is tested on Linux, x86-64 and aarch64.

GPU (optional). KLBasisGenerator(..., use_gpu=True) builds and diagonalizes the covariance with CuPy. Install the CuPy wheel for your CUDA version, e.g. pip install cupy-cuda12x, or conda install -c conda-forge cupy. Without CuPy it falls back to the CPU with a warning, and the modes are the same either way.

Quick start

import numpy as np
import aobasis

# 1. Actuator positions: (N, 2) in metres, centred, in your DM's actuator order
positions = aobasis.make_circular_actuator_grid(telescope_diameter=10.0, grid_size=20)

# 2. A generator and its physical parameters
kl = aobasis.KLBasisGenerator(positions, fried_parameter=0.16, outer_scale=30.0)

# 3. The M2C: (276, 50), piston-free
m2c = kl.generate(n_modes=50, ignore_piston=True)

# 4. Use, inspect, keep
coefficients = np.zeros(50); coefficients[3] = 1.0  # some of mode 3
commands = m2c @ coefficients                       # put modal coefficients on the DM
c2m = aobasis.command_to_mode_matrix(m2c)           # and back
kl.plot(count=6)                                    # needs aobasis[plot]
kl.save("kl_m2c.npz")                               # modes + parameters + options + eigenvalues

What's in it

Bases

Generator Modes Typical use
KLBasisGenerator Karhunen-Loève modes of Von Kármán (or Kolmogorov, outer_scale=np.inf) turbulence at the actuators, with eigenvalues closed-loop control; turbulence statistics and priors
DMKLBasisGenerator KL modes of the DM surface from its influence functions (double diagonalization) control with a real DM
ZernikeBasisGenerator Noll-normalized Zernikes in Noll, OSA/ANSI or Fringe order; annular Zernikes with obscuration= aberration commands, optical testing, NCPA
FourierBasisGenerator sines and cosines, aliased frequencies skipped frequency response, spatial filtering
HadamardBasisGenerator ±1 patterns, Sylvester or Paley matrices (construction="smallest"), selection="balanced" low-noise interaction-matrix calibration
ZonalBasisGenerator single-actuator pokes simplest calibration
ZonalFastBasisGenerator groups of pokes at least min_distance apart (optimal lattice colourings) calibration in a few frames

Shaping a basis. These options are the same on every generator:

  • ignore_piston=True makes every mode exactly zero-mean;
  • remove="tiptilt" (or any (n_actuators, k) array, e.g. waffle) keeps other modes out;
  • orthonormalize=True Gram-Schmidts the modes in order;
  • normalize="rms" | "l2" | "peak" | "pv" sets the scale.

Unknown options raise TypeError. Zernike, Fourier and Hadamard warn when the modes are linearly dependent; check_rank=False skips that check, which is most of the cost of a large Zernike basis.

Zernike helpers.

  • noll_to_nm(j) and nm_to_noll(n, m) convert between Noll indices and (n, m), with ansi_to_nm/nm_to_ansi and fringe_to_nm/nm_to_fringe for the other orderings. m > 0 is the cosine term, so Noll 2 = (1, 1) is tip along +x and Noll 3 = (1, -1) tilt along +y. They take integers or integer arrays.
  • zernike_modes_on_mask(mask, n_modes, pupil_radius=...) evaluates Zernikes on the pixels of a 2-D mask and returns (n_modes, ny, nx) images, zero outside the mask.

Geometry.

  • make_circular_actuator_grid builds a square grid by grid_size or pitch, with actuators on the rim or in cell centres.
  • make_hexagonal_actuator_grid and make_concentric_actuator_grid cover other layouts.
  • All three take a central obstruction and spider arms.
  • positions_from_mask reads a boolean DM map, and any (N, 2) array works too.

Influence functions. fit_to_influence_functions gives least-squares commands whose DM surface matches modes sampled on the pupil. gaussian_influence_functions and make_pupil_points build the inputs.

A DM model. DeformableMirror turns commands into a surface on a pixel grid (indexed [y, x]) or at arbitrary points, with the same Gaussian influence functions. With actuators on a square lattice it uses their separability (surface = G_y C G_x^T), which equals influence_matrix() @ cmd to round-off without building the matrix: for 877 actuators on 144×144 pixels, 0.19 ms instead of 4.3 ms (14 ms on one thread) on an 8-core Intel i7-10700 (examples/dm_surface.py). transformed(shift=(dx, dy), rotation_deg=..., magnification=...) returns the misregistered DM for robustness studies.

actuators = aobasis.make_circular_actuator_grid(8.0, 33)          # pitch 0.25 m
axis = (np.arange(144) - 71.5) * (8.0 / 144)                      # pixel centres
dm = aobasis.DeformableMirror(actuators, pitch=0.25, coupling=0.15, grid=(axis, axis))
surface = dm.surface(np.ones(dm.n_actuators))                     # (144, 144); a batch gives (k, 144, 144)
true_dm = dm.transformed(shift=(0.02, 0.0), rotation_deg=0.5)     # the DM as it really sits in the pupil

Turbulence covariance. von_karman_covariance(points_a, points_b=None, r0=..., L0=..., wavelength=500e-9, units="phase" | "opd") is the von Kármán covariance between points (rad² of phase at the wavelength of r0, or m² of OPD), e.g. an MMSE prior on the actuators; von_karman_covariance_of_distance takes separations. KL modes are built from the same function. Its structure function tends to 6.88 (r/r0)^(5/3) for r << L0.

Using and keeping a basis.

  • command_to_mode_matrix and fit_coefficients give modal coefficients.
  • basis_report (or generator.report()) summarizes rank, conditioning, orthogonality and piston content.
  • save/load (.npz) and save_fits/load_fits store the modes together with the generator's parameters, the generate() options, KL eigenvalues and the aobasis version.

Reproducible. KL modes follow a fixed sign and degenerate-rotation convention: tip along +x, tilt along +y. The same geometry gives the same modes on every machine, CPU or GPU.

Tutorials and examples

tutorials/ holds eight Jupyter walkthroughs; see tutorials/README.md for a guide.

Notebooks
Getting started 01 · Quickstart, 02 · A tour of the bases
Everyday use 03 · Shaping a basis, 04 · Actuator geometry
In depth 05 · KL and turbulence, 06 · Influence functions and DM KL, 07 · Calibration patterns, 08 · Saving and sharing

examples/ holds ready-to-run scripts (each has --help):

python examples/make_m2c.py kl --grid-size 20 --n-modes 100 --remove tiptilt -o kl.fits --plot kl.png
python examples/dm_kl.py --n-modes 80 -o dmkl.fits
python examples/dm_surface.py --n-pixels 144
python examples/calibration.py

Performance

python examples/benchmark.py --grid-sizes 16 32 64 --n-modes 100 --gpu --markdown. Each entry is the best of 3 runs of generate(100) (zonal fast: its full pattern set) on a square grid clipped by a 10 m pupil.

Host: an 80-core Arm Neoverse-N1 server, using 4 cores (OPENBLAS_NUM_THREADS=4), with an NVIDIA RTX 4060 and an RTX A400 (select one with CUDA_DEVICE_ORDER=PCI_BUS_ID CUDA_VISIBLE_DEVICES=...).

Basis (100 modes) 16×16 (172 acts) 32×32 (740 acts) 64×64 (3096 acts)
KL (CPU) 0.015 s 0.138 s 1.79 s
KL (GPU, RTX 4060) 0.012 s 0.045 s 0.98 s
KL (GPU, RTX A400) 0.018 s 0.145 s 4.38 s
Zernike 0.003 s 0.008 s 0.027 s
Fourier 0.005 s 0.019 s 0.092 s
Hadamard 0.001 s 0.022 s 0.146 s
Zonal <0.001 s <0.001 s 0.002 s
Zonal fast (3-pitch spacing) 0.005 s 0.023 s 0.122 s

Full bases (--n-modes all) on 3096 actuators take 5.0 s for KL on the CPU (1.6 s on the RTX 4060), 3.3 s for Zernike, 6.9 s for Fourier and 2.9 s for Hadamard. That includes the rank check, which check_rank=False skips (a full Zernike basis then takes 0.5 s).

Some notes on these numbers:

  • KL costs O(N³). For a few modes the CPU uses a partial eigensolver, which is why 100 modes are faster than the full basis.
  • The covariance is evaluated once per distinct actuator separation.
  • KL works in float64, so GPU speed follows the card's float64 throughput. The RTX 4060 is about 2× faster than 4 CPU cores at 3096 actuators. The RTX A400, with far less float64 throughput, is slower than the CPU.
  • The GPU path always computes every eigenpair (CuPy has no partial eigensolver), so its advantage is largest for full bases.

Development and testing

git clone https://github.com/jacotay7/aobasis.git
cd aobasis
pip install -e ".[dev]"
pytest

CI runs the test suite on Python 3.8–3.14, on aarch64 and without matplotlib, runs every example script, and executes every tutorial notebook.

CI cannot run the GPU tests: they need CuPy and a CUDA device, and skip without them. Run them locally before changing GPU code:

pip install cupy-cuda12x
pytest -k gpu -rs   # -rs shows a skip reason if no device is found

Contributing

Contributions are welcome. Open an issue to report a bug or propose a feature, or send a pull request with a test for the change and an entry in CHANGELOG.md.

Contact

Jacob Taylor, jacobataylor7@gmail.com

License

MIT; see LICENSE.

Metadata

Release files for aobasis 2.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aobasis 2.3.0
File Size Uploaded
aobasis-2.3.0.tar.gz 82.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for aobasis 2.3.0
File Interpreter ABI Platform
aobasis-2.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 141.7 kB

Release files / aobasis-2.3.0.tar.gz

Download URL aobasis-2.3.0.tar.gz
Size 82.7 kB
Tags Source
SHA-256 checksum
How to use checksums
936d48f6065143c62daa53059f6c50b58bd6080988dd8bedf6ba6a2fea0e4156
BLAKE2b-256 checksum
How to use checksums
b01eb3e540551a5108829963d05557d419300f2fea87645a39da75454d48a841
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release files / aobasis-2.3.0-py3-none-any.whl

Download URL aobasis-2.3.0-py3-none-any.whl
Size 59.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e9f048ebc348217de5674939d82b1e3b8e5a3d9e3c9ba1dadcec39db5a71905e
BLAKE2b-256 checksum
How to use checksums
b2e3a33209465e2703cdf750dc9e5e6b056d1edefba1bf86d48a95c32692046f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

2.3.0 This release

2 release files

2.2.0

2 release files

2.1.0

2 release files

2.0.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page