From 45e81a1912dec7a56d848355c561eb65ba30610e Mon Sep 17 00:00:00 2001 From: Roy Smart Date: Wed, 19 Aug 2026 14:04:21 -0600 Subject: [PATCH] Expand the README and the documentation landing page The landing page had no installation instructions and no runnable example, so the first executable code a visitor met was inside an API docstring. The README feature list had fallen behind the one in the documentation, missing image simulation, the linear forward model, sensor simulation, glass materials, and the ruling profiles. The landing page gains an installation section and two worked examples: the transmissivity of an aluminum filter, which shows the L edge these instruments are designed around, and the effective quantum efficiency of a back-illuminated CCD compared against an ideal back surface. A complete system is linked rather than duplicated, since `SequentialSystem` already carries a worked Newtonian telescope. The README lists the same features as the documentation, links to the documentation, and adds a "Key concepts" section covering what differs most from Zemax: surfaces are placed in global coordinates, the field of view and entrance pupil are computed rather than specified, rulings are a property of a surface, and any parameter can be an array. Also adds a development section and fixes the spelling of "uncertainty". Co-Authored-By: Claude Opus 5 --- README.md | 90 +++++++++++++++++++++++++++++++++++++++++++++++--- docs/index.rst | 83 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 166 insertions(+), 7 deletions(-) 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 =========