Skip to content

Aberrations

Zernike

Bases: Base

A Zernike polynomial that can be generated dynamically in a way that is both jit and grad safe. If you want a static zernike (most use cases), use the zernike functions found in utils.zernikes and load the basis into a BasisOptic class.

The 'jth' zernike polynomial is defined here. The basic translation between the noll index and the pair of numbers is shown below:

1 -> (0, 0) : Piston

2, 3 -> (1, -1), (1, 1) : Tip, Tilt

4, 5, 6 -> (2, -2), (2, 0), (2, 2) : Defocus, Astigmatism

7, 8, 9, 10 -> (3, -3), (3, -1), (3, 1), (3, 3) : Coma, Trefoil

UML

UML

Attributes:

Name Type Description
j int

The Zernike (noll) index.

n int

The radial order of the Zernike polynomial.

m int

The azimuthal order of the Zernike polynomial.

name str

The name of the Zernike polynomial.

_c Array

The array of normalisation coefficients used in the radial calculation. This is a pre-calculated parameter and should not be changed.

_k Array

The array of powers using the radial calculation. This is a pre-calculated parameter and should not be changed.

Source code in src/dLux/layers/aberrations.py
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
class Zernike(Base):
    """
    A Zernike polynomial that can be generated dynamically in a way that is both jit and
    grad safe. If you want a _static_ zernike (most use cases), use the zernike
    functions found in `utils.zernikes` and load the basis into a `BasisOptic` class.

    The 'jth' zernike polynomial is defined [here](https://oeis.org/A176988). The basic
    translation between the noll index and the pair of numbers is shown below:

    1 -> (0, 0) : Piston

    2, 3 -> (1, -1), (1, 1) : Tip, Tilt

    4, 5, 6 -> (2, -2), (2, 0), (2, 2) : Defocus, Astigmatism

    7, 8, 9, 10 -> (3, -3), (3, -1), (3, 1), (3, 3) : Coma, Trefoil

    ??? abstract "UML"
        ![UML](../../assets/uml/Zernike.png)

    Attributes
    ----------
    j : int
        The Zernike (noll) index.
    n : int
        The radial order of the Zernike polynomial.
    m : int
        The azimuthal order of the Zernike polynomial.
    name : str
        The name of the Zernike polynomial.
    _c : Array
        The array of normalisation coefficients used in the radial calculation.
        This is a pre-calculated parameter and should not be changed.
    _k : Array
        The array of powers using the radial calculation. This is a pre-calculated
        parameter and should not be changed.
    """

    j: int
    n: int
    m: int
    name: str
    _c: Array
    _k: Array

    def __init__(self: Zernike, j: int):
        """
        Construct for the Zernike class.

        Parameters
        ----------
        j : int
            The Zernike (noll) index.
        """
        if int(j) < 1:
            raise ValueError("The Zernike index must be greater than 0.")
        self.j = int(j)
        self.name = dlu.zernike_name(j)
        self.n, self.m = dlu.noll_indices(j)
        self._c, self._k = dlu.zernike_factors(j)

    def calculate(self: Zernike, coordinates: Array, nsides: int = 0) -> Array:
        """
        Calculates the Zernike polynomial.

        Note: standard zernike polynomials are only defined up to a radial value of 1,
        so generating one that spans the entire aperture needs a diameter of 2.

        Parameters
        ----------
        coordinates : Array
            The Cartesian coordinates to calculate the Zernike upon.
        nsides : int
            The number of sides of the aperture. If 0, the Zernike is calculated
            on a circular aperture.

        Returns
        -------
        zernike : Array
            The Zernike polynomial.
        """
        if nsides == 0:
            return dlu.zernike_fast(
                self.n, self.m, self._c, self._k, coordinates
            )
        else:
            return dlu.polike_fast(
                nsides, self.n, self.m, self._c, self._k, coordinates
            )

__init__(j)

Construct for the Zernike class.

Parameters:

Name Type Description Default
j int

The Zernike (noll) index.

required
Source code in src/dLux/layers/aberrations.py
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
def __init__(self: Zernike, j: int):
    """
    Construct for the Zernike class.

    Parameters
    ----------
    j : int
        The Zernike (noll) index.
    """
    if int(j) < 1:
        raise ValueError("The Zernike index must be greater than 0.")
    self.j = int(j)
    self.name = dlu.zernike_name(j)
    self.n, self.m = dlu.noll_indices(j)
    self._c, self._k = dlu.zernike_factors(j)

calculate(coordinates, nsides=0)

Calculates the Zernike polynomial.

Note: standard zernike polynomials are only defined up to a radial value of 1, so generating one that spans the entire aperture needs a diameter of 2.

Parameters:

Name Type Description Default
coordinates Array

The Cartesian coordinates to calculate the Zernike upon.

required
nsides int

The number of sides of the aperture. If 0, the Zernike is calculated on a circular aperture.

0

Returns:

Name Type Description
zernike Array

The Zernike polynomial.

Source code in src/dLux/layers/aberrations.py
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def calculate(self: Zernike, coordinates: Array, nsides: int = 0) -> Array:
    """
    Calculates the Zernike polynomial.

    Note: standard zernike polynomials are only defined up to a radial value of 1,
    so generating one that spans the entire aperture needs a diameter of 2.

    Parameters
    ----------
    coordinates : Array
        The Cartesian coordinates to calculate the Zernike upon.
    nsides : int
        The number of sides of the aperture. If 0, the Zernike is calculated
        on a circular aperture.

    Returns
    -------
    zernike : Array
        The Zernike polynomial.
    """
    if nsides == 0:
        return dlu.zernike_fast(
            self.n, self.m, self._c, self._k, coordinates
        )
    else:
        return dlu.polike_fast(
            nsides, self.n, self.m, self._c, self._k, coordinates
        )
