structured-optics 0.2__tar.gz
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.
- structured_optics-0.2/PKG-INFO +9 -0
- structured_optics-0.2/pyproject.toml +18 -0
- structured_optics-0.2/setup.cfg +4 -0
- structured_optics-0.2/setup.py +8 -0
- structured_optics-0.2/structured_optics/__init__.py +3 -0
- structured_optics-0.2/structured_optics/algebra_utils.py +90 -0
- structured_optics-0.2/structured_optics/hologram.py +70 -0
- structured_optics-0.2/structured_optics/modes.py +393 -0
- structured_optics-0.2/structured_optics/prop_methods.py +46 -0
- structured_optics-0.2/structured_optics/slmdisplay.py +85 -0
- structured_optics-0.2/structured_optics/struct_opt.py +392 -0
- structured_optics-0.2/structured_optics/utils.py +331 -0
- structured_optics-0.2/structured_optics.egg-info/PKG-INFO +9 -0
- structured_optics-0.2/structured_optics.egg-info/SOURCES.txt +15 -0
- structured_optics-0.2/structured_optics.egg-info/dependency_links.txt +1 -0
- structured_optics-0.2/structured_optics.egg-info/requires.txt +3 -0
- structured_optics-0.2/structured_optics.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "structured_optics"
|
|
7
|
+
version = "0.2"
|
|
8
|
+
description = "Package for Structured Light simulation"
|
|
9
|
+
requires-python = ">=3.9"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "Altilano C. Barbosa" }
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
dependencies = [
|
|
15
|
+
"numpy",
|
|
16
|
+
"scipy",
|
|
17
|
+
"PyQt6"
|
|
18
|
+
]
|
|
@@ -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,393 @@
|
|
|
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
|
+
def hg_astigmatic_mode(Beam, N, M, wx, wy, z=0, pol_index=None):
|
|
32
|
+
#get a astigmatic HG mode at distance z of order N+M
|
|
33
|
+
zrx = np.pi*wx**2/Beam.lamb
|
|
34
|
+
q0x = 1j*zrx
|
|
35
|
+
qx = -z + q0x
|
|
36
|
+
k = 2*np.pi/Beam.lamb
|
|
37
|
+
wxz = wx*np.sqrt(1 + (z/zrx)**2)
|
|
38
|
+
Cn = np.sqrt(np.sqrt(2/np.pi)*q0x/(2**N * special.factorial(N) * qx * wx))
|
|
39
|
+
un = Cn*(-np.conjugate(qx)/qx)**(N/2)*hermite(np.sqrt(2)*(Beam.x-Beam.x0)/wxz, N)*np.exp(-1j*k*(Beam.x-Beam.x0)**2/(2*qx))
|
|
40
|
+
|
|
41
|
+
zry = np.pi*wy**2/Beam.lamb
|
|
42
|
+
q0y = 1j*zry
|
|
43
|
+
qy = -z + q0y
|
|
44
|
+
wyz = wy*np.sqrt(1 + (z/zry)**2)
|
|
45
|
+
Cm = np.sqrt(np.sqrt(2/np.pi)*q0y/(2**M * special.factorial(M) * qy * wy))
|
|
46
|
+
um = Cm*(-np.conjugate(qy)/qy)**(M/2)*hermite(np.sqrt(2)*(Beam.y-Beam.y0)/wyz, M)*np.exp(-1j*k*(Beam.y-Beam.y0)**2/(2*qy))
|
|
47
|
+
F = un*um*np.exp(1j*k*z)
|
|
48
|
+
|
|
49
|
+
if pol_index == None:
|
|
50
|
+
if Beam.pol_dim == 1:
|
|
51
|
+
Beam.field = F
|
|
52
|
+
elif Beam.pol_dim == 2:
|
|
53
|
+
Beam.field = np.array([F, F])/np.sqrt(2)
|
|
54
|
+
elif Beam.pol_dim == 3:
|
|
55
|
+
Beam.field = np.array([F, F, F])/np.sqrt(3)
|
|
56
|
+
else:
|
|
57
|
+
Beam.field[pol_index] = F
|
|
58
|
+
return Beam
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def lg_mode(Beam,l,p, z, pol_index=None):
|
|
62
|
+
#get a LG mode at distance z of order abs(N) + 2M
|
|
63
|
+
zr = Beam.zr()
|
|
64
|
+
k = 2*np.pi/Beam.lamb
|
|
65
|
+
w = Beam.waist*np.sqrt(1 + (z/zr)**2)
|
|
66
|
+
R = (z**2 + zr**2)
|
|
67
|
+
gouy = (np.abs(l) + 2*p + 1)*np.arctan(z/zr)
|
|
68
|
+
r = np.sqrt((Beam.x-Beam.x0)**2 + (Beam.y-Beam.y0)**2)
|
|
69
|
+
Cte = np.sqrt(2*special.factorial(p) / (np.pi*special.factorial(p+np.abs(l))))
|
|
70
|
+
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) *\
|
|
71
|
+
np.exp(1j*(k*z + k*z*(r**2)/(2*R) + l*np.arctan2(Beam.y-Beam.y0,Beam.x-Beam.x0) - gouy))
|
|
72
|
+
if pol_index == None:
|
|
73
|
+
if Beam.pol_dim == 1:
|
|
74
|
+
Beam.field = F
|
|
75
|
+
elif Beam.pol_dim == 2:
|
|
76
|
+
Beam.field = np.array([F, F])/np.sqrt(2)
|
|
77
|
+
elif Beam.pol_dim == 3:
|
|
78
|
+
Beam.field = np.array([F, F, F])/np.sqrt(3)
|
|
79
|
+
else:
|
|
80
|
+
Beam.field[pol_index] = F
|
|
81
|
+
return Beam
|
|
82
|
+
|
|
83
|
+
def bessel_mode(Beam, N, z, pol_index=None):
|
|
84
|
+
#get a Bessel mode of order N at distance z
|
|
85
|
+
k = 2*np.pi / Beam.lamb # wavenumber
|
|
86
|
+
j01 = special.jn_zeros(abs(N), 1)[0] # first zero of J_N
|
|
87
|
+
# enforce the first zero at r = Beam.waist
|
|
88
|
+
kr = j01 / Beam.waist
|
|
89
|
+
kz = np.sqrt(k**2 - kr**2)
|
|
90
|
+
r = np.sqrt((Beam.x-Beam.x0)**2 + (Beam.y-Beam.y0)**2)
|
|
91
|
+
phi = np.arctan2(Beam.y-Beam.y0, Beam.x-Beam.x0)
|
|
92
|
+
F = (np.exp(1j*kz*z) * special.jv(abs(N), kr*r) * np.exp(-1j*N*phi))
|
|
93
|
+
if pol_index == None:
|
|
94
|
+
if Beam.pol_dim == 1:
|
|
95
|
+
Beam.field = F
|
|
96
|
+
elif Beam.pol_dim == 2:
|
|
97
|
+
Beam.field = np.array([F, F])
|
|
98
|
+
elif Beam.pol_dim == 3:
|
|
99
|
+
Beam.field = np.array([F, F, F])
|
|
100
|
+
else:
|
|
101
|
+
Beam.field[pol_index] = F
|
|
102
|
+
Beam.norm_beam()
|
|
103
|
+
return Beam
|
|
104
|
+
|
|
105
|
+
def gbessel_mode(Beam, N, r0, pol_index):
|
|
106
|
+
#get a gaussian bessel beam of order N at z=0
|
|
107
|
+
rad = 2*np.pi*Beam.waist**2/r0
|
|
108
|
+
r = np.sqrt((Beam.x-Beam.x0)**2 + (Beam.y-Beam.y0)**2)
|
|
109
|
+
F = special.jv(np.abs(N),rad*r/Beam.waist**2)*\
|
|
110
|
+
np.exp(-1j*N*np.arctan2(Beam.y-Beam.y0, Beam.x-Beam.x0))*np.exp(-(r/Beam.waist)**2)
|
|
111
|
+
if pol_index == None:
|
|
112
|
+
if Beam.pol_dim == 1:
|
|
113
|
+
Beam.field = F
|
|
114
|
+
elif Beam.pol_dim == 2:
|
|
115
|
+
Beam.field = np.array([F, F])
|
|
116
|
+
elif Beam.pol_dim == 3:
|
|
117
|
+
Beam.field = np.array([F, F, F])
|
|
118
|
+
else:
|
|
119
|
+
Beam.field[pol_index] = F
|
|
120
|
+
Beam.norm_beam()
|
|
121
|
+
return Beam
|
|
122
|
+
|
|
123
|
+
def lg_prod_mode(Beam, N, ls, centers, pol_index):
|
|
124
|
+
if centers is None:
|
|
125
|
+
centers = np.zeros((N, 2))
|
|
126
|
+
centers = np.asarray(centers)
|
|
127
|
+
if ls is None:
|
|
128
|
+
ls = np.ones(N, dtype=int)
|
|
129
|
+
ls = np.asarray(ls)
|
|
130
|
+
|
|
131
|
+
F = np.ones((Beam.Dy, Beam.Dx), dtype='complex128')
|
|
132
|
+
for i in range(N):
|
|
133
|
+
x0 = centers[i, 0]
|
|
134
|
+
y0 = centers[i, 1]
|
|
135
|
+
l = ls[i]
|
|
136
|
+
sign = -1 if l < 0 else 1
|
|
137
|
+
exp_pow = int(np.abs(l))
|
|
138
|
+
arg = (Beam.x - x0) + 1j * sign * (Beam.y - y0)
|
|
139
|
+
r2 = (Beam.x - x0)**2 + (Beam.y - y0)**2
|
|
140
|
+
lg = arg**exp_pow * np.exp(-r2/N / ((Beam.waist)**2))
|
|
141
|
+
F *= lg
|
|
142
|
+
if pol_index == None:
|
|
143
|
+
if Beam.pol_dim == 1:
|
|
144
|
+
Beam.field = F
|
|
145
|
+
elif Beam.pol_dim == 2:
|
|
146
|
+
Beam.field = np.array([F, F])
|
|
147
|
+
elif Beam.pol_dim == 3:
|
|
148
|
+
Beam.field = np.array([F, F, F])
|
|
149
|
+
else:
|
|
150
|
+
Beam.field[pol_index] = F
|
|
151
|
+
Beam.norm_beam()
|
|
152
|
+
return Beam
|
|
153
|
+
|
|
154
|
+
def frac_oam_mode(Beam, Ma, n_modes, beta, theta_0, z, pol_index):
|
|
155
|
+
#get a fractional OAM beam, with OAM Ma (!= integer), by the method of LG supperpositions.
|
|
156
|
+
mu = Ma%1
|
|
157
|
+
m = Ma-mu
|
|
158
|
+
if mu >=0.5:
|
|
159
|
+
n_min = np.ceil(Ma - n_modes / 2)
|
|
160
|
+
else:
|
|
161
|
+
n_min = np.floor(Ma - n_modes / 2)
|
|
162
|
+
n_max = n_min + n_modes - 1
|
|
163
|
+
F = np.zeros((Beam.Dy, Beam.Dx), dtype='complex128')
|
|
164
|
+
for l in np.arange(int(n_min), int(n_max)+1):
|
|
165
|
+
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))
|
|
166
|
+
if Beam.pol_dim == 1:
|
|
167
|
+
F += coef*(Beam.lg(l, 0, z = z).field)
|
|
168
|
+
else:
|
|
169
|
+
F += coef*(Beam.lg(l, 0, z = z, pol_index=0).field[0])
|
|
170
|
+
if pol_index == None:
|
|
171
|
+
if Beam.pol_dim == 1:
|
|
172
|
+
Beam.field = F
|
|
173
|
+
elif Beam.pol_dim == 2:
|
|
174
|
+
Beam.field = np.array([F, F])
|
|
175
|
+
elif Beam.pol_dim == 3:
|
|
176
|
+
Beam.field = np.array([F, F, F])
|
|
177
|
+
else:
|
|
178
|
+
Beam.field[pol_index] = F
|
|
179
|
+
Beam.norm_beam()
|
|
180
|
+
return Beam
|
|
181
|
+
|
|
182
|
+
def frac_oam_qs_mode(Beam, Ma, n_modes, beta, theta_0, z, pol_index):
|
|
183
|
+
#get a quasi-stable fractional oam mode.
|
|
184
|
+
n_min = np.round(Ma-n_modes/2)
|
|
185
|
+
n_max = n_min + n_modes - 1
|
|
186
|
+
mu = Ma%1
|
|
187
|
+
m = Ma-mu
|
|
188
|
+
F = np.zeros((Beam.Dy, Beam.Dx), dtype='complex128')
|
|
189
|
+
for l in np.arange(int(n_min), int(n_max)+1):
|
|
190
|
+
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))
|
|
191
|
+
p = np.floor((np.abs(Ma) + n_modes/2 -np.abs(l))/2)
|
|
192
|
+
if Beam.pol_dim == 1:
|
|
193
|
+
F += coef*(Beam.lg(l, p, z = z).field)
|
|
194
|
+
else:
|
|
195
|
+
F += coef*(Beam.lg(l, p, z = z, pol_index=0).field[0])
|
|
196
|
+
if pol_index == None:
|
|
197
|
+
if Beam.pol_dim == 1:
|
|
198
|
+
Beam.field = F
|
|
199
|
+
elif Beam.pol_dim == 2:
|
|
200
|
+
Beam.field = np.array([F, F])
|
|
201
|
+
elif Beam.pol_dim == 3:
|
|
202
|
+
Beam.field = np.array([F, F, F])
|
|
203
|
+
else:
|
|
204
|
+
Beam.field[pol_index] = F
|
|
205
|
+
Beam.norm_beam()
|
|
206
|
+
return Beam
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def IG_even_mode(Beam, p, m, q, z, pol_index):
|
|
211
|
+
#Even Ince-Gaussian mode IG_p,m^e(x,y)
|
|
212
|
+
|
|
213
|
+
xi, eta = cartesian_to_elliptic(Beam.x-Beam.x0, Beam.y-Beam.y0, q, Beam.waist, z, Beam.lamb)
|
|
214
|
+
|
|
215
|
+
Ce_xi = C_ince(1j*xi, p, m, q)
|
|
216
|
+
Ce_eta = C_ince(eta, p, m, q)
|
|
217
|
+
|
|
218
|
+
zr = np.pi * Beam.waist**2 / Beam.lamb
|
|
219
|
+
k = 2*np.pi/Beam.lamb
|
|
220
|
+
wz = Beam.waist * np.sqrt(1 + (z * Beam.lamb / (np.pi * Beam.waist**2))**2)
|
|
221
|
+
Rz = (z**2 + zr**2)
|
|
222
|
+
r2 = (Beam.x-Beam.x0)**2 + (Beam.y-Beam.y0)**2
|
|
223
|
+
gouy = (p+1)*np.arctan(z / zr)
|
|
224
|
+
F = Ce_xi * Ce_eta * np.exp(-r2 / (wz**2))*np.exp(1j*(k*z + k*z*(r2)/(2*Rz) - gouy))
|
|
225
|
+
if pol_index == None:
|
|
226
|
+
if Beam.pol_dim == 1:
|
|
227
|
+
Beam.field = F
|
|
228
|
+
elif Beam.pol_dim == 2:
|
|
229
|
+
Beam.field = np.array([F, F])
|
|
230
|
+
elif Beam.pol_dim == 3:
|
|
231
|
+
Beam.field = np.array([F, F, F])
|
|
232
|
+
else:
|
|
233
|
+
Beam.field[pol_index] = F
|
|
234
|
+
Beam.norm_beam()
|
|
235
|
+
return Beam
|
|
236
|
+
|
|
237
|
+
def IG_odd_mode(Beam, p, m, q, z, pol_index):
|
|
238
|
+
#Odd Ince-Gaussian mode IG_p,m^o(x,y)
|
|
239
|
+
|
|
240
|
+
xi, eta = cartesian_to_elliptic(Beam.x-Beam.x0, Beam.y-Beam.y0, q, Beam.waist, z, Beam.lamb)
|
|
241
|
+
So_xi = S_ince(1j*xi, p, m, q)
|
|
242
|
+
So_eta = S_ince(eta, p, m, q)
|
|
243
|
+
|
|
244
|
+
zr = np.pi * Beam.waist**2 / Beam.lamb
|
|
245
|
+
k = 2*np.pi/Beam.lamb
|
|
246
|
+
wz = Beam.waist * np.sqrt(1 + (z * Beam.lamb / (np.pi * Beam.waist**2))**2)
|
|
247
|
+
Rz = (z**2 + zr**2)
|
|
248
|
+
r2 = (Beam.x-Beam.x0)**2 + (Beam.y-Beam.y0)**2
|
|
249
|
+
gouy = (p+1)*np.arctan(z / zr)
|
|
250
|
+
F = So_xi * So_eta * np.exp(-r2 / (wz**2))*np.exp(1j*(k*z + k*z*(r2)/(2*Rz) - gouy))
|
|
251
|
+
if pol_index == None:
|
|
252
|
+
if Beam.pol_dim == 1:
|
|
253
|
+
Beam.field = F
|
|
254
|
+
elif Beam.pol_dim == 2:
|
|
255
|
+
Beam.field = np.array([F, F])
|
|
256
|
+
elif Beam.pol_dim == 3:
|
|
257
|
+
Beam.field = np.array([F, F, F])
|
|
258
|
+
else:
|
|
259
|
+
Beam.field[pol_index] = F
|
|
260
|
+
Beam.norm_beam()
|
|
261
|
+
return Beam
|
|
262
|
+
|
|
263
|
+
def HInceG_mode(Beam, p, m, q, z, helicity, pol_index):
|
|
264
|
+
#Hermite-Ince-Gaussian mode HIG_p,m^e(x,y), combining even and odd Ince-Gaussian modes with given helicity.
|
|
265
|
+
#For some reason IG_odd has a 3*pi/2 phase shift wrt IG_even, so the helicity sign is inverted here.
|
|
266
|
+
xi, eta = cartesian_to_elliptic(Beam.x-Beam.x0, Beam.y-Beam.y0, q, Beam.waist, z, Beam.lamb)
|
|
267
|
+
|
|
268
|
+
Ce_xi = C_ince(1j*xi, p, m, q)
|
|
269
|
+
Ce_eta = C_ince(eta, p, m, q)
|
|
270
|
+
|
|
271
|
+
So_xi = S_ince(1j*xi, p, m, q)
|
|
272
|
+
So_eta = S_ince(eta, p, m, q)
|
|
273
|
+
|
|
274
|
+
zr = np.pi * Beam.waist**2 / Beam.lamb
|
|
275
|
+
k = 2*np.pi/Beam.lamb
|
|
276
|
+
wz = Beam.waist * np.sqrt(1 + (z * Beam.lamb / (np.pi * Beam.waist**2))**2)
|
|
277
|
+
Rz = (z**2 + zr**2)
|
|
278
|
+
r2 = (Beam.x-Beam.x0)**2 + (Beam.y-Beam.y0)**2
|
|
279
|
+
gouy = (p+1)*np.arctan(z / zr)
|
|
280
|
+
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))
|
|
281
|
+
if pol_index == None:
|
|
282
|
+
if Beam.pol_dim == 1:
|
|
283
|
+
Beam.field = F
|
|
284
|
+
elif Beam.pol_dim == 2:
|
|
285
|
+
Beam.field = np.array([F, F])
|
|
286
|
+
elif Beam.pol_dim == 3:
|
|
287
|
+
Beam.field = np.array([F, F, F])
|
|
288
|
+
else:
|
|
289
|
+
Beam.field[pol_index] = F
|
|
290
|
+
Beam.norm_beam()
|
|
291
|
+
return Beam
|
|
292
|
+
|
|
293
|
+
def circle_mode(Beam, center, radius, pol_index):
|
|
294
|
+
if radius == None:
|
|
295
|
+
radius = Beam.waist
|
|
296
|
+
#Create a circular mode
|
|
297
|
+
center_x, center_y = center
|
|
298
|
+
# Calculate distance from center for all points
|
|
299
|
+
distances = np.sqrt((Beam.x - center_x)**2 + (Beam.y - center_y)**2)
|
|
300
|
+
# Set points within radius to 1
|
|
301
|
+
mask = distances <= radius
|
|
302
|
+
F = np.zeros((Beam.Dy, Beam.Dx), dtype='complex')
|
|
303
|
+
F[mask] = 1
|
|
304
|
+
if pol_index == None:
|
|
305
|
+
if Beam.pol_dim == 1:
|
|
306
|
+
Beam.field = F
|
|
307
|
+
elif Beam.pol_dim == 2:
|
|
308
|
+
Beam.field = np.array([F, F])
|
|
309
|
+
elif Beam.pol_dim == 3:
|
|
310
|
+
Beam.field = np.array([F, F, F])
|
|
311
|
+
else:
|
|
312
|
+
Beam.field[pol_index] = F
|
|
313
|
+
Beam.norm_beam()
|
|
314
|
+
return Beam
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def square_mode(Beam, center, side_length, pol_index):
|
|
318
|
+
if side_length == None:
|
|
319
|
+
side_length = Beam.waist
|
|
320
|
+
center_x, center_y = center
|
|
321
|
+
|
|
322
|
+
# Calculate square boundaries
|
|
323
|
+
half_side = side_length / 2.0
|
|
324
|
+
|
|
325
|
+
# Create mask for points inside square
|
|
326
|
+
mask = (np.abs(Beam.x - center_x) <= half_side) & (np.abs(Beam.y - center_y) <= half_side)
|
|
327
|
+
|
|
328
|
+
# Set points inside square to 1
|
|
329
|
+
F = np.zeros((Beam.Dy, Beam.Dx), dtype='complex')
|
|
330
|
+
F[mask] = 1
|
|
331
|
+
if pol_index == None:
|
|
332
|
+
if Beam.pol_dim == 1:
|
|
333
|
+
Beam.field = F
|
|
334
|
+
elif Beam.pol_dim == 2:
|
|
335
|
+
Beam.field = np.array([F, F])
|
|
336
|
+
elif Beam.pol_dim == 3:
|
|
337
|
+
Beam.field = np.array([F, F, F])
|
|
338
|
+
else:
|
|
339
|
+
Beam.field[pol_index] = F
|
|
340
|
+
Beam.norm_beam()
|
|
341
|
+
return Beam
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def triangle_mode(Beam, center, side_length, pol_index):
|
|
345
|
+
if side_length == None:
|
|
346
|
+
side_length = Beam.waist
|
|
347
|
+
center_x, center_y = center
|
|
348
|
+
|
|
349
|
+
# Height of equilateral triangle
|
|
350
|
+
height = side_length * np.sqrt(3) / 2
|
|
351
|
+
|
|
352
|
+
# Vertices for upward-pointing equilateral triangle
|
|
353
|
+
# Top vertex
|
|
354
|
+
v0_x = center_x
|
|
355
|
+
v0_y = center_y + height * 2/3
|
|
356
|
+
|
|
357
|
+
# Bottom left vertex
|
|
358
|
+
v1_x = center_x - side_length / 2
|
|
359
|
+
v1_y = center_y - height * 1/3
|
|
360
|
+
|
|
361
|
+
# Bottom right vertex
|
|
362
|
+
v2_x = center_x + side_length / 2
|
|
363
|
+
v2_y = center_y - height * 1/3
|
|
364
|
+
|
|
365
|
+
# Use barycentric coordinates to determine if point is inside triangle
|
|
366
|
+
def sign(px, py, v1_x, v1_y, v2_x, v2_y):
|
|
367
|
+
return (px - v2_x) * (v1_y - v2_y) - (v1_x - v2_x) * (py - v2_y)
|
|
368
|
+
|
|
369
|
+
# Compute signs for all points
|
|
370
|
+
d1 = sign(Beam.x, Beam.y, v0_x, v0_y, v1_x, v1_y)
|
|
371
|
+
d2 = sign(Beam.x, Beam.y, v1_x, v1_y, v2_x, v2_y)
|
|
372
|
+
d3 = sign(Beam.x, Beam.y, v2_x, v2_y, v0_x, v0_y)
|
|
373
|
+
|
|
374
|
+
# Point is inside if all signs are same
|
|
375
|
+
has_neg = (d1 < 0) | (d2 < 0) | (d3 < 0)
|
|
376
|
+
has_pos = (d1 > 0) | (d2 > 0) | (d3 > 0)
|
|
377
|
+
|
|
378
|
+
mask = ~(has_neg & has_pos)
|
|
379
|
+
F = np.zeros((Beam.Dy, Beam.Dx), dtype='complex')
|
|
380
|
+
F[mask] = 1
|
|
381
|
+
if pol_index == None:
|
|
382
|
+
if Beam.pol_dim == 1:
|
|
383
|
+
Beam.field = F
|
|
384
|
+
elif Beam.pol_dim == 2:
|
|
385
|
+
Beam.field = np.array([F, F])
|
|
386
|
+
elif Beam.pol_dim == 3:
|
|
387
|
+
Beam.field = np.array([F, F, F])
|
|
388
|
+
else:
|
|
389
|
+
Beam.field[pol_index] = F
|
|
390
|
+
Beam.norm_beam()
|
|
391
|
+
return Beam
|
|
392
|
+
|
|
393
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy import fft
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def propagate_fresnel(Beam, z):
|
|
6
|
+
#Propagate the field by a distance z using fresnel integral. convolution method
|
|
7
|
+
k = 2*np.pi/Beam.lamb
|
|
8
|
+
prop = np.exp(-1j * z * (Beam.kx ** 2 + Beam.ky ** 2) / (2 * k) + 1j*k*z)
|
|
9
|
+
Beam.fourier_field = fft.fft2(Beam.field)
|
|
10
|
+
Beam.field = fft.ifft2(prop*Beam.fourier_field)
|
|
11
|
+
return Beam
|
|
12
|
+
|
|
13
|
+
def propagate_incoherent(Beam, z):
|
|
14
|
+
#Propagates the INTENSITY by a distance z using incoherent propagation. Loses phase information.
|
|
15
|
+
#without pupil function doesn't seems to work
|
|
16
|
+
k = 2*np.pi/Beam.lamb
|
|
17
|
+
if z != 0:
|
|
18
|
+
h2 = np.ones_like(Beam.field)/(Beam.lamb*z)**2
|
|
19
|
+
else:
|
|
20
|
+
h2 = np.ones_like(Beam.field)
|
|
21
|
+
h2_fourier = fft.fft2(h2)
|
|
22
|
+
I_fourier = fft.fft2(Beam.int_profile())
|
|
23
|
+
Beam.field = np.sqrt(fft.ifft2(h2_fourier*I_fourier))
|
|
24
|
+
return Beam
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def propagate_fraunhofer(Beam, z):
|
|
28
|
+
#Propagate the field by a distance z using fraunhofer integral. Changes XY grid.
|
|
29
|
+
|
|
30
|
+
if z < 2/Beam.lamb*Beam.waist**2:
|
|
31
|
+
print("z small. Fraunhofer is probably not a good aproximation.")
|
|
32
|
+
if z != 0:
|
|
33
|
+
k = 2*np.pi/Beam.lamb
|
|
34
|
+
dx = Beam.x[0,1] - Beam.x[0,0]
|
|
35
|
+
dy = Beam.y[1,0] - Beam.y[0,0]
|
|
36
|
+
fx = np.fft.fftshift(np.fft.fftfreq(Beam.Dx, d=dx))
|
|
37
|
+
fy = np.fft.fftshift(np.fft.fftfreq(Beam.Dy, d=dy))
|
|
38
|
+
FX, FY = np.meshgrid(fx, fy, indexing="xy")
|
|
39
|
+
Beam.x = Beam.lamb * z * FX
|
|
40
|
+
Beam.y = Beam.lamb * z * FY
|
|
41
|
+
Beam.nix = np.max(Beam.x)
|
|
42
|
+
Beam.niy = np.max(Beam.y)
|
|
43
|
+
C = np.exp(1j*k*z)/(1j*Beam.lamb*z)*np.exp(1j*k*(Beam.x**2 + Beam.y**2)/(2*z))
|
|
44
|
+
Beam.fourier_field = fft.fft2(Beam.field)
|
|
45
|
+
Beam.field = C*fft.fftshift(Beam.fourier_field)*dx*dy
|
|
46
|
+
return Beam
|