Source code for optable.component_group

from .optical_component import *
from .solver import *


[docs] class ComponentGroup(OpticalComponent):
[docs] def __init__(self, origin, **kwargs): super().__init__(origin, **kwargs) self.transform_matrix = np.identity(3) self.surface = Plane() # Default surface is a plane self._bbox = ( None, None, None, None, None, None, ) # xmin, xmax, ymin, ymax, zmin, zmax self._bboxes = [] self.components = [] self.monitors = [] self.rays = [] self.refpoints = [] self.name = kwargs.get("name", None)
def __repr__(self): return f"ComponentGroup(origin={self.origin}, transform_matrix={self.transform_matrix})" @property def bbox(self): if self._bbox == (None, None, None, None, None, None): self._bbox = tuple(self.get_bbox()) return tuple(self._bbox) @property def bboxes(self): if not self._bboxes: self.get_bboxes() return self._bboxes
[docs] def get_bboxes(self) -> List[tuple]: self._bboxes = [c.bbox for c in self.components] return self._bboxes
[docs] def get_bbox(self) -> tuple: self.get_bboxes() bbox_merged = self.surface.merge_bboxs(self._bboxes) return bbox_merged
def _RotAroundLocal(self, axis, localpoint, theta): # Note that localpoint is in the local coord, and origins are all in lab coord R = self.R(axis, theta) localpoint = np.array(localpoint) self.transform_matrix = np.dot(R, self.transform_matrix) old_origin = self.origin self.origin = self.origin + np.dot(R, -localpoint) + localpoint # for ray in self.rays: lp = -(ray.origin - (old_origin + localpoint)) ray._RotAroundLocal(axis, lp, theta) for component in self.components: lp = -(component.origin - (old_origin + localpoint)) component._RotAroundLocal(axis, lp, theta) for monitor in self.monitors: lp = -(monitor.origin - (old_origin + localpoint)) monitor._RotAroundLocal(axis, lp, theta) for point in self.refpoints: lp = -(point.origin - (old_origin + localpoint)) point._RotAroundLocal(axis, lp, theta) return self def _RotAroundCenter(self, axis, theta): return self._RotAroundLocal(axis, [0, 0, 0], theta) def _Translate(self, movement): self.origin += np.array(movement) for ray in self.rays: ray._Translate(movement) for component in self.components: component._Translate(movement) for monitor in self.monitors: monitor._Translate(movement) for point in self.refpoints: point._Translate(movement) return self
[docs] def render(self, ax, type: str, **kwargs): for component in self.components: component.render(ax, type, **kwargs) for point in self.refpoints: point.render(ax, type, **kwargs)
[docs] def interact(self, ray: Ray) -> Union[Tuple[float, List[Ray]], Tuple[None, None]]: tList = [] new_rays_list = [] # first does this ray even intersect with the bbox of this component group EPS = 1e-9 _, _, hits = solve_ray_bboxes_intersections( ray.origin, ray.direction, self.bbox ) if not hits[0]: return None, None # then check each component bboxes = [np.array(c.bbox) for c in self.components] t1s, t2s, hits = solve_ray_bboxes_intersections( ray.origin, ray.direction, bboxes ) # print(f"ComponentGroup.interact: ray {ray._id} hits {np.sum(hits)} components") hits_idx_mask = np.where(hits)[0] for i in hits_idx_mask: component = self.components[i] t, new_rays = component.interact(ray) if t is not None: tList.append(t) new_rays_list.append(new_rays) # # Find the closest intersection if len(tList) > 0: idx = np.argmin(tList) return tList[idx], new_rays_list[idx] else: return None, None
[docs] def add_rays(self, rays): self.rays.extend(rays)
[docs] def add_component(self, component): self.components.append(component) if hasattr(component, "rays"): self.rays.extend(component.rays)
[docs] def add_components(self, components): self.components.extend(components) for component in components: if hasattr(component, "rays"): self.rays.extend(component.rays)
[docs] def add_monitor(self, monitor): self.monitors.append(monitor)
[docs] def add_monitors(self, monitors): self.monitors.extend(monitors)
[docs] def add_refpoint(self, point): self.refpoints.append(point)
[docs] class GlassSlab(ComponentGroup):
[docs] def __init__( self, origin, width=1.0, height=1.0, thickness=1.0, n1=1.0, n2=1.5, reflectivity=0, transmission=1, **kwargs, ): super().__init__(origin, **kwargs) self.add_component( SquareRefractive( origin + np.array([0, 0, 0]), width, height, n1, n2, reflectivity=reflectivity, transmission=transmission, **kwargs, ) ) self.add_component( SquareRefractive( origin + np.array([-thickness, 0, 0]), width, height, n2, n1, reflectivity=reflectivity, transmission=transmission, **kwargs, ) )
[docs] class CircleGlassSlab(ComponentGroup):
[docs] def __init__( self, origin, radius=1.0, thickness=1.0, n1=1.0, n2=1.5, reflectivity1=0, transmission1=1, reflectivity2=0, transmission2=1, **kwargs, ): super().__init__(origin, **kwargs) self.radius = radius self.add_component( CircleRefractive( origin + np.array([0, 0, 0]), radius, n1, n2, reflectivity=reflectivity1, transmission=transmission1, **kwargs, ) ) self.add_component( CircleRefractive( origin + np.array([-thickness, 0, 0]), radius, n2, n1, reflectivity=reflectivity2, transmission=transmission2, **kwargs, ) )
[docs] class MLA(ComponentGroup):
[docs] def __init__(self, origin, N, pitch, focal_length, radius, focal_drift=0, **kwargs): super().__init__(origin) self.pitch = pitch self.focal_length = focal_length self.radius = radius # if isinstance(N, int): N = (N, 1) ny, nz = N # for i in range(nz): for j in range(ny): z = (i - (nz - 1) / 2) * pitch y = (j - (ny - 1) / 2) * pitch o = np.array([0, y, z]) + self.origin f = focal_length * (1 + focal_drift * np.random.randn()) lens = Lens(origin=o, focal_length=f, radius=radius, **kwargs) self.add_component(lens)
[docs] class MMA(ComponentGroup): """Microlens Mirror Array"""
[docs] def __init__(self, origin, N, pitch, roc, n, thickness, roc_drift=0, **kwargs): super().__init__(origin, **kwargs) self.pitch = pitch # if not isinstance(N, tuple): N = (N, 1) ny, nz = N if not isinstance(pitch, tuple): pitch = (pitch, pitch) py, pz = pitch if not isinstance(roc, tuple): roc = np.ones((nz, ny)) * roc else: roc = np.array(roc) assert roc.shape == ( nz, ny, ), f"roc shape {roc.shape} does not match ({nz}, {ny})" mma_shifty = kwargs.get("mma_shifty", 0) mma_shiftz = kwargs.get("mma_shiftz", 0) shifty_z = kwargs.get("shifty_z", 0) shiftz_y = kwargs.get("shiftz_y", 0) # for i in range(nz): for j in range(ny): z = (i - (nz - 1) / 2) * pz + (j * shiftz_y) + mma_shiftz y = (j - (ny - 1) / 2) * py + (i * shifty_z) + mma_shifty o = np.array([0, y, z]) + self.origin roci = roc[i, j] * (1 + roc_drift * np.random.randn()) # print(kwargs.get("transmission", None)) mirror = SphereRefractive( origin=o + [-roci, 0, 0], radius=roci, height=roci - np.sqrt(roci**2 - (self.pitch / 2) ** 2), n1=n, n2=1.0, **kwargs, ) self.add_component(mirror) mma_width = kwargs.get("mma_width", ny * py) mma_height = kwargs.get("mma_height", nz * pz) back_reflectivity = kwargs.get("back_reflectivity", 0) back_transmission = kwargs.get("back_transmission", 1) backsurface = SquareRefractive( origin=self.origin + np.array([thickness, 0, 0]), width=mma_width, height=mma_height, n1=1, n2=n, reflectivity=back_reflectivity, transmission=back_transmission, ) self.add_component(backsurface)
[docs] class MMADisordered(ComponentGroup): """Microlens Mirror Array"""
[docs] def __init__( self, origin, PList, pitch, roc, n, thickness, roc_drift=0, nList=None, **kwargs, ): super().__init__(origin, **kwargs) self.pitch = pitch # PList = np.array(PList) assert PList.shape[1] == 3, "PList must be a list of 3D points" if nList is not None: nList = np.array(nList) assert nList.shape[0] == PList.shape[0], "nList must match PList length" if isinstance(roc, (int, float)): roc = np.ones(PList.shape[0]) * roc else: roc = np.array(roc) assert roc.shape[0] == PList.shape[0], "roc must match PList length" # for i in range(len(PList)): o = np.array(PList[i]) + self.origin roci = roc[i] * (1 + roc_drift * np.random.randn()) # print(kwargs.get("transmission", None)) mirror = SphereRefractive( origin=o + [-roci, 0, 0], radius=roci, height=roci - np.sqrt(roci**2 - (self.pitch / 2) ** 2), n1=n, n2=1.0, **kwargs, ) if nList is not None: n1 = mirror.normal n2 = -nList[i] axis, theta = solve_normal_to_normal_rotation(n1, n2) mirror._RotAroundLocal(axis, [roci, 0, 0], theta) self.add_component(mirror) backsurface = SquareRefractive( origin=self.origin + np.array([thickness, 0, 0]), width=(np.max(PList[:, 1]) - np.min(PList[:, 1])) * 1.1, height=(np.max(PList[:, 2]) - np.min(PList[:, 2])) * 1.1, n1=1, n2=n, reflectivity=0, transmission=1, ) self.add_component(backsurface)
[docs] class DMD(ComponentGroup): """Digital Micromirror Device"""
[docs] def __init__(self, origin, N, pitch, tilt_angle=np.pi / 4, **kwargs): super().__init__(origin) self.pitch = pitch self.tilt_angle = tilt_angle # if isinstance(N, int): N = (N, 1) ny, nz = N # for i in range(nz): for j in range(ny): z = (i - (nz - 1) / 2) * pitch y = (j - (ny - 1) / 2) * pitch o = np.array([0, y, z]) + self.origin mirror = SquareMirror( origin=o, width=pitch, height=pitch, reflectivity=1.0, **kwargs, ).RotZ(self.tilt_angle) self.add_component(mirror)
[docs] class WedgePlate(ComponentGroup):
[docs] def __init__( self, origin, width=1.0, height=1.0, thickness=1.0, wedge_angle=0.0, n1=1.0, n2=1.5, reflectivity=0, transmission=1, **kwargs, ): super().__init__(origin, **kwargs) self.add_component( SquareRefractive( origin + np.array([thickness / 2, 0, 0]), width, height, n1, n2, reflectivity=reflectivity, transmission=transmission, **kwargs, ).RotZ(wedge_angle / 2) ) self.add_component( SquareRefractive( origin + np.array([-thickness / 2, 0, 0]), width, height, n2, n1, reflectivity=reflectivity, transmission=transmission, **kwargs, ).RotZ(-wedge_angle / 2) )
class MirrorPair(ComponentGroup): def __init__( self, origin, width=1.0, height=1.0, angle: float = np.pi / 2, reflectivity_1=1, transmission_1=0, reflectivity_2=1, transmission_2=0, **kwargs, ): """Symmetric two-mirror pair. Two square mirrors are centered at ``y=+width/2`` and ``y=-width/2`` and rotated symmetrically about the z-axis so that the angle between the two mirror faces is ``angle``. Parameters ---------- origin : array-like Group reference origin in lab coordinates. width : float, default 1.0 Mirror width and separation scale (also used in mirror center offsets). height : float, default 1.0 Mirror height along z. angle : float, default π/2 Included angle (radians) between the two mirror faces. reflectivity_1, reflectivity_2 : float, default 1 Reflectivity for mirror 1 and mirror 2. transmission_1, transmission_2 : float, default 0 Transmission for mirror 1 and mirror 2. **kwargs Forwarded to underlying ``SquareMirror`` constructors. """ super().__init__(origin, **kwargs) self.name = kwargs.get("name", self.__class__.__name__) # +45deg face self.add_component( SquareMirror( origin + np.array([0, width / 2, 0]), width=width, height=height, reflectivity=reflectivity_1, transmission=transmission_1, name=f"{self.name} mirror 1", **kwargs, )._RotAroundLocal([0, 0, 1], [0, -width / 2, 0], (np.pi - angle) / 2) ) # -45deg face self.add_component( SquareMirror( origin + np.array([0, -width / 2, 0]), width=width, height=height, reflectivity=reflectivity_2, transmission=transmission_2, name=f"{self.name} mirror 2", **kwargs, )._RotAroundLocal([0, 0, 1], [0, width / 2, 0], -(np.pi - angle) / 2) )
[docs] class Prism(ComponentGroup):
[docs] def __init__( self, origin, width=1.0, height=1.0, n1=1.0, n2=1.5, angle: float = np.pi / 2, reflectivity_leg=0, transmission_leg=1, reflectivity_hyp=0, transmission_hyp=1, **kwargs, ): """Isoceles prism with two equal legs of length `width` and apex `angle`. Cross-section (view along z):: |\\ | \\ leg 1 | \\ hypotenuse | \\ | / | / | / leg 2 |/ Origin is at the apex (right-angle corner). Parameters ---------- origin : array-like Position of the apex corner. width : float Length of each leg. height : float Extent along the z-axis. n1 : float Refractive index outside the prism. n2 : float Refractive index of the prism material. angle : float Full apex angle in radians (default π/2). reflectivity_leg : float Reflectivity of the two leg faces. transmission_leg : float Transmission of the two leg faces. reflectivity_hyp : float Reflectivity of the hypotenuse face. transmission_hyp : float Transmission of the hypotenuse face. """ super().__init__(origin, **kwargs) self.name = kwargs.get("name", self.__class__.__name__) # +45deg face self.add_component( SquareRefractive( origin + np.array([0, width / 2, 0]), width, height, n1, n2, reflectivity=reflectivity_leg, transmission=transmission_leg, name=f"{self.name} leg 1", **kwargs, )._RotAroundLocal([0, 0, 1], [0, -width / 2, 0], (np.pi - angle) / 2) ) # -45deg face self.add_component( SquareRefractive( origin + np.array([0, -width / 2, 0]), width, height, n1, n2, reflectivity=reflectivity_leg, transmission=transmission_leg, name=f"{self.name} leg 2", **kwargs, )._RotAroundLocal([0, 0, 1], [0, width / 2, 0], -(np.pi - angle) / 2) ) # sqrt(2)*L face self.add_component( SquareRefractive( origin + np.array([-width * np.cos(angle / 2), 0, 0]), width * 2 * np.sin(angle / 2), height, n2, n1, reflectivity=reflectivity_hyp, transmission=transmission_hyp, name=f"{self.name} hypotenuse", **kwargs, ) )
[docs] class TriangularPrism(ComponentGroup):
[docs] def __init__( self, origin, width=1.0, height=1.0, n1=1.0, n2=1.5, alpha=np.pi / 4, beta=np.pi / 2, reflectivity_1=0, reflectivity_2=0, reflectivity_3=0, transmission_1=1, transmission_2=1, transmission_3=1, max_interact_count_2=5, max_interact_count_3=5, **kwargs, ): """Triangular prism defined by three faces. Face 1 (entrance) is perpendicular to the x-axis, has length ``width`` and extent ``height`` along z. The origin sits at the midpoint of the bottom edge of face 1. Face 2 departs from the top edge of face 1, tilted by angle ``alpha`` (measured from face 1 towards the interior). Face 3 departs from the bottom edge of face 1, tilted by angle ``beta`` (measured from face 1 towards the interior). Cross-section (view along z, beam propagates in +x):: ╱│ 2 / │ 1 (width) / │ ╱───┘ 3 Parameters ---------- origin : array-like Position of the bottom-centre of face 1. width : float Length of face 1 (the entrance face). height : float Extent of every face along the z-axis. n1 : float Refractive index outside the prism. n2 : float Refractive index of the prism material. alpha : float Angle (rad) between face 1 and face 2 (default π/4). beta : float Angle (rad) between face 1 and face 3 (default π/2). reflectivity_{1,2,3} : float Reflectivity of each face. transmission_{1,2,3} : float Transmission of each face. max_interact_count_{2,3} : int Maximum interaction count for faces 2 and 3. """ super().__init__(origin, **kwargs) self.name = kwargs.get("name", self.__class__.__name__) # 1 face self.add_component( SquareRefractive( origin=origin + np.array([0, width / 2, 0]), width=width, height=height, n1=n1, n2=n2, reflectivity=reflectivity_1, transmission=transmission_1, **kwargs, ) ) # 2 face l2 = ( width * np.sin(beta) / np.sin(np.pi - alpha - beta) ) # length of the l2 faces self.add_component( SquareRefractive( origin=origin + np.array([0, width - l2 / 2, 0]), width=l2, height=height, n1=n2, n2=n1, reflectivity=reflectivity_2, transmission=transmission_2, max_interact_count=max_interact_count_2, **kwargs, )._RotAroundLocal([0, 0, 1], [0, l2 / 2, 0], -alpha) ) # 3 face l3 = ( width * np.sin(alpha) / np.sin(np.pi - alpha - beta) ) # length of the l3 faces self.add_component( SquareRefractive( origin=origin + np.array([0, l3 / 2, 0]), width=l3, height=height, n1=n2, n2=n1, reflectivity=reflectivity_3, transmission=transmission_3, max_interact_count=max_interact_count_3, **kwargs, )._RotAroundLocal([0, 0, 1], [0, -l3 / 2, 0], beta) )
[docs] class MirrorPrism(ComponentGroup):
[docs] def __init__( self, origin, width=1.0, height=1.0, angle: float = np.pi / 2, reflectivity=1.0, transmission=0.0, **kwargs, ): """L=width, H=height,(L,L,sqrt(2)L)x H, origin at right-angle corner""" super().__init__(origin, **kwargs) self.add_component( SquareMirror( origin + np.array([0, width / 2, 0]), width, height, reflectivity=reflectivity, transmission=transmission, **kwargs, )._RotAroundLocal([0, 0, 1], [0, -width / 2, 0], (np.pi - angle) / 2) ) self.add_component( SquareMirror( origin + np.array([0, -width / 2, 0]), width, height, reflectivity=reflectivity, transmission=transmission, **kwargs, )._RotAroundLocal([0, 0, 1], [0, width / 2, 0], -(np.pi - angle) / 2) )
[docs] class MirrorCube(ComponentGroup):
[docs] def __init__( self, origin, L=1.0, reflectivity=1.0, **kwargs, ): """Corner cube with the x-axis forming equal angles to all three mirrors.""" super().__init__(origin, **kwargs) mirror_x = SquareMirror( self.origin + np.array([0.0, L / 2, L / 2]), width=L, height=L, reflectivity=reflectivity, **kwargs, ) mirror_y = SquareMirror( self.origin + np.array([L / 2, 0.0, L / 2]), width=L, height=L, reflectivity=reflectivity, **kwargs, ).RotZ(np.pi / 2) mirror_z = SquareMirror( self.origin + np.array([L / 2, L / 2, 0.0]), width=L, height=L, reflectivity=reflectivity, **kwargs, ).RotY(-np.pi / 2) self.add_components([mirror_x, mirror_y, mirror_z]) self._RotAroundLocal([0, 0, 1], [0, 0, 0], -np.pi / 4) self._RotAroundLocal([0, 1, 0], [0, 0, 0], np.arccos(np.sqrt(2 / 3)))
[docs] class DovePrism(ComponentGroup):
[docs] def __init__(self, origin, L, D, Ng, **kwargs): """ L: length of the base D: height of the prism Ng: refractive index of the prism material """ super().__init__(origin, **kwargs) self.L = L self.D = D self.Ng = Ng Lu = L - 2 * D # 2) Arbitrary 3-D triangle verts2d = np.array( [ [-L / 2, 0], [L / 2, 0], [Lu / 2, D], [-Lu / 2, D], ] ) poly2d = Polygon(verts2d) SL = BaseRefraciveSurface(origin=[-D / 2, 0, 0], n1=Ng, n2=1) SL.surface = poly2d SR = BaseRefraciveSurface(origin=[D / 2, 0, 0], n1=1, n2=Ng) SR.surface = poly2d SU = SquareRefractive(origin=[0, 0, D], width=Lu, height=D, n1=Ng, n2=1).RotY( np.pi / 2 ) SB = SquareRefractive(origin=[0, 0, 0], width=L, height=D, n1=1, n2=Ng).RotY( np.pi / 2 ) verts3dI = np.array( [ [-D / 2, -L / 2, 0], [D / 2, -L / 2, 0], [D / 2, -Lu / 2, D], [-D / 2, -Lu / 2, D], ] ) verts3dO = np.array( [ [-D / 2, L / 2, 0], [D / 2, L / 2, 0], [D / 2, Lu / 2, D], [-D / 2, Lu / 2, D], ] ) poly3dI = Polygon(verts3dI) poly3dO = Polygon(verts3dO) SI = BaseRefraciveSurface(origin=[0, 0, 0], n1=Ng, n2=1) SI.surface = poly3dI SO = BaseRefraciveSurface(origin=[0, 0, 0], n1=1, n2=Ng) SO.surface = poly3dO self.add_components([SL, SR, SU, SB, SI, SO])
@property def z0(self): """the height of rays that will go straight through the prism""" theta = np.arcsin((np.sqrt(2) / 2) / self.Ng) return (self.L / 2) / (1 + np.tan(np.pi / 4 + theta))
[docs] class PlanoConvexLens(ComponentGroup):
[docs] def __init__(self, origin, EFL, CT, diameter, R, **kwargs): super().__init__(origin, **kwargs) # calculate refractive index from EFL and R n = 1 + R / EFL # D = principal plane location from flat surface, the light enters from curved side # which means the light goes +x direction D = CT / n height_curve = R - np.sqrt(R**2 - (diameter / 2) ** 2) curved_face = SphereRefractive( origin=np.array([R - (CT - D), 0, 0]) + self.origin, radius=R, height=height_curve, n1=1.0, n2=n, **kwargs, ).RotZ(np.pi) self.add_component(curved_face) flat_face = CircleRefractive( origin=self.origin + np.array([+D, 0, 0]), radius=diameter / 2, n1=n, n2=1.0, **kwargs, ).RotZ(np.pi) self.add_component(flat_face)
[docs] class BiConvexLens(ComponentGroup):
[docs] def __init__(self, origin, CT, R1, R2, diameter, EFL=None, n=None, **kwargs): super().__init__(origin, **kwargs) # R1 is on the left, R2 is on the right if +x is the right direction (incoming beam +x) # R1 and R2 is defined seen from the incoming beam # 1/f = (n-1)(1/R1 - 1/R2+(n-1)CT/nR1R2) if n is None: assert EFL is not None, "Either n or EFL must be provided" # calculate the refractive index from focal length and radii, 1st order approx n = 1 + (1 / EFL) / (1 / R1 - 1 / R2) # refine the n to second order for i in range(3): n = 1 + (1 / EFL) / (1 / R1 - 1 / R2 + (n - 1) * CT / (n * R1 * R2)) # print( # f"BiConvexLens: calculated n={n} for EFL={EFL}, R1={R1}, R2={R2}, CT={CT}" # ) else: assert isinstance(n, (int, float)), "n must be a number" # create the two curved surfaces # R1 if R1 > 0: # convex o1 = np.array([R1, 0, 0]) + self.origin height_curve1 = R1 - np.sqrt(R1**2 - (diameter / 2) ** 2) curved_face1 = SphereRefractive( origin=o1, radius=R1, height=height_curve1, n1=1.0, n2=n, **kwargs ).RotZ(np.pi) else: # concave o1 = np.array([R1, 0, 0]) + self.origin height_curve1 = (-R1) - np.sqrt(R1**2 - (diameter / 2) ** 2) curved_face1 = SphereRefractive( origin=o1, radius=-R1, height=height_curve1, n1=n, n2=1.0, **kwargs ) # R2 if R2 > 0: # concave o2 = np.array([R2 + CT, 0, 0]) + self.origin height_curve2 = R2 - np.sqrt(R2**2 - (diameter / 2) ** 2) curved_face2 = SphereRefractive( origin=o2, radius=R2, height=height_curve2, n1=n, n2=1.0, **kwargs, ).RotZ(np.pi) else: # convex o2 = np.array([R2 + CT, 0, 0]) + self.origin height_curve2 = (-R2) - np.sqrt(R2**2 - (diameter / 2) ** 2) curved_face2 = SphereRefractive( origin=o2, radius=-R2, height=height_curve2, n1=1.0, n2=n, **kwargs, ) # self.add_component(curved_face1) self.add_component(curved_face2)
[docs] class Doublet(ComponentGroup):
[docs] def __init__(self, origin, CT1, CT2, R1, R2, R3, diameter, n12, n23, **kwargs): super().__init__(origin, **kwargs) # R1 - R2 - R3 with +x direction incoming beam # Rs defined seen from the incoming beam # 1/f = (n-1)(1/R1 - 1/R2+(n-1)CT/nR1R2) # create the three curved surfaces # R1 if R1 > 0: # convex o1 = np.array([R1, 0, 0]) + self.origin height_curve1 = R1 - np.sqrt(R1**2 - (diameter / 2) ** 2) curved_face1 = SphereRefractive( origin=o1, radius=R1, height=height_curve1, n1=1.0, n2=n12, **kwargs ).RotZ(np.pi) else: # concave o1 = np.array([R1, 0, 0]) + self.origin height_curve1 = (-R1) - np.sqrt(R1**2 - (diameter / 2) ** 2) curved_face1 = SphereRefractive( origin=o1, radius=-R1, height=height_curve1, n1=n12, n2=1.0, **kwargs ) # R2 if R2 > 0: # concave o2 = np.array([R2 + CT1, 0, 0]) + self.origin height_curve2 = R2 - np.sqrt(R2**2 - (diameter / 2) ** 2) curved_face2 = SphereRefractive( origin=o2, radius=R2, height=height_curve2, n1=n12, n2=n23, **kwargs, ).RotZ(np.pi) else: # convex o2 = np.array([R2 + CT1, 0, 0]) + self.origin height_curve2 = (-R2) - np.sqrt(R2**2 - (diameter / 2) ** 2) curved_face2 = SphereRefractive( origin=o2, radius=-R2, height=height_curve2, n1=n23, n2=n12, **kwargs, ) # R3 if R3 > 0: # concave o3 = np.array([R3 + CT1 + CT2, 0, 0]) + self.origin height_curve3 = R3 - np.sqrt(R3**2 - (diameter / 2) ** 2) curved_face3 = SphereRefractive( origin=o3, radius=R3, height=height_curve3, n1=n23, n2=1.0, **kwargs, ).RotZ(np.pi) else: # convex o3 = np.array([R3 + CT1 + CT2, 0, 0]) + self.origin height_curve3 = (-R3) - np.sqrt(R3**2 - (diameter / 2) ** 2) curved_face3 = SphereRefractive( origin=o3, radius=-R3, height=height_curve3, n1=1.0, n2=n23, **kwargs, ) # self.add_component(curved_face1) self.add_component(curved_face2) self.add_component(curved_face3)
[docs] class ASphericLens(ComponentGroup):
[docs] def __init__( self, origin, CT, f_asphere_1: Callable, f_asphere_2: Union[Callable, None], diameter, n, **kwargs, ): super().__init__(origin, **kwargs) self.f_asphere_1 = f_asphere_1 self.f_asphere_2 = f_asphere_2 # # create aspheric lens surface # surface_1 asphere_surface_1 = ASphere(diameter / 2, f_asphere_1) surface_1 = BaseRefraciveSurface( origin=self.origin, n1=1, n2=n, surface=asphere_surface_1, **kwargs ).RotZ(np.pi) # surface_2 if f_asphere_2 is not None: asphere_surface_2 = ASphere(diameter / 2, f_asphere_2) surface_2 = BaseRefraciveSurface( origin=self.origin + np.array([CT, 0, 0]), n1=n, n2=1.0, surface=asphere_surface_2, **kwargs, ).RotZ(np.pi) else: surface_2 = CircleRefractive( origin=self.origin + np.array([CT, 0, 0]), radius=diameter / 2, n1=n, n2=1.0, **kwargs, ).RotZ(np.pi) # self.add_component(surface_1) self.add_component(surface_2)
[docs] class ASphericExactSphericalLens(ASphericLens):
[docs] def __init__(self, origin, EFL, CT, diameter, n, **kwargs): def f_asphere_1(r): return (EFL / (n + 1)) * ( -1 + np.sqrt(1 + (n + 1) / (n - 1) * (r**2) / (EFL**2)) ) super().__init__( origin, CT, f_asphere_1, None, diameter, n, **kwargs, )
[docs] class ASphericParametricLens(ASphericLens):
[docs] def __init__( self, origin, CT, diameter, n, R, kappa, a4=0, a6=0, a8=0, **kwargs, ): def f_asphere_1(r): term1 = r**2 / (R * (1 + np.sqrt(1 - (1 + kappa) * (r**2) / (R**2)))) term2 = a4 * r**4 term3 = a6 * r**6 term4 = a8 * r**8 return term1 + term2 + term3 + term4 super().__init__( origin, CT, f_asphere_1, None, diameter, n, **kwargs, )