Coverage for src/susi/model/grating.py: 100%
32 statements
« prev ^ index » next coverage.py v7.5.0, created at 2025-06-13 14:15 +0000
« prev ^ index » next coverage.py v7.5.0, created at 2025-06-13 14:15 +0000
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3"""
4Routines to obtain incidence angle, angle of diffraction and dispersion on the camera
6@authors: iglesias, feller, hoelken
7"""
8from typing import Union
9import numpy as np
10import math
13#: Grating constant [nm]
14GRATING_D = 1667
15#: Spread angle of diffraction (AOD) - angle of incidence (AOI) [deg]
16GRATING_SPREAD = 8.23
17#: Grating blaze wavelength
18GRATING_WLB = 1820
19#: Camera pixel size [um]
20PX_SIZE = 12
21#: Camera focal length M4+M5 [mm]
22FOC_LENGTH = 2615
23# micro meter to nano meter scale
24UM2NM = 1e-3
27def order(wl: float, wlb: int = GRATING_WLB) -> float:
28 """
29 Return incidence angle for a given wavelength
31 Parameters
32 ----------
33 wl: float
34 wavelength [nm]
35 wlb: optional
36 blaze wavelength [nm]
38 Returns
39 -------
40 diffraction order (n)
41 """
42 return np.round(wlb / wl)
45def incidence_angle(wl: float, d: int = GRATING_D, theta: float = GRATING_SPREAD, wlb: int = GRATING_WLB) -> float:
46 """
47 Return incidence angle for a given wavelength
49 Parameters
50 ----------
51 wl: float
52 wavelength [nm]
53 d: optional
54 grating constant [nm]
55 theta: optional
56 spread between angles of incidence and diffraction [deg]
57 wlb: optional
58 blaze wavelength [nm]
60 Returns
61 -------
62 float
63 angle of incidence [deg]
64 """
65 n = order(wl, wlb)
66 theta = math.radians(theta)
67 a = 2 * d**2 * (1 + np.cos(theta))
68 b = n * wl * d * (1 + np.cos(theta))
69 c = (n * wl)**2 - (d * np.sin(theta))**2
70 s = (b - np.sqrt(b**2 - a * c)) / a
71 return math.degrees(np.arcsin(s))
74def diffraction_angle(wl: Union[float, np.array], aoi: float) -> Union[float, np.array]:
75 """
76 Return angle of diffraction
78 Parameters
79 ----------
80 wl: float or numpy.array
81 wavelength [nm]
82 aoi: float
83 angle of incidence [deg]
85 Returns
86 -------
87 float or numpy.array
88 angle of diffraction [deg]
89 """
90 return np.degrees(np.arcsin((order(wl) * wl / GRATING_D) - math.sin(math.radians(aoi))))
93def linear_dispersion(wl, aod: float = None, f: float = FOC_LENGTH, px: int = PX_SIZE) -> float:
94 """
95 Return linear dispersion on camera
97 Parameters
98 ----------
99 wl: float
100 wavelength [nm]
101 aod: float, optional
102 angle of diffraction [deg]
103 f: optional
104 spectrograph camera focal length [mm]
105 px: optional
106 camera pixel pitch [mu]
108 Returns
109 -------
110 float
111 dispersion [nm/px]
112 """
113 if aod is None:
114 aod = diffraction_angle(wl, incidence_angle(wl))
115 return GRATING_D * math.cos(math.radians(aod)) * px * UM2NM / (f * order(wl))
118def efficiency(wl: Union[float, np.array], wlb: int = GRATING_WLB, n: int = None) -> Union[float, np.array]:
119 """
120 Return grating efficiency
122 The efficiencies are rough estimates, based on simple sinc approximation
124 Parameters
125 ----------
126 wl: float or numpy.array
127 wavelength [nm]
128 wlb: float, optional
129 blaze wavelength [nm]
130 n: int, optional
131 grating order
132 default: round(wlb / wl)
134 Returns
135 -------
136 float
137 grating efficiency
138 """
140 if n is None:
141 n = order(wl, wlb)
142 return np.sinc(np.pi * (n - wlb / wl))
145def wavelength(aoi: Union[float, np.array], aod: Union[float, np.array],
146 n: Union[int, np.array]) -> Union[float, np.array]:
147 """
148 Return wavelength for given angle(s) of incidence, angle(s) of diffraction
149 and grating order
151 Parameters
152 ----------
153 aoi: float or numpy.array
154 angle of incidence [deg]
156 aod: float or numpy.array
157 angle of diffraction [deg]
159 n: int or numpy.array
160 grating order
162 Returns
163 -------
164 float or numpy.array
165 wavelength [nm]
166 """
167 return GRATING_D * (np.sin(np.radians(aoi)) + np.sin(np.radians(aod))) / n