Source code for lenstest.lenstest

# pylint: disable=invalid-name
# pylint: disable=too-many-arguments

"""
Utility routines for lens testing.

Documentation and examples are available at <https://lenstest.readthedocs.io>
"""

import numpy as np
import matplotlib.pyplot as plt

__all__ = ('sagitta',
           'XY_test_points',
           'knife_polygon',
           'circle_polygon',
           'draw_circle',
           'draw_lens',
           'draw_mirror',
           'conic_string',
           )


[docs] def sagitta(RoC, conic, X, Y, A=0, D=0): """ Calculate sagitta for conic surface at points X, Y. This assumes that the point source of light is located at the center of the mirror radius of curvature. The Ronchi grating is located at RoC + z_offset and oriented so lines are perpendicular to the x-axis The conic section is specified by conic:: conic = ∞ for surface that is flat, conic > 0 for surface that is an oblate spheroid, conic = 0 for surface that is a sphere, 0<conic<-1 for surface that is a prolate spheroid, conic = -1 for surface that is a paraboloid conic < -1 for surface that is a hyperboloid Args: RoC: radius of curvature of mirror [mm] conic: conic constant or Schwartzchild constant [-] X: x-value(s) of points [mm] Y: y-value(s) of points [mm] A: spherical aberration [1/mm³] D: defocus [1/mm] Returns: sagitta at each (x, y) point [mm] """ # find the x value where the ray passes through the Ronchi ruling rho_sqr = X**2 + Y**2 heights = rho_sqr / (RoC + np.sqrt(RoC * RoC - (1 + conic) * rho_sqr)) heights += A * rho_sqr**2 heights += D * rho_sqr return heights
[docs] def draw_circle(R, X0=0, Y0=0, color='black'): """ Draw a circle. If the circle that is plotted should be round, then `plt.gca().set_aspect('equal')` so aspect ratio on both axes is equal. Args: R: radius of circle X0: x-value of center of circle Y0: y-value of center of circle color: color of the circle Returns: X, Y: arrays of test points """ theta = np.linspace(0, 2 * np.pi, 100) plt.plot(X0 + R * np.sin(theta), Y0 + R * np.cos(theta), color=color)
[docs] def XY_test_points(D, N=100000, on_grid=False): """ Generate test points for lens test. The generated points all fall within a circle of radius D. Args: D: diameter of mirror [mm] N: number of points to generate on_grid: if False generate points on a grid Returns: X, Y: arrays of test points """ if on_grid: gridpts = np.linspace(-D / 2, D / 2, int(np.sqrt(N))) x_grid, y_grid = np.meshgrid(gridpts, gridpts) r_mask = x_grid**2 + y_grid**2 > D * D / 4 X = np.ma.masked_where(r_mask, x_grid) Y = np.ma.masked_where(r_mask, y_grid) else: U1 = np.random.uniform(size=N) U2 = np.random.uniform(size=N) X = D / 2 * np.sqrt(U2) * np.cos(2 * np.pi * U1) Y = D / 2 * np.sqrt(U2) * np.sin(2 * np.pi * U1) return X, Y
[docs] def knife_polygon(s, phi, ds): """ Create a polygon for a rotated knife edge. The polygon is a vertical 1:2 rectangle that is shifted and then rotated counter clockwise. When the shift is zero, then the edge of the knife edge is at the origin. Specifing a shift translates the knife edge across the origin. Then the knife is rotated about the origin by an angle phi. Args: s: short side of the rectangle [mm] phi: CCW rotation from vertical [radians] ds: shift of knife edge [mm] Returns: x, y: coordinates of knife polygon """ # vertical s x 2s rectangle with center of edge shifted by ds xp = np.array([-s, -s, 0, 0, -s]) + ds yp = np.array([-s, s, s, -s, -s]) # rotate rectangle CCW x = xp * np.cos(phi) - yp * np.sin(phi) y = xp * np.sin(phi) + yp * np.cos(phi) return x, y
[docs] def circle_polygon(R, X0=0, Y0=0): """ Create a polygon for a circle. If you want to plot the circle then the aspect ratio for the axes should to be set to equal: `plt.gca().set_aspect('equal')` so that the circle is looks round. Args: R: radius of the circle X0: x-value of center of circle Y0: y-value of center of circle Returns: x, y: coordinates of polygon """ theta = np.linspace(0, 2 * np.pi, 100) return X0 + R * np.sin(theta), Y0 + R * np.cos(theta)
[docs] def draw_lens(D, RoC, middle=0): """ Draw and fill a biconvex lens shape. If the glass has an index of 1.5, then the radius-of-curvature will equal the focal length. The lens is drawn with its center at `center` Args: D: diameter of the lens RoC: x-value of center of circle middle: y-value of center of circle Returns: Nothing. Use `plt.show()` to see result """ theta_max = np.arctan2(D / 2, RoC) theta = np.linspace(-theta_max, theta_max, 11) x = -RoC * np.cos(theta) y = -RoC * np.sin(theta) dx = RoC + x dx -= dx[0] # offset surface to center the lens on the vertex xx = np.concatenate((middle - dx, middle + dx)) yy = np.concatenate((y, -y)) plt.fill(xx, yy, color='gray', alpha=0.8)
[docs] def draw_mirror(D, RoC, vertex=0): """Draw one face of a mirror.""" theta_max = np.arctan2(D / 2, RoC) theta = np.linspace(-theta_max, theta_max, 51) x = -RoC * np.cos(theta) y = -RoC * np.sin(theta) dx = RoC + x dx -= dx[0] # offset surface to center the lens on the vertex xx_last = np.array([vertex - dx[0] - D / 10, vertex - dx[0] - D / 10, vertex - dx[-1]]) yy_last = np.array([y[-1], y[0], y[0]]) xx = np.concatenate((vertex + dx, xx_last)) yy = np.concatenate((y, yy_last)) plt.fill(xx, yy, color='gray', alpha=0.8)
[docs] def conic_string(conic): """Create string that describes a conic value.""" if np.isinf(conic): return 'flat' if conic > 0: return "oblate spheroid" if conic == 0: return "sphere" if conic > -1: return "prolate spheroid" if conic == -1: return "paraboloid" return "hyperboloid"