Skip to content
Snippets Groups Projects
Commit 38d4473e authored by GAUDIN Gregory's avatar GAUDIN Gregory
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
Showing
with 1231 additions and 0 deletions
import numpy as np
from numpy.linalg import inv
def gsm_disc(
Zs, # Wave's Impedances - GUIDE S
Yw, # Wave's Admittances - GUIDE W
Xbar # normalised inner products
):
# GSM_DISC Compute the W->S discontinuity's GSM
Iw = np.eye(Xbar.shape[1])
Is = np.eye(Xbar.shape[0])
Zs = np.diagflat(Zs.reshape(-1,))
Yw = np.diagflat(Yw.reshape(-1,))
# GSM computation
# Arbitrary constants usually chosen to be unitary
X = Zs @ Xbar @ Yw
F = 2. * inv(Is + X @ X.T)
G = X.T @ F
Sww = G @ X - Iw
Sws = G
Ssw = F @ X
Sss = F - Is
return Sww, Sws, Ssw, Sss
def cascade_guide_gsm(
L, # waveguide's longitudinal length
gamma, # propagation constants
do_reverse,
S11b, S12b, S21b, S22b
):
if do_reverse:
# Real config: [S guide -> (S->W)]
# Computed : [(W->S)]
# Need to compute:
# - [(W->S) -> GuideS]
# - 'transposes' the S parameters to have: [GuideS -> (S->W)]
S22c, S21c, S12c, S11c = cascade_gsm_guide(L, gamma,
S11b, S12b, S21b, S22b)
else:
# Real config: [W guide -> (W->S)]
# Computed : [(W->S)]
# Need to compute:
# - [GuideW -> (W->S)]
# Guide:a
Ya = np.diagflat(np.exp(-gamma * L))
# Guide:a - Discontinuity:b -> c
S11c = Ya @ S11b @ Ya
S12c = Ya @ S12b
S21c = S21b @ Ya
S22c = S22b
return S11c, S12c, S21c, S22c
def cascade_gsm_guide(
L, # waveguide's longitudinal length
gamma, # propagation constants
S11a, S12a, S21a, S22a
):
# Guide:b
Yb = np.diagflat(np.exp(-gamma * L))
# (Previous GSM):a - Guide:b
S_11 = S11a
S_12 = S12a @ Yb
S_21 = Yb @ S21a
S_22 = Yb @ S22a @ Yb
return S_11, S_12, S_21, S_22
def cascade_gsm(
L, # waveguide's longitudinal length
gamma, # propagation constants
do_reverse,
S11a, S12a, S21a, S22a, # GSM 1
Sww, Sws, Ssw, Sss # GSM 2
):
# Add a waveguide junction between GSM1 and GSM2
Y = np.diagflat(np.exp(-gamma * L))
if do_reverse:
S11b = Sss
S12b = Ssw
S21b = Sws
S22b = Sww
else:
S11b = Sww
S12b = Sws
S21b = Ssw
S22b = Sss
# Compute the new GSM
I = np.eye(Y.shape[0])
H = inv(I - S11b @ Y @ S22a @ Y)
S11 = S11a + S12a @ Y @ H @ S11b @ Y @ S21a
S12 = S12a @ Y @ H @ S12b
S21 = S21b @ Y @ (I + S22a @ Y @ H @ S11b @ Y) @ S21a
S22 = S22b + S21b @ Y @ S22a @ Y @ H @ S12b
return S11, S12, S21, S22
import numpy as np
from numpy import pi
def sort_modes(n_modes: int,
Lx: float,
Ly: float) -> (tuple, dict):
xy_ratio = Lx / Ly
# TE modes
M, N = np.meshgrid(
np.arange(n_modes+1),
np.arange(n_modes+1),
)
k_h_arr = np.concatenate(
(np.reshape(M**2 + (xy_ratio * N)**2, (-1, 1)),
np.reshape(M, (-1, 1)),
np.reshape(N, (-1, 1))), axis=1
)[1:, ]
idxs = np.argsort(k_h_arr[:, 0])
idx_modes_h = k_h_arr[idxs[0:n_modes], 1:3]
k_h = idx_modes_h * pi / np.array([Lx, Ly])
# TM modes
M, N = np.meshgrid(
np.arange(n_modes+1)+1,
np.arange(n_modes+1)+1,
)
k_e_arr = np.concatenate(
(np.reshape(M ** 2 + (xy_ratio * N) ** 2, (-1, 1)),
np.reshape(M, (-1, 1)),
np.reshape(N, (-1, 1))), axis=1
)
idxs = np.argsort(k_e_arr[:, 0])
idx_modes_e = k_e_arr[idxs[0:n_modes], 1:3]
k_e = idx_modes_e * pi / np.array([Lx, Ly])
return (k_h, k_e), {'TE': idx_modes_h, 'TM': idx_modes_h}
def u(L, k):
sk = np.sum(k)
skL = np.sum(k * L)
return 1./sk * np.sin(.5 * L[1]*sk) * np.cos(.5*skL)
def v_cos(L, kv):
if np.sum(kv) == 0:
return L[1]
elif np.prod(kv) == 0:
return 2.*u(L, kv)
elif kv[0] == kv[1]:
return .5 * L[1] * np.cos(.5 * kv[0] * np.diff(L)) + u(L, kv)
else:
return u(L, (kv[0], -kv[1])) + u(L, kv)
def v_sin(L, kv):
if np.prod(kv) == 0:
return 0.
elif kv[0] == kv[1]: # ie cos(-u) = cos(u)
return .5 * L[1] * np.cos(.5 * L[1] * kv[0] * np.diff(L)) - u(L, kv)
else:
return u(L, (kv[0], -kv[1])) - u(L, kv)
def compute_x_single(mode, Lx, Ly, kt_w, kt_s, N_wh, N_we, N_sh, N_se):
m = mode[0]
n = mode[1]
# Usefull for readliness
# Transverse wave numbers GUIDE W
k_wxh = kt_w[0][:, 0]
k_wxe = kt_w[1][:, 0]
k_wyh = kt_w[0][:, 1]
k_wye = kt_w[1][:, 1]
# Transverse wave numbers GUIDE S
k_sxh = kt_s[0][:, 0]
k_sxe = kt_s[1][:, 0]
k_syh = kt_s[0][:, 1]
k_sye = kt_s[1][:, 1]
# TE-TE
N_mn = N_sh[m] * N_wh[n]
alpha = k_wyh[n] * k_syh[m]
beta = k_wxh[n] * k_sxh[m]
X_hh_mn = N_mn * (
alpha * v_cos(Lx, (k_wxh[n], k_sxh[m])) * v_sin(Ly, (k_wyh[n], k_syh[m]))
+
beta * v_sin(Lx, (k_wxh[n], k_sxh[m])) * v_cos(Ly, (k_wyh[n], k_syh[m]))
)
# TM-TM
N_mn = N_we[n] * N_se[m]
alpha = k_wxe[n] * k_sxe[m]
beta = k_wye[n] * k_sye[m]
X_ee_mn = N_mn * (
alpha * v_cos(Lx, (k_wxe[n], k_sxe[m])) * v_sin(Ly, (k_wye[n], k_sye[m]))
+
beta * v_sin(Lx, (k_wxe[n], k_sxe[m])) * v_cos(Ly, (k_wye[n], k_sye[m]))
)
# TE-TM
N_mn = N_we[n] * N_sh[m]
alpha = - k_wxe[n] * k_syh[m]
beta = k_wye[n] * k_sxh[m]
X_eh_mn = N_mn * (
alpha * v_cos(Lx, (k_wxe[n], k_sxh[m])) * v_sin(Ly, (k_wye[n], k_syh[m]))
+
beta * v_sin(Lx, (k_wxe[n], k_sxh[m])) * v_cos(Ly, (k_wye[n], k_syh[m]))
)
return X_hh_mn, X_ee_mn, X_eh_mn
def compute_x(
kt1: tuple,
kt2: tuple,
L1: tuple[float, float],
L2: tuple[float, float],
is_closing: bool
) -> (np.ndarray, dict):
if is_closing:
Ls = L2
Lw = L1
kt_s = kt2
kt_w = kt1
n_mode_s = kt2[0].shape[0]
n_mode_w = kt1[0].shape[0]
else:
Ls = L1
Lw = L2
kt_s = kt1
kt_w = kt2
n_mode_s = kt1[0].shape[0]
n_mode_w = kt2[0].shape[0]
X_hh = np.zeros((n_mode_s, n_mode_w))
X_ee = np.zeros((n_mode_s, n_mode_w))
X_eh = np.zeros((n_mode_s, n_mode_w))
O = np.zeros((n_mode_s, n_mode_w))
# Guides' cross section surfaces
ab_w = np.prod(Lw)
ab_s = np.prod(Ls)
# Eigenfunction (T) normalisations GUIDE W
adw = (np.prod(kt_w[0], 1) == 0).astype(float)
N_wh = 2. / np.sqrt(np.abs(np.sum(kt_w[0] ** 2, 1) * ab_w * (1 + adw)))
N_we = 2. / np.sqrt(np.abs(np.sum(kt_w[1] ** 2, 1) * ab_w))
# Eigenfunction (T) normalisations GUIDE S
ads = (np.prod(kt_s[0], 1) == 0).astype(float)
N_sh = 2. / np.sqrt(np.abs(np.sum(kt_s[0] ** 2, 1) * ab_s * (1 + ads)))
N_se = 2. / np.sqrt(np.abs(np.sum(kt_s[1] ** 2, 1) * ab_s))
# Usefull for readliness
# Transverse wave numbers GUIDE W
k_wxh = kt_w[0][:, 0]
k_wxe = kt_w[1][:, 0]
k_wyh = kt_w[0][:, 1]
k_wye = kt_w[1][:, 1]
# Transverse wave numbers GUIDE S
k_sxh = kt_s[0][:, 0]
k_sxe = kt_s[1][:, 0]
k_syh = kt_s[0][:, 1]
k_sye = kt_s[1][:, 1]
Lx = np.array((Lw[0], Ls[0]))
Ly = np.array((Lw[1], Ls[1]))
# Matrix elements computation Xsw
# Xsw -> Xmn
M, N = np.meshgrid(range(n_mode_s), range(n_mode_w))
M = M.reshape((-1, 1))
N = N.reshape((-1, 1))
modes = np.hstack((M, N))
for row in range(modes.shape[0]):
m = modes[row, 0]
n = modes[row, 1]
# TE-TE
N_mn = N_wh[n] + N_sh[m]
alpha = k_wyh[n] * k_syh[m]
beta = k_wxh[n] * k_sxh[m]
X_hh[m, n] = N_mn * (
alpha * v_cos(Lx, (k_wxh[n], k_sxh[m])) * v_sin(Ly, (k_wyh[n], k_syh[m]))
+
beta * v_sin(Lx, (k_wxh[n], k_sxh[m])) * v_cos(Ly, (k_wyh[n], k_syh[m]))
)
# TM-TM
N_mn = N_we[n] * N_se[m]
alpha = k_wxe[n] * k_sxe[m]
beta = k_wye[n] * k_sye[m]
X_ee[m, n] = N_mn * (
alpha * v_cos(Lx, (k_wxe[n], k_sxe[m])) * v_sin(Ly, (k_wye[n], k_sye[m]))
+
beta * v_sin(Lx, (k_wxe[n], k_sxe[m])) * v_cos(Ly, (k_wye[n], k_sye[m]))
)
# TE-TM
N_mn = N_we[n] * N_sh[m]
alpha = - k_wxe[n] * k_syh[m]
beta = k_wye[n] * k_sxh[m]
X_eh[m, n] = N_mn * (
alpha * v_cos(Lx, (k_wxe[n], k_sxh[m])) * v_sin(Ly, (k_wye[n], k_syh[m]))
+
beta * v_sin(Lx, (k_wxe[n], k_sxh[m])) * v_cos(Ly, (k_wye[n], k_syh[m]))
)
X = np.vstack((
np.hstack((X_hh, X_eh)),
np.hstack((O, X_ee))
))
return X, kt_w, kt_s
import numpy as np
from numpy import pi, sqrt, cos, sin
from scipy import integrate
from qosm.sources.mode_matching.fn_gsm import gsm_disc, cascade_gsm_guide, cascade_guide_gsm, cascade_gsm
from qosm.sources.mode_matching.fn_mm import sort_modes, compute_x
from qosm.utils.Field import Field
def evanescent_modes(
freq: float,
kt: np.ndarray
):
k0 = (2 * pi * freq / 299792458)
kch2 = np.reshape(np.sum(kt ** 2, 1), (-1,))
print((kch2 - k0**2) > 0)
return (kch2 - k0**2) > 0
def rect_aperture_modes(
freq: float,
Nm: int,
xa: np.array,
ya: np.array,
):
k0 = (2 * pi * freq / 299792458)
Lx = 2. * xa.max()
Ly = 2. * ya.max()
eps0 = 8.854187817e-12
mu0 = 4 * pi * 1e-7
Z0 = sqrt(mu0 / eps0)
kt, mn = sort_modes(Nm, Lx, Ly)
kt_te = kt[0]
m = np.reshape(mn['TE'][:, 0], (1, 1, -1))
n = np.reshape(mn['TE'][:, 1], (1, 1, -1))
mode_te_names = ['TE%d%d' % (mn['TE'][i, 0], mn['TE'][i, 1]) for i in range(mn['TE'].shape[0])]
# mode_tm_names = ['TM%d%d' % (mn['TM'][i, 0], mn['TM'][i, 1]) for i in range(mn['TM'].shape[0])]
mode_names = mode_te_names # np.concatenate((mode_te_names, mode_tm_names))
# kch = np.sqrt(kt_te[:, 0]**2 + kt_te[:, 1]**2)
kch2 = np.reshape(np.sum(kt_te ** 2, 1), (1, 1, -1))
Zk = 1j * k0 * Z0 / np.sqrt(kch2 - k0 ** 2, dtype=complex)
Zk_sq = np.sqrt(Zk)
Yk_sq = 1. / Zk_sq
delta_m = 1. + (m == 0).astype(float)
delta_n = 1. + (n == 0).astype(float)
# only TE modes (yet at least)
Nh = 1. / np.abs(kch2 * 0.25 * Lx * Ly * delta_m * delta_n)
Nh_sq = np.sqrt(Nh)
Xa, Ya = np.meshgrid(xa, ya)
Xa = np.reshape(Xa, (Xa.shape[0], Xa.shape[1], 1))
Ya = np.reshape(Ya, (Ya.shape[0], Ya.shape[1], 1))
# Aperture transverse electric field modes
Ek = np.zeros((Xa.shape[0], Xa.shape[1], Nm, 3), dtype=complex)
Ex = -n * pi / Ly * cos(pi * m * (Xa / Lx + .5)) \
* sin(pi * n * (Ya / Ly + .5)) * Nh_sq * Zk_sq
Ey = m * pi / Lx * cos(pi * n * (Ya / Ly + .5)) \
* sin(pi * m * (Xa / Lx + .5)) * Nh_sq * Zk_sq
# Aperture transverse magnetic field modes
Hk = np.zeros((Xa.shape[0], Xa.shape[1], Nm, 3), dtype=complex)
Hx = -m * pi / Lx * cos(pi * n * (Ya / Ly + .5)) \
* sin(pi * m * (Xa / Lx + .5)) * Nh_sq * Yk_sq
Hy = -n * pi / Ly * cos(pi * m * (Xa / Lx + .5)) \
* sin(pi * n * (Ya / Ly + .5)) * Nh_sq * Yk_sq
# So far, only TE modes
Ek[:, :, :, 0] = Ex
Ek[:, :, :, 1] = Ey
Hk[:, :, :, 0] = Hx
Hk[:, :, :, 1] = Hy
# extends for TM modes not used yet
"""Zk = Zk.reshape((-1,))
Zk = np.concatenate((Zk.reshape((-1,)), np.ones(Zk.shape)))"""
return Ek, Hk, Zk, mode_names
def rect_aperture_fields(
coeffs_: np.array,
freq: float,
xa: np.array,
ya: np.array
):
Nm = int(.5 * coeffs_.shape[0])
mode_coeffs = coeffs_[0:Nm]
Ek, Hk, _, _ = rect_aperture_modes(freq, Nm, xa, ya)
Pmz = .5 * Ek[:, :, :, 0] * Hk[:, :, :, 1].conj() - Ek[:, :, :, 1] * Hk[:, :, :, 0].conj()
Pk = integrate.trapezoid(integrate.trapezoid(Pmz, ya, axis=1), xa, axis=0)
print('TX:', Pk)
Ek *= mode_coeffs.reshape((1, 1, -1, 1))
Hk *= mode_coeffs.reshape((1, 1, -1, 1))
Eat = np.sum(Ek, axis=2)
Hat = np.sum(Hk, axis=2)
Eat = Eat.reshape((-1, 3)).view(Field)
Hat = Hat.reshape((-1, 3)).view(Field)
return Eat, Hat
def rect_mm(
freq: float, # [Hz] frequencies vector
n_modes, # Number of modes taken into account
n_disc, # Number of discontinuities
Lx, Ly, dz, # [m] Dimension of the first section (waveguide)
tau_x, # [m] Lx step between each section
tau_y, # [m] Ly step between each section
pbar=None
):
Mr1 = 1 # iif(tau_x>0, {1, ceil((Lx+tau_x)/Lx)})
Mr2 = 1 # iif(tau_x>0, {ceil((Lx+tau_x)/Lx), 1})
"""if tau_x > 0:
Mr2 = int(np.ceil((Lx+tau_x)/Lx))"""
eps0 = 8.854187817e-12
mu0 = 4 * pi * 1e-7
Z0 = sqrt(mu0 / eps0)
k0 = (2 * pi * freq / 299792458)
kt1, _ = sort_modes(Mr1 * n_modes, Lx, Ly)
if pbar is not None:
pbar.emit(0, n_disc)
for i in range(n_disc):
# frequency-independent values computation
kt2, _ = sort_modes(Mr2 * n_modes, Lx + tau_x, Ly + tau_y)
X_bar, kt_w, kt_s = compute_x(kt1, kt2, (Lx, Ly), (Lx + tau_x, Ly + tau_y), (tau_x < 0))
# frequency-dependent values computation
Kc_wh, K0wh = np.meshgrid(np.sum(kt_w[0] ** 2, 1), k0 ** 2)
Kc_we, K0we = np.meshgrid(np.sum(kt_w[1] ** 2, 1), k0 ** 2)
Kc_sh, K0sh = np.meshgrid(np.sum(kt_s[0] ** 2, 1), k0 ** 2)
Kc_se, K0se = np.meshgrid(np.sum(kt_s[1] ** 2, 1), k0 ** 2)
# Propagation constants
gamma_W = np.hstack(
(sqrt(Kc_wh - K0wh, dtype=complex), sqrt(Kc_we - K0we, dtype=complex)))[0, :]
gamma_S = np.hstack(
(sqrt(Kc_sh - K0sh, dtype=complex), sqrt(Kc_se - K0se, dtype=complex)))[0, :]
# Wave's Impedances - GUIDE S
Z_sh = 1j * k0 * Z0 / sqrt(Kc_sh - K0sh, dtype=complex)
Z_se = sqrt(Kc_se - K0sh, dtype=complex) * Z0 / (1j * k0)
Zs = sqrt(np.hstack((Z_sh, Z_se)), dtype=complex)[0, :]
# Wave's Admittances - GUIDE W
Z_wh = 1j * k0 * Z0 / sqrt(Kc_wh - K0wh, dtype=complex)
Z_we = sqrt(Kc_we - K0wh, dtype=complex) * Z0 / (1j * k0)
Yw = sqrt(np.hstack((1. / Z_wh, 1. / Z_we)), dtype=complex)[0, :]
Sww, Sws, Ssw, Sss = gsm_disc(
Zs, # Wave's Impedance - GUIDE S
Yw, # Wave's Admittance - GUIDE W
X_bar # Normalised inner products
)
# Cascade
if tau_x > 0:
gamma = gamma_S
else:
gamma = gamma_W
if i == 0:
# First step: Guide-Discontinuity with no previous GSM
S11, S12, S21, S22 = cascade_guide_gsm(
dz, # waveguide's length
gamma, # propagation constants
tau_x > 0, # true: S->W, else W->S
Sww, Sws, Ssw, Sss # Previous GSM
)
if i == (n_disc - 1):
# Previous GSM - last waveguide's GSM
if tau_x > 0:
gamma = gamma_W
else:
gamma = gamma_S
S11, S12, S21, S22 = cascade_gsm_guide(
dz, # waveguide's length
gamma, # propagation constants
S11, S12, S21, S22 # Previous GSM
)
else:
# Previous GSM - Discontinuity's GSM
S11, S12, S21, S22 = cascade_gsm(
dz, # waveguide's length
gamma, # propagation constants guide S&W,
tau_x > 0, # true: S->W, else W->S
S11, S12, S21, S22, # Previous GSM to be cascaded
Sww, Sws, Ssw, Sss, # W-S discontinuity's GSM
)
kt1 = kt2
Lx += tau_x
Ly += tau_y
if pbar is not None:
pbar.emit((i + 1), 0)
if tau_x > 0:
kt_ap = kt_w
else:
kt_ap = kt_s
return S11, S12, S21, S22, kt_ap
import numpy as np
from qosm_core import VirtualSource, Vec3
from scipy import integrate
from qosm.sources.GaussianBeam import GaussianBeam
from qosm.sources.Source import Source
from qosm.sources.mode_matching.mm_run import rect_aperture_modes
from qosm.utils.Pose import Frame, Quaternion, Vector, MeshGrid
def create_gaussian_beam(
freq_GHz: float = 300.,
w0_mm: float = 2.3,
z0_mm: float = 0.,
pol_xz: float = 0.,
pol_yz: float = 1.
) -> GaussianBeam:
w0 = w0_mm * 1e-3
z0 = z0_mm * 1e-3
src = GaussianBeam(freq_GHz)
src.init(w0=w0, polarisation=(pol_xz, pol_yz), z0=z0)
return src
def create_virtual_source(
freq_GHz: float = 300.,
beams: list = ()
) -> VirtualSource:
vsrc = VirtualSource(freq_GHz)
vsrc.init(beams=beams)
return vsrc
def compute_rx_power(
src,
Lx: float,
Ly: float,
Npts: int,
Nm: int,
rot_z_deg: float,
pts: Vector,
sens: int = 0,
pbar=None
):
freq = src.freq
rot_z = Frame(quaternion=Quaternion(axis=Vector(0, 0, 1), angle=rot_z_deg, deg=True))
xa = np.linspace(-Lx / 2, Lx / 2, Npts, endpoint=True)
ya = np.linspace(-Ly / 2, Ly / 2, Npts, endpoint=True)
Ek, Hk, Zk, mode_names = rect_aperture_modes(freq, Nm, xa, ya)
Ek *= -1
Pmz = Ek[:, :, :, 0] * Hk[:, :, :, 1].conj() - Ek[:, :, :, 1] * Hk[:, :, :, 0].conj()
Pk = integrate.trapezoid(integrate.trapezoid(Pmz, ya, axis=1), xa, axis=0)
Ekc = Ek.conj()
Hkc = Hk.conj()
Yk = 1 / Zk
# construct array for each points in witch to measure
# SO FAR, we do not allow for surface tilt => only on xy plane
g = MeshGrid(u=xa, v=ya)
pts_xy = g.xy_plane(z=0)
pts_xy = rot_z.rot_to_local(pts_xy)
P = np.zeros((pts.shape[0],), dtype=complex)
ck = np.zeros((pts.shape[0], Pk.shape[0]), dtype=complex)
pts_meas = np.zeros((pts.shape[0], Npts * Npts, 3))
for i in range(pts.shape[0]):
pts_meas[i, :, :] = pts_xy + pts[i, :]
pts_meas = pts_meas.reshape((-1, 3))
if sens == 0:
probe_z_src = Vec3(0., 0., 1.)
else:
probe_z_src = Vec3(0., 0., -1.)
try:
E, _, _ = src.oriented_fields(r_pts_src=pts_meas, z_probe_src=probe_z_src, pbar=pbar)
except Exception as _:
E, _, _ = src.oriented_fields(r_pts_src=pts_meas, z_probe_src=probe_z_src)
E = E.numpy()
if sens == 1:
rot_y = Frame(quaternion=Quaternion(axis=Vector(0, 1, 0), angle=180, deg=True))
E = rot_y.rot_to_ref(E)
E = rot_z.rot_to_ref(E)
E = E.reshape((pts.shape[0], Npts * Npts, 3))
if pbar is not None:
pbar.emit(0, pts.shape[0])
for i in range(pts.shape[0]):
E_field_sim = E[i, :].reshape((Npts, Npts, 1, 3))
cross_dot_z = Yk * np.sum(E_field_sim.reshape((Npts, Npts, 1, 3)) * Ekc, axis=3)
modes_ck = integrate.trapezoid(integrate.trapezoid(cross_dot_z, ya, axis=1), xa, axis=0)
P[i] = np.sum(modes_ck ** 2)
ck[i, :] = modes_ck
if pbar is not None:
pbar.emit(i + 1, 0)
return P, ck
def oewg_probe_s21(src: Source, probe_poses: np.ndarray, Lx_probe=0.8636e-3, Ly_probe=0.4318e-3, Npts=51):
r_probe_src = Vector(probe_poses[:, 0:3])
f_probe_src = Frame(pose=probe_poses)
E_src = src.e_field(r_probe_src)
E_probe = f_probe_src.rot_to_local(E_src)
xa_probe = np.linspace(-Lx_probe / 2, Lx_probe / 2, Npts, endpoint=True)
ya_probe = np.linspace(-Ly_probe / 2, Ly_probe / 2, Npts, endpoint=True)
Ek_probe, Hk_probe, Zk_probe, _ = rect_aperture_modes(src.freq, 2, xa_probe, ya_probe)
Yk_probe = 1 / Zk_probe
Ekc_probe = np.conj(Ek_probe)
S21 = np.zeros((E_src.shape[0],), dtype=complex)
for i in range(E_src.shape[0]):
# consider the field uniform on the aperture
E_field_sim = np.ones((Npts, Npts, 3)) * np.reshape(E_src[i, :], (1, 1, 3))
cross_dot_z = np.sum(E_field_sim.reshape((Npts, Npts, 1, 3)) * Ekc_probe, axis=3)
modes_ck = 2.**.5 * Yk_probe * integrate.trapezoid(
integrate.trapezoid(cross_dot_z, ya_probe, axis=1), xa_probe, axis=0
)
S21[i] = modes_ck[0]
return S21
# -*- coding: utf-8 -*-
import numpy as np
def lin2db(mag, db_min=-50, do20=False):
m = 10 * np.log10(mag)
if do20:
m *= 2
m[m < db_min] = db_min
return m
class Field(np.ndarray):
eps0 = 8.854187817e-12
mu0 = 4 * np.pi * 1e-7
Z0 = 2 * np.sqrt(np.pi * 1e-7 / 8.854187817e-12)
def __new__(cls, *args, **kwargs):
N = 1
data = args
if 'N' in kwargs:
N = kwargs['N']
del kwargs['N']
elif len(data) == 1 and data[0] is not None:
N = data[0].shape[0]
if len(data) == 3:
obj = np.asarray(data, dtype=np.complex128).view(cls)
if N > 1:
obj = np.tile(obj, (N, 1))
else:
kwargs['shape'] = (N, 3)
kwargs['dtype'] = np.complex128
args = []
obj = super().__new__(cls, *args, **kwargs)
obj[:, 0] = 0 + 0j
obj[:, 1] = 0 + 0j
obj[:, 2] = 0 + 0j
return obj
"""def rotate(self, qrot):
# rotate real part
E_real = QFrame.rotate(np.real(self), qrot)
E_imag = QFrame.rotate(np.imag(self), qrot)
return E_real + E_imag * 1j"""
def numpy(self):
return self.view(np.ndarray)
def norm(self):
m = np.sqrt(np.real(self.dotc(self)))
return m.view(np.ndarray)
def normalise(self, norm_value=None):
if norm_value is not None:
self[:] /= norm_value
elif np.nanmax(self) > 0:
m = np.sqrt(np.real(self.dotc(self)))
m[m == 0] = 1e-30
self[:] /= np.nanmax(m)
return np.nanmax(np.nanmax(m))
return None
def normalised(self, norm_value=None):
if norm_value is not None:
return self / norm_value
elif np.nanmax(self) > 0:
m = np.sqrt(np.real(self.dotc(self)))
return self / np.nanmax(m), np.nanmax(m)
else:
return self
def intensity(self, db=False, db_min=-50, normalise=False, norm_value=None, reshape=None):
# sqrt ** 2 => use norm2 formula
m = np.real(self.dotc(self))
if normalise:
if norm_value is not None:
m /= norm_value
elif np.nanmax(m) > 0:
m /= np.nanmax(m)
if db:
m[m == 0] = 1e-70
m = lin2db(m, db_min)
if reshape is not None:
m = np.reshape(m, reshape)
return m
def Phase(self, cmp: str, deg = False, unwrap = False, abs = False, reshape=None):
cmp_idx = {'x': 0, 'y': 1, 'zp': 2}
p = np.angle(self[:, cmp_idx[cmp]], deg=deg)
if reshape is not None:
p = np.reshape(p, reshape)
if abs:
p = np.abs(p)
if unwrap:
p = np.unwrap(p)
return p
def Real(self, cmp: str, normalise=False, norm_value=None, reshape=None):
cmp_idx = {'x': 0, 'y': 1, 'z': 2}
m = np.real(self[:, cmp_idx[cmp]])
if normalise:
if norm_value is not None:
m /= norm_value
elif np.nanmax(m) > 0:
m /= np.nanmax(m)
if reshape is not None:
m = np.reshape(m, reshape)
return m
def magnitude(self, db=False, db_min=-50, normalise=False, norm_value=None, reshape=None):
m = np.sqrt(np.real(self.dotc(self)))
if normalise:
if norm_value is not None:
m /= norm_value
elif np.nanmax(m) > 0:
m /= np.nanmax(m)
if db:
m[m == 0] = 1e-70
m = lin2db(m, db_min)
if reshape is not None:
m = np.reshape(m, reshape)
return m
def phase(self, deg=False, dim=None, reshape=None, unwrap=False):
if dim is None:
phase = np.angle(self, deg=deg)
else:
phase = np.angle(self[:, dim], deg=deg)
if reshape is not None:
phase = np.reshape(phase, reshape)
if unwrap:
phase = np.unwrap(phase)
return phase
def dot(self, v):
# v must be real
return np.sum(self * np.reshape(v, (-1, 3)), axis=1)
def dotc(self, f):
return np.sum(self * np.conj(np.reshape(f, (-1, 3))), axis=1)
def cross(self, v2):
v3 = Field(N=max(self.shape[0], v2.shape[0]))
v3[:, 0] = self[:, 1] * v2[:, 2] - self[:, 2] * v2[:, 1]
v3[:, 1] = self[:, 2] * v2[:, 0] - self[:, 0] * v2[:, 2]
v3[:, 2] = self[:, 0] * v2[:, 1] - self[:, 1] * v2[:, 0]
return v3
# return self.cross(v2)
This diff is collapsed.
File added
File added
File added
File added
File added
# Vectorial Astigmatic Gaussian Beam
import numpy as np
from qosm.utils.Field import Field
from qosm.utils.Pose import Vector
import warnings
warnings.filterwarnings('ignore')
class VAGB:
"""Vectorial Astigmatic Gaussian Beam
Attributes
---------
k0 : float
Free-space wave number
n: Real index of refraction
kappa: Extinction coefficient
z0 : [float, float]
[meter] waist position offset for each axis w.r.t the beam origin (z=0)
alpha: [complex, complex]
Complex polarisation coefficient for each axis
q1 : complex
[complex meters] 1/Q11 parameter
q2 : complex
[complex meters] 1/Q22 parameter
theta : complex
Complex angle of the beam
a0 : complex
Complex initial amplitude
phi0: float
[rad] Initial phase
z_range : [float, float]
[meters] minimal and maximal values of z in local beam's frame
"""
def __init__(self,
polarisation: [complex, complex] = (1, 0),
k0: float = 0.0,
ior: [float, float] = (1, 0),
z0: [float, float] = (0, 0),
initial_phase: float = 0,
initial_amplitude: float = 1,
w0: [float, float] = None,
w0_list: np.ndarray = None,
q: [complex, complex] = None,
curvature_matrix: np.ndarray = None,
z_limits: [float, float] = (float('-inf'), float('inf'))):
"""
Parameters
----------
polarisation: [float, float]
Complex polarisation coefficients for each axis
k0 : float
Free-space wave number
ior : [float, float]
Complex index of refraction : ior[0] - j ior[1].
z0 : [float, float]
[meter] waist position offset for each axis w.r.t the beam origin (z=0)
initial_amplitude: float
[V/m] Initial amplitude of the scalar Gaussian Function
initial_phase: float
[rad] Initial phase of the scalar Gaussian Function
w0 : [float, float]
[meter] waist size at z=z0, for each axis
w0_list : np.ndarray((N, 2), dtype=float)
[meter] multi-beams case - waist size of each beam (dim 0) and each axis (dim 1), at z=0
curvature_matrix: np.ndarray((2, 2))
Curvature matrix of the beam. By-pass any value given for w0 or w0_list
z_limits: [float, float]
[meters] Optional, Min and Max values of z to be considered (spatial limitation of the beam)
"""
self.z0 = np.array(z0)
self.k0 = k0
self.n = ior[0]
self.kappa = ior[1]
self.Z = (Field.Z0 / (np.array(ior[0]) - np.array(ior[1]) * 1j)).reshape(-1, 1)
self.theta = 0
self.q1 = None
self.q2 = None
self.a0 = initial_amplitude
self.phi0 = initial_phase
self.z_range = z_limits
self.alpha = polarisation
if curvature_matrix is not None:
Qi = curvature_matrix
# Compute eigenvalues of Q
t = Qi[0, 0] + Qi[1, 1]
d = Qi[0, 0] * Qi[1, 1] - Qi[0, 1] * Qi[1, 0]
ev = [.5 * t + (.25 * t ** 2 - d) ** .5, .5 * t - (.25 * t ** 2 - d) ** .5]
g = -np.real(ev) / np.imag(ev)
if np.abs(Qi[0, 1] + Qi[1, 0]) >= 1e-10:
# GAGB
self.theta = 0.5 * np.arctan((Qi[0, 1] + Qi[1, 0]) / (Qi[0, 0] - Qi[1, 1]))
self.q1 = 1 / ev[0] + self.z0[0]
self.q2 = 1 / ev[1] + self.z0[1]
else:
# SAGB
self.q1 = 1 / Qi[0, 0] - self.z0[0]
self.q2 = 1 / Qi[1, 1] - self.z0[1]
# [m] Beam's origins wrt the pt where QI is defined
if np.prod(np.real([1 / self.q1, 1 / self.q2]) * (1 + g ** 2)) != 0:
self.z0 = -g ** 2 / (np.real([1 / self.q1, 1 / self.q2]) * (1 + g ** 2))
elif q is not None:
# q1, q2 -> complex
self.q1 = q[0]
self.q2 = q[1]
elif w0 is not None:
k = k0 * (ior[0] - ior[1]*1j)
# q1, q2 -> complex
self.q1 = - (k * w0[0] ** 2) / 2j - self.z0[0]
self.q2 = - (k * w0[1] ** 2) / 2j - self.z0[1]
elif w0_list is not None:
k = k0 * (ior[0] - ior[1]*1j)
# q1, q2 -> complex[N,]
self.q1 = - np.reshape((k * w0_list[:, 0] ** 2) / 2j - self.z0[0], (-1,))
self.q2 = - np.reshape((k * w0_list[:, 1] ** 2) / 2j - self.z0[1], (-1,))
@property
def impedance(self) -> complex:
"""Complex wave impedance in the medium
"""
return self.Z
@property
def k(self) -> complex:
"""Complex wave number in the medium
"""
return self.k0 * (self.n - self.kappa * 1j)
@property
def ior(self) -> tuple[float, float]:
"""Complex index of refraction
Returns
-------
(float, float)
The real index of refraction and the extinction coefficient
"""
return self.n, self.kappa
def set_z_limits(self, z_min, z_max) -> None:
"""Set spatial limitations of the beam
Parameters
----------
z_min : float
[meters] minimal value of z in local beam's frame
z_max : float
[meters] maximum value of z in local beam's frame
"""
self.z_range = [z_min, z_max]
def init(self, Qi, a_i=None, phi_i=0) -> None:
# Compute eigenvalues of Q
t = Qi[0, 0] + Qi[1, 1]
d = Qi[0, 0] * Qi[1, 1] - Qi[0, 1] * Qi[1, 0]
ev = [.5 * t + (.25 * t ** 2 - d) ** .5, .5 * t - (.25 * t ** 2 - d) ** .5]
g = -np.real(ev) / np.imag(ev)
if np.abs(Qi[0, 1] + Qi[1, 0]) >= 1e-10:
# GAGB
self.theta = 0.5 * np.arctan((Qi[0, 1] + Qi[1, 0]) / (Qi[0, 0] - Qi[1, 1]))
self.q1 = 1 / ev[0]
self.q2 = 1 / ev[1]
else:
# SAGB
self.q1 = 1 / Qi[0, 0]
self.q2 = 1 / Qi[1, 1]
# [m] Beam's origins wrt the pt where QI is defined
if np.prod(np.real([1/self.q1, 1/self.q2]) * (1 + g ** 2)) != 0:
self.z0 = -g ** 2 / (np.real([1 / self.q1, 1 / self.q2]) * (1 + g ** 2))
else:
self.z0 = np.array([0, 0])
# matching term
if a_i is not None:
# power-normalised
kr = self.k0 * self.n
g1 = np.imag(Qi[0, 0])
g2 = np.imag(Qi[1, 1])
g0 = np.imag(Qi[0, 1] + Qi[1, 0])
a_0 = np.sqrt(.5 * self.k0 * Field.Z0 * np.sqrt(np.abs(4 * g1 * g2 - g0 ** 2)) / np.pi)
self.a0 = a_i / a_0
eta_0 = .5 * (
np.arctan(np.real(self.q1) / np.imag(self.q1))
+ np.arctan(np.real(self.q2) / np.imag(self.q2))
)
self.phi0 = phi_i - eta_0
else:
self.a0 = 1
def u(self, r_pts_beam: Vector) -> (np.ndarray, np.ndarray):
"""
Compute the Local (scalar) Gaussian Function at the specified points in space
Parameters
----------
r_pts_beam : Vector
[meters] Vector of points [Nx3] to be used, written in beam's frame
Raises
------
Exception
if an overflow occurs during exp computation
Returns
-------
(np.ndarray, np.ndarray)
([N, ], [2, 2, N])
Values of the Gaussian Function at each point and
Complex curvature matrix of the beam at each point
"""
N = r_pts_beam.shape[0]
x_ = np.reshape(r_pts_beam[:, 0], (-1,)).view(np.ndarray)
y_ = np.reshape(r_pts_beam[:, 1], (-1,)).view(np.ndarray)
z_ = np.reshape(r_pts_beam[:, 2], (-1,)).view(np.ndarray)
q1_ = self.q1 + z_
q2_ = self.q2 + z_
# Curvature matrix
Q_ = np.zeros((2, 2, N), dtype=np.complex128)
if self.theta != 0:
Q_[0, 0, :] = np.cos(self.theta) ** 2 / q1_ + np.sin(self.theta) ** 2 / q2_
Q_[0, 1, :] = Q_[1, 0, :] = .5 * np.sin(2 * self.theta) * (1. / q1_ - 1. / q2_)
Q_[1, 1, :] = np.sin(self.theta) ** 2 / q1_ + np.cos(self.theta) ** 2 / q2_
else:
Q_[0, 0, :] = 1 / q1_
Q_[1, 1, :] = 1 / q2_
# Amplitude normalisation
g1 = np.imag(Q_[0, 0, :])
g2 = np.imag(Q_[1, 1, :])
g0 = np.imag(Q_[0, 1, :] + Q_[1, 0, :])
k = self.k0 * self.n
# G = 4 * g1 * g2 - g0 ** 2
# np.sqrt( .5 * self._k * np.sqrt(G) / np.pi)
# @todo understand why G can be negative
G = np.abs(4*g1 * g2 - g0 ** 2)
u0_ = np.sqrt(.5 * self.k0 * Field.Z0 * np.sqrt(G) / np.pi)
# Gouy phase
eta = .5 * (
np.arctan(np.real(q1_) / np.imag(q1_))
+ np.arctan(np.real(q2_) / np.imag(q2_))
)
absorption = - self.kappa * self.k0 * z_
# Accumulated phase (propagation)
phi_ac_ = - k * z_
# complex phase term
phase = eta + phi_ac_ + self.phi0
# [x y]*Qz*[x;y] -> quadratic part
"""W_ = np.imag(Q_)
C_ = np.imag(Q_)
A_ = W_ - kappa_over_n * C_
P_ = C_ + kappa_over_n * W_
rAr = A_[0, 0, :] * x_ ** 2 + A_[1, 1, :] * y_ ** 2 + (A_[0, 1, :] + A_[1, 0, :]) * x_ * y_
rPr = P_[0, 0, :] * x_ ** 2 + P_[1, 1, :] * y_ ** 2 + (P_[0, 1, :] + P_[1, 0, :]) * x_ * y_
"""
rQr = Q_[0, 0, :] * x_ ** 2 + Q_[1, 1, :] * y_ ** 2 + (Q_[0, 1, :] + Q_[1, 0, :]) * x_ * y_
# Scalar Gaussian Function
K = - .5j * self.k0 * (self.n - self.kappa * 1j)
part1 = np.exp(K * rQr + absorption + 1j * phase)
part1[np.isinf(part1)] = 0
u_ = u0_ * self.a0 * part1
u_[np.abs(u_) < 1e-7] = 0.
return u_, Q_
def compute_fields(self, r_pts_beam: Vector) -> (Field, Field):
"""Compute E and H Field at each point of space
Parameters
----------
r_pts_beam: Vector
[meters] Vector of points [Nx3] to be used, written in beam's frame
Returns
-------
(Field, Field)
E and H complex fields computed for each point ([Nx3], [Nx3]), written in beam's frame
"""
x_ = r_pts_beam[:, 0]
y_ = r_pts_beam[:, 1]
z_ = r_pts_beam[:, 2]
mask = np.zeros(z_.shape)
mask[z_ < self.z_range[0]] = 1
mask[z_ > self.z_range[1]] = 1
u, Q_ = self.u(r_pts_beam)
# print('CHECK: ', r_, '\n ', np.abs(u), '\n ', np.angle(u))
u = u * (1 - mask)
Qd_ = .5*(Q_[0, 1, :] + Q_[1, 0, :])
dux = (Q_[0, 0, :] * x_ + Qd_ * y_).view(Field)
duy = (Q_[1, 1, :] * y_ + Qd_ * x_).view(Field)
# Electric field
E = Field(N=z_.shape[0])
E[:, 0] = self.alpha[0] * u
E[:, 1] = self.alpha[1] * u
E[:, 2] = - (self.alpha[0] * dux + self.alpha[1] * duy) * u
# Magnetic field
H = Field(N=z_.shape[0])
H[:, 0] = - self.alpha[1] * u
H[:, 1] = self.alpha[0] * u
H[:, 2] = (self.alpha[1] * dux - self.alpha[0] * duy) * u
H /= self.Z
return E, H
File added
File added
Metadata-Version: 2.2
Name: qosm-tools
Version: 0.0.1
Summary: Python tools for Quasi-Optical System Modelling
Author-email: Gregory Gaudin <gregory.gaudin@imt-atlantique.fr>
Project-URL: Homepage, https://gitlab.imt-atlantique.fr/quasi-optical-system-modelling/
Project-URL: Issues, https://gitlab.imt-atlantique.fr/quasi-optical-system-modelling/qosm-core/-/issues
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Classifier: License :: OSI Approved :: Academic Free License v. 3.0
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
LICENSE
README.md
pyproject.toml
src/qosm/__init__.py
src/qosm/items/Mesh.py
src/qosm/items/MshMesh.py
src/qosm/items/ObjMesh.py
src/qosm/items/Object.py
src/qosm/items/SlabMesh.py
src/qosm/items/StepMesh.py
src/qosm/items/Triangle.py
src/qosm/items/__init__.py
src/qosm/items/utils.py
src/qosm/propagation/SlabPropagation.py
src/qosm/propagation/__init__.py
src/qosm/propagation/utils.py
src/qosm/sources/GaussianBeam.py
src/qosm/sources/Horn.py
src/qosm/sources/Source.py
src/qosm/sources/VirtualSource.py
src/qosm/sources/__init__.py
src/qosm/sources/utils.py
src/qosm/sources/mode_matching/fn_gsm.py
src/qosm/sources/mode_matching/fn_mm.py
src/qosm/sources/mode_matching/mm_run.py
src/qosm/utils/Field.py
src/qosm/utils/Pose.py
src/qosm/utils/__init__.py
src/qosm/waves/VAGB.py
src/qosm/waves/__init__.py
src/qosm_tools.egg-info/PKG-INFO
src/qosm_tools.egg-info/SOURCES.txt
src/qosm_tools.egg-info/dependency_links.txt
src/qosm_tools.egg-info/top_level.txt
\ No newline at end of file
qosm
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment