structured-optics 0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ from .struct_opt import *
2
+
3
+ __all__ = struct_opt.__all__ if "core" in globals() else []
@@ -0,0 +1,90 @@
1
+ from structured_optics.utils import overlap
2
+ import numpy as np
3
+
4
+
5
+
6
+ def hg_proj(Beam,N, completeness = False):
7
+ overlaps = np.zeros((N+1, N+1), dtype='complex')
8
+ auxiliary_beam = Beam.copy_clean()
9
+ for n in range(N+1):
10
+ for m in range(N+1):
11
+ auxiliary_beam.hg(n,m)
12
+ overlaps[n,m] = overlap(Beam, auxiliary_beam)
13
+ n, m = np.meshgrid(np.arange(0, N+1, 1), np.arange(0, N+1, 1))
14
+ comp = np.sum(np.abs(overlaps)**2)
15
+ if completeness == True:
16
+ return n, m, overlaps, comp
17
+ else:
18
+ return n, m, overlaps
19
+
20
+
21
+ def lg_proj(Beam, N, completeness = False):
22
+ overlaps = np.zeros((int(2*N)+1, int(N/2)+1), dtype='complex')
23
+ auxiliary_beam = Beam.copy_clean()
24
+ for l in np.arange(-N, N+1, 1):
25
+ for p in range(int(N/2)+1):
26
+ auxiliary_beam.lg(l,p)
27
+ overlaps[l+N,p] = overlap(Beam, auxiliary_beam)
28
+ p, l = np.meshgrid(np.arange(0, int(N/2)+1, 1), np.arange(-N, N+1, 1))
29
+ comp = np.sum(np.abs(overlaps)**2)
30
+ if completeness == True:
31
+ return l, p, overlaps, comp
32
+ else:
33
+ return l, p, overlaps
34
+
35
+ def hg_basis(Beam, N, waist = None, norm1 = False):
36
+ aux = Beam.copy_clean()
37
+ if waist != None:
38
+ aux.waist = waist
39
+ basis = np.empty((N+1, N+1, Beam.Dy, Beam.Dx), dtype = 'complex')
40
+ for n in range(N+1):
41
+ for m in range(N+1):
42
+ aux.hg(n, m)
43
+ basis[n, m] = aux.field
44
+ if norm1 == True:
45
+ basis = basis/np.max(basis)
46
+ return basis
47
+
48
+ def lg_basis(Beam, N, waist=None, norm1=False):
49
+ aux = Beam.copy_clean()
50
+ if waist != None:
51
+ aux.waist = waist
52
+ basis = np.empty((N+1, N+1, Beam.Dy, Beam.Dx), dtype = 'complex')
53
+ for n in range(N+1):
54
+ for m in range(N+1):
55
+ l = m-n
56
+ p = min(n,m)
57
+ aux.lg(l, p)
58
+ basis[n, m] = aux.field
59
+ if norm1 == True:
60
+ basis = basis/np.max(basis)
61
+ return basis
62
+
63
+ def bessel_basis(Beam, Nmax, waist=None, norm1=False):
64
+ aux = Beam.copy_clean()
65
+ if waist is not None:
66
+ aux.waist = waist
67
+ # basis[n] = Bessel beam of order n
68
+ basis = np.empty((2*Nmax+1, aux.Dy, aux.Dx), dtype='complex')
69
+ for n in range(2*Nmax+1):
70
+ aux.bessel(n-Nmax) # or aux.bessel(n, theta=...)
71
+ basis[n] = aux.field
72
+ if norm1:
73
+ basis = basis / np.max(np.abs(basis))
74
+ return basis
75
+
76
+ def build_from_coefs_and_basis(Beam, coefs, basis):
77
+ N = len(coefs)
78
+ aux = Beam.copy_clean()
79
+ for i in range(N):
80
+ for j in range(N):
81
+ aux.field = aux.field + coefs[i,j] * basis[i,j]
82
+ Beam.field = aux.field
83
+ return Beam
84
+
85
+ def astigmatic_mode_converter(Beam, N, theta):
86
+ n, m, overlaps = Beam.hg_projector(N)
87
+ converter = np.exp(1j*(m-n)*theta)
88
+ overlaps = overlaps*converter
89
+ Beam.build_from_coefs_and_basis(overlaps, Beam.hg_basis(N))
90
+ return Beam
@@ -0,0 +1,70 @@
1
+ from structured_optics.struct_opt import *
2
+ import numpy as np
3
+ from structured_optics.utils import *
4
+ import time
5
+
6
+
7
+ def slm_hologram(Beam, x_grating, y_grating, method, input_beam, eps, max_range):
8
+ #generate a hologram for a slm
9
+ if Beam.pol_dim != 1:
10
+ print('Beam must be scalar (pol_dim=1) to generate holograms')
11
+ return None
12
+
13
+ dx = (Beam.x[0,1] - Beam.x[0,0])
14
+ dy = (Beam.y[1,0] - Beam.y[0,0])
15
+ if input_beam is None:
16
+ input_field = np.exp(-((Beam.x**2 + Beam.y**2)/(2*np.max(Beam.x))**2))
17
+ else:
18
+ input_field = input_beam.field
19
+ desired = Beam.field/np.max(np.abs(Beam.field))
20
+ lamb_x = dx*x_grating
21
+ lamb_y = dy*y_grating
22
+ Ain = np.abs(input_field)
23
+ Ain = Ain / Ain.max()
24
+ Ades = np.abs(desired)
25
+ s = np.min(Ain / (Ades + eps))
26
+ Ades = s * Ades
27
+ A_rel = Ades / Ain
28
+ phi_g = np.mod(2*np.pi*(Beam.x/lamb_x + Beam.y/lamb_y), 2*np.pi)
29
+ phi_relg = np.angle(desired) - np.angle(input_field) + phi_g
30
+ if method == 'simple':
31
+ H = A_rel*phi_relg
32
+ elif method == 'g_holo':
33
+ edg = desired*np.exp(1j*phi_g)
34
+ N = np.min(np.abs(input_field)/(np.abs(desired) + 1e-9))
35
+ H = np.angle(N*edg + input_field)
36
+ elif method == 'davis':
37
+ H = (1-inv_sinc(A_rel)/np.pi)*phi_relg
38
+ elif method == 'bolduc':
39
+ M = 1+inv_sinc(A_rel)/np.pi
40
+ H = M*(phi_relg - np.pi*M)
41
+ elif method == 'bessel0':
42
+ H = phi_relg + inv_J0(A_rel)*np.sin(phi_relg)
43
+ elif method == 'bessel1':
44
+ H = inv_J1(A_rel)*np.sin(phi_relg)
45
+ else:
46
+ print('Error! method must be simple, g_holo, davis, bolduc, bessel0 or bessel1.')
47
+ H = max_range * (H - H.min()) / (H.max() - H.min())
48
+ return H.astype(np.uint8)
49
+
50
+ def dmd_hologram(Beam, cx, cy, sign):
51
+ #generates a hologram for a dmd
52
+ if Beam.pol_dim != 1:
53
+ print('Beam must be scalar (pol_dim=1) to generate holograms')
54
+ return None
55
+ U = np.abs(Beam.field)
56
+ U = U/np.amax(U)
57
+ phi = Beam.phase()
58
+ A = np.arcsin(U)
59
+
60
+ fx = cx*Beam.Dx/(2*Beam.nix)
61
+ fy = cy*Beam.Dy/(2*Beam.niy)
62
+ g = [fx, fy]
63
+ G = g[0]*Beam.x + g[1]*Beam.y
64
+
65
+ H = 0.5 + sign*0.5*np.sign(np.cos(phi + 2*np.pi*G) - np.cos(A))
66
+ return H
67
+
68
+
69
+
70
+
@@ -0,0 +1,234 @@
1
+ from structured_optics.utils import *
2
+ import numpy as np
3
+ from scipy import special
4
+
5
+
6
+ #modes implementations
7
+
8
+ def hg_mode(Beam, N, M, z, pol_index=None):
9
+ #get a HG mode at distance z of order N+M
10
+ zr = Beam.zr()
11
+ q0 = 1j*zr
12
+ q = -z + 1j*zr
13
+ k = 2*np.pi/Beam.lamb
14
+ w = Beam.waist*np.sqrt(1 + (z/zr)**2)
15
+ Cn = np.sqrt(np.sqrt(2/np.pi)*q0/(2**N * special.factorial(N) * q * Beam.waist))
16
+ un = Cn*(-np.conjugate(q)/q)**(N/2)*hermite(np.sqrt(2)*(Beam.x-Beam.x0)/w, N)*np.exp(-1j*k*(Beam.x-Beam.x0)**2/(2*q))
17
+ Cm = np.sqrt(np.sqrt(2/np.pi)*q0/(2**M * special.factorial(M) * q * Beam.waist))
18
+ um = Cm*(-np.conjugate(q)/q)**(M/2)*hermite(np.sqrt(2)*(Beam.y-Beam.y0)/w, M)*np.exp(-1j*k*(Beam.y-Beam.y0)**2/(2*q))
19
+ F = un*um*np.exp(1j*k*z)
20
+ if pol_index == None:
21
+ if Beam.pol_dim == 1:
22
+ Beam.field = F
23
+ elif Beam.pol_dim == 2:
24
+ Beam.field = np.array([F, F])/np.sqrt(2)
25
+ elif Beam.pol_dim == 3:
26
+ Beam.field = np.array([F, F, F])/np.sqrt(3)
27
+ else:
28
+ Beam.field[pol_index] = F
29
+ return Beam
30
+
31
+
32
+ def lg_mode(Beam,l,p, z, pol_index=None):
33
+ #get a LG mode at distance z of order abs(N) + 2M
34
+ zr = Beam.zr()
35
+ k = 2*np.pi/Beam.lamb
36
+ w = Beam.waist*np.sqrt(1 + (z/zr)**2)
37
+ R = (z**2 + zr**2)
38
+ gouy = (np.abs(l) + 2*p + 1)*np.arctan(z/zr)
39
+ r = np.sqrt((Beam.x-Beam.x0)**2 + (Beam.y-Beam.y0)**2)
40
+ Cte = np.sqrt(2*special.factorial(p) / (np.pi*special.factorial(p+np.abs(l))))
41
+ F = Cte*(1/w)*(np.sqrt(2)*r/w)**(np.abs(l)) * np.exp(-(r/w)**2) * laguerre(2*(r/w)**2, np.abs(l), p) *\
42
+ np.exp(1j*(k*z + k*z*(r**2)/(2*R) + l*np.arctan2(Beam.y-Beam.y0,Beam.x-Beam.x0) - gouy))
43
+ if pol_index == None:
44
+ if Beam.pol_dim == 1:
45
+ Beam.field = F
46
+ elif Beam.pol_dim == 2:
47
+ Beam.field = np.array([F, F])/np.sqrt(2)
48
+ elif Beam.pol_dim == 3:
49
+ Beam.field = np.array([F, F, F])/np.sqrt(3)
50
+ else:
51
+ Beam.field[pol_index] = F
52
+ return Beam
53
+
54
+ def bessel_mode(Beam, N, z, pol_index=None):
55
+ #get a Bessel mode of order N at distance z
56
+ k = 2*np.pi / Beam.lamb # wavenumber
57
+ j01 = special.jn_zeros(abs(N), 1)[0] # first zero of J_N
58
+ # enforce the first zero at r = self.waist
59
+ kr = j01 / Beam.waist
60
+ kz = np.sqrt(k**2 - kr**2)
61
+ r = np.sqrt((Beam.x-Beam.x0)**2 + (Beam.y-Beam.y0)**2)
62
+ phi = np.arctan2(Beam.y-Beam.y0, Beam.x-Beam.x0)
63
+ F = (np.exp(1j*kz*z) * special.jv(abs(N), kr*r) * np.exp(-1j*N*phi))
64
+ if pol_index == None:
65
+ if Beam.pol_dim == 1:
66
+ Beam.field = F
67
+ elif Beam.pol_dim == 2:
68
+ Beam.field = np.array([F, F])
69
+ elif Beam.pol_dim == 3:
70
+ Beam.field = np.array([F, F, F])
71
+ else:
72
+ Beam.field[pol_index] = F
73
+ Beam.norm_beam()
74
+ return Beam
75
+
76
+ def gbessel_mode(Beam, N, r0, pol_index):
77
+ #get a gaussian bessel beam of order N at z=0
78
+ rad = 2*np.pi*Beam.waist**2/r0
79
+ r = np.sqrt((Beam.x-Beam.x0)**2 + (Beam.y-Beam.y0)**2)
80
+ F = special.jv(np.abs(N),rad*r/Beam.waist**2)*\
81
+ np.exp(-1j*N*np.arctan2(Beam.y-Beam.y0, Beam.x-Beam.x0))*np.exp(-(r/Beam.waist)**2)
82
+ if pol_index == None:
83
+ if Beam.pol_dim == 1:
84
+ Beam.field = F
85
+ elif Beam.pol_dim == 2:
86
+ Beam.field = np.array([F, F])
87
+ elif Beam.pol_dim == 3:
88
+ Beam.field = np.array([F, F, F])
89
+ else:
90
+ Beam.field[pol_index] = F
91
+ Beam.norm_beam()
92
+ return Beam
93
+
94
+ def lg_prod_mode(Beam, N, ls, centers, pol_index):
95
+ if centers is None:
96
+ centers = np.zeros((N, 2))
97
+ centers = np.asarray(centers)
98
+ if ls is None:
99
+ ls = np.ones(N, dtype=int)
100
+ ls = np.asarray(ls)
101
+
102
+ F = np.ones((Beam.Dy, Beam.Dx), dtype='complex128')
103
+ for i in range(N):
104
+ x0 = centers[i, 0]
105
+ y0 = centers[i, 1]
106
+ l = ls[i]
107
+ sign = -1 if l < 0 else 1
108
+ exp_pow = int(np.abs(l))
109
+ arg = (Beam.x - x0) + 1j * sign * (Beam.y - y0)
110
+ r2 = (Beam.x - x0)**2 + (Beam.y - y0)**2
111
+ lg = arg**exp_pow * np.exp(-r2/N / ((Beam.waist)**2))
112
+ F *= lg
113
+ if pol_index == None:
114
+ if Beam.pol_dim == 1:
115
+ Beam.field = F
116
+ elif Beam.pol_dim == 2:
117
+ Beam.field = np.array([F, F])
118
+ elif Beam.pol_dim == 3:
119
+ Beam.field = np.array([F, F, F])
120
+ else:
121
+ Beam.field[pol_index] = F
122
+ Beam.norm_beam()
123
+ return Beam
124
+
125
+ def frac_oam_mode(Beam, Ma, n_modes, beta, theta_0, z, pol_index):
126
+ #get a fractional OAM beam, with OAM Ma (!= integer), by the method of LG supperpositions.
127
+ mu = Ma%1
128
+ m = Ma-mu
129
+ if mu >=0.5:
130
+ n_min = np.ceil(Ma - n_modes / 2)
131
+ else:
132
+ n_min = np.floor(Ma - n_modes / 2)
133
+ n_max = n_min + n_modes - 1
134
+ F = np.zeros((Beam.Dy, Beam.Dx), dtype='complex128')
135
+ for l in np.arange(int(n_min), int(n_max)+1):
136
+ coef = np.exp(-1j*mu*beta)*1j*np.exp(1j*(Ma-l)*theta_0)/(2*np.pi*(Ma-l))*np.exp(1j*(m-l)*beta)*(1-np.exp(1j*mu*2*np.pi))
137
+ if Beam.pol_dim == 1:
138
+ F += coef*(Beam.lg(l, 0, z = z).field)
139
+ else:
140
+ F += coef*(Beam.lg(l, 0, z = z, pol_index=0).field[0])
141
+ if pol_index == None:
142
+ if Beam.pol_dim == 1:
143
+ Beam.field = F
144
+ elif Beam.pol_dim == 2:
145
+ Beam.field = np.array([F, F])
146
+ elif Beam.pol_dim == 3:
147
+ Beam.field = np.array([F, F, F])
148
+ else:
149
+ Beam.field[pol_index] = F
150
+ Beam.norm_beam()
151
+ return Beam
152
+
153
+ def IG_even_mode(Beam, p, m, q, z, pol_index):
154
+ #Even Ince-Gaussian mode IG_p,m^e(x,y)
155
+
156
+ xi, eta = cartesian_to_elliptic(Beam.x-Beam.x0, Beam.y-Beam.y0, q, Beam.waist, z, Beam.lamb)
157
+
158
+ Ce_xi = C_ince(1j*xi, p, m, q)
159
+ Ce_eta = C_ince(eta, p, m, q)
160
+
161
+ zr = np.pi * Beam.waist**2 / Beam.lamb
162
+ k = 2*np.pi/Beam.lamb
163
+ wz = Beam.waist * np.sqrt(1 + (z * Beam.lamb / (np.pi * Beam.waist**2))**2)
164
+ Rz = (z**2 + zr**2)
165
+ r2 = (Beam.x-Beam.x0)**2 + (Beam.y-Beam.y0)**2
166
+ gouy = (p+1)*np.arctan(z / zr)
167
+ F = Ce_xi * Ce_eta * np.exp(-r2 / (wz**2))*np.exp(1j*(k*z + k*z*(r2)/(2*Rz) - gouy))
168
+ if pol_index == None:
169
+ if Beam.pol_dim == 1:
170
+ Beam.field = F
171
+ elif Beam.pol_dim == 2:
172
+ Beam.field = np.array([F, F])
173
+ elif Beam.pol_dim == 3:
174
+ Beam.field = np.array([F, F, F])
175
+ else:
176
+ Beam.field[pol_index] = F
177
+ Beam.norm_beam()
178
+ return Beam
179
+
180
+ def IG_odd_mode(Beam, p, m, q, z, pol_index):
181
+ #Odd Ince-Gaussian mode IG_p,m^o(x,y)
182
+
183
+ xi, eta = cartesian_to_elliptic(Beam.x-Beam.x0, Beam.y-Beam.y0, q, Beam.waist, z, Beam.lamb)
184
+ So_xi = S_ince(1j*xi, p, m, q)
185
+ So_eta = S_ince(eta, p, m, q)
186
+
187
+ zr = np.pi * Beam.waist**2 / Beam.lamb
188
+ k = 2*np.pi/Beam.lamb
189
+ wz = Beam.waist * np.sqrt(1 + (z * Beam.lamb / (np.pi * Beam.waist**2))**2)
190
+ Rz = (z**2 + zr**2)
191
+ r2 = (Beam.x-Beam.x0)**2 + (Beam.y-Beam.y0)**2
192
+ gouy = (p+1)*np.arctan(z / zr)
193
+ F = So_xi * So_eta * np.exp(-r2 / (wz**2))*np.exp(1j*(k*z + k*z*(r2)/(2*Rz) - gouy))
194
+ if pol_index == None:
195
+ if Beam.pol_dim == 1:
196
+ Beam.field = F
197
+ elif Beam.pol_dim == 2:
198
+ Beam.field = np.array([F, F])
199
+ elif Beam.pol_dim == 3:
200
+ Beam.field = np.array([F, F, F])
201
+ else:
202
+ Beam.field[pol_index] = F
203
+ Beam.norm_beam()
204
+ return Beam
205
+
206
+ def HInceG_mode(Beam, p, m, q, z, helicity, pol_index):
207
+ #Hermite-Ince-Gaussian mode HIG_p,m^e(x,y), combining even and odd Ince-Gaussian modes with given helicity.
208
+ #For some reason IG_odd has a 3*pi/2 phase shift wrt IG_even, so the helicity sign is inverted here.
209
+ xi, eta = cartesian_to_elliptic(Beam.x-Beam.x0, Beam.y-Beam.y0, q, Beam.waist, z, Beam.lamb)
210
+
211
+ Ce_xi = C_ince(1j*xi, p, m, q)
212
+ Ce_eta = C_ince(eta, p, m, q)
213
+
214
+ So_xi = S_ince(1j*xi, p, m, q)
215
+ So_eta = S_ince(eta, p, m, q)
216
+
217
+ zr = np.pi * Beam.waist**2 / Beam.lamb
218
+ k = 2*np.pi/Beam.lamb
219
+ wz = Beam.waist * np.sqrt(1 + (z * Beam.lamb / (np.pi * Beam.waist**2))**2)
220
+ Rz = (z**2 + zr**2)
221
+ r2 = (Beam.x-Beam.x0)**2 + (Beam.y-Beam.y0)**2
222
+ gouy = (p+1)*np.arctan(z / zr)
223
+ F = (Ce_xi * Ce_eta - helicity*So_xi * So_eta) * np.exp(-r2 / (wz**2))*np.exp(1j*(k*z + k*z*(r2)/(2*Rz) - gouy))
224
+ if pol_index == None:
225
+ if Beam.pol_dim == 1:
226
+ Beam.field = F
227
+ elif Beam.pol_dim == 2:
228
+ Beam.field = np.array([F, F])
229
+ elif Beam.pol_dim == 3:
230
+ Beam.field = np.array([F, F, F])
231
+ else:
232
+ Beam.field[pol_index] = F
233
+ Beam.norm_beam()
234
+ return Beam
@@ -0,0 +1,12 @@
1
+ import numpy as np
2
+ from scipy import fft
3
+
4
+
5
+ def propagate_conv(space, z):
6
+ #propagate the field by a distance z using fresnel integral. convolution method
7
+ k = 2*np.pi/space.lamb
8
+ prop = np.exp(-1j * z * (space.kx ** 2 + space.ky ** 2) / (2 * k) - 1j*k*z)
9
+ space.fourier_field = fft.fft2(space.field)
10
+ space.field = fft.ifft2(prop*space.fourier_field)
11
+ return space
12
+
@@ -0,0 +1,85 @@
1
+ import sys
2
+ import numpy as np
3
+ from PyQt6.QtWidgets import QApplication, QLabel
4
+ from PyQt6.QtGui import QImage, QPixmap
5
+ from PyQt6.QtCore import Qt
6
+
7
+ class My_SLM(QLabel):
8
+ def __init__(self, screen_index=0):
9
+ self.app = QApplication.instance() or QApplication(sys.argv)
10
+ super().__init__()
11
+
12
+ screens = self.app.screens()
13
+ if screen_index >= len(screens):
14
+ raise ValueError("Invalid screen index")
15
+
16
+ screen = screens[screen_index]
17
+
18
+ # Force native window creation (CRITICAL on Linux)
19
+ self.setAttribute(Qt.WidgetAttribute.WA_NativeWindow)
20
+
21
+ self.setWindowFlags(
22
+ Qt.WindowType.FramelessWindowHint
23
+ )
24
+
25
+ self.show() # must come before windowHandle()
26
+
27
+ self.windowHandle().setScreen(screen)
28
+ geo = screen.geometry()
29
+
30
+ # Store logical screen size
31
+ self.width = geo.width()
32
+ self.height = geo.height()
33
+ self.setGeometry(geo)
34
+
35
+ self.showFullScreen()
36
+
37
+ self._img_ref = None
38
+
39
+
40
+ def update_image(self, img: np.ndarray):
41
+ """
42
+ img:
43
+ - (H, W) uint8 → grayscale
44
+ - (H, W, 3) uint8 → RGB
45
+ """
46
+ if img.dtype != np.uint8:
47
+ raise TypeError("Image must be uint8")
48
+
49
+ self._img_ref = img # keep reference alive
50
+
51
+ if img.ndim == 2:
52
+ h, w = img.shape
53
+ stride = img.strides[0]
54
+
55
+ qimg = QImage(
56
+ img.data,
57
+ w,
58
+ h,
59
+ stride,
60
+ QImage.Format.Format_Grayscale8
61
+ )
62
+
63
+ elif img.ndim == 3 and img.shape[2] == 3:
64
+ h, w, _ = img.shape
65
+ stride = img.strides[0]
66
+
67
+ qimg = QImage(
68
+ img.data,
69
+ w,
70
+ h,
71
+ stride,
72
+ QImage.Format.Format_RGB888
73
+ )
74
+ else:
75
+ raise ValueError("Image must be (H,W) or (H,W,3)")
76
+
77
+ self.setPixmap(QPixmap.fromImage(qimg))
78
+ self.app.processEvents()
79
+
80
+ def close(self):
81
+ if not self.isVisible():
82
+ return
83
+ self.hide()
84
+ self.setParent(None) # detach from Qt ownership
85
+ self.deleteLater()
@@ -0,0 +1,357 @@
1
+
2
+
3
+ import numpy as np
4
+ from scipy import fft, ndimage
5
+ from structured_optics.prop_methods import *
6
+ import copy
7
+ from structured_optics.utils import *
8
+ from structured_optics.modes import *
9
+ from structured_optics.algebra_utils import *
10
+ from structured_optics.hologram import *
11
+
12
+
13
+
14
+ class Beam():
15
+ def __init__(self, nix: float, #Region of interest in x (from -nix to +nix)
16
+ Dx: int, #Number of points in x
17
+ niy: float=None, #Region of interest in y (from -niy to +niy), equals nix if None
18
+ Dy: int=None, #Number of points in y, equals Dx if None
19
+ sparse:bool =True, #Whether to use sparse meshgrid for x and y
20
+ pol_dim:int=1, #Number of polarization components, Ex, Ey, Ez
21
+ waist:float=1e-3, #Beam waist. Standard value 1mm
22
+ lamb:float = 1064e-9, #Wavelength. Standard value 1064nm
23
+ x0:float = 0, #Beam center x position
24
+ y0:float = 0) -> object: #Beam center y position
25
+ # Initiate an object with the necessary parameters for calculating transverse fields
26
+
27
+ self.sparse = sparse
28
+ self.nix = nix
29
+ if niy == None:
30
+ self.niy = nix
31
+ else:
32
+ self.niy = niy
33
+ self.Dx = Dx
34
+ if Dy == None:
35
+ self.Dy = Dx
36
+ else:
37
+ self.Dy = Dy
38
+ self.pol_dim = pol_dim
39
+ self.x, self.y = np.meshgrid(np.linspace(-self.nix, self.nix, self.Dx), np.linspace(-self.niy, self.niy, self.Dy), sparse=sparse)
40
+ self.field = np.zeros((pol_dim, self.Dy, self.Dx), dtype='complex128')
41
+ self.kx, self.ky = np.meshgrid(2*np.pi*fft.fftfreq(self.Dx, 2*self.nix/self.Dx), 2*np.pi*fft.fftfreq(self.Dy, 2*self.niy/self.Dy), sparse=sparse)
42
+ self.lamb = lamb
43
+ self.waist = waist
44
+ self.x0 = x0
45
+ self.y0 = y0
46
+
47
+
48
+
49
+
50
+ #Auxialiary
51
+
52
+ def copy(self):
53
+ #Perform a deep copy of the Beam object
54
+ return copy.deepcopy(self)
55
+
56
+ def copy_clean(self):
57
+ #Perform a deep copy of the Beam object, but with field initialized to zero
58
+ new = self.copy()
59
+ new.field = np.zeros((self.pol_dim, self.Dy, self.Dx), dtype='complex128')
60
+ return new
61
+
62
+
63
+
64
+
65
+ #Operator overloading
66
+
67
+ def __mul__(self, other):
68
+ #Multiply the transverse fields point by point if multiplied by another field, or globally if multiplied by number.
69
+ if isinstance(other, (type(self))):
70
+ new = self.copy()
71
+ new.field = self.field*other.field
72
+ return new
73
+ if isinstance(other, (int, float, complex)):
74
+ new = self.copy()
75
+ new.field = other*self.field
76
+ return new
77
+
78
+ else:
79
+ raise TypeError(f"sorry, don't know how to multiply by {type(other).__name__}")
80
+
81
+ __rmul__ = __mul__
82
+
83
+ def __add__(self, other:object):
84
+ #Add the transverse fields point by point
85
+ if isinstance(other, (Beam, type(self))):
86
+ new = self.copy()
87
+ new.field = other.field + self.field
88
+ return new
89
+ else:
90
+ raise TypeError(f"sorry, don't know how to add by {type(other).__name__}")
91
+
92
+ __radd__ = __add__
93
+
94
+ def __sub__(self, other:object):
95
+ #Subtract the transverse fields point by point
96
+ if isinstance(other, Beam):
97
+ new = self.copy()
98
+ new.field = self.field - other.field
99
+ return new
100
+ else:
101
+ raise TypeError(f"sorry, don't know how to subtract by {type(other).__name__}")
102
+
103
+ __rsub__ = __sub__
104
+
105
+
106
+
107
+
108
+ #modes
109
+ #pol_index: polarization index to store the mode, if None uses all polarizations
110
+ #returns the Beam object with the mode stored in field
111
+ def hg(self, n:int, m:int, z:float=0, pol_index:int=None) -> object:
112
+ #get a HG mode at distance z of order n+m
113
+ return hg_mode(self, n, m, z, pol_index)
114
+
115
+ def lg(self,l:int,p:int, z:float=0, pol_index:int=None) -> object:
116
+ #get a LG mode at distance z of order abs(N) + 2M
117
+ return lg_mode(self, l, p, z, pol_index)
118
+
119
+ def bessel(self, N:int, z:float=0, pol_index:int=None) -> object:
120
+ #get a Bessel mode of order N at distance z
121
+ return bessel_mode(self, N, z, pol_index)
122
+
123
+ def gbessel(self, N:int, r0:int, pol_index:int=None) -> object:
124
+ #get a gaussian bessel beam of order N at z=0, r0 is the radius of the first intensity null
125
+ return gbessel_mode(self, N, r0, pol_index)
126
+
127
+ def lg_prod(self, N:int, ls:tuple=None, centers:tuple=None, pol_index:int=None) -> object:
128
+ #get a product superposition of N LG modes, with list of OAMs ls and list of centers
129
+ return lg_prod_mode(self, N, ls, centers, pol_index)
130
+
131
+ def frac_oam(self, Ma:float, n_modes:int, beta:float = 0, theta_0:float=0, z:float = 0, pol_index=None) -> object:
132
+ #get a fractional OAM beam, with OAM Ma (!= integer).
133
+ return frac_oam_mode(self, Ma, n_modes, beta, theta_0, z, pol_index)
134
+
135
+ def IG_even(self, p:int, m:int, q:float, z:float=0, pol_index:int=None) -> object:
136
+ #get an even Ince-Gaussian beam IG_p,m^e at distance z with ellipticity q
137
+ return IG_even_mode(self, p, m, q, z, pol_index)
138
+
139
+ def IG_odd(self, p:int, m:int, q:float, z:float=0, pol_index:int=None) -> object:
140
+ #get an odd Ince-Gaussian beam IG_p,m^o at distance z with ellipticity q
141
+ return IG_odd_mode(self, p, m, q, z, pol_index)
142
+
143
+ def HelIG(self, p:int, m:int, q:float, z:float=0, helicity:int=1, pol_index:int=None) -> object:
144
+ #get a Hermite-Ince-Gaussian beam HIG_p,m at distance z with ellipticity q and given helicity (+1 or -1)
145
+ return HInceG_mode(self, p, m, q, z, helicity, pol_index)
146
+
147
+ #Holograms put dmd and slm hologram here
148
+
149
+
150
+
151
+ #Linear Algebra with modes utils
152
+
153
+ def hg_projector(self,N:int, completeness:bool = False) -> tuple:
154
+ #Project the beam into HG basis up to order N
155
+ #returns tuple with n, m index and overlaps array, if completeness=True also returns completeness value
156
+ return hg_proj(self, N, completeness)
157
+
158
+ def lg_projector(self, N:int, completeness:bool = False) -> tuple:
159
+ #Project the beam into LG basis up to order N
160
+ #returns tuple with l, p index and overlaps array, if completeness=True also returns completeness value
161
+ return lg_proj(self, N, completeness)
162
+
163
+ def hg_basis(self, N:int, waist:float=None, norm1:bool = False) -> np.ndarray:
164
+ #Create a HG basis up to order N
165
+ return hg_basis(self, N, waist, norm1)
166
+
167
+ def lg_basis(self, N:int, waist:float=None, norm1:bool=False) -> np.ndarray:
168
+ #Create a LG basis up to order N
169
+ return lg_basis(self, N, waist, norm1)
170
+
171
+ def bessel_basis(self, Nmax:int, waist:float=None, norm1:bool=False) -> np.ndarray:
172
+ #Create a Bessel basis up to Nmax
173
+ return bessel_basis(self, Nmax, waist, norm1)
174
+
175
+ def build_from_coefs_and_basis(self, coefs:np.ndarray, basis:np.ndarray) -> object:
176
+ #Build beam from given coefficients and basis
177
+ return build_from_coefs_and_basis(self, coefs, basis)
178
+
179
+ def mode_converter(self, N:int, theta:float=np.pi/4) -> object:
180
+ #Astigmatic mode converter that converts HG to LG modes and viceversa
181
+ return astigmatic_mode_converter(self, N, theta)
182
+
183
+
184
+
185
+
186
+ #Beam physical atributes and its utilities
187
+
188
+ def Power(self, pol_index:int=None) -> float:
189
+ #Get the total Power of a field within the region of interest
190
+ if pol_index == None:
191
+ return np.sum(self.int_profile())*(4*self.nix/self.Dx)*(self.niy/self.Dy)
192
+ else:
193
+ return np.sum(self.int_profile(pol_index))*(4*self.nix/self.Dx)*(self.niy/self.Dy)
194
+
195
+ def zr(self) -> float:
196
+ #Get the Rayleigh range of the beam
197
+ return np.pi*self.waist**2/self.lamb
198
+
199
+ def int_profile(self, pol_index:int=None) -> np.ndarray:
200
+ #Get intensity profile of the field within Beam
201
+ I = np.abs(self.field)**2
202
+ if pol_index == None:
203
+ return I
204
+ else:
205
+ return I[pol_index]
206
+
207
+ def phase(self, twopi:bool=False) -> np.ndarray:
208
+ #Get phase profile of the field. If twopi=True, phase is given in [0, 2pi], else in [-pi, pi]
209
+ if twopi == False:
210
+ return np.angle(self.field)
211
+ if twopi == True:
212
+ p = np.angle(self.field)
213
+ neg = p<0
214
+ p = p + neg*2*np.pi
215
+ return p
216
+
217
+ def center_mass(self, pol_index:int=0) -> tuple:
218
+ #Calculates the center of mass of intensities of a given field in given polarization
219
+ #pol_index: polarization index to use for the calculation, default 0
220
+ if self.pol_dim >1:
221
+ c = ndimage.center_of_mass(self.int_profile(pol_index))
222
+ else:
223
+ c = ndimage.center_of_mass(self.int_profile())
224
+ return c
225
+
226
+ def std(self, pol_index:int=0) -> float:
227
+ #Calculate the standard deviation of intensities
228
+ #pol_index: polarization index to use for the calculation, default 0
229
+ if self.pol_dim >1:
230
+ c = self.center_mass(pol_index)
231
+ return np.sqrt(np.average((self.x-self.x[0,int(c[1])])**2+(self.y-self.y[int(c[0]),0])**2, weights=self.int_profile(pol_index)))
232
+ else:
233
+ c = self.center_mass()
234
+ return np.sqrt(np.average((self.x-self.x[0,int(c[1])])**2+(self.y-self.y[int(c[0]),0])**2, weights=self.int_profile()))
235
+
236
+ def section(self, ang_min:float, ang_max:float) -> np.ndarray:
237
+ #Return field distribution of given section, defined by minimum angle and maximum angle
238
+ return get_section(self, ang_min, ang_max)
239
+
240
+ def int_section(self, ang_min:float, ang_max:float)-> np.ndarray:
241
+ #Get intensity profile of given section, defined by minimum angle and maximum angle
242
+ return np.abs(self.section(ang_min, ang_max))**2
243
+
244
+ def Power_section(self, ang_min:float, ang_max:float)-> float:
245
+ #Get power of given section
246
+ return np.sum(np.abs(self.section(ang_min, ang_max))**2*(4*self.nix/self.Dx)*(self.niy/self.Dy))
247
+
248
+ def norm_beam(self)-> object:
249
+ #normalize beam so that Total Power = 1 in region of interest
250
+ self.field = self.field/np.sqrt(self.Power())
251
+ return self
252
+
253
+ def Max_int1(self)-> object:
254
+ #rescale the field such that the maximum Intensity point is equal to one.
255
+ self.field = self.field/np.sqrt(np.amax(self.int_profile()))
256
+ return self
257
+
258
+ def crop(self, center:tuple=None, std:float=None, window:float=2, pol_index:int=0)-> object:
259
+ #Crops a field by its std*window arround the center of mass
260
+ #Here pol_index is the field taken as reference for calcutale std and center
261
+ return get_crop(self, center, std, window, pol_index)
262
+
263
+
264
+
265
+
266
+
267
+
268
+
269
+
270
+ #masks
271
+ def lens(self, f:float, f0:tuple=(0,0))-> object:
272
+ #apply a lens operator to the field, with lens center at f0 and focus lenght equal to f
273
+ k = 2*np.pi/self.lamb
274
+ self.field = self.field*np.exp(-1j*k*(((self.x-f0[0])**2 + (self.y-f0[1])**2)/(2*f)))
275
+ return self
276
+
277
+ def astigmatic_lens(self, fx:float, fy:float, f0:tuple=(0,0))-> object:
278
+ #apply a astigmatic lens with two focal axis, with focus fx and fy.
279
+ k = 2*np.pi/self.lamb
280
+ self.field = self.field*np.exp(-1j*k*(((self.x-f0[0])**2)/fx + ((self.y-f0[1])**2)/fy)/2)
281
+ return self
282
+
283
+ def tilted_lens(self, f:float, phi:float, f0:tuple=(0,0))-> object:
284
+ #apply a astigmatic lens with two focal axis, with each focus given by a tilt phi.
285
+ fx = f*np.cos(phi)**3
286
+ fy = f*np.cos(phi)
287
+ return self.astigmatic_lens(fx,fy, f0)
288
+
289
+ def tilted_lens_y(self, f:float, phi:float, f0:tuple=(0,0))-> object:
290
+ #apply a astigmatic lens with two focal axis, with each focus given by a tilt phi, this time the tilt is in y direction.
291
+ fx = f*np.cos(phi)
292
+ fy = f*np.cos(phi)**3
293
+ return self.astigmatic_lens(fx,fy, f0)
294
+
295
+
296
+ def stripe_v(self, size, move=0):
297
+ """Def vertical stripe"""
298
+ d = move*self.Dx/(2*self.nix)
299
+ s = size*self.Dx/(2*self.nix)
300
+ self.field[:,int(self.Dx/2 - s + d):int(self.Dx/2+s+d)] = 0
301
+ return self
302
+
303
+ def stripe_h(self, size):
304
+ """Def horizontal stripe"""
305
+ s = size*self.Dy/(2*self.niy)
306
+ self.field[int(self.Dy/2 - s):int(self.Dy/2+s),:] = 0
307
+ return self
308
+
309
+ def stripe_cross(self, size, hsize=None):
310
+ """Def cross stripe"""
311
+ if hsize == None:
312
+ hsize = size
313
+ s = size*self.Dx/(2*self.nix)
314
+ sh = hsize*self.Dy/(2*self.niy)
315
+ self.field[int(self.Dy/2 - sh):int(self.Dy/2+sh),:] = 0
316
+ self.field[:,int(self.Dx/2 - s):int(self.Dx/2+s)] = 0
317
+ return self
318
+
319
+ def slit(self, size):
320
+ """Def 1 slit"""
321
+ s = size*self.Dx/(2*self.nix)
322
+ self.field[:,:int(self.Dx/2-s)] = 0
323
+ self.field[:,int(self.Dx/2 + s):] = 0
324
+ return self
325
+
326
+ def double_slit(self, size, dis):
327
+ """Def double slit"""
328
+ s = size*self.Dx/(2*self.nix)
329
+ d = dis*self.Dx/(2*self.nix)
330
+ self.field[:,0:int((self.Dx-d-s)/2)] = 0
331
+ self.field[:,int((self.Dx-d+s)/2):int((self.Dx+d-s)/2)] = 0
332
+ self.field[:,int((self.Dx+d+s)/2):] = 0
333
+ return self
334
+
335
+
336
+
337
+
338
+
339
+ #Propagation
340
+ def propagate(self, z, method='conv', renorm=False): #propagate the field by a distance z using fresnel integral with exp(-ikz)
341
+ if method == 'conv':
342
+ self = propagate_conv(self, z)
343
+ if renorm == True:
344
+ self.norm_beam()
345
+ return self
346
+
347
+
348
+ #Holograms
349
+
350
+ def slm_holo(self, x_grating:int, y_grating:int, method:str = 'bessel1', input_beam:object= None,
351
+ eps:float = 1e-12, max_range:int = 255)-> np.ndarray:
352
+ #generates hologram for slm
353
+ return slm_hologram(self, x_grating, y_grating, method = method, input_beam = input_beam, eps = eps, max_range = max_range)
354
+
355
+ def dmd_holo(self, cx:float, cy:float, sign:int=1)-> np.ndarray:
356
+ #generates hologram for dmd
357
+ return dmd_hologram(self, cx, cy, sign)
@@ -0,0 +1,306 @@
1
+ import numpy as np
2
+ from scipy import special
3
+
4
+
5
+
6
+ #utils for modes
7
+
8
+ def hermite(X, N): #hermite polynomial
9
+ HER = special.hermite(N)
10
+ sn = HER(X)
11
+ return sn
12
+
13
+ def laguerre(X, L, P): #laguerre polynomial
14
+ LAG = special.genlaguerre(P, L)
15
+ sn = LAG(X)
16
+ return sn
17
+
18
+ def even_coeffs(p, q, kind):
19
+ #Compute coefficients A_r of even Ince polynomials C_p^m.
20
+ if p % 2 != 0:
21
+ raise ValueError("p must be even for even coefs")
22
+
23
+ N = p // 2
24
+
25
+ if kind =='C':
26
+ M = np.zeros((N+1, N+1), dtype=float)
27
+ # First equation
28
+ M[0,0] = 0
29
+ if N >= 1:
30
+ M[0, 1] = (p/2 + 1) * q
31
+
32
+ # Second equation
33
+ if N >= 1:
34
+ M[1, 0] = p * q
35
+ M[1, 1] = 4
36
+ if N >= 2:
37
+ M[1, 2] = (p/2 + 2) * q
38
+
39
+ # General recurrence
40
+ for r in range(2,N):
41
+
42
+ M[r, r-1] = -(r -1 - p/2) * q
43
+ M[r, r] = 4 * (r)**2
44
+ M[r, r+1] = (p/2 + r + 1) * q
45
+ # Last row
46
+ if N >=2:
47
+ M[N, N-1] = q
48
+ M[N, N] = 4 * (N)**2
49
+
50
+
51
+ elif kind =='S':
52
+ M = np.zeros((N+1, N+1), dtype=float)
53
+
54
+ # First equation (r=1)
55
+ if N >= 1:
56
+ M[1, 1] = 4
57
+ if N >= 2:
58
+ M[1, 2] = (p/2 + 2) * q
59
+
60
+ # General recurrence
61
+ for r in range(2, N):
62
+ M[r, r-1] = -(r - 1 - p/2) * q
63
+ M[r, r] = 4 * (r)**2
64
+ M[r, r+1] = (p/2 + r + 1) * q
65
+
66
+ # Last row
67
+ if N >= 2:
68
+ M[N, N-1] = -(N - 1 - p/2) * q
69
+ M[N, N] = 4 * (N)**2
70
+
71
+ eigvals, eigvecs = np.linalg.eig(M)
72
+
73
+ # Move the eigenvector with zero eigenvalue to the front (only for 'S' kind) and sort the rest.
74
+ if kind == 'S':
75
+ #find zero position
76
+ zero_idx = np.argmin(np.abs(eigvals))
77
+
78
+ mask = np.arange(len(eigvals)) != zero_idx
79
+
80
+ non_zero_eigvals = eigvals[mask]
81
+ non_zero_eigvecs = eigvecs[:, mask]
82
+
83
+ #sort non-zero eigenvalues and eigenvectors
84
+ sorted_idx = np.argsort(non_zero_eigvals)
85
+ non_zero_eigvals_sorted = non_zero_eigvals[sorted_idx]
86
+ non_zero_eigvecs_sorted = non_zero_eigvecs[:, sorted_idx]
87
+
88
+ #combine zero eigenvalue and sorted non-zero ones
89
+ eigvals_sorted = np.concatenate([[eigvals[zero_idx]], non_zero_eigvals_sorted])
90
+ eigvecs_sorted = np.column_stack([eigvecs[:, zero_idx:zero_idx+1], non_zero_eigvecs_sorted])
91
+ else:
92
+ # For 'C' kind, just sort normally
93
+ idx = np.argsort(eigvals)
94
+ eigvals_sorted = eigvals[idx]
95
+ eigvecs_sorted = eigvecs[:, idx]
96
+
97
+ # Sign convention: ensure sum of coefficients is positive
98
+ for i in range(eigvecs_sorted.shape[1]):
99
+ if np.sum(eigvecs_sorted[:, i]) < 0:
100
+ eigvecs_sorted[:, i] *= -1
101
+
102
+ return eigvals_sorted, eigvecs_sorted
103
+
104
+
105
+ def odd_coeffs(p, q, kind):
106
+
107
+ if p % 2 != 1:
108
+ raise ValueError("p must be odd for odd coefs")
109
+
110
+ N = p // 2
111
+ M = np.zeros((N+1, N+1), dtype=float)
112
+
113
+ if kind == 'C':
114
+ # First equation
115
+ M[0, 0] = q/2 * (p + 1) + 1
116
+ if N >= 1:
117
+ M[0, 1] = q/2 * (p + 3)
118
+
119
+ # General recurrence (r >= 1)
120
+ for r in range(1, N):
121
+ M[r, r-1] = -q/2 * (2*r - p - 1)
122
+ M[r, r] = (2*r + 1)**2
123
+ M[r, r+1] = q/2 * (p + 2*r + 3)
124
+ # Last row
125
+ if N >=1:
126
+ M[N, N-1] = q
127
+ M[N, N] = (2*N + 1)**2
128
+
129
+ if kind == 'S':
130
+ # First equation
131
+ M[0, 0] = 1 - q/2 * (p + 1)
132
+ if N >= 1:
133
+ M[0, 1] = q/2 * (p + 3)
134
+
135
+ # General recurrence (r >= 1)
136
+ for r in range(1, N):
137
+ M[r, r-1] = -q/2 * (2*r - p - 1)
138
+ M[r, r] = (2*r + 1)**2
139
+ M[r, r+1] = q/2 * (p + 2*r + 3)
140
+ # Last row
141
+ if N >=1:
142
+ M[N, N-1] = q
143
+ M[N, N] = (2*N + 1)**2
144
+
145
+ eigvals, eigvecs = np.linalg.eig(M)
146
+
147
+ # Sort eigenvalues and eigenvectors
148
+ idx = np.argsort(eigvals)
149
+ eigvals_sorted = eigvals[idx]
150
+ eigvecs_sorted = eigvecs[:, idx]
151
+
152
+ # Sign convention: ensure sum of coefficients is positive
153
+ for i in range(eigvecs_sorted.shape[1]):
154
+ if np.sum(eigvecs_sorted[:, i]) < 0:
155
+ eigvecs_sorted[:, i] *= -1
156
+
157
+ return eigvals_sorted, eigvecs_sorted
158
+
159
+
160
+ def C_ince(xi, p, m, q):
161
+ # Compute even Ince polynomial C_p^m(xi, q)
162
+ if p<0 or m < 0 or m > p:
163
+ raise ValueError("invalid p,m")
164
+ if p % 2 == 0 and m % 2 == 0:
165
+ eigvals, eigvecs = even_coeffs(p, q, kind='C')
166
+ idx = m // 2
167
+ A = eigvecs[:, idx]
168
+ s = sum(A[r] * np.cos(2*r*xi) for r in range(len(A)))
169
+ return s
170
+ if p % 2 == 1 and m % 2 == 1:
171
+ eigvals, eigvecs = odd_coeffs(p, q, kind='C')
172
+ idx = m // 2
173
+ B = eigvecs[:, idx]
174
+ s = sum(B[r] * np.cos((2*r+1)*xi) for r in range(len(B)))
175
+ return s
176
+ else:
177
+ raise ValueError("C_ince is only defined for even (p-m)")
178
+
179
+ def S_ince(xi, p, m, q):
180
+ # Compute odd Ince polynomial S_p^m(xi, q)
181
+ if p<0 or m < 0 or m > p:
182
+ raise ValueError("invalid p,m")
183
+ if p % 2 == 0 and m % 2 == 0:
184
+ eigvals, eigvecs = even_coeffs(p, q, kind='S')
185
+ idx = m // 2
186
+ A = eigvecs[:, idx]
187
+ s = sum(A[r] * np.sin(2*r*xi) for r in range(1,len(A)))
188
+ return s
189
+ if p % 2 == 1 and m % 2 == 1:
190
+ eigvals, eigvecs = odd_coeffs(p, q, kind='S')
191
+ idx = (m-1) // 2
192
+ B = eigvecs[:, idx]
193
+ s = sum(B[r] * np.sin((2*r+1)*xi) for r in range(len(B)))
194
+ return s
195
+ else:
196
+ raise ValueError("S_ince is only defined for even (p-m)")
197
+
198
+
199
+ def cartesian_to_elliptic(x, y, q, w0, z, lamb):
200
+ #Convert Cartesian (x,y) to elliptic coordinates (xi, eta) with elipticity q.
201
+
202
+ f0 = w0*np.sqrt(q/2)
203
+ w = w0 * np.sqrt(1 + (z * lamb / (np.pi * w0**2))**2)
204
+ f = f0*w/w0
205
+ r_plus = np.sqrt((x + f)**2 + y**2)
206
+ r_minus = np.sqrt((x - f)**2 + y**2)
207
+
208
+ xi = np.arccosh((r_plus + r_minus) / (2*f))
209
+ eta = np.sign(y)*np.arccos((r_plus - r_minus) / (2*f))
210
+
211
+ return xi, eta
212
+
213
+ def elliptic_to_cartesian(xi, eta, q, w0, z, lamb):
214
+ #Convert elliptic coordinates (xi, eta) to Cartesian (x,y) with elipticity q.
215
+ f0 = w0*np.sqrt(q/2)
216
+ w = w0 * np.sqrt(1 + (z * lamb / (np.pi * w0**2))**2)
217
+ f = f0*w/w0
218
+ x = f * np.cosh(xi) * np.cos(eta)
219
+ y = f * np.sinh(xi) * np.sin(eta)
220
+ return x, y
221
+
222
+
223
+
224
+ #utils for Beam class parameters calculation
225
+
226
+ def overlap(first_beam:object, second_beam:object)->complex:
227
+ # calculate overlap between two beams, only properly works if ni and D of beams are equal.
228
+ return np.sum(first_beam.field*np.conjugate(second_beam.field))*4*(first_beam.nix/first_beam.Dx) \
229
+ *(first_beam.niy/first_beam.Dy)/np.sqrt(first_beam.Power()*second_beam.Power())
230
+
231
+ def int_overlap(first_beam:object, second_beam:object)->float:
232
+ return np.sum(first_beam.int_profile()*second_beam.int_profile())*4*(first_beam.nix/first_beam.Dx) \
233
+ *(first_beam.niy/first_beam.Dy)/(first_beam.Power()*second_beam.Power())
234
+
235
+
236
+ def get_section(Beam, ang_min, ang_max):
237
+ #Return field distribution of given section, defined by minimum angle and maximum angle
238
+ if ang_min > ang_max:
239
+ ang_min, ang_max = ang_max, ang_min
240
+ if ang_max <= np.pi and ang_min <= np.pi:
241
+ sec = (np.arctan2(Beam.y,Beam.x)>=ang_min)*(np.arctan2(Beam.y,Beam.x)<ang_max)
242
+ if ang_max> np.pi and ang_min <= np.pi:
243
+ sec1 = (np.arctan2(Beam.y,Beam.x)>=ang_min)
244
+ ang_max = ang_max-2*np.pi
245
+ sec2 = (np.arctan2(Beam.y,Beam.x)<ang_max)
246
+ sec = sec1 + sec2
247
+ if ang_max>np.pi and ang_min > np.pi:
248
+ ang_max = ang_max - 2*np.pi
249
+ ang_min = ang_min - 2*np.pi
250
+ sec = (np.arctan2(Beam.y,Beam.x)>=ang_min)*(np.arctan2(Beam.y,Beam.x)<ang_max)
251
+ return Beam.field*sec
252
+
253
+
254
+ def get_crop(Beam, center=None, std=None, window=2, pol_index:int=0):
255
+ #Crops a field by its std*window arround the center of mass
256
+ if center == None:
257
+ center = Beam.center_mass(pol_index)
258
+ if std == None:
259
+ std = Beam.std(pol_index)
260
+ xmin = int(center[1] - window*std*Beam.Dx/Beam.nix/2)
261
+ xmax = int(center[1] + window*std*Beam.Dx/Beam.nix/2)
262
+ ymin = int(center[0] - window*std*Beam.Dy/Beam.niy/2)
263
+ ymax = int(center[0] + window*std*Beam.Dy/Beam.niy/2)
264
+ Beam.x = Beam.x[:,xmin:xmax]
265
+ Beam.y = Beam.y[ymin:ymax, :]
266
+ if Beam.pol_dim >1:
267
+ Beam.field = Beam.field[:,ymin:ymax, xmin:xmax]
268
+ else:
269
+ Beam.field = Beam.field[ymin:ymax, xmin:xmax]
270
+ Beam.Dx = len(Beam.x[0,:])
271
+ Beam.Dy = len(Beam.y[:,0])
272
+ Beam.nix = (Beam.x[0,-1] - Beam.x[0,0])/2
273
+ Beam.niy = (Beam.y[-1,0] - Beam.y[0,0])/2
274
+ Beam.x0 = center[1]
275
+ Beam.y0 = center[0]
276
+ return Beam
277
+
278
+
279
+ #utils for holograms
280
+
281
+ def inv_sinc(A, n=10000):
282
+ #invert function sinc
283
+ x = np.linspace(0, np.pi, n)
284
+ y = np.sinc(x/np.pi)
285
+
286
+ return np.interp(A, y[::-1], x[::-1])
287
+
288
+ def inv_J0(A, n=10000):
289
+ #invert bessel function J0
290
+ j01 = 2.404825557695773
291
+ x = np.linspace(0.0, j01, n)
292
+ y = special.j0(x)
293
+
294
+ return np.interp(A, y[::-1], x[::-1])
295
+
296
+
297
+ def inv_J1(A, a=None, n=10000):
298
+ #invert bessel function J1
299
+ x1_max = 1.8411837813406593
300
+ if a == None:
301
+ a = special.j1(x1_max)
302
+ A = np.clip(A, 0.0, 1.0)
303
+ x = np.linspace(0.0, x1_max, n)
304
+ y = special.j1(x)
305
+
306
+ return np.interp(a * A, y, x)
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: structured_optics
3
+ Version: 0.1
4
+ Summary: Package for Structured Light simulation
5
+ Author: Altilano C. Barbosa
6
+ Requires-Python: >=3.9
7
+ Requires-Dist: numpy
8
+ Requires-Dist: scipy
9
+ Requires-Dist: PyQt6
10
+ Dynamic: author
@@ -0,0 +1,12 @@
1
+ structured_optics/__init__.py,sha256=fOIda-DvXD8aSlijBC2bYj-MHFUlBBeNTdHf_ejhOz4,86
2
+ structured_optics/algebra_utils.py,sha256=kECxFBjza074AbBlM40CjUdoltNQT-_kGmAUQTAkgrs,2861
3
+ structured_optics/hologram.py,sha256=nm93Ma4u4X88KJQAOt7JYlM0X5SU1dTVzcPeMuWNa-c,2216
4
+ structured_optics/modes.py,sha256=nemaiAKPqHAwimObKgbUXItU3DSv2TTDYixnqf4U9xc,8668
5
+ structured_optics/prop_methods.py,sha256=NZcwjHdhkF3coZ-XcI1TbZGWI-2Hk9h4d53Ap-btwEY,421
6
+ structured_optics/slmdisplay.py,sha256=sMeixkJxSeqaXXyieWTpX3U7oqNWPQqDxb_qZGFUQog,2215
7
+ structured_optics/struct_opt.py,sha256=CW1MYL_fy8FvE9abPJCMkJSO6_2curAUfwgeXbSAWQg,14678
8
+ structured_optics/utils.py,sha256=Ml2bGIBalOSTQPv-7CYkQPY8D0gT3nk_079hnolj5WY,9414
9
+ structured_optics-0.1.dist-info/METADATA,sha256=So3HcKl_05c-0Asxbd2kmtgpLU1Rg_6dN8whwH9iKrQ,238
10
+ structured_optics-0.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
11
+ structured_optics-0.1.dist-info/top_level.txt,sha256=3YZZDRH--fgZIAQh3FIjP6BuDbC6XDj9OTEVwSiqDt4,18
12
+ structured_optics-0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ structured_optics