Skip to content

fitting

Functions for fitting against the mirror surface.

mirror_fit(points, a, compensate=0, to_points=True, **kwargs)

Fit points against the mirror surface. Ideally the points should be in the mirror's local coordinate system.

Parameters:

Name Type Description Default
points NDArray[floating]

Array of points to compare against the mirror. Should have shape (npoint, 3).

required
a NDArray[floating]

Coeffecients of the mirror function. Use a_primary for the primary mirror and a_secondary for the secondary.

required
compensate float

Amount to compensate the mirror surface by. This is useful to model things like the surface traced out by an SMR.

0.0
to_points bool

If True, the transform will be inverted to align the model to the points.

True
**kwargs

Additional arguments to pass on to scipy.optimize.minimize.

{}

Returns:

Name Type Description
transform_pars NDArray[floating]

Flattened affine transform and shift, has to be 1d for use with minimizers. Will have shape (12,) where the first 9 elements are the flattened affine transform, and the last 3 are the shift in (x, y, z) applied after the affine transform.

rms float

The RMS error between the transformed points and the model.

Source code in lat_alignment/fitting.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
101
102
103
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
def mirror_fit(
    points: NDArray[np.floating],
    a: NDArray[np.floating],
    compensate: float = 0,
    to_points: bool = True,
    **kwargs,
) -> tuple[NDArray[np.floating], float]:
    """
    Fit points against the mirror surface.
    Ideally the points should be in the mirror's local coordinate system.

    Parameters
    ----------
    points : NDArray[np.floating]
        Array of points to compare against the mirror.
        Should have shape (npoint, 3).
    a : NDArray[np.floating]
        Coeffecients of the mirror function.
        Use a_primary for the primary mirror and a_secondary for the secondary.
    compensate : float, default: 0.0
        Amount to compensate the mirror surface by.
        This is useful to model things like the surface traced out by an SMR.
    to_points : bool, default: True
        If True, the transform will be inverted to align the model to the points.
    **kwargs
        Additional arguments to pass on to scipy.optimize.minimize.

    Returns
    -------
    transform_pars : NDArray[np.floating]
        Flattened affine transform and shift, has to be 1d for use with minimizers.
        Will have shape (12,) where the first 9 elements are the flattened affine transform,
        and the last 3 are the shift in (x, y, z) applied after the affine transform.
    rms : float
        The RMS error between the transformed points and the model.
    """

    def _fit_func(transform_pars, points, a, compensate):
        points_transformed = mirror_transform(transform_pars, points)
        chisq = mirror_objective(points_transformed, a, compensate)
        return chisq

    x0 = np.concatenate((np.eye(3).ravel(), np.zeros(3)))
    res = opt.minimize(_fit_func, x0, args=(points, a, compensate), **kwargs)

    transform_pars = res.x
    transformed = mirror_transform(transform_pars, points)
    z = mr.mirror(transformed[:, 0], transformed[:, 1], a, compensate)
    rms = np.sqrt(np.mean((z - transformed[:, 2]) ** 2))

    if to_points:
        aff = transform_pars[:9].reshape((3, 3))
        sft = transform_pars[9:]
        aff = np.linalg.inv(aff)
        sft = (-1 * sft) @ aff
        transform_pars = np.concatenate((aff.ravel(), sft))

    return transform_pars, rms

mirror_objective(points, a, compensate=0)

Objective function to minimize when fitting to mirror surface. Essentially just a curvature weighted chisq.

Parameters:

Name Type Description Default
points NDArray[floating]

Array of points to compare against the mirror. Should have shape (npoint, 3).

required
a NDArray[floating]

Coeffecients of the mirror function. Use a_primary for the primary mirror and a_secondary for the secondary.

required
compensate float

Amount to compensate the mirror surface by. This is useful to model things like the surface traced out by an SMR.

0.0

Returns:

Name Type Description
chisq float

The value to minimize when fitting to.