ZernikeBasis

Bases: Base

A Basis of Zernike polynomials that can be generated dynamically in a way that is both jit and grad safe. If you want a static zernike (most use cases), use the zernike functions found in utils.zernikes and load the basis into a BasisOptic class.

The 'jth' zernike polynomial is defined here. The basic translation between the noll index and the pair of numbers is shown below:

1 -> (0, 0) : Piston

2, 3 -> (1, -1), (1, 1) : Tip, Tilt

4, 5, 6 -> (2, -2), (2, 0), (2, 2) : Defocus, Astigmatism

7, 8, 9, 10 -> (3, -3), (3, -1), (3, 1), (3, 3) : Coma, Trefoil

UML

UML

Attributes:

Name Type Description
basis list[Zernike]

The list of Zernike polynomial classes to calculate.

Source code in src/dLux/layers/aberrations.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
class ZernikeBasis(Base):
    """
    A Basis of Zernike polynomials that can be generated dynamically in a way that is
    both jit and grad safe. If you want a _static_ zernike (most use cases), use the
    zernike functions found in `utils.zernikes` and load the basis into a `BasisOptic`
    class.

    The 'jth' zernike polynomial is defined [here](https://oeis.org/A176988). The basic
    translation between the noll index and the pair of numbers is shown below:

    1 -> (0, 0) : Piston

    2, 3 -> (1, -1), (1, 1) : Tip, Tilt

    4, 5, 6 -> (2, -2), (2, 0), (2, 2) : Defocus, Astigmatism

    7, 8, 9, 10 -> (3, -3), (3, -1), (3, 1), (3, 3) : Coma, Trefoil

    ??? abstract "UML"
        ![UML](../../assets/uml/ZernikeBasis.png)

    Attributes
    ----------
    basis : list[Zernike]
        The list of `Zernike` polynomial classes to calculate.
    """

    basis: list[Zernike]

    def __init__(self: ZernikeBasis, js: list[int]):
        """
        Constructor for the DynamicZernike class.

        Parameters
        ----------
        js : list[int]
            The list of Zernike (noll) indices to calculate.
        """
        self.basis = [Zernike(j) for j in js]

    def calculate_basis(
        self: ZernikeBasis, coordinates: Array, nsides: int = 0
    ) -> Array:
        """
        Calculates the full Zernike polynomial basis.

        Note: standard zernike polynomials are only defined up to a radial value of 1,
        so generating a basis that spans the entire aperture needs a diameter of 2.

        Parameters
        ----------
        coordinates : Array
            The Cartesian coordinates to calculate the Zernike basis upon.
        nsides : int
            The number of sides of the aperture. If 0, the Zernike basis is calculated
            on a circular aperture.

        Returns
        -------
        basis : Array
            The Zernike polynomial basis.
        """
        leaf_fn = lambda leaf: isinstance(leaf, Zernike)
        calculate_fn = lambda z: z.calculate(coordinates, nsides)
        return np.array(
            jtu.tree_map(calculate_fn, self.basis, is_leaf=leaf_fn)
        )

__init__(js)

Constructor for the DynamicZernike class.

Parameters:

Name Type Description Default
js list[int]

The list of Zernike (noll) indices to calculate.

required
Source code in src/dLux/layers/aberrations.py
133
134
135
136
137
138
139
140
141
142
def __init__(self: ZernikeBasis, js: list[int]):
    """
    Constructor for the DynamicZernike class.

    Parameters
    ----------
    js : list[int]
        The list of Zernike (noll) indices to calculate.
    """
    self.basis = [Zernike(j) for j in js]

calculate_basis(coordinates, nsides=0)

Calculates the full Zernike polynomial basis.

Note: standard zernike polynomials are only defined up to a radial value of 1, so generating a basis that spans the entire aperture needs a diameter of 2.

Parameters:

Name Type Description Default
coordinates Array

The Cartesian coordinates to calculate the Zernike basis upon.

required
nsides int

The number of sides of the aperture. If 0, the Zernike basis is calculated on a circular aperture.

0

Returns:

Name Type Description
basis Array

The Zernike polynomial basis.

Source code in src/dLux/layers/aberrations.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
def calculate_basis(
    self: ZernikeBasis, coordinates: Array, nsides: int = 0
) -> Array:
    """
    Calculates the full Zernike polynomial basis.

    Note: standard zernike polynomials are only defined up to a radial value of 1,
    so generating a basis that spans the entire aperture needs a diameter of 2.

    Parameters
    ----------
    coordinates : Array
        The Cartesian coordinates to calculate the Zernike basis upon.
    nsides : int
        The number of sides of the aperture. If 0, the Zernike basis is calculated
        on a circular aperture.

    Returns
    -------
    basis : Array
        The Zernike polynomial basis.
    """
    leaf_fn = lambda leaf: isinstance(leaf, Zernike)
    calculate_fn = lambda z: z.calculate(coordinates, nsides)
    return np.array(
        jtu.tree_map(calculate_fn, self.basis, is_leaf=leaf_fn)
    )