diff --git a/README.md b/README.md index aa433980..204c5719 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,13 @@ A Python library for simulating optical systems, similar to Zemax. +`optika` computes the spectral response and resolution of an arbitrary optical system, and can optimize it using `scipy.optimize`. +Surfaces carry their own sag profile, aperture, material, and rulings, and are placed in global coordinates, so a system is an ordinary Python object that can be built, modified, and swept over programmatically. + +Because every parameter can be an array from [`named-arrays`](https://github.com/sun-data/named-arrays), a whole configuration space of designs propagates through the raytrace at once, and an uncertain parameter carries its uncertainty through to the performance of the system. + +More information is available in the [documentation](https://optika.readthedocs.io/en/latest/). + ## Installation Optika can be installed using pip: @@ -18,13 +25,35 @@ pip install optika ``` ## Features -- Sequential raytrace modeling -- Spherical, conical and toroidal surface sag profiles -- Ruled surfaces, constant, variable, and holographic line spacing + +- Sequential raytrace modeling of an optical system +- Stratified random sampling of input rays for faster convergence +- Image simulation of a given scene using an optical system +- A fast linear forward model approximating a raytraced system, for imaging many scenes without raytracing each one +- Spherical, conical, and toroidal surface sag profiles - Circular, rectangular, and polygonal apertures -- multilayer reflectivity and transmissivity +- Mirrors and arbitrary multilayer coatings +- Refractive glass materials with Sellmeier dispersion (e.g. N-BK7, F2) +- Diffraction gratings, with constant, polynomial, and holographic ruling spacing, and sinusoidal, square, rectangular, sawtooth, and triangular ruling profiles +- CCD/CMOS sensor simulation, including quantum efficiency, noise, and charge diffusion - n-dimensional configurations of the optical system using [named-arrays](https://github.com/sun-data/named-arrays) -- uncertainity propagation using [named-arrays](https://github.com/sun-data/named-arrays) +- Uncertainty propagation using [named-arrays](https://github.com/sun-data/named-arrays) + +## Key concepts + +**Surfaces are placed in global coordinates.** +Unlike Zemax, where each surface is positioned relative to the one before it, an `optika` surface carries a `transformation` giving its position and orientation in the coordinate system of the whole instrument. +Moving one surface therefore does not move everything downstream of it. + +**The field of view and entrance pupil are computed, not specified.** +The apertures of the surfaces determine them, so marking a surface with `is_pupil_stop` or `is_field_stop` is enough. + +**Rulings are a property of a surface.** +A diffraction grating is an ordinary surface with a `rulings` field, so switching between ruling designs does not mean switching to a different type of surface. + +**Any parameter can be an array.** +Giving a parameter an extra named axis sweeps the system over that axis, and every ray traced through it carries that axis along, which is how `optika` explores a configuration space without a loop. +An [`UncertainScalarArray`](https://named-arrays.readthedocs.io/en/latest/_autosummary/named_arrays.UncertainScalarArray.html) parameter propagates its uncertainty through the raytrace by the Monte Carlo method. ## Example Gallery @@ -42,3 +71,54 @@ by specifying the materials and thicknesses of the layers. Model the [quantum efficiency of a backilluminated CCD](https://optika.readthedocs.io/en/latest/_autosummary/optika.sensors.quantum_efficiency_effective.html#optika.sensors.quantum_efficiency_effective) ![QE example](https://optika.readthedocs.io/en/latest/_images/optika.sensors.quantum_efficiency_effective_0_0.png) + +Compute the [transmissivity of a thin filter](https://optika.readthedocs.io/en/latest/#examples), +such as the aluminum filters used to reject visible light on solar instruments. + +```python +import matplotlib.pyplot as plt +import astropy.units as u +import named_arrays as na +import optika + +# Define the wavelengths at which to compute the transmissivity +wavelength = na.geomspace(100, 800, axis="wavelength", num=201) * u.AA + +# Compute the efficiency of a 100 nm layer of aluminum +reflectivity, transmissivity = optika.materials.multilayer_efficiency( + wavelength=wavelength, + layers=optika.materials.Layer( + chemical="Al", + thickness=1000 * u.AA, + ), +) + +# Plot the transmissivity, which drops sharply at the aluminum L edge +fig, ax = plt.subplots(constrained_layout=True) +na.plt.plot(wavelength, transmissivity.average, ax=ax, axis="wavelength"); +ax.set_xscale("log"); +ax.set_xlabel(f"wavelength ({wavelength.unit:latex_inline})"); +ax.set_ylabel("transmissivity"); +``` +![aluminum filter example](https://optika.readthedocs.io/en/latest/_images/index_0_0.png) + +## Development + +Install the package in editable mode along with its test dependencies, and run the test suite using [pytest](https://docs.pytest.org): +```bash +pip install -e .[test] +pytest +``` + +This project is formatted using [black](https://black.readthedocs.io), linted using [ruff](https://docs.astral.sh/ruff), and type-checked using [mypy](https://mypy-lang.org), all of which are checked by continuous integration: +```bash +black . +ruff check . +mypy optika +``` + +To build the documentation locally: +```bash +pip install -e .[doc] +sphinx-build docs docs/_build/html +``` diff --git a/docs/index.rst b/docs/index.rst index 4a948282..d2489a30 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,5 +1,5 @@ -Introduction -============ +optika +====== :mod:`optika` is a Python package for designing optical systems inspired by `Zemax `_. @@ -27,6 +27,14 @@ Furthermore, :mod:`named_arrays` provides an implementation of a 3D vector, :class:`~named_arrays.Cartesian3dVectorArray`, which is convenient to use since many of the inputs and outputs of :mod:`optika` can be represented as 3D vectors. +Installation +------------ + +:mod:`optika` is published on PyPI and can be installed using:: + + pip install optika + + Features -------- @@ -77,6 +85,77 @@ Differences from Zemax designs. +Examples +======== + +Compute the transmissivity of a thin filter, such as the aluminum filters used +to reject visible light on solar instruments. + +.. jupyter-execute:: + + import matplotlib.pyplot as plt + import astropy.units as u + import named_arrays as na + import optika + + # Define the wavelengths at which to compute the transmissivity + wavelength = na.geomspace(100, 800, axis="wavelength", num=201) * u.AA + + # Compute the efficiency of a 100 nm layer of aluminum + reflectivity, transmissivity = optika.materials.multilayer_efficiency( + wavelength=wavelength, + layers=optika.materials.Layer( + chemical="Al", + thickness=1000 * u.AA, + ), + ) + + # Plot the transmissivity, which drops sharply at the aluminum L edge + fig, ax = plt.subplots(constrained_layout=True) + na.plt.plot(wavelength, transmissivity.average, ax=ax, axis="wavelength"); + ax.set_xscale("log"); + ax.set_xlabel(f"wavelength ({wavelength.unit:latex_inline})"); + ax.set_ylabel("transmissivity"); + +| + +Compute the effective quantum efficiency of a back-illuminated CCD, and compare +it to the theoretical maximum for the same sensor. + +.. jupyter-execute:: + + # Define the wavelengths at which to compute the quantum efficiency + wavelength = na.geomspace(10, 10000, axis="wavelength", num=1001) * u.AA + + # Compute the effective quantum efficiency of the sensor + eqe = optika.sensors.quantum_efficiency_effective( + wavelength=wavelength, + ) + + # Compute the quantum efficiency of an ideal back surface + eqe_max = optika.sensors.quantum_efficiency_effective( + wavelength=wavelength, + cce_backsurface=1, + ) + + # Plot both + fig, ax = plt.subplots(constrained_layout=True) + na.plt.plot(wavelength, eqe, ax=ax, axis="wavelength", label="effective"); + na.plt.plot(wavelength, eqe_max, ax=ax, axis="wavelength", label="ideal back surface"); + ax.set_xscale("log"); + ax.set_xlabel(f"wavelength ({wavelength.unit:latex_inline})"); + ax.set_ylabel("quantum efficiency"); + ax.legend(); + +| + +For a complete optical system, including a raytrace and a simulated image, see +the worked Newtonian telescope in +:class:`optika.systems.SequentialSystem`. + +| + + Tutorials =========