Source code in lat_alignment/fitting.py
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
def mirror_objective(
    points: NDArray[np.floating], a: NDArray[np.floating], compensate: float = 0
) -> float:
    """
    Objective function to minimize when fitting to mirror surface.
    Essentially just a curvature weighted chisq.

    Parameters
    ----------
    points : NDArray[np.floating]
        Array of points to compare against the mirror.
        Should have shape (npoint, 3).
    a : NDArray[np.floating]
        Coeffecients of the mirror function.
        Use a_primary for the primary mirror and a_secondary for the secondary.
    compensate : float, default: 0.0
        Amount to compensate the mirror surface by.
        This is useful to model things like the surface traced out by an SMR.

    Returns
    -------
    chisq : float
        The value to minimize when fitting to.
    """
    surface = mr.mirror(points[:, 0], points[:, 1], a, compensate)
    norm = mr.mirror_norm(points[:, 0], points[:, 1], a)
    res = (points[:, 2] - surface) * (norm[2] ** 2)

    return res @ res.T

mirror_transform(transform_pars, points)

Function to apply an affine transform to the mirror. This is the transform we are fitting for.

Parameters:

Name Type Description Default
transform_pars NDArray[floating]

Flattened affine transform and shift, has to be 1d for use with minimizers. Should have shape (12,) where the first 9 elements are the flattened affine transform, and the last 3 are the shift in (x, y, z) applied after the affine transform.

required
points NDArray[floating]

Array of points to compare against the mirror. Should have shape (npoint, 3).

required

Returns:

Name Type Description
points_transformed NDArray[floating]

Array of transformed points. Will have shape (npoint, 3).

Source code in lat_alignment/fitting.py
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
def mirror_transform(
    transform_pars: NDArray[np.floating], points: NDArray[np.floating]
) -> NDArray[np.floating]:
    """
    Function to apply an affine transform to the mirror.
    This is the transform we are fitting for.

    Parameters
    ----------
    transform_pars : NDArray[np.floating]
        Flattened affine transform and shift, has to be 1d for use with minimizers.
        Should have shape (12,) where the first 9 elements are the flattened affine transform,
        and the last 3 are the shift in (x, y, z) applied after the affine transform.
    points : NDArray[np.floating]
        Array of points to compare against the mirror.
        Should have shape (npoint, 3).

    Returns
    -------
    points_transformed : NDArray[np.floating]
        Array of transformed points.
        Will have shape (npoint, 3).
    """
    aff = transform_pars[:9].reshape((3, 3))
    sft = transform_pars[9:]
    return points @ aff + sft

res_auto_corr(residuals)

Compute auto correlation of residuals from fit.

Parameters:

Name Type Description Default
residuals NDArray[floating]

Residuals between measured point cloud and fit model.

required

Returns:

Name Type Description
ac NDArray[floating]

Auto correlation, really just the deviations in mm at each distance scale.

ac_dists NDArray[floating]

Distance scale of each value in ac.

Source code in lat_alignment/fitting.py
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
def res_auto_corr(
    residuals: NDArray[np.floating],
) -> tuple[NDArray[np.floating], NDArray[np.floating]]:
    """
    Compute auto correlation of residuals from fit.

    Parameters
    ----------
    residuals : NDArray[np.floating]
        Residuals between measured point cloud and fit model.

    Returns
    -------
    ac : NDArray[np.floating]
        Auto correlation, really just the deviations in mm at each distance scale.
    ac_dists : NDArray[np.floating]
        Distance scale of each value in ac.
    """
    dists = np.zeros((len(residuals), len(residuals)))
    res_diff = np.zeros((len(residuals), len(residuals)))

    for i in range(len(residuals)):
        res1 = residuals[i]
        for j in range(i):
            res2 = residuals[j]
            dist = np.linalg.norm((res1[0] - res2[0], res1[1] - res2[1]))
            dists[i, j] = dist
            res_diff[i, j] = abs(res1[2] - res2[2])
    tri_i = np.tril_indices(len(residuals), k=-1)
    dists = dists[tri_i]
    res_diff = res_diff[tri_i]
    ac, bin_e, _ = binned_statistic(dists, res_diff, bins=100)
    ac_dists = bin_e[:-1] + np.diff(bin_e) / 2.0

    return ac, ac_dists

tension_fit(residuals, **kwargs)

Fit a power law model of tension to a point cloud of residuals.

Parameters:

Name Type Description Default
residuals NDArray[floating]

Residuals between measured point cloud and fit model.

required
**kwargs

Arguments to be passed to scipy.optimize.minimize

{}

Returns:

Name Type Description
tension_pars NDArray[floating]

The fit parameters, see docstring of tension_model for details.

rms float

The rms between the input residuals and the fit model.

Source code in lat_alignment/fitting.py
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
def tension_fit(
    residuals: NDArray[np.floating], **kwargs
) -> tuple[NDArray[np.floating], float]:
    """
    Fit a power law model of tension to a point cloud of residuals.

    Parameters
    ----------
    residuals : NDArray[np.floating]
        Residuals between measured point cloud and fit model.
    **kwargs
        Arguments to be passed to scipy.optimize.minimize

    Returns
    -------
    tension_pars : NDArray[np.floating]
        The fit parameters, see docstring of tension_model for details.
    rms : float
        The rms between the input residuals and the fit model.
    """

    def min_func(pars, residuals):
        _z = tension_model(*pars[:5], residuals)
        return np.sqrt(np.mean((residuals[:, 2] - _z) ** 2))

    if "bounds" not in kwargs:
        ptp = np.ptp(residuals[:, 2])
        bounds = [
            (np.min(residuals[:, 0]), np.max(residuals[:, 0])),
            (np.min(residuals[:, 1]), np.max(residuals[:, 1])),
            (-1 * ptp, ptp),
            (1e-10, np.inf),
            (0, np.inf),
        ]
        kwargs["bounds"] = bounds
    x0 = [np.mean(residuals[:, 0]), np.mean(residuals[:, 1]), 0, 1, 0]
    res = opt.minimize(min_func, x0, (residuals,), **kwargs)
    return res.x, res.fun

tension_model(x0, y0, t, a, b, points)

Function to model incorrect panel tensioning. Currently the model used is a radial power law.

Parameters:

Name Type Description Default
x0 float

Center of the power law in x.

required
y0 float

Center of the power law in y.

required
t float.

Amplitude of power law, nominally the offset due to tensioning in the center of panel.

required
a float

Base of power law.

required
b float

Exponential scale factor of power law

required
points NDArray[floating]

Points to compute power law at. Only the x and y coordinates are used (first two collumns). So should be (npoint, 2) but (npoint, ndim>2) is also fine.

required

Returns:

Name Type Description
z NDArray[floating]

Power law model at each xy. Will have shape (npoint,).

Source code in lat_alignment/fitting.py
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
171
172
173
def tension_model(
    x0: float, y0: float, t: float, a: float, b: float, points: NDArray[np.floating]
) -> NDArray[np.floating]:
    """
    Function to model incorrect panel tensioning.
    Currently the model used is a radial power law.


    Parameters
    ----------
    x0 : float
        Center of the power law in x.
    y0 : float
        Center of the power law in y.
    t : float.
        Amplitude of power law,
        nominally the offset due to tensioning in the center of panel.
    a : float
        Base of power law.
    b : float
        Exponential scale factor of power law
    points : NDArray[np.floating]
        Points to compute power law at.
        Only the x and y coordinates are used (first two collumns).
        So should be (npoint, 2) but (npoint, ndim>2) is also fine.

    Returns
    -------
    z : NDArray[np.floating]
        Power law model at each xy.
        Will have shape (npoint,).
    """
    # Avoid divide by 0 error
    if a == 0:
        return np.zeros(len(points))

    # Compute radius at each point
    r = np.sqrt((points[:, 0] - x0) ** 2 + (points[:, 1] - y0) ** 2)

    # Return power law
    return t * (a ** (-b * r))