aobasis 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
aobasis-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Jacob Taylor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
aobasis-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,221 @@
1
+ Metadata-Version: 2.4
2
+ Name: aobasis
3
+ Version: 1.0.0
4
+ Summary: A package for generating AO basis sets (KL, Zernike, Fourier)
5
+ Author-email: Jacob Taylor <jtaylor@keck.hawaii.edu>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 Jacob Taylor
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/jacotay7/aobasis
29
+ Project-URL: Bug Tracker, https://github.com/jacotay7/aobasis/issues
30
+ Classifier: Programming Language :: Python :: 3
31
+ Classifier: Operating System :: OS Independent
32
+ Requires-Python: >=3.8
33
+ Description-Content-Type: text/markdown
34
+ License-File: LICENSE
35
+ Requires-Dist: numpy>=1.20
36
+ Requires-Dist: scipy>=1.7
37
+ Requires-Dist: matplotlib>=3.5
38
+ Provides-Extra: dev
39
+ Requires-Dist: pytest; extra == "dev"
40
+ Requires-Dist: pytest-cov; extra == "dev"
41
+ Requires-Dist: imageio; extra == "dev"
42
+ Dynamic: license-file
43
+
44
+ # AO Basis (aobasis)
45
+
46
+ A Python package for generating various modal basis sets for Adaptive Optics (AO) systems. This tool allows you to easily create, visualize, and save basis sets for any deformable mirror geometry.
47
+
48
+ ## Features
49
+
50
+ - **Karhunen-Loève (KL) Modes**: Optimized for atmospheric turbulence (Von Kármán spectrum).
51
+ - Optional GPU acceleration available for large systems (requires CuPy).
52
+ - **Zernike Polynomials**: Standard optical aberration modes (Noll indexing).
53
+ - **Fourier Modes**: Sinusoidal basis sets.
54
+ - **Zonal Basis**: Single actuator pokes (Identity).
55
+ - **Hadamard Basis**: Orthogonal binary patterns for calibration.
56
+ - **Flexible Geometry**: Works with arbitrary actuator positions (defaulting to circular grids).
57
+ - **Piston Removal**: Option to exclude piston/DC modes from generation.
58
+ - **Visualization**: Built-in plotting tools for quick inspection.
59
+ - **Serialization**: Save and load basis sets to/from `.npz` files.
60
+
61
+ ## Installation
62
+
63
+ ### Prerequisites
64
+ - Python 3.8 or higher
65
+ - (Optional) For GPU-accelerated KL generation: CUDA-compatible GPU and CuPy
66
+
67
+ ### Install from Source
68
+ Clone the repository and install using pip:
69
+
70
+ ```bash
71
+ git clone https://github.com/jacotay7/aobasis.git
72
+ cd aobasis
73
+ pip install .
74
+ ```
75
+
76
+ For development (editable install with test dependencies):
77
+ ```bash
78
+ pip install -e ".[dev]"
79
+ ```
80
+
81
+ ### GPU Acceleration (Optional)
82
+ To enable GPU acceleration for KL basis generation, you need to install CuPy and ensure you have a CUDA-compatible GPU.
83
+
84
+ #### Requirements
85
+ - NVIDIA GPU with CUDA support
86
+ - CUDA Toolkit (version 11.x or 12.x)
87
+
88
+ #### Installation via Conda (Recommended)
89
+ This method automatically handles CUDA dependencies:
90
+
91
+ ```bash
92
+ # Create a new conda environment (optional but recommended)
93
+ conda create -n aobasis python=3.12
94
+ conda activate aobasis
95
+
96
+ # Install CuPy from conda-forge (auto-detects CUDA version)
97
+ conda install -c conda-forge cupy
98
+
99
+ # Install CUDA toolkit if not already present
100
+ conda install -c nvidia cuda-toolkit
101
+ ```
102
+
103
+ #### Installation via Pip
104
+ If you prefer pip and already have CUDA installed on your system:
105
+
106
+ ```bash
107
+ # For CUDA 12.x
108
+ pip install cupy-cuda12x
109
+
110
+ # For CUDA 11.x
111
+ pip install cupy-cuda11x
112
+ ```
113
+
114
+ #### Verify Installation
115
+ Test that CuPy is working correctly:
116
+
117
+ ```python
118
+ import cupy as cp
119
+ print(f"CuPy version: {cp.__version__}")
120
+ print(f"CUDA available: {cp.cuda.is_available()}")
121
+
122
+ # Simple test
123
+ a = cp.array([1, 2, 3])
124
+ b = cp.array([4, 5, 6])
125
+ print(f"Sum: {cp.asnumpy(a + b)}") # Should print [5, 7, 9]
126
+ ```
127
+
128
+ If you encounter any issues, consult the [CuPy installation guide](https://docs.cupy.dev/en/stable/install.html).
129
+
130
+ ## Quick Start
131
+
132
+ Here is a simple example of generating and plotting KL modes for a 10-meter telescope:
133
+
134
+ ```python
135
+ from aobasis import KLBasisGenerator, make_circular_actuator_grid
136
+
137
+ # 1. Define the actuator geometry
138
+ positions = make_circular_actuator_grid(telescope_diameter=10.0, grid_size=20)
139
+
140
+ # 2. Initialize the generator (use_gpu=True for GPU acceleration if available)
141
+ kl_gen = KLBasisGenerator(positions, fried_parameter=0.16, outer_scale=30.0, use_gpu=False)
142
+
143
+ # 3. Generate modes (excluding piston)
144
+ modes = kl_gen.generate(n_modes=50, ignore_piston=True)
145
+
146
+ # 4. Plot the first 6 modes
147
+ kl_gen.plot(count=6, title_prefix="KL Mode")
148
+
149
+ # 5. Save to disk
150
+ kl_gen.save("my_kl_basis.npz")
151
+ ```
152
+
153
+ ## Performance
154
+
155
+ Generation times for 100 modes benchmarked on the following system:
156
+ - **CPU**: AMD Ryzen 9 9950X3D (16-core, 32-thread)
157
+ - **GPU**: NVIDIA GeForce RTX 5090 (32 GB)
158
+ - **OS**: Linux (Ubuntu)
159
+
160
+ | Basis | 16x16 Grid (~170 acts) | 32x32 Grid (~740 acts) | 64x64 Grid (~3100 acts) |
161
+ |-------|------------------------|------------------------|-------------------------|
162
+ | **KL (CPU)** | 0.010s | 0.170s | 3.008s |
163
+ | **KL (GPU)** | 0.005s | 0.019s | 0.202s |
164
+ | **Zernike** | 0.001s | 0.002s | 0.005s |
165
+ | **Fourier** | <0.001s | 0.001s | 0.003s |
166
+ | **Zonal** | <0.001s | <0.001s | 0.003s |
167
+ | **Hadamard** | <0.001s | 0.001s | 0.031s |
168
+
169
+ *Note: KL basis generation is computationally intensive ($O(N^3)$) due to the dense covariance matrix diagonalization. GPU acceleration provides significant speedup (8-15x) for larger grids.*
170
+
171
+ ## Tutorials
172
+
173
+ We provide Jupyter notebooks to help you get started.
174
+
175
+ 1. **Getting Started**: `tutorials/getting_started.ipynb` covers all supported basis types and features.
176
+
177
+ To run the tutorials:
178
+ ```bash
179
+ # Install Jupyter if you haven't already
180
+ pip install jupyter
181
+
182
+ # Launch the notebook server
183
+ jupyter notebook tutorials/getting_started.ipynb
184
+ ```
185
+
186
+ ## Development & Testing
187
+
188
+ This project uses `pytest` for testing. To run the test suite:
189
+
190
+ ```bash
191
+ # Install dev dependencies
192
+ pip install -e ".[dev]"
193
+
194
+ # Run tests
195
+ pytest
196
+ ```
197
+
198
+ ## Contributing
199
+
200
+ Contributions are welcome! Please feel free to submit a Pull Request.
201
+
202
+ 1. Fork the repository.
203
+ 2. Create your feature branch (`git checkout -b feature/AmazingFeature`).
204
+ 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`).
205
+ 4. Push to the branch (`git push origin feature/AmazingFeature`).
206
+ 5. Open a Pull Request.
207
+
208
+ ## Issues
209
+
210
+ If you encounter any bugs or have feature requests, please file an issue on the [GitHub Issues](https://github.com/jacotay7/aobasis/issues) page.
211
+
212
+ ## Contact
213
+
214
+ For questions or support, please contact:
215
+
216
+ **User Name**
217
+ Email: jtaylor@keck.hawaii.edu
218
+
219
+ ## License
220
+
221
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,178 @@
1
+ # AO Basis (aobasis)
2
+
3
+ A Python package for generating various modal basis sets for Adaptive Optics (AO) systems. This tool allows you to easily create, visualize, and save basis sets for any deformable mirror geometry.
4
+
5
+ ## Features
6
+
7
+ - **Karhunen-Loève (KL) Modes**: Optimized for atmospheric turbulence (Von Kármán spectrum).
8
+ - Optional GPU acceleration available for large systems (requires CuPy).
9
+ - **Zernike Polynomials**: Standard optical aberration modes (Noll indexing).
10
+ - **Fourier Modes**: Sinusoidal basis sets.
11
+ - **Zonal Basis**: Single actuator pokes (Identity).
12
+ - **Hadamard Basis**: Orthogonal binary patterns for calibration.
13
+ - **Flexible Geometry**: Works with arbitrary actuator positions (defaulting to circular grids).
14
+ - **Piston Removal**: Option to exclude piston/DC modes from generation.
15
+ - **Visualization**: Built-in plotting tools for quick inspection.
16
+ - **Serialization**: Save and load basis sets to/from `.npz` files.
17
+
18
+ ## Installation
19
+
20
+ ### Prerequisites
21
+ - Python 3.8 or higher
22
+ - (Optional) For GPU-accelerated KL generation: CUDA-compatible GPU and CuPy
23
+
24
+ ### Install from Source
25
+ Clone the repository and install using pip:
26
+
27
+ ```bash
28
+ git clone https://github.com/jacotay7/aobasis.git
29
+ cd aobasis
30
+ pip install .
31
+ ```
32
+
33
+ For development (editable install with test dependencies):
34
+ ```bash
35
+ pip install -e ".[dev]"
36
+ ```
37
+
38
+ ### GPU Acceleration (Optional)
39
+ To enable GPU acceleration for KL basis generation, you need to install CuPy and ensure you have a CUDA-compatible GPU.
40
+
41
+ #### Requirements
42
+ - NVIDIA GPU with CUDA support
43
+ - CUDA Toolkit (version 11.x or 12.x)
44
+
45
+ #### Installation via Conda (Recommended)
46
+ This method automatically handles CUDA dependencies:
47
+
48
+ ```bash
49
+ # Create a new conda environment (optional but recommended)
50
+ conda create -n aobasis python=3.12
51
+ conda activate aobasis
52
+
53
+ # Install CuPy from conda-forge (auto-detects CUDA version)
54
+ conda install -c conda-forge cupy
55
+
56
+ # Install CUDA toolkit if not already present
57
+ conda install -c nvidia cuda-toolkit
58
+ ```
59
+
60
+ #### Installation via Pip
61
+ If you prefer pip and already have CUDA installed on your system:
62
+
63
+ ```bash
64
+ # For CUDA 12.x
65
+ pip install cupy-cuda12x
66
+
67
+ # For CUDA 11.x
68
+ pip install cupy-cuda11x
69
+ ```
70
+
71
+ #### Verify Installation
72
+ Test that CuPy is working correctly:
73
+
74
+ ```python
75
+ import cupy as cp
76
+ print(f"CuPy version: {cp.__version__}")
77
+ print(f"CUDA available: {cp.cuda.is_available()}")
78
+
79
+ # Simple test
80
+ a = cp.array([1, 2, 3])
81
+ b = cp.array([4, 5, 6])
82
+ print(f"Sum: {cp.asnumpy(a + b)}") # Should print [5, 7, 9]
83
+ ```
84
+
85
+ If you encounter any issues, consult the [CuPy installation guide](https://docs.cupy.dev/en/stable/install.html).
86
+
87
+ ## Quick Start
88
+
89
+ Here is a simple example of generating and plotting KL modes for a 10-meter telescope:
90
+
91
+ ```python
92
+ from aobasis import KLBasisGenerator, make_circular_actuator_grid
93
+
94
+ # 1. Define the actuator geometry
95
+ positions = make_circular_actuator_grid(telescope_diameter=10.0, grid_size=20)
96
+
97
+ # 2. Initialize the generator (use_gpu=True for GPU acceleration if available)
98
+ kl_gen = KLBasisGenerator(positions, fried_parameter=0.16, outer_scale=30.0, use_gpu=False)
99
+
100
+ # 3. Generate modes (excluding piston)
101
+ modes = kl_gen.generate(n_modes=50, ignore_piston=True)
102
+
103
+ # 4. Plot the first 6 modes
104
+ kl_gen.plot(count=6, title_prefix="KL Mode")
105
+
106
+ # 5. Save to disk
107
+ kl_gen.save("my_kl_basis.npz")
108
+ ```
109
+
110
+ ## Performance
111
+
112
+ Generation times for 100 modes benchmarked on the following system:
113
+ - **CPU**: AMD Ryzen 9 9950X3D (16-core, 32-thread)
114
+ - **GPU**: NVIDIA GeForce RTX 5090 (32 GB)
115
+ - **OS**: Linux (Ubuntu)
116
+
117
+ | Basis | 16x16 Grid (~170 acts) | 32x32 Grid (~740 acts) | 64x64 Grid (~3100 acts) |
118
+ |-------|------------------------|------------------------|-------------------------|
119
+ | **KL (CPU)** | 0.010s | 0.170s | 3.008s |
120
+ | **KL (GPU)** | 0.005s | 0.019s | 0.202s |
121
+ | **Zernike** | 0.001s | 0.002s | 0.005s |
122
+ | **Fourier** | <0.001s | 0.001s | 0.003s |
123
+ | **Zonal** | <0.001s | <0.001s | 0.003s |
124
+ | **Hadamard** | <0.001s | 0.001s | 0.031s |
125
+
126
+ *Note: KL basis generation is computationally intensive ($O(N^3)$) due to the dense covariance matrix diagonalization. GPU acceleration provides significant speedup (8-15x) for larger grids.*
127
+
128
+ ## Tutorials
129
+
130
+ We provide Jupyter notebooks to help you get started.
131
+
132
+ 1. **Getting Started**: `tutorials/getting_started.ipynb` covers all supported basis types and features.
133
+
134
+ To run the tutorials:
135
+ ```bash
136
+ # Install Jupyter if you haven't already
137
+ pip install jupyter
138
+
139
+ # Launch the notebook server
140
+ jupyter notebook tutorials/getting_started.ipynb
141
+ ```
142
+
143
+ ## Development & Testing
144
+
145
+ This project uses `pytest` for testing. To run the test suite:
146
+
147
+ ```bash
148
+ # Install dev dependencies
149
+ pip install -e ".[dev]"
150
+
151
+ # Run tests
152
+ pytest
153
+ ```
154
+
155
+ ## Contributing
156
+
157
+ Contributions are welcome! Please feel free to submit a Pull Request.
158
+
159
+ 1. Fork the repository.
160
+ 2. Create your feature branch (`git checkout -b feature/AmazingFeature`).
161
+ 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`).
162
+ 4. Push to the branch (`git push origin feature/AmazingFeature`).
163
+ 5. Open a Pull Request.
164
+
165
+ ## Issues
166
+
167
+ If you encounter any bugs or have feature requests, please file an issue on the [GitHub Issues](https://github.com/jacotay7/aobasis/issues) page.
168
+
169
+ ## Contact
170
+
171
+ For questions or support, please contact:
172
+
173
+ **User Name**
174
+ Email: jtaylor@keck.hawaii.edu
175
+
176
+ ## License
177
+
178
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "aobasis"
7
+ version = "1.0.0"
8
+ description = "A package for generating AO basis sets (KL, Zernike, Fourier)"
9
+ readme = "README.md"
10
+ authors = [{ name = "Jacob Taylor", email = "jtaylor@keck.hawaii.edu" }]
11
+ license = { file = "LICENSE" }
12
+ classifiers = [
13
+ "Programming Language :: Python :: 3",
14
+ "Operating System :: OS Independent",
15
+ ]
16
+ dependencies = [
17
+ "numpy>=1.20",
18
+ "scipy>=1.7",
19
+ "matplotlib>=3.5",
20
+ ]
21
+ requires-python = ">=3.8"
22
+
23
+ [project.optional-dependencies]
24
+ dev = [
25
+ "pytest",
26
+ "pytest-cov",
27
+ "imageio",
28
+ ]
29
+
30
+ [project.urls]
31
+ "Homepage" = "https://github.com/jacotay7/aobasis"
32
+ "Bug Tracker" = "https://github.com/jacotay7/aobasis/issues"
33
+
34
+ [tool.setuptools.packages.find]
35
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,26 @@
1
+ from importlib.metadata import version, PackageNotFoundError
2
+
3
+ try:
4
+ __version__ = version("aobasis")
5
+ except PackageNotFoundError:
6
+ __version__ = "unknown"
7
+
8
+ from .base import BasisGenerator
9
+ from .kl import KLBasisGenerator
10
+ from .zernike import ZernikeBasisGenerator
11
+ from .fourier import FourierBasisGenerator
12
+ from .zonal import ZonalBasisGenerator
13
+ from .hadamard import HadamardBasisGenerator
14
+ from .utils import make_circular_actuator_grid, make_concentric_actuator_grid, plot_basis_modes
15
+
16
+ __all__ = [
17
+ "BasisGenerator",
18
+ "KLBasisGenerator",
19
+ "ZernikeBasisGenerator",
20
+ "FourierBasisGenerator",
21
+ "ZonalBasisGenerator",
22
+ "HadamardBasisGenerator",
23
+ "make_circular_actuator_grid",
24
+ "make_concentric_actuator_grid",
25
+ "plot_basis_modes",
26
+ ]
@@ -0,0 +1,76 @@
1
+ from abc import ABC, abstractmethod
2
+ import numpy as np
3
+ from pathlib import Path
4
+ from typing import Tuple, Optional, Union
5
+ from .utils import plot_basis_modes
6
+
7
+ class BasisGenerator(ABC):
8
+ """
9
+ Abstract base class for AO basis generators.
10
+ """
11
+
12
+ def __init__(self, positions: np.ndarray):
13
+ """
14
+ Args:
15
+ positions: (N, 2) array of actuator coordinates (x, y) in meters.
16
+ """
17
+ self.positions = np.array(positions)
18
+ self.n_actuators = self.positions.shape[0]
19
+ self.modes: Optional[np.ndarray] = None
20
+
21
+ @abstractmethod
22
+ def generate(self, n_modes: int, **kwargs) -> np.ndarray:
23
+ """
24
+ Generate the basis modes.
25
+
26
+ Args:
27
+ n_modes: Number of modes to generate.
28
+
29
+ Returns:
30
+ modes: (n_actuators, n_modes) matrix.
31
+ """
32
+ pass
33
+
34
+ def save(self, filepath: Union[str, Path]) -> None:
35
+ """
36
+ Save the generated basis and actuator positions to a .npz file.
37
+ """
38
+ if self.modes is None:
39
+ raise ValueError("No modes generated yet. Call generate() first.")
40
+
41
+ np.savez(
42
+ filepath,
43
+ modes=self.modes,
44
+ positions=self.positions,
45
+ basis_type=self.__class__.__name__
46
+ )
47
+
48
+ @classmethod
49
+ def load(cls, filepath: Union[str, Path]) -> 'BasisGenerator':
50
+ """
51
+ Load a basis from a .npz file.
52
+ Note: This returns a generic container or re-instantiates the specific class if possible.
53
+ For simplicity here, we might just return the data or a generic wrapper.
54
+ """
55
+ data = np.load(filepath)
56
+ positions = data['positions']
57
+ modes = data['modes']
58
+
59
+ # Create a generic instance to hold the data
60
+ # In a more complex system, we might factory this based on basis_type
61
+ instance = ConcreteBasis(positions)
62
+ instance.modes = modes
63
+ return instance
64
+
65
+ def plot(self, count: int = 6, outfile: Optional[Union[str, Path]] = None, **kwargs):
66
+ """Plot the generated modes."""
67
+ if self.modes is None:
68
+ raise ValueError("No modes to plot.")
69
+ plot_basis_modes(self.modes, self.positions, count=count, outfile=outfile, **kwargs)
70
+
71
+ class ConcreteBasis(BasisGenerator):
72
+ """Helper class for loading existing bases."""
73
+ def generate(self, n_modes: int, **kwargs) -> np.ndarray:
74
+ if self.modes is None:
75
+ raise NotImplementedError("This is a loaded basis container.")
76
+ return self.modes[:, :n_modes]
@@ -0,0 +1,82 @@
1
+ import numpy as np
2
+ from .base import BasisGenerator
3
+
4
+ class FourierBasisGenerator(BasisGenerator):
5
+ """
6
+ Generates Fourier modes (sine/cosine) on the actuator grid.
7
+ """
8
+
9
+ def __init__(self, positions: np.ndarray, pupil_diameter: float):
10
+ super().__init__(positions)
11
+ self.pupil_diameter = pupil_diameter
12
+
13
+ def generate(self, n_modes: int, ignore_piston: bool = False, **kwargs) -> np.ndarray:
14
+ """
15
+ Generate Fourier modes.
16
+ We generate pairs of sin/cos for increasing spatial frequencies.
17
+ """
18
+ x = self.positions[:, 0]
19
+ y = self.positions[:, 1]
20
+
21
+ modes_list = []
22
+
23
+ # Piston
24
+ if not ignore_piston:
25
+ modes_list.append(np.ones_like(x))
26
+
27
+ # Loop through spatial frequencies
28
+ # kx, ky integers
29
+ # We'll spiral out or just loop kx, ky
30
+ # Simple ordering: by magnitude of k vector
31
+
32
+ k_pairs = []
33
+ # Generate a pool of k-vectors
34
+ k_max = int(np.sqrt(n_modes)) + 2
35
+ for kx in range(-k_max, k_max + 1):
36
+ for ky in range(-k_max, k_max + 1):
37
+ if kx == 0 and ky == 0:
38
+ continue
39
+ # We only need half the plane for real Fourier basis (sin/cos)
40
+ # But simpler to just generate sin/cos pairs for positive k's?
41
+ # Let's stick to standard real Fourier series expansion logic
42
+ # cos(2pi(ux + vy)), sin(2pi(ux + vy))
43
+ k_pairs.append((kx, ky))
44
+
45
+ # Sort by spatial frequency magnitude
46
+ k_pairs.sort(key=lambda k: k[0]**2 + k[1]**2)
47
+
48
+ # Filter duplicates/redundancies for real basis
49
+ # We want unique spatial frequencies.
50
+ # For each (kx, ky), we can have cos and sin.
51
+ # But (kx, ky) and (-kx, -ky) are redundant.
52
+ # So we keep only "positive" half plane.
53
+
54
+ unique_ks = []
55
+ seen = set()
56
+ for kx, ky in k_pairs:
57
+ if (kx, ky) in seen or (-kx, -ky) in seen:
58
+ continue
59
+ seen.add((kx, ky))
60
+ unique_ks.append((kx, ky))
61
+
62
+ # Generate modes
63
+ # Fundamental frequency base: 1 cycle per pupil diameter
64
+ f0 = 1.0 / self.pupil_diameter
65
+
66
+ for kx, ky in unique_ks:
67
+ if len(modes_list) >= n_modes:
68
+ break
69
+
70
+ arg = 2 * np.pi * f0 * (kx * x + ky * y)
71
+
72
+ # Cosine component
73
+ modes_list.append(np.cos(arg))
74
+
75
+ if len(modes_list) >= n_modes:
76
+ break
77
+
78
+ # Sine component
79
+ modes_list.append(np.sin(arg))
80
+
81
+ self.modes = np.column_stack(modes_list)
82
+ return self.modes
@@ -0,0 +1,51 @@
1
+ import numpy as np
2
+ from scipy.linalg import hadamard
3
+ from .base import BasisGenerator
4
+
5
+ class HadamardBasisGenerator(BasisGenerator):
6
+ """
7
+ Generates Hadamard modes.
8
+ Useful for interaction matrix calibration (multiplexing).
9
+ """
10
+
11
+ def generate(self, n_modes: int, **kwargs) -> np.ndarray:
12
+ """
13
+ Generate Hadamard modes.
14
+
15
+ Since Hadamard matrices exist only for sizes 2^k (or multiples of 4),
16
+ we find the next power of 2 >= n_actuators, generate the Hadamard matrix,
17
+ and truncate it to the number of actuators (rows) and requested modes (columns).
18
+ """
19
+ # Find next power of 2 covering the number of actuators
20
+ # We need at least n_actuators rows to define the pattern on the grid
21
+ # And we need enough columns for n_modes
22
+
23
+ # Usually for calibration, we want a square matrix that covers all actuators.
24
+ # So size >= n_actuators.
25
+
26
+ size = 1
27
+ while size < self.n_actuators:
28
+ size *= 2
29
+
30
+ # If n_modes is larger than this size, we might need a larger matrix?
31
+ # But usually we can't have more orthogonal modes than actuators (degrees of freedom).
32
+ # However, Hadamard patterns are defined by the full matrix.
33
+
34
+ if n_modes > size:
35
+ # If user asks for more modes than the natural Hadamard block size covering actuators,
36
+ # we might need to go bigger.
37
+ while size < n_modes:
38
+ size *= 2
39
+
40
+ H = hadamard(size)
41
+
42
+ # Truncate to actuators (rows) and modes (cols)
43
+ # Note: Truncated Hadamard is not necessarily orthogonal!
44
+ # But it is the standard way to project Hadamard patterns onto a smaller aperture.
45
+
46
+ self.modes = H[:self.n_actuators, :n_modes]
47
+
48
+ # Optional: Normalize? Hadamard entries are 1 and -1.
49
+ # Keeping them as is is standard.
50
+
51
+ return self.modes