Skip to content

error

Script for calculating HWFE and pointing error. Also tells you how to move whatever elements are included.

get_hwfe(data, get_transform, add_err=False)

Get the HWFE errors based on the mirror and receiver positions. This calculation is from Parshely et al.

Parameters:

Name Type Description Default
data dict[str, NDArray]

Dict that contains the following for each included element:

  • {element} : (npoint, ndim) array with the measured points
  • {element}_ref : (npoint, ndim) array with the nominal points
  • {element}_msk: (npoint,) boolean array that is False for points to exclude
  • {element}_err: (npoint, ndim) array of error to add to the measured points. np.nan is treated as 0.

This dict must at least contain the primary mirror.

required
get_transform Callable[[NDArray[float64], NDArray[float64]], tuple[NDArray[float64], NDArray[float64]]]

Function that takes in two point clouds and returns an affine matrix and a shift to align them.

required
add_err bool

If True add the error term to the data.

False

Returns:

Name Type Description
hwfe float

The HWFE in um-rms.

Source code in lat_alignment/error.py
 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
101
102
103
104
105
106
def get_hwfe(
    data: dict[str, NDArray],
    get_transform: Callable[
        [NDArray[np.float64], NDArray[np.float64]],
        tuple[NDArray[np.float64], NDArray[np.float64]],
    ],
    add_err: bool = False,
) -> float:
    """
    Get the HWFE errors based on the mirror and receiver positions.
    This calculation is from Parshely et al.

    Parameters
    ----------
    data : dict[str, NDArray]
        Dict that contains the following for each included element:

        * {element} : `(npoint, ndim)` array with the measured points
        * {element}_ref : `(npoint, ndim)` array with the nominal points
        * {element}_msk: `(npoint,)` boolean array that is False for points to exclude
        * {element}_err: `(npoint, ndim)` array of error to add to the measured points.
        `np.nan` is treated as 0.

        This dict must at least contain the primary mirror.
    get_transform : Callable[[NDArray[np.float64], NDArray[np.float64]], tuple[NDArray[np.float64], NDArray[np.float64]]]
        Function that takes in two point clouds and returns an affine matrix and a shift to align them.
    add_err : bool, default: False
        If True add the error term to the data.

    Returns
    -------
    hwfe : float
        The HWFE in um-rms.
    """
    # Put everything in M1 coordinates
    data_m1 = deepcopy(data)
    for element in elements:
        dat = np.array(data_m1[element], np.float64)
        if add_err:
            dat += np.nan_to_num(data_m1[f"{element}_err"])
        data_m1[element] = coord_transform(dat, "opt_global", "opt_primary")
        data_m1[f"{element}_ref"] = coord_transform(
            data_m1[f"{element}_ref"], "opt_global", "opt_primary"
        )

    # Transform for M1 perfect
    aff_m1, sft_m1 = get_transform(
        data_m1["primary"][data_m1["primary_msk"]],
        data_m1["primary_ref"][data_m1["primary_msk"]],
    )

    hwfe = 0
    for element in hwfe_factors.keys():
        src = data_m1[element][data_m1[f"{element}_msk"]]
        dst = data_m1[f"{element}_ref"][data_m1[f"{element}_msk"]]

        # Apply the transform to align M1
        src = apply_transform(src, aff_m1, sft_m1)

        # Get the new transform
        aff, sft = get_transform(src, dst)
        _, _, rot = decompose_affine(aff)
        rot = decompose_rotation(rot)

        # compute HWFE
        vals = np.hstack([sft * mm_to_um, rot * rad_to_arcsec]).ravel()
        hwfe += float(np.sum((np.array(hwfe_factors[element]) * vals) ** 2))
    return np.sqrt(hwfe)

get_pointing_error(data, get_transform, add_err=False, thresh=0.01)

Get the pointing error based on the mirror and receiver positions.

Parameters:

Name Type Description Default
data dict[str, NDArray]

Dict that contains the following for each included element:

  • {element} : (npoint, ndim) array with the measured points
  • {element}_ref : (npoint, ndim) array with the nominal points
  • {element}_msk: (npoint,) boolean array that is False for points to exclude
  • {element}_err: (npoint, ndim) array of error to add to the measured points. np.nan is treated as 0.

This dict must at least contain the primary mirror.

required
get_transform Callable[[NDArray[float64], NDArray[float64]], tuple[NDArray[float64], NDArray[float64]]]

Function that takes in two point clouds and returns an affine matrix and a shift to align them.

required
add_err bool

If True add the error term to the data.

False
thresh float

The threshold in arcsecs to discard a rotation.

.01

Returns:

Name Type Description
pe float

The pointing error in arcsecs.

Source code in lat_alignment/error.py
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
171
172
173
174
175
def get_pointing_error(
    data: dict[str, NDArray],
    get_transform: Callable[
        [NDArray[np.float64], NDArray[np.float64]],
        tuple[NDArray[np.float64], NDArray[np.float64]],
    ],
    add_err: bool = False,
    thresh: float = 0.01,
) -> float:
    """
    Get the pointing error based on the mirror and receiver positions.

    Parameters
    ----------
    data : dict[str, NDArray]
        Dict that contains the following for each included element:

        * {element} : `(npoint, ndim)` array with the measured points
        * {element}_ref : `(npoint, ndim)` array with the nominal points
        * {element}_msk: `(npoint,)` boolean array that is False for points to exclude
        * {element}_err: `(npoint, ndim)` array of error to add to the measured points.
        `np.nan` is treated as 0.

        This dict must at least contain the primary mirror.
    get_transform : Callable[[NDArray[np.float64], NDArray[np.float64]], tuple[NDArray[np.float64], NDArray[np.float64]]]
        Function that takes in two point clouds and returns an affine matrix and a shift to align them.
    add_err : bool, default: False
        If True add the error term to the data.
    thresh : float, default: .01
        The threshold in arcsecs to discard a rotation.

    Returns
    -------
    pe : float
        The pointing error in arcsecs.
    """
    thresh = np.deg2rad(thresh / 3600)
    rots = np.zeros((2, 3))
    # Get rotations
    for i, (element, factor) in enumerate([("primary", 1), ("secondary", 2)]):
        src = np.array(data[element])
        if add_err:
            src += np.nan_to_num(data[f"{element}_err"])
        # Put things in the local coords
        src = coord_transform(src, "opt_global", f"opt_{element}")[
            data[f"{element}_msk"]
        ]
        dst = coord_transform(
            np.array(data[f"{element}_ref"]), "opt_global", f"opt_{element}"
        )[data[f"{element}_msk"]]
        # Get rotation
        aff, _ = get_transform(src, dst)
        *_, rot = decompose_affine(aff)
        rot = decompose_rotation(rot)
        rot[abs(rot) < thresh] = 0  # Help prevent float errors
        rot[-1] = 0  # clocking doesn't matter
        # Put into global coords
        aff = R.from_euler("xyz", rot, False).as_matrix()
        aff, _ = affine_basis_transform(
            aff, np.zeros(3, np.float64), f"opt_{element}", "opt_global"
        )
        *_, rot = decompose_affine(aff)
        rot = decompose_rotation(rot)
        rot[abs(rot) < thresh] = 0
        rots[i] = rot * factor
    tot_rot = np.linalg.norm(np.sum(rots, 0))
    return 3600 * np.rad2deg(tot_rot)