diff --git a/QA/1D_grating_in_2D_pattern.py b/QA/1D_grating_in_2D_pattern.py
deleted file mode 100644
index 4df7d8b..0000000
--- a/QA/1D_grating_in_2D_pattern.py
+++ /dev/null
@@ -1,58 +0,0 @@
-import numpy as np
-
-from meent.main import call_mee
-
-
-def test():
- backend = 0
- pol = 1 # 0: TE, 1: TM
-
- n_top = 1 # n_incidence
- n_bot = 1 # n_transmission
-
- theta = 1E-10 # angle of incidence in radian
- phi = 0 # azimuth angle in radian
-
- wavelength = 300 # wavelength
- thickness = [460, 22]
- period = [700, 700]
- fto = [10, 0]
-
- # 1D
- ucell = np.array([
- [
- [1, 1, 1, 3.48, 3.48, 3.48, 1, 1, 1, 1],
- ],
- [
- [1, 1, 1, 3.48, 3.48, 3.48, 1, 1, 1, 1],
- ],
- ])
-
- AA = call_mee(backend=backend, pol=pol, n_top=n_top, n_bot=n_bot, theta=theta, phi=phi,
- fto=fto, wavelength=wavelength, period=period, ucell=ucell, thickness=thickness)
- de_ri, de_ti = AA.conv_solve()
- print('1D', de_ri.sum(), de_ti.sum())
-
- # 2D case
-
- ucell = np.array([
- [
- [1, 1, 1, 3.48, 3.48, 3.48, 1, 1, 1, 1],
- [1, 1, 1, 3.48, 3.48, 3.48, 1, 1, 1, 1],
- [1, 1, 1, 3.48, 3.48, 3.48, 1, 1, 1, 1],
- ],
- [
- [1, 1, 1, 3.48, 3.48, 3.48, 1, 1, 1, 1],
- [1, 1, 1, 3.48, 3.48, 3.48, 1, 1, 1, 1],
- [1, 1, 1, 3.48, 3.48, 3.48, 1, 1, 1, 1],
- ],
- ])
-
- AA = call_mee(backend=backend, pol=pol, n_top=n_top, n_bot=n_bot, theta=theta, phi=phi,
- fto=fto, wavelength=wavelength, period=period, ucell=ucell, thickness=thickness)
- de_ri, de_ti = AA.conv_solve()
- print('2D', de_ri.sum(), de_ti.sum())
-
-
-if __name__ == '__main__':
- test()
diff --git a/QA/1d_pattern_in_1dc_and_2d.py b/QA/1d_pattern_in_1dc_and_2d.py
new file mode 100644
index 0000000..ddc5ac4
--- /dev/null
+++ b/QA/1d_pattern_in_1dc_and_2d.py
@@ -0,0 +1,87 @@
+# This demo shows a case with 1D grating and TM polarization.
+# If phi is set to 'None', this will use 1D TETM formulation (without azimuthal rotation, phi == 0)
+# But if phi is set to '0', then the simulation will be taken for 1D conical or 2D case which is general but slower.
+
+import numpy as np
+from time import time
+
+from meent import call_mee
+
+
+def compare():
+ backend = 0
+ pol = 1 # 0: TE, 1: TM
+
+ n_top = 1 # n_incidence
+ n_bot = 1 # n_transmission
+
+ theta = 1E-10 # angle of incidence in radian
+
+ wavelength = 300 # wavelength
+ thickness = [460, 22]
+ period = [700, 700]
+ fto = [100, 0]
+
+ ucell_1d = np.array([
+ [
+ [1, 1, 1, 3.48, 3.48, 3.48, 1, 1, 1, 1],
+ ],
+ [
+ [1, 1, 1, 3.48, 3.48, 3.48, 1, 1, 1, 1],
+ ],
+ ])
+ ucell_2d = np.array([
+ [
+ [1, 1, 1, 3.48, 3.48, 3.48, 1, 1, 1, 1],
+ [1, 1, 1, 3.48, 3.48, 3.48, 1, 1, 1, 1],
+ ],
+ [
+ [1, 1, 1, 3.48, 3.48, 3.48, 1, 1, 1, 1],
+ [1, 1, 1, 3.48, 3.48, 3.48, 1, 1, 1, 1],
+ ],
+ ])
+
+ mee = call_mee(backend=backend, pol=pol, n_top=n_top, n_bot=n_bot, theta=theta, fto=fto,
+ wavelength=wavelength, period=period, thickness=thickness)
+
+ # 1D
+ mee.phi = None # which is default
+ mee.ucell = ucell_1d
+
+ t0_1d = time()
+ res = mee.conv_solve().res
+ t1_1d = time()
+ de_ri1, de_ti1 = res.de_ri, res.de_ti
+ print('1D (de_ri, de_ti): ', de_ri1, de_ti1)
+
+ # 1D conical
+ mee.phi = 0
+ t0_1dc = time()
+ res = mee.conv_solve().res
+ t1_1dc = time()
+ de_ri1c, de_ti1c = res.de_ri, res.de_ti
+ print('1Dc (de_ri, de_ti): ', de_ri1c, de_ti1c)
+
+ # 2D
+ mee.phi = 0
+ t0_2d = time()
+ mee.ucell = ucell_2d
+ res = mee.conv_solve().res
+ t1_2d = time()
+ de_ri2, de_ti2 = res.de_ri, res.de_ti
+ print('2D (de_ri, de_ti): ', de_ri2, de_ti2)
+
+ print('time for 1D formulation: ', t1_1d-t0_1d, 's')
+ print('time for 1Dc formulation: ', t1_1dc-t0_1dc, 's')
+ print('time for 2D formulation: ', t1_2d-t0_2d, 's')
+ print('Simulation Difference between 1D and 1Dc formulation: ',
+ np.linalg.norm(de_ri1 - de_ri1c), np.linalg.norm(de_ti1 - de_ti1c))
+ print('Simulation Difference between 1D and 2D formulation: ',
+ np.linalg.norm(de_ri1 - de_ri2), np.linalg.norm(de_ti1 - de_ti2))
+
+ print('Simulation Difference between 1Dc and 2D formulation: ',
+ np.linalg.norm(de_ri1c - de_ri2), np.linalg.norm(de_ti1c - de_ti2))
+
+
+if __name__ == '__main__':
+ compare()
diff --git a/QA/autograd_complex_ucell.py b/QA/autodiff_raster1.py
similarity index 88%
rename from QA/autograd_complex_ucell.py
rename to QA/autodiff_raster1.py
index e7b97ed..0054364 100644
--- a/QA/autograd_complex_ucell.py
+++ b/QA/autodiff_raster1.py
@@ -8,7 +8,7 @@
import torch
import meent
-from meent.on_torch.optimizer.loss import LossDeflector
+
type_complex = 0
device = 0
@@ -48,7 +48,19 @@
pois = ['ucell', 'thickness'] # Parameter Of Interests
forward = jmee.conv_solve
-loss_fn = LossDeflector(x_order=0, y_order=0)
+
+
+class Loss:
+ def __call__(self, meent_result, *args, **kwargs):
+ res_psi, res_te, res_ti = meent_result.res, meent_result.res_te_inc, meent_result.res_tm_inc
+ de_ti = res_psi.de_ti
+ center = [a // 2 for a in de_ti.shape]
+ res = de_ti[center[0], center[1]+1]
+
+ return res
+
+
+loss_fn = Loss()
# case 1: Gradient
grad_j = jmee.grad(pois, forward, loss_fn)
@@ -58,7 +70,7 @@
print('thickness gradient:')
print(grad_j['thickness'])
-optimizer = optax.sgd(learning_rate=1e-2)
+optimizer = optax.sgd(learning_rate=1E2)
t0 = time.time()
res_j = jmee.fit(pois, forward, loss_fn, optimizer, iteration=iteration)
print('Time JAX', time.time() - t0)
@@ -74,7 +86,6 @@
thickness=thickness, type_complex=type_complex, device=device)
forward = tmee.conv_solve
-loss_fn = LossDeflector(x_order=0) # predefined in meent
grad_t = tmee.grad(pois, forward, loss_fn)
print('ucell gradient:')
@@ -83,7 +94,7 @@
print(grad_t['thickness'])
opt_torch = torch.optim.SGD
-opt_options = {'lr': 1E-2}
+opt_options = {'lr': 1E2}
t0 = time.time()
res_t = tmee.fit(pois, forward, loss_fn, opt_torch, opt_options, iteration=iteration)
@@ -102,6 +113,6 @@
print('End')
-# Note that the gradient in JAX is conjugated.
+# Note that the gradient in JAX is conjugation of PyTorch's.
# https://github.com/google/jax/issues/4891
# https://pytorch.org/docs/stable/notes/autograd.html#autograd-for-complex-numbers
diff --git a/QA/autodiff_raster2.py b/QA/autodiff_raster2.py
new file mode 100644
index 0000000..d65aefc
--- /dev/null
+++ b/QA/autodiff_raster2.py
@@ -0,0 +1,172 @@
+import jax
+import torch
+
+import jax.numpy as jnp
+import numpy as np
+
+from time import time
+
+from meent import call_mee
+
+
+def load_setting():
+ pol = 1 # 0: TE, 1: TM
+
+ n_top = 1 # n_incidence
+ n_bot = 1 # n_transmission
+
+ theta = 0 * np.pi / 180
+ phi = 0 * np.pi / 180
+
+ wavelength = 900
+
+ fto = [5, 5]
+
+ period = [1000, 1000]
+ thickness = [1120]
+
+ ucell = np.array([[[2.58941352 + 0.47745679j, 4.17771602 + 0.88991205j,
+ 2.04255624 + 2.23670125j, 2.50478974 + 2.05242759j,
+ 3.32747593 + 2.3854387j],
+ [2.80118605 + 0.53053715j, 4.46498861 + 0.10812571j,
+ 3.99377545 + 1.0441131j, 3.10728537 + 0.6637353j,
+ 4.74697849 + 0.62841253j],
+ [3.80944424 + 2.25899274j, 3.70371553 + 1.32586402j,
+ 3.8011133 + 1.49939415j, 3.14797238 + 2.91158289j,
+ 4.3085404 + 2.44344691j],
+ [2.22510179 + 2.86017146j, 2.36613053 + 2.82270351j,
+ 4.5087168 + 0.2035904j, 3.15559949 + 2.55311298j,
+ 4.29394604 + 0.98362617j],
+ [3.31324163 + 2.77590131j, 2.11744834 + 1.65894674j,
+ 3.59347907 + 1.28895345j, 3.85713467 + 1.90714056j,
+ 2.93805426 + 2.63385392j]]])
+ ucell = ucell.real
+
+ type_complex = 0
+ device = 0
+
+ setting = {'pol': pol, 'n_top': n_top, 'n_bot': n_bot, 'theta': theta, 'phi': phi, 'fto': fto,
+ 'wavelength': wavelength, 'period': period, 'ucell': ucell, 'thickness': thickness, 'device': device,
+ 'type_complex': type_complex}
+
+ return setting
+
+
+def optimize_jax(setting):
+ ucell = setting['ucell']
+
+ mee = call_mee(backend=1, **setting)
+
+ @jax.jit
+ def grad_loss(ucell):
+ mee.ucell = ucell
+ res = mee.conv_solve().res
+ de_ri, de_ti = res.de_ri, res.de_ti
+
+ loss = de_ti[de_ti.shape[0] // 2, de_ti.shape[1] // 2]
+
+ return loss
+
+ def grad_numerical(ucell, delta):
+ grad_arr = jnp.zeros(ucell.shape, dtype=ucell.dtype)
+
+ @jax.jit
+ def compute(ucell):
+ mee.ucell = ucell
+ result = mee.conv_solve()
+ de_ti = result.res.de_ti
+ loss = de_ti[de_ti.shape[0] // 2, de_ti.shape[1] // 2]
+
+ return loss
+
+ for layer in range(ucell.shape[0]):
+ for r in range(ucell.shape[1]):
+ for c in range(ucell.shape[2]):
+ ucell_delta_m = ucell.copy()
+ ucell_delta_m[layer, r, c] -= delta
+ mee.ucell = ucell_delta_m
+ de_ti_delta_m = compute(ucell_delta_m, )
+
+ ucell_delta_p = ucell.copy()
+ ucell_delta_p[layer, r, c] += delta
+ mee.ucell = ucell_delta_p
+ de_ti_delta_p = compute(ucell_delta_p, )
+
+ grad_numeric = (de_ti_delta_p - de_ti_delta_m) / (2 * delta)
+ grad_arr = grad_arr.at[layer, r, c].set(grad_numeric)
+
+ return grad_arr
+
+ jax.grad(grad_loss)(ucell) # Dry run for jit compilation. This is to make time comparison fair.
+ t0 = time()
+ grad_ad = jax.grad(grad_loss)(ucell)
+ t_ad = time() - t0
+ print('JAX grad_ad:\n', grad_ad)
+ t0 = time()
+ grad_nume = grad_numerical(ucell, 1E-6)
+ t_nume = time() - t0
+ print('JAX grad_numeric:\n', grad_nume)
+ print('JAX norm of difference: ', jnp.linalg.norm(grad_nume - grad_ad) / grad_nume.size)
+ return t_ad, t_nume
+
+
+def optimize_torch(setting):
+ mee = call_mee(backend=2, **setting)
+
+ mee.ucell.requires_grad = True
+
+ t0 = time()
+ res = mee.conv_solve().res
+ de_ri, de_ti = res.de_ri, res.de_ti
+
+ loss = de_ti[de_ti.shape[0] // 2, de_ti.shape[1] // 2]
+
+ loss.backward()
+ grad_ad = mee.ucell.grad
+ t_ad = time() - t0
+
+ def grad_numerical(ucell, delta):
+ ucell.requires_grad = False
+ grad_arr = torch.zeros(ucell.shape, dtype=ucell.dtype)
+
+ for layer in range(ucell.shape[0]):
+ for r in range(ucell.shape[1]):
+ for c in range(ucell.shape[2]):
+ ucell_delta_m = ucell.clone().detach()
+ ucell_delta_m[layer, r, c] -= delta
+ mee.ucell = ucell_delta_m
+ res = mee.conv_solve().res
+ de_ri_delta_m, de_ti_delta_m = res.de_ri, res.de_ti
+
+ ucell_delta_p = ucell.clone().detach()
+ ucell_delta_p[layer, r, c] += delta
+ mee.ucell = ucell_delta_p
+ res = mee.conv_solve().res
+ de_ri_delta_p, de_ti_delta_p = res.de_ri, res.de_ti
+
+ cy, cx = np.array(de_ti_delta_p.shape) // 2
+ grad_numeric = (de_ti_delta_p[cy, cx] - de_ti_delta_m[cy, cx]) / (2 * delta)
+ grad_arr[layer, r, c] = grad_numeric
+
+ return grad_arr
+
+ t0 = time()
+ grad_nume = grad_numerical(mee.ucell, 1E-6)
+ t_nume = time() - t0
+
+ print('Torch grad_ad:\n', grad_ad)
+ print('Torch grad_numeric:\n', grad_nume)
+ print('torch.norm: ', torch.linalg.norm(grad_nume - grad_ad) / grad_nume.numel())
+ return t_ad, t_nume
+
+
+if __name__ == '__main__':
+ setting = load_setting()
+
+ print('JaxMeent')
+ j_t_ad, j_t_nume = optimize_jax(setting)
+ print('TorchMeent')
+ t_t_ad, t_t_nume = optimize_torch(setting)
+
+ print(f'Time for Backprop, JAX, AD: {j_t_ad} s, Numerical: {j_t_nume} s')
+ print(f'Time for Backprop, Torch, AD: {t_t_ad} s, Numerical: {t_t_nume} s')
diff --git a/QA/autodiff_vector.py b/QA/autodiff_vector.py
new file mode 100644
index 0000000..b9a9447
--- /dev/null
+++ b/QA/autodiff_vector.py
@@ -0,0 +1,165 @@
+import meent
+
+
+def run_jax():
+ print('RUN JAXMeent')
+ import jax
+ import optax
+ import jax.numpy as jnp
+
+ backend = 1
+
+ period = [1000., 1000.]
+ thickness = ([300.])
+ wavelength = 900
+
+ input_length1 = jnp.array([160], dtype=jnp.float64)
+ input_length2 = jnp.array([100], dtype=jnp.float64)
+ input_length3 = jnp.array([30], dtype=jnp.float64)
+ input_length4 = jnp.array([20], dtype=jnp.float64)
+
+ fto = [5, 5]
+
+ mee = meent.call_mee(backend=backend, fto=fto, wavelength=wavelength, thickness=thickness, period=period,
+ device=0, type_complex=0)
+
+ opt = optax.sgd(learning_rate=1E5, momentum=0)
+
+ def forward(param_list):
+ [length1, length2, length3, length4] = param_list
+ ucell = [
+ [3 - 1j, [
+ ['rectangle', 0 + 1000, 410 + 1000, length1, 80, 4, 0, 0, 0], # obj 1
+ ['ellipse', 0 + 1000, -10 + 1000, length2, 80, 4, 1, 20, 20], # obj 2
+ ['rectangle', 120 + 1000, 500 + 1000, length3, 160, 4 + 0.3j, 1.1, 5, 5], # obj 3
+ ['ellipse', -400 + 1000, -700 + 1000, length4, 160, 4, 0.4, 20, 20], # obj 4
+ ], ],
+ ]
+ mee.ucell = ucell
+
+ res = mee.conv_solve().res
+ de_ti = res.de_ti
+
+ cy, cx = de_ti.shape[0] // 2, de_ti.shape[1] // 2
+ loss = -de_ti[cy, cx + 1]
+
+ return loss
+
+ pois = [input_length1, input_length2, input_length3, input_length4]
+ opt_state = opt.init(pois)
+
+ for i in range(10):
+ print('Parameters: ', [p.item() for p in pois])
+
+ input_length1, input_length2, input_length3, input_length4 = pois
+
+ dx = 1E-5
+ loss_a = forward([input_length1 + dx, input_length2, input_length3, input_length4])
+ loss_b = forward([input_length1 - dx, input_length2, input_length3, input_length4])
+ grad1 = (loss_a - loss_b) / (2 * dx)
+
+ loss_a = forward([input_length1, input_length2 + dx, input_length3, input_length4])
+ loss_b = forward([input_length1, input_length2 - dx, input_length3, input_length4])
+ grad2 = (loss_a - loss_b) / (2 * dx)
+
+ loss_a = forward([input_length1, input_length2, input_length3 + dx, input_length4])
+ loss_b = forward([input_length1, input_length2, input_length3 - dx, input_length4])
+ grad3 = (loss_a - loss_b) / (2 * dx)
+
+ loss_a = forward([input_length1, input_length2, input_length3, input_length4 + dx])
+ loss_b = forward([input_length1, input_length2, input_length3, input_length4 - dx])
+ grad4 = (loss_a - loss_b) / (2 * dx)
+
+ print('grad_nume: ', grad1.item(), grad2.item(), grad3.item(), grad4.item())
+
+ # grad = jax.grad(forward)(pois)
+ loss, grad = jax.value_and_grad(forward)(pois)
+ updates, opt_state = opt.update(grad, opt_state, pois)
+
+ pois = optax.apply_updates(pois, updates)
+ print('grad_auto: ', *[g.item() for g in grad])
+ print('Loss:', loss)
+
+
+def run_torch():
+ print('RUN TorchMeent')
+ import torch
+ backend = 2
+
+ period = [1000., 1000.]
+ thickness = torch.tensor([300.])
+ wavelength = 900
+
+ input_length1 = 160
+ input_length2 = 100
+ input_length3 = 30
+ input_length4 = 20
+
+ fto = [5, 5]
+
+ # layer_base = torch.tensor(n_index_base)
+ input_length1 = torch.tensor([input_length1], dtype=torch.float64, requires_grad=True)
+ input_length2 = torch.tensor([input_length2], dtype=torch.float64, requires_grad=True)
+ input_length3 = torch.tensor([input_length3], dtype=torch.float64, requires_grad=True)
+ input_length4 = torch.tensor([input_length4], dtype=torch.float64, requires_grad=True)
+
+ mee = meent.call_mee(backend=backend, fto=fto, wavelength=wavelength, thickness=thickness, period=period,
+ device=0, type_complex=0)
+
+ opt = torch.optim.SGD([input_length1, input_length2, input_length3, input_length4], lr=1E5, momentum=0)
+
+ def forward(length1, length2, length3, length4):
+
+ ucell = [
+ [3 - 1j, [
+ ['rectangle', 0+1000, 410+1000, length1, 80, 4, 0, 0, 0], # obj 1
+ ['ellipse', 0+1000, -10+1000, length2, 80, 4, 1, 20, 20], # obj 2
+ ['rectangle', 120+1000, 500+1000, length3, 160, 4+0.3j, 1.1, 5, 5], # obj 3
+ ['ellipse', -400+1000, -700+1000, length4, 160, 4, 0.4, 20, 20], # obj 4
+ ], ],
+ ]
+ mee.ucell = ucell
+
+ res = mee.conv_solve().res
+ de_ti = res.de_ti
+
+ cy, cx = de_ti.shape[0] // 2, de_ti.shape[1] // 2
+ loss = -de_ti[cy, cx + 1]
+
+ return loss
+
+ for i in range(10):
+ print('Parameters: ', input_length1.detach().numpy(), input_length2.detach().numpy(),
+ input_length3.detach().numpy(), input_length4.detach().numpy())
+ dx = 1E-5
+ loss_a = forward(input_length1 + dx, input_length2, input_length3, input_length4)
+ loss_b = forward(input_length1 - dx, input_length2, input_length3, input_length4)
+ grad1 = (loss_a - loss_b) / (2 * dx)
+
+ loss_a = forward(input_length1, input_length2 + dx, input_length3, input_length4)
+ loss_b = forward(input_length1, input_length2 - dx, input_length3, input_length4)
+ grad2 = (loss_a - loss_b) / (2 * dx)
+
+ loss_a = forward(input_length1, input_length2, input_length3 + dx, input_length4)
+ loss_b = forward(input_length1, input_length2, input_length3 - dx, input_length4)
+ grad3 = (loss_a - loss_b) / (2 * dx)
+
+ loss_a = forward(input_length1, input_length2, input_length3, input_length4 + dx)
+ loss_b = forward(input_length1, input_length2, input_length3, input_length4 - dx)
+ grad4 = (loss_a - loss_b) / (2 * dx)
+
+ print('grad_nume: ', grad1.item(), grad2.item(), grad3.item(), grad4.item())
+
+ loss = forward(input_length1, input_length2, input_length3, input_length4)
+ loss.backward()
+ print('grad_auto: ', input_length1.grad.numpy()[0], input_length2.grad.numpy()[0], input_length3.grad.numpy()[0],
+ input_length4.grad.numpy()[0])
+
+ opt.step()
+ opt.zero_grad()
+ print('Loss:', loss)
+
+
+if __name__ == '__main__':
+ run_jax()
+ run_torch()
diff --git a/QA/autograd_raster.py b/QA/autograd_raster.py
deleted file mode 100644
index 1794911..0000000
--- a/QA/autograd_raster.py
+++ /dev/null
@@ -1,191 +0,0 @@
-import warnings
-import jax
-import jax.numpy as jnp
-import torch
-
-import numpy as np
-
-from copy import deepcopy
-
-from meent import call_mee
-
-
-def load_setting():
- pol = 1 # 0: TE, 1: TM
-
- n_top = 1 # n_incidence
- n_bot = 1 # n_transmission
-
- theta = 0 * np.pi / 180
- phi = 0 * np.pi / 180
- psi = 0 * np.pi / 180 if pol else 90 * np.pi / 180
-
- wavelength = 900
-
- fto = [2, 2]
-
- # case 1
- period = [1000, 1000]
- thickness = [1120., 400, 300]
-
- ucell = np.array(
- [
- [
- [3.1, 1.1, 1.2, 1.6, 3.1],
- [3.5, 1.4, 1.1, 1.2, 3.6],
- ],
- [
- [3.5, 1.2, 1.5, 1.2, 3.3],
- [3.1, 1.5, 1.5, 1.4, 3.1],
- ],
- [
- [3.5, 1.2, 1.5, 1.2, 3.3],
- [3.1, 1.5, 1.5, 1.4, 3.1],
- ],
- ]
- )
-
- # Case 4
- thickness = [1120]
-
- ucell = np.array([[[2.58941352 + 0.47745679j, 4.17771602 + 0.88991205j,
- 2.04255624 + 2.23670125j, 2.50478974 + 2.05242759j,
- 3.32747593 + 2.3854387j],
- [2.80118605 + 0.53053715j, 4.46498861 + 0.10812571j,
- 3.99377545 + 1.0441131j, 3.10728537 + 0.6637353j,
- 4.74697849 + 0.62841253j],
- [3.80944424 + 2.25899274j, 3.70371553 + 1.32586402j,
- 3.8011133 + 1.49939415j, 3.14797238 + 2.91158289j,
- 4.3085404 + 2.44344691j],
- [2.22510179 + 2.86017146j, 2.36613053 + 2.82270351j,
- 4.5087168 + 0.2035904j, 3.15559949 + 2.55311298j,
- 4.29394604 + 0.98362617j],
- [3.31324163 + 2.77590131j, 2.11744834 + 1.65894674j,
- 3.59347907 + 1.28895345j, 3.85713467 + 1.90714056j,
- 2.93805426 + 2.63385392j]]])
- ucell = ucell.real
-
- type_complex = 0
- device = 0
- return pol, n_top, n_bot, theta, phi, psi, wavelength, thickness, period, fto, type_complex, device, ucell
-
-
-def optimize_jax(setting):
- pol, n_top, n_bot, theta, phi, psi, wavelength, thickness, period, fto, \
- type_complex, device, ucell = setting
-
- mee = call_mee(backend=1, pol=pol, n_top=n_top, n_bot=n_bot, theta=theta, phi=phi,
- fto=fto, wavelength=wavelength, period=period, ucell=ucell,
- thickness=thickness, device=device,
- type_complex=type_complex)
- ucell = mee.ucell
-
- @jax.grad
- def grad_loss(ucell):
- mee.ucell = ucell
- # de_ri, de_ti, _, _, _ = mee._conv_solve()
- de_ri, de_ti = mee.conv_solve()
- try:
- loss = de_ti[de_ti.shape[0] // 2, de_ti.shape[1] // 2]
- except:
- loss = de_ti[de_ti.shape[0] // 2]
- return loss
-
- def grad_numerical(ucell, delta):
- grad_arr = jnp.zeros(ucell.shape, dtype=ucell.dtype)
- for layer in range(ucell.shape[0]):
- for r in range(ucell.shape[1]):
- for c in range(ucell.shape[2]):
- ucell_delta_m = ucell.at[layer, r, c].set(ucell[layer, r, c] - delta)
- mee.ucell = ucell_delta_m
- # de_ri_delta_m, de_ti_delta_m, _, _, _ = mee._conv_solve()
- de_ri_delta_m, de_ti_delta_m = mee.conv_solve()
- ucell_delta_p = ucell.at[layer, r, c].set(ucell[layer, r, c] + delta)
- mee.ucell = ucell_delta_p
- # de_ri_delta_p, de_ti_delta_p, _, _, _ = mee._conv_solve()
- de_ri_delta_p, de_ti_delta_p = mee.conv_solve()
- try:
- grad_numeric = \
- (de_ti_delta_p[de_ti_delta_p.shape[0] // 2, de_ti_delta_p.shape[1] // 2]
- - de_ti_delta_m[de_ti_delta_p.shape[0] // 2, de_ti_delta_p.shape[1] // 2]) / (2 * delta)
- except:
- grad_numeric = \
- (de_ti_delta_p[de_ti_delta_p.shape[0] // 2]
- - de_ti_delta_m[de_ti_delta_p.shape[0] // 2]) / (2 * delta)
- grad_arr = grad_arr.at[layer, r, c].set(grad_numeric)
-
- return grad_arr
-
- grad_ad = grad_loss(ucell)
- print('JAX grad_ad:\n', grad_ad)
- grad_nume = grad_numerical(ucell, 1E-6)
- print('JAX grad_numeric:\n', grad_nume)
- print('JAX norm: ', jnp.linalg.norm(grad_nume - grad_ad) / grad_nume.size)
-
-
-def optimize_torch(setting):
- """
- out of date.
- Will be updated.
- """
-
- pol, n_top, n_bot, theta, phi, psi, wavelength, thickness, period, fto, \
- type_complex, device, ucell = setting
-
- tmee = call_mee(backend=2, pol=pol, n_top=n_top, n_bot=n_bot, theta=theta, phi=phi,
- fto=fto, wavelength=wavelength, period=period, ucell=ucell,
- thickness=thickness, device=device,
- type_complex=type_complex, )
- tmee.ucell.requires_grad = True
- de_ri, de_ti = tmee.conv_solve()
-
- try:
- loss = de_ti[de_ti.shape[0] // 2, de_ti.shape[1] // 2]
- except:
- loss = de_ti[de_ti.shape[0] // 2]
-
- loss.backward()
- grad_ad = tmee.ucell.grad
-
- def grad_numerical(ucell, delta):
- ucell.requires_grad = False
- grad_arr = torch.zeros(ucell.shape, dtype=ucell.dtype)
-
- for layer in range(ucell.shape[0]):
- for r in range(ucell.shape[1]):
- for c in range(ucell.shape[2]):
- ucell_delta_m = deepcopy(ucell)
- ucell_delta_m[layer, r, c] -= delta
- tmee.ucell = ucell_delta_m
- de_ri_delta_m, de_ti_delta_m = tmee.conv_solve()
-
- ucell_delta_p = deepcopy(ucell)
- ucell_delta_p[layer, r, c] += delta
- tmee.ucell = ucell_delta_p
- de_ri_delta_p, de_ti_delta_p = tmee.conv_solve()
- try:
- grad_numeric = \
- (de_ti_delta_p[de_ti_delta_p.shape[0] // 2, de_ti_delta_p.shape[1] // 2]
- - de_ti_delta_m[de_ti_delta_p.shape[0] // 2, de_ti_delta_p.shape[1] // 2]) / (2 * delta)
- except:
- grad_numeric = \
- (de_ti_delta_p[de_ti_delta_p.shape[0] // 2]
- - de_ti_delta_m[de_ti_delta_p.shape[0] // 2]) / (2 * delta)
- grad_arr[layer, r, c] = grad_numeric
-
- return grad_arr
-
- grad_nume = grad_numerical(tmee.ucell, 1E-6)
- print('Torch grad_ad:\n', grad_ad)
- print('Torch grad_numeric:\n', grad_nume)
- print('torch.norm: ', torch.linalg.norm(grad_nume - grad_ad) / grad_nume.numel())
-
-
-if __name__ == '__main__':
- setting = load_setting()
-
- print('JaxMeent')
- optimize_jax(setting)
-
- print('TorchMeent')
- optimize_torch(setting)
diff --git a/QA/autograd_vector.py b/QA/autograd_vector.py
deleted file mode 100644
index 285ea27..0000000
--- a/QA/autograd_vector.py
+++ /dev/null
@@ -1,84 +0,0 @@
-import torch
-
-import meent
-
-
-backend = 2
-
-period = [1000., 1000.]
-thickness = torch.tensor([300.])
-wavelength = 900
-
-input_length1 = 160
-input_length2 = 100
-input_length3 = 30
-input_length4 = 20
-
-fto = [5, 5]
-
-# layer_base = torch.tensor(n_index_base)
-input_length1 = torch.tensor([input_length1], dtype=torch.float64, requires_grad=True)
-input_length2 = torch.tensor([input_length2], dtype=torch.float64, requires_grad=True)
-input_length3 = torch.tensor([input_length3], dtype=torch.float64, requires_grad=True)
-input_length4 = torch.tensor([input_length4], dtype=torch.float64, requires_grad=True)
-
-mee = meent.call_mee(backend=backend, fto=fto, wavelength=wavelength, thickness=thickness, period=period,
- device=0, type_complex=0)
-
-opt = torch.optim.SGD([input_length1, input_length2, input_length3, input_length4], lr=1E6, momentum=0.9)
-
-
-def forward(length1, length2, length3, length4):
-
- ucell = [
- [3 - 1j, [
- ['rectangle', 0+1000, 410+1000, length1, 80, 4, 0, 0, 0], # obj 1
- ['ellipse', 0+1000, -10+1000, length2, 80, 4, 1, 20, 20], # obj 2
- ['rectangle', 120+1000, 500+1000, length3, 160, 4+0.3j, 1.1, 5, 5], # obj 3
- ['ellipse', -400+1000, -700+1000, length4, 160, 4, 0.4, 20, 20], # obj 4
- ], ],
- ]
- mee.ucell = ucell
-
- de_ri, de_ti = mee.conv_solve()
-
- center = de_ti.shape[0] // 2
- loss = -de_ti[center + 0, center + 0]
-
- return loss
-
-
-for i in range(50):
- print('Parameters: ', input_length1.detach().numpy(), input_length2.detach().numpy(),
- input_length3.detach().numpy(), input_length4.detach().numpy())
- dx = 1E-5
- loss_a = forward(input_length1 + dx, input_length2, input_length3, input_length4)
- loss_b = forward(input_length1 - dx, input_length2, input_length3, input_length4)
- grad1 = (loss_a - loss_b) / (2 * dx)
-
- loss_a = forward(input_length1, input_length2 + dx, input_length3, input_length4)
- loss_b = forward(input_length1, input_length2 - dx, input_length3, input_length4)
- grad2 = (loss_a - loss_b) / (2 * dx)
-
- loss_a = forward(input_length1, input_length2, input_length3 + dx, input_length4)
- loss_b = forward(input_length1, input_length2, input_length3 - dx, input_length4)
- grad3 = (loss_a - loss_b) / (2 * dx)
-
- loss_a = forward(input_length1, input_length2, input_length3, input_length4 + dx)
- loss_b = forward(input_length1, input_length2, input_length3, input_length4 - dx)
- grad4 = (loss_a - loss_b) / (2 * dx)
-
- print('grad_nume: ', grad1.item(), grad2.item(), grad3.item(), grad4.item())
-
- loss = forward(input_length1, input_length2, input_length3, input_length4)
- loss.backward()
- print('grad_auto: ', input_length1.grad.numpy()[0], input_length2.grad.numpy()[0], input_length3.grad.numpy()[0],
- input_length4.grad.numpy()[0])
-
- opt.step()
- opt.zero_grad()
- print('Loss:', loss)
-
- print()
-
-print(input_length1, input_length2, input_length3, input_length4)
diff --git a/QA/fourier_analysis_methods.py b/QA/fourier_analysis_methods.py
index 850289d..0e7268e 100644
--- a/QA/fourier_analysis_methods.py
+++ b/QA/fourier_analysis_methods.py
@@ -28,13 +28,16 @@ def compare_conv_mat_method(backend, type_complex, device):
mee.fourier_type = 0
mee.enhanced_dfs = False
- de_ri_dfs, de_ti_dfs = mee.conv_solve()
+ res_dfs = mee.conv_solve().res
+ de_ri_dfs, de_ti_dfs = res_dfs.de_ri, res_dfs.de_ti
mee.enhanced_dfs = True
- de_ri_efs, de_ti_efs = mee.conv_solve()
+ res_efs = mee.conv_solve().res
+ de_ri_efs, de_ti_efs = res_efs.de_ri, res_efs.de_ti
mee.fourier_type = 1
- de_ri_cfs, de_ti_cfs = mee.conv_solve()
+ res_cfs = mee.conv_solve().res
+ de_ri_cfs, de_ti_cfs = res_cfs.de_ri, res_cfs.de_ti
a = np.linalg.norm(de_ri_dfs - de_ri_efs)
b = np.linalg.norm(de_ti_dfs - de_ti_efs)
@@ -43,9 +46,9 @@ def compare_conv_mat_method(backend, type_complex, device):
e = np.linalg.norm(de_ri_efs - de_ri_cfs)
f = np.linalg.norm(de_ti_efs - de_ti_cfs)
- print('DFS-EFS ', a, b)
- print('DFS-CFS ', c, d)
- print('EFS-CFS ', e, f)
+ print('Norm of DFS-EFS: ', a, b)
+ print('Norm of DFS-CFS: ', c, d)
+ print('Norm of EFS-CFS: ', e, f)
if __name__ == '__main__':
diff --git a/QA/rcwa_backend_consistency.py b/QA/rcwa_backend_consistency.py
index fd9db4b..cc23e6f 100644
--- a/QA/rcwa_backend_consistency.py
+++ b/QA/rcwa_backend_consistency.py
@@ -5,71 +5,70 @@
def consistency_check(option):
- mee = meent.call_mee(backend=0, perturbation=1E-30, **option) # NumPy
- de_ri_numpy, de_ti_numpy = mee.conv_solve()
- field_cell_numpy = mee.calculate_field(res_z=50, res_x=50)
-
- mee = meent.call_mee(backend=1, perturbation=1E-30, **option) # JAX
- de_ri_jax, de_ti_jax = mee.conv_solve()
- field_cell_jax = mee.calculate_field(res_z=50, res_x=50)
-
- mee = meent.call_mee(backend=2, perturbation=1E-30, **option) # PyTorch
- de_ri_torch, de_ti_torch = mee.conv_solve()
- field_cell_torch = mee.calculate_field(res_z=50, res_x=50)
- de_ri_torch, de_ti_torch = de_ri_torch.numpy(), de_ti_torch.numpy()
- field_cell_torch = field_cell_torch.numpy()
-
- digit = 20
-
- res1 = [(np.linalg.norm(de_ri_numpy - de_ri_jax) / de_ri_numpy.size).round(digit),
- (np.linalg.norm(de_ri_jax - de_ri_torch) / de_ri_numpy.size).round(digit),
- (np.linalg.norm(de_ri_torch - de_ri_numpy) / de_ri_numpy.size).round(digit),]
- res2 = [(np.linalg.norm(de_ti_numpy - de_ti_jax) / de_ti_numpy.size).round(digit),
- (np.linalg.norm(de_ti_jax - de_ti_torch) / de_ti_numpy.size).round(digit),
- (np.linalg.norm(de_ti_torch - de_ti_numpy) / de_ti_numpy.size).round(digit),]
- res3 = [(np.linalg.norm(field_cell_numpy - field_cell_jax) / field_cell_numpy.size).round(digit),
- (np.linalg.norm(field_cell_jax - field_cell_torch) / field_cell_numpy.size).round(digit),
- (np.linalg.norm(field_cell_torch - field_cell_numpy) / field_cell_numpy.size).round(digit),]
+ mee = meent.call_mee(backend=0, **option) # NumPy
+ res_nupmy = mee.conv_solve()
+ res_numpy_psi = res_nupmy.res
+ res_numpy_te = res_nupmy.res_te_inc
+ res_numpy_tm = res_nupmy.res_tm_inc
- print('Refle', res1)
- print('Trans', res2)
- print('Field', res3)
-
-
-def consistency_check_vector(option, instructions):
-
- mee = meent.call_mee(backend=0, perturbation=1E-30, **option) # NumPy
- mee.modeling_vector_instruction(instructions)
-
- de_ri_numpy, de_ti_numpy = mee.conv_solve()
field_cell_numpy = mee.calculate_field(res_z=50, res_x=50)
- mee = meent.call_mee(backend=1, perturbation=1E-30, **option) # JAX
- mee.modeling_vector_instruction(instructions)
- de_ri_jax, de_ti_jax = mee.conv_solve()
+ mee = meent.call_mee(backend=1, **option) # JAX
+ res_jax = mee.conv_solve()
+ res_jax_psi = res_jax.res
+ res_jax_te = res_jax.res_te_inc
+ res_jax_tm = res_jax.res_tm_inc
+
field_cell_jax = mee.calculate_field(res_z=50, res_x=50)
- mee = meent.call_mee(backend=2, perturbation=1E-30, **option) # PyTorch
- mee.modeling_vector_instruction(instructions)
- de_ri_torch, de_ti_torch = mee.conv_solve()
+ mee = meent.call_mee(backend=2, **option) # PyTorch
+ res_torch = mee.conv_solve()
+ res_torch_psi = res_torch.res
+ res_torch_te = res_torch.res_te_inc
+ res_torch_tm = res_torch.res_tm_inc
+
field_cell_torch = mee.calculate_field(res_z=50, res_x=50)
- de_ri_torch, de_ti_torch = de_ri_torch.numpy(), de_ti_torch.numpy()
field_cell_torch = field_cell_torch.numpy()
- digit = 20
-
- res1 = [(np.linalg.norm(de_ri_numpy - de_ri_jax) / de_ri_numpy.size).round(digit),
- (np.linalg.norm(de_ri_jax - de_ri_torch) / de_ri_numpy.size).round(digit),
- (np.linalg.norm(de_ri_torch - de_ri_numpy) / de_ri_numpy.size).round(digit),]
- res2 = [(np.linalg.norm(de_ti_numpy - de_ti_jax) / de_ti_numpy.size).round(digit),
- (np.linalg.norm(de_ti_jax - de_ti_torch) / de_ti_numpy.size).round(digit),
- (np.linalg.norm(de_ti_torch - de_ti_numpy) / de_ti_numpy.size).round(digit),]
- res3 = [(np.linalg.norm(field_cell_numpy - field_cell_jax) / field_cell_numpy.size).round(digit),
- (np.linalg.norm(field_cell_jax - field_cell_torch) / field_cell_numpy.size).round(digit),
- (np.linalg.norm(field_cell_torch - field_cell_numpy) / field_cell_numpy.size).round(digit),]
+ check_attr = ['R_s', 'R_p', 'T_s', 'T_p', 'de_ri_s', 'de_ri_p', 'de_ri', 'de_ti_s', 'de_ti_p', 'de_ti']
+
+ print('res_psi')
+ for attr in check_attr:
+ a = getattr(res_numpy_psi, attr)
+ b = getattr(res_jax_psi, attr)
+ c = getattr(res_torch_psi, attr).numpy()
+
+ res1 = [float(np.linalg.norm(a - b) / a.size),
+ float(np.linalg.norm(b - c) / a.size),
+ float(np.linalg.norm(c - a) / a.size),]
+ print(attr, res1)
+
+ print('res_te')
+ for attr in check_attr:
+ a = getattr(res_numpy_te, attr)
+ b = getattr(res_jax_te, attr)
+ c = getattr(res_torch_te, attr).numpy()
+
+ res1 = [float(np.linalg.norm(a - b) / a.size),
+ float(np.linalg.norm(b - c) / a.size),
+ float(np.linalg.norm(c - a) / a.size),]
+ print(attr, res1)
+
+ print('res_tm')
+ for attr in check_attr:
+ a = getattr(res_numpy_tm, attr)
+ b = getattr(res_jax_tm, attr)
+ c = getattr(res_torch_tm, attr).numpy()
+
+ res1 = [float(np.linalg.norm(a - b) / a.size),
+ float(np.linalg.norm(b - c) / a.size),
+ float(np.linalg.norm(c - a) / a.size),]
+ print(attr, res1)
+
+ res3 = [float(np.linalg.norm(field_cell_numpy - field_cell_jax) / field_cell_numpy.size),
+ float(np.linalg.norm(field_cell_jax - field_cell_torch) / field_cell_numpy.size),
+ float(np.linalg.norm(field_cell_torch - field_cell_numpy) / field_cell_numpy.size),]
- print('Refle', res1)
- print('Trans', res2)
print('Field', res3)
@@ -83,11 +82,11 @@ def consistency_check_vector(option, instructions):
'ucell': np.array([[[3, 3, 3.3, 3, 3, 4, 1, 1, 1, 1.2, 1.1, 3, 2, 1.1]], ])}
option3 = {'psi': 40/180*np.pi, 'n_top': 1, 'n_bot': 1, 'theta': 0 * np.pi / 180, 'phi': 12 * np.pi / 180,
- 'fto': 1,
+ 'fto': 80,
'period': [200], 'wavelength': 1000, 'thickness': [100], 'fourier_type': 0, 'enhanced_dfs': False,
'ucell': np.array([[[3, 3, 3.3, 3, 3, 4, 1, 1, 1, 1.2, 1.1, 3, 2, 1.1]], ])}
- option4 = {'psi': 10/180*np.pi, 'n_top': 1, 'n_bot': 1, 'theta': 0 * np.pi / 180, 'phi': 12 * np.pi / 180,
+ option4 = {'psi': 10/180*np.pi, 'n_top': 1, 'n_bot': 1.5, 'theta': 30 * np.pi / 180, 'phi': 0 * np.pi / 180,
'fto': [10, 10],
'period': [200, 600], 'wavelength': 1000, 'thickness': [100, 111, 222, 102, 44], 'fourier_type': 0,
'enhanced_dfs': True,
@@ -96,41 +95,40 @@ def consistency_check_vector(option, instructions):
ucell5 = [
# layer 1
[1,[
- ['rectangle', 0+240, 120+240, 160, 80, 4, 0, 0, 0], # obj 1
+ ['rectangle', 0+240, 120+240, 160, 80, 4, 1, 20, 20], # obj 1
['rectangle', 0+240, -120+240, 160, 80, 4, 0, 0, 0], # obj 2
['rectangle', 120+240, 0+240, 80, 160, 4, 0, 0, 0], # obj 3
['rectangle', -120+240, 0+240, 80, 160, 4, 0, 0, 0], # obj 4
], ],
]
- option5 = {'pol': 0, 'n_top': 2, 'n_bot': 1, 'theta': 12 * np.pi / 180, 'phi': 0 * np.pi / 180, 'fto': 0,
+ option5 = {'pol': 0, 'n_top': 2, 'n_bot': 1, 'theta': 12 * np.pi / 180, 'phi': 0 * np.pi / 180, 'fto': [5,5],
'period': [770], 'wavelength': 777, 'thickness': [100], 'fourier_type': 0,
'ucell': ucell5}
ucell6 = [
# layer 1
- [3 - 1j, [
+ [3, [
['rectangle', 0+1000, 410+1000, 160, 80, 4, 0, 0, 0], # obj 1
['ellipse', 0+1000, -10+1000, 160, 80, 4, 1, 20, 20], # obj 2
- ['rectangle', 120+1000, 500+1000, 80, 160, 4+0.3j, 1.1, 5, 5], # obj 3
+ ['rectangle', 120+1000, 500+1000, 80, 160, 4, 1, 5, 5], # obj 3
['ellipse', -400+1000, -700+1000, 80, 160, 4, 0.4, 20, 20], # obj 4
], ],
# layer 2
- [3.1, [
- ['rectangle', 0+240, 120+240, 160, 80, 4, 0.4, 5, 5], # obj 1
- ['ellipse', 0+240, -120+240, 160, 80, 4, 0.1, 20, 20], # obj 2
- ['ellipse', 120+240, 0+240, 80, 160, 4, 1, 20, 20], # obj 3
- ['rectangle', -120+240, 0+240, 80, 160, 4, 2, 5, 5], # obj 4
+ [3.1 - 1j, [
+ ['rectangle', 0+240, 120+240, 160, 80, 4, 0, 10, 10], # obj 1
+ ['ellipse', 0+240, -120+240, 160, 80, 4, 0, 20, 20], # obj 2
+ ['ellipse', 120+240, 0+240, 80, 160, 4, 0.4, 20, 20], # obj 3
+ ['rectangle', -120+240, 0+240, 80, 160, 4+3j, 1, 10, 10], # obj 4
], ],
]
- option6 = {'pol': 0, 'n_top': 2, 'n_bot': 1, 'theta': 12 * np.pi / 180, 'phi': 0 * np.pi / 180, 'fto': [5,5],
- 'period': [770], 'wavelength': 777, 'thickness': [100, 333], 'fourier_type': 0,
+ option6 = {'pol': 0, 'n_top': 2, 'n_bot': 2, 'theta': 2 * np.pi / 180, 'phi': 10 * np.pi / 180, 'fto': [5, 5],
+ 'period': [770], 'wavelength': 777, 'thickness': [100, 90], 'fourier_type': 0,
'ucell': ucell6}
- # consistency_check(option1)
- # consistency_check(option2)
- # consistency_check(option3)
- # consistency_check(option4)
+ cands = [option1, option2, option3, option4, option5, option6]
+ # cands = [option6]
+ for i, case in enumerate(cands):
- consistency_check(option5)
- consistency_check(option6)
+ print(f'case {i+1}')
+ consistency_check(case)
diff --git a/benchmarks/interface/Reticolo.py b/benchmarks/interface/Reticolo.py
index 19ecee1..11e507f 100644
--- a/benchmarks/interface/Reticolo.py
+++ b/benchmarks/interface/Reticolo.py
@@ -97,7 +97,7 @@ def run_res3(self, grating_type, period, fto, ucell, thickness, theta, phi, pol,
matlab_plot_field=0, res3_npts=0, *args, **kwargs):
# theta *= (180 / np.pi)
- phi *= (180 / np.pi)
+ # phi *= (180 / np.pi)
if grating_type in (0, 1):
period = period[0]
@@ -148,22 +148,32 @@ def run_res3(self, grating_type, period, fto, ucell, thickness, theta, phi, pol,
profile = np.array([[0, *thickness, 0], range(1, len(thickness) + 3)])
- top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, field_cell = \
+ (top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm, bottom_refl_info_te,
+ bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, field_cell) = \
self._run(pol, theta, phi, period, n_top, fto, textures, profile, wavelength, grating_type,
cal_field=True, matlab_plot_field=matlab_plot_field, res3_npts=res3_npts)
- return top_refl_info.efficiency, top_tran_info.efficiency, bottom_refl_info.efficiency, \
- bottom_tran_info.efficiency, field_cell
+ return (top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm,
+ bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, field_cell)
def _run(self, pol, theta, phi, period, n_top, fto,
textures, profile, wavelength, grating_type, cal_field=False, matlab_plot_field=0, res3_npts=0):
+ if phi is None:
+ phi = 0
+ else:
+ phi = phi * (180 / np.pi)
+
if cal_field:
- top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, field_cell = \
+ # top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, field_cell = \
+ (top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm, bottom_refl_info_te,
+ bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, field_cell) = \
self.eng.reticolo_res3(pol, theta, phi, period, n_top, fto,
textures, profile, wavelength, grating_type, matlab_plot_field, res3_npts,
- nout=5)
- res = (top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, field_cell)
+ nout=9)
+
+ res = (top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm, bottom_refl_info_te,
+ bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, field_cell)
else:
top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info = \
self.eng.reticolo_res2(pol, theta, phi, period, n_top, fto,
diff --git a/benchmarks/interface/reti_2d.m b/benchmarks/interface/reti_2d.m
index 5b53e8b..182b935 100644
--- a/benchmarks/interface/reti_2d.m
+++ b/benchmarks/interface/reti_2d.m
@@ -1,26 +1,28 @@
-function [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = reti_2d(ex_case);
+function [top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm, bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, e] = reti_2d(ex_case);
warning('off', 'Octave:possible-matlab-short-circuit-operator');
warning('off', 'Invalid UTF-8 byte sequences have been replaced.');
warning('off', 'findstr is obsolete; use strfind instead');
if ex_case == 1
- [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = reti_2d_1();
+ [top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm, bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, e] = reti_2d_1();
elseif ex_case == 2
- [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = reti_2d_2();
+ [top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm, bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, e] = reti_2d_2();
elseif ex_case == 3
- [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = reti_2d_3();
+ [top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm, bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, e] = reti_2d_3();
elseif ex_case == 4
- [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = reti_2d_4();
+ [top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm, bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, e] = reti_2d_4();
elseif ex_case == 5
- [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = reti_2d_5();
+ [top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm, bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, e] = reti_2d_5();
elseif ex_case == 6
- [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = reti_2d_6();
+ [top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm, bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, e] = reti_2d_6();
+ elseif ex_case == 7
+ [top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm, bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, e] = reti_2d_7();
end
end
-function [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = reti_2d_1();
+function [top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm, bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, e] = reti_2d_1();
warning('off', 'Octave:possible-matlab-short-circuit-operator');
warning('off', 'Invalid UTF-8 byte sequences have been replaced.');
@@ -71,10 +73,10 @@
aa = res1(wavelength,period,textures,nn,k_parallel, phi, parm);
res = res2(aa, profile);
- x = linspace(-period(1)/2, period(1)/2, 50);
- y = linspace(period(2)/2, -period(2)/2, 50);
- x = linspace(0, period(1), 50);
- y = linspace(period(2), 0, 50);
+ x = linspace(-period(1)/2, period(1)/2, 11);
+ y = linspace(period(2)/2, -period(2)/2, 11);
+ x = linspace(0, period(1), 11);
+ y = linspace(period(2), 0, 11);
% x = [0:1:49] * period(1) / 50 - period(1)/2;
% x = [1:1:50] * period(1) / 50 - period(1)/2;
@@ -93,20 +95,31 @@
end
[e,z,o]=res3(x,y,aa,profile,einc, parm);
- if pol == 1 % TE
- top_refl_info = res.TEinc_top_reflected;
- top_tran_info = res.TEinc_top_transmitted;
- bottom_refl_info = res.TEinc_bottom_reflected;
- bottom_tran_info = res.TEinc_bottom_transmitted;
- else % TM
- top_refl_info = res.TMinc_top_reflected;
- top_tran_info = res.TMinc_top_transmitted;
- bottom_refl_info = res.TMinc_bottom_reflected;
- bottom_tran_info = res.TMinc_bottom_transmitted;
- end
+% if pol == 1 % TE
+% top_refl_info = res.TEinc_top_reflected;
+% top_tran_info = res.TEinc_top_transmitted;
+% bottom_refl_info = res.TEinc_bottom_reflected;
+% bottom_tran_info = res.TEinc_bottom_transmitted;
+% else % TM
+% top_refl_info = res.TMinc_top_reflected;
+% top_tran_info = res.TMinc_top_transmitted;
+% bottom_refl_info = res.TMinc_bottom_reflected;
+% bottom_tran_info = res.TMinc_bottom_transmitted;
+% end
+
+ top_refl_info_te = res.TEinc_top_reflected;
+ top_tran_info_te = res.TEinc_top_transmitted;
+ top_refl_info_tm = res.TMinc_top_reflected;
+ top_tran_info_tm = res.TMinc_top_transmitted;
+
+ bottom_refl_info_te = res.TEinc_bottom_reflected;
+ bottom_tran_info_te = res.TEinc_bottom_transmitted;
+ bottom_refl_info_tm = res.TMinc_bottom_reflected;
+ bottom_tran_info_tm = res.TMinc_bottom_transmitted;
+
end
-function [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = reti_2d_2();
+function [top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm, bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, e] = reti_2d_2();
warning('off', 'Octave:possible-matlab-short-circuit-operator');
warning('off', 'Invalid UTF-8 byte sequences have been replaced.');
@@ -152,11 +165,11 @@
aa = res1(wavelength,period,textures,nn,k_parallel, phi, parm);
res = res2(aa, profile);
- x = linspace(-period(1)/2, period(1)/2, 50);
- y = linspace(-period(2)/2, period(2)/2, 50);
- y = linspace(period(2)/2, -period(2)/2, 50);
- x = linspace(0, period(1), 50);
- y = linspace(period(2), 0, 50);
+ x = linspace(-period(1)/2, period(1)/2, 11);
+ y = linspace(-period(2)/2, period(2)/2, 11);
+ y = linspace(period(2)/2, -period(2)/2, 11);
+ x = linspace(0, period(1), 11);
+ y = linspace(period(2), 0, 11);
% x = [0:1:49] * period(1) / 50 - period(1)/2;
% x = [1:1:50] * period(1) / 50 - period(1)/2;
@@ -165,7 +178,7 @@
% y = [50:-1:1] .* period(2) / 50 - period(2)/2
% y = [49:-1:0] .* period(2) / 50 - period(2)/2
- parm.res3.trace=1; %trace automatique % automatic trace
+ parm.res3.trace=0; %trace automatique % automatic trace
if pol == 1
einc = [0, 1];
@@ -176,21 +189,31 @@
end
[e,z,o]=res3(x,y,aa,profile,einc, parm);
- if pol == 1 % TE
- top_refl_info = res.TEinc_top_reflected;
- top_tran_info = res.TEinc_top_transmitted;
- bottom_refl_info = res.TEinc_bottom_reflected;
- bottom_tran_info = res.TEinc_bottom_transmitted;
- else % TM
- top_refl_info = res.TMinc_top_reflected;
- top_tran_info = res.TMinc_top_transmitted;
- bottom_refl_info = res.TMinc_bottom_reflected;
- bottom_tran_info = res.TMinc_bottom_transmitted;
- end
+% if pol == 1 % TE
+% top_refl_info = res.TEinc_top_reflected;
+% top_tran_info = res.TEinc_top_transmitted;
+% bottom_refl_info = res.TEinc_bottom_reflected;
+% bottom_tran_info = res.TEinc_bottom_transmitted;
+% else % TM
+% top_refl_info = res.TMinc_top_reflected;
+% top_tran_info = res.TMinc_top_transmitted;
+% bottom_refl_info = res.TMinc_bottom_reflected;
+% bottom_tran_info = res.TMinc_bottom_transmitted;
+% end
+
+ top_refl_info_te = res.TEinc_top_reflected;
+ top_tran_info_te = res.TEinc_top_transmitted;
+ top_refl_info_tm = res.TMinc_top_reflected;
+ top_tran_info_tm = res.TMinc_top_transmitted;
+
+ bottom_refl_info_te = res.TEinc_bottom_reflected;
+ bottom_tran_info_te = res.TEinc_bottom_transmitted;
+ bottom_refl_info_tm = res.TMinc_bottom_reflected;
+ bottom_tran_info_tm = res.TMinc_bottom_transmitted;
end
-function [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = reti_2d_3();
+function [top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm, bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, e] = reti_2d_3();
warning('off', 'Octave:possible-matlab-short-circuit-operator');
warning('off', 'Invalid UTF-8 byte sequences have been replaced.');
@@ -219,7 +242,7 @@
parm = res0;
parm.res1.champ = 1; % calculate precisely
- parm.res1.trace = 1;
+ parm.res1.trace = 0;
k_parallel = n_top*sind(theta); % n_air, or whatever the refractive index of the medium where light is coming in.
@@ -233,11 +256,11 @@
aa = res1(wavelength,period,textures,nn,k_parallel, phi, parm);
res = res2(aa, profile);
- x = linspace(-period(1)/2, period(1)/2, 50);
- y = linspace(-period(2)/2, period(2)/2, 50);
- y = linspace(period(2)/2, -period(2)/2, 50);
- x = linspace(0, period(1), 50);
- y = linspace(period(2), 0, 50);
+ x = linspace(-period(1)/2, period(1)/2, 11);
+ y = linspace(-period(2)/2, period(2)/2, 11);
+ y = linspace(period(2)/2, -period(2)/2, 11);
+ x = linspace(0, period(1), 11);
+ y = linspace(period(2), 0, 11);
% x = [0:1:49] * period(1) / 50 - period(1)/2;
% x = [1:1:50] * period(1) / 50 - period(1)/2;
@@ -246,7 +269,7 @@
% y = [50:-1:1] .* period(2) / 50 - period(2)/2
% y = [49:-1:0] .* period(2) / 50 - period(2)/2
- parm.res3.trace=1; %trace automatique % automatic trace
+ parm.res3.trace=0; %trace automatique % automatic trace
if pol == 1
einc = [0, 1];
@@ -257,21 +280,31 @@
end
[e,z,o]=res3(x,y,aa,profile,einc, parm);
- if pol == 1 % TE
- top_refl_info = res.TEinc_top_reflected;
- top_tran_info = res.TEinc_top_transmitted;
- bottom_refl_info = res.TEinc_bottom_reflected;
- bottom_tran_info = res.TEinc_bottom_transmitted;
- else % TM
- top_refl_info = res.TMinc_top_reflected;
- top_tran_info = res.TMinc_top_transmitted;
- bottom_refl_info = res.TMinc_bottom_reflected;
- bottom_tran_info = res.TMinc_bottom_transmitted;
- end
+% if pol == 1 % TE
+% top_refl_info = res.TEinc_top_reflected;
+% top_tran_info = res.TEinc_top_transmitted;
+% bottom_refl_info = res.TEinc_bottom_reflected;
+% bottom_tran_info = res.TEinc_bottom_transmitted;
+% else % TM
+% top_refl_info = res.TMinc_top_reflected;
+% top_tran_info = res.TMinc_top_transmitted;
+% bottom_refl_info = res.TMinc_bottom_reflected;
+% bottom_tran_info = res.TMinc_bottom_transmitted;
+% end
+
+ top_refl_info_te = res.TEinc_top_reflected;
+ top_tran_info_te = res.TEinc_top_transmitted;
+ top_refl_info_tm = res.TMinc_top_reflected;
+ top_tran_info_tm = res.TMinc_top_transmitted;
+
+ bottom_refl_info_te = res.TEinc_bottom_reflected;
+ bottom_tran_info_te = res.TEinc_bottom_transmitted;
+ bottom_refl_info_tm = res.TMinc_bottom_reflected;
+ bottom_tran_info_tm = res.TMinc_bottom_transmitted;
end
-function [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = reti_2d_4();
+function [top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm, bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, e] = reti_2d_4();
warning('off', 'Octave:possible-matlab-short-circuit-operator');
warning('off', 'Invalid UTF-8 byte sequences have been replaced.');
@@ -302,7 +335,7 @@
parm = res0;
parm.res1.champ = 1; % calculate precisely
- parm.res1.trace = 1;
+ parm.res1.trace = 0;
k_parallel = n_top*sind(theta); % n_air, or whatever the refractive index of the medium where light is coming in.
@@ -335,8 +368,8 @@
% x = linspace(-period(1)/2, period(1)/2, 50);
% y = linspace(-period(2)/2, period(2)/2, 50);
% y = linspace(period(2)/2, -period(2)/2, 50);
- x = linspace(0, period(1), 50);
- y = linspace(period(2), 0, 50);
+ x = linspace(0, period(1), 11);
+ y = linspace(period(2), 0, 11);
% x = [0:1:49] * period(1) / 50 - period(1)/2;
% x = [1:1:50] * period(1) / 50 - period(1)/2;
@@ -345,7 +378,7 @@
% y = [50:-1:1] .* period(2) / 50 - period(2)/2
% y = [49:-1:0] .* period(2) / 50 - period(2)/2
- parm.res3.trace=1; %trace automatique % automatic trace
+ parm.res3.trace=0; %trace automatique % automatic trace
% parm.res3.npts = res3_npts;
@@ -358,20 +391,30 @@
end
[e,z,o]=res3(x,y,aa,profile,einc, parm);
- if pol == 1 % TE
- top_refl_info = res.TEinc_top_reflected;
- top_tran_info = res.TEinc_top_transmitted;
- bottom_refl_info = res.TEinc_bottom_reflected;
- bottom_tran_info = res.TEinc_bottom_transmitted;
- else % TM
- top_refl_info = res.TMinc_top_reflected;
- top_tran_info = res.TMinc_top_transmitted;
- bottom_refl_info = res.TMinc_bottom_reflected;
- bottom_tran_info = res.TMinc_bottom_transmitted;
- end
+% if pol == 1 % TE
+% top_refl_info = res.TEinc_top_reflected;
+% top_tran_info = res.TEinc_top_transmitted;
+% bottom_refl_info = res.TEinc_bottom_reflected;
+% bottom_tran_info = res.TEinc_bottom_transmitted;
+% else % TM
+% top_refl_info = res.TMinc_top_reflected;
+% top_tran_info = res.TMinc_top_transmitted;
+% bottom_refl_info = res.TMinc_bottom_reflected;
+% bottom_tran_info = res.TMinc_bottom_transmitted;
+% end
+
+ top_refl_info_te = res.TEinc_top_reflected;
+ top_tran_info_te = res.TEinc_top_transmitted;
+ top_refl_info_tm = res.TMinc_top_reflected;
+ top_tran_info_tm = res.TMinc_top_transmitted;
+
+ bottom_refl_info_te = res.TEinc_bottom_reflected;
+ bottom_tran_info_te = res.TEinc_bottom_transmitted;
+ bottom_refl_info_tm = res.TMinc_bottom_reflected;
+ bottom_tran_info_tm = res.TMinc_bottom_transmitted;
end
-function [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = reti_2d_5();
+function [top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm, bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, e] = reti_2d_5();
warning('off', 'Octave:possible-matlab-short-circuit-operator');
warning('off', 'Invalid UTF-8 byte sequences have been replaced.');
@@ -418,7 +461,7 @@
parm = res0;
parm.res1.champ = 1; % calculate precisely
- parm.res1.trace = 1;
+ parm.res1.trace = 0;
k_parallel = n_top*sind(theta); % n_air, or whatever the refractive index of the medium where light is coming in.
@@ -448,13 +491,13 @@
%parm.res3.sens=1;
%##parm.res3.gauss_x = 100
- x = linspace(-period(1)/2, period(1)/2, 50);
- y = linspace(-period(2)/2, period(2)/2, 50);
- y = linspace(period(2)/2, -period(2)/2, 50);
+ x = linspace(-period(1)/2, period(1)/2, 11);
+ y = linspace(-period(2)/2, period(2)/2, 11);
+ y = linspace(period(2)/2, -period(2)/2, 11);
- x = linspace(0, period(1), 50);
- y = linspace(period(2), 0, 50);
+ x = linspace(0, period(1), 11);
+ y = linspace(period(2), 0, 11);
% x = [0:1:49] * period(1) / 50 - period(1)/2;
% x = [1:1:50] * period(1) / 50 - period(1)/2;
@@ -463,7 +506,7 @@
% y = [50:-1:1] .* period(2) / 50 - period(2)/2
% y = [49:-1:0] .* period(2) / 50 - period(2)/2
- parm.res3.trace=1; %trace automatique % automatic trace
+ parm.res3.trace=0; %trace automatique % automatic trace
% parm.res3.npts = res3_npts;
@@ -476,20 +519,30 @@
end
[e,z,o]=res3(x,y,aa,profile,einc, parm);
- if pol == 1 % TE
- top_refl_info = res.TEinc_top_reflected;
- top_tran_info = res.TEinc_top_transmitted;
- bottom_refl_info = res.TEinc_bottom_reflected;
- bottom_tran_info = res.TEinc_bottom_transmitted;
- else % TM
- top_refl_info = res.TMinc_top_reflected;
- top_tran_info = res.TMinc_top_transmitted;
- bottom_refl_info = res.TMinc_bottom_reflected;
- bottom_tran_info = res.TMinc_bottom_transmitted;
- end
+% if pol == 1 % TE
+% top_refl_info = res.TEinc_top_reflected;
+% top_tran_info = res.TEinc_top_transmitted;
+% bottom_refl_info = res.TEinc_bottom_reflected;
+% bottom_tran_info = res.TEinc_bottom_transmitted;
+% else % TM
+% top_refl_info = res.TMinc_top_reflected;
+% top_tran_info = res.TMinc_top_transmitted;
+% bottom_refl_info = res.TMinc_bottom_reflected;
+% bottom_tran_info = res.TMinc_bottom_transmitted;
+% end
+
+ top_refl_info_te = res.TEinc_top_reflected;
+ top_tran_info_te = res.TEinc_top_transmitted;
+ top_refl_info_tm = res.TMinc_top_reflected;
+ top_tran_info_tm = res.TMinc_top_transmitted;
+
+ bottom_refl_info_te = res.TEinc_bottom_reflected;
+ bottom_tran_info_te = res.TEinc_bottom_transmitted;
+ bottom_refl_info_tm = res.TMinc_bottom_reflected;
+ bottom_tran_info_tm = res.TMinc_bottom_transmitted;
end
-function [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = reti_2d_6();
+function [top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm, bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, e] = reti_2d_6();
warning('off', 'Octave:possible-matlab-short-circuit-operator');
warning('off', 'Invalid UTF-8 byte sequences have been replaced.');
@@ -520,7 +573,7 @@
parm = res0;
parm.res1.champ = 1; % calculate precisely
- parm.res1.trace = 1;
+ parm.res1.trace = 0;
k_parallel = n_top*sind(theta); % n_air, or whatever the refractive index of the medium where light is coming in.
@@ -531,7 +584,7 @@
% parm.res3.npts=[0,1,0];
parm.res3.npts=[11,11,11];
- %parm.res1.trace = 1; % show the texture
+ %parm.res1.trace = 0; % show the texture
%
%textures = cell(1, size(_textures, 2));
%for i = 1:length(_textures)
@@ -553,8 +606,8 @@
% x = linspace(-period(1)/2, period(1)/2, 50);
% y = linspace(-period(2)/2, period(2)/2, 50);
% y = linspace(period(2)/2, -period(2)/2, 50);
- x = linspace(0, period(1), 50);
- y = linspace(period(2), 0, 50);
+ x = linspace(0, period(1), 11);
+ y = linspace(period(2), 0, 11);
% x = [0:1:49] * period(1) / 50 - period(1)/2;
% x = [1:1:50] * period(1) / 50 - period(1)/2;
@@ -563,7 +616,7 @@
% y = [50:-1:1] .* period(2) / 50 - period(2)/2
% y = [49:-1:0] .* period(2) / 50 - period(2)/2
- parm.res3.trace=1; %trace automatique % automatic trace
+ parm.res3.trace=0; %trace automatique % automatic trace
% parm.res3.npts = res3_npts;
@@ -576,17 +629,137 @@
end
[e,z,o]=res3(x,y,aa,profile,einc, parm);
- if pol == 1 % TE
- top_refl_info = res.TEinc_top_reflected;
- top_tran_info = res.TEinc_top_transmitted;
- bottom_refl_info = res.TEinc_bottom_reflected;
- bottom_tran_info = res.TEinc_bottom_transmitted;
- else % TM
- top_refl_info = res.TMinc_top_reflected;
- top_tran_info = res.TMinc_top_transmitted;
- bottom_refl_info = res.TMinc_bottom_reflected;
- bottom_tran_info = res.TMinc_bottom_transmitted;
+% if pol == 1 % TE
+% top_refl_info = res.TEinc_top_reflected;
+% top_tran_info = res.TEinc_top_transmitted;
+% bottom_refl_info = res.TEinc_bottom_reflected;
+% bottom_tran_info = res.TEinc_bottom_transmitted;
+% else % TM
+% top_refl_info = res.TMinc_top_reflected;
+% top_tran_info = res.TMinc_top_transmitted;
+% bottom_refl_info = res.TMinc_bottom_reflected;
+% bottom_tran_info = res.TMinc_bottom_transmitted;
+% end
+
+ top_refl_info_te = res.TEinc_top_reflected;
+ top_tran_info_te = res.TEinc_top_transmitted;
+ top_refl_info_tm = res.TMinc_top_reflected;
+ top_tran_info_tm = res.TMinc_top_transmitted;
+
+ bottom_refl_info_te = res.TEinc_bottom_reflected;
+ bottom_tran_info_te = res.TEinc_bottom_transmitted;
+ bottom_refl_info_tm = res.TMinc_bottom_reflected;
+ bottom_tran_info_tm = res.TMinc_bottom_transmitted;
+end
+
+function [top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm, bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, e] = reti_2d_7();
+
+ warning('off', 'Octave:possible-matlab-short-circuit-operator');
+ warning('off', 'Invalid UTF-8 byte sequences have been replaced.');
+ warning('off', 'findstr is obsolete; use strfind instead');
+
+ factor = 1;
+ pol = 1;
+ n_top = 1;
+ n_bot = 1;
+ theta = 10;
+ phi = 20;
+ nn = [2,2];
+ period = [480/factor, 480/factor];
+ wavelength = 550/factor;
+ thickness = 220/factor;
+
+
+ a = [0+240, 120+240, 160, 80, 4-1i, 1];
+ b = [0+240, -120+240, 160, 80, 4, 1];
+ c = [120+240, 0+240, 80, 160, 4+5i, 1];
+ d = [-120+240, 0+240, 80, 160, 4, 1];
+
+ textures = cell(1,3);
+ textures{1} = n_top;
+ textures{2} = {1,a, b, c, d};
+ textures{3} = n_bot;
+
+
+ parm = res0;
+ parm.res1.champ = 1; % calculate precisely
+ parm.res1.trace = 0;
+
+ k_parallel = n_top*sind(theta); % n_air, or whatever the refractive index of the medium where light is coming in.
+
+ parm = res0;
+
+ parm.not_io = 1; % no write data on hard disk
+ parm.res1.champ = 1; % the electromagnetic field is calculated accurately
+% parm.res3.npts=[0,1,0];
+ parm.res3.npts=[11,11,11];
+
+ %parm.res1.trace = 0; % show the texture
+ %
+ %textures = cell(1, size(_textures, 2));
+ %for i = 1:length(_textures)
+ % textures(i) = _textures(i);
+ %end
+ %
+ %profile = cell(1, size(_profile, 1));
+ %profile(1) = _profile(1, :);
+ %profile(2) = _profile(2, :);
+
+ profile = {[0, thickness, 0], [1, 2, 3]};
+ aa = res1(wavelength,period,textures,nn,k_parallel, phi, parm);
+ res = res2(aa, profile);
+
+ %res3(aa)
+ %parm.res3.sens=1;
+ %##parm.res3.gauss_x = 100
+
+% x = linspace(-period(1)/2, period(1)/2, 50);
+% y = linspace(-period(2)/2, period(2)/2, 50);
+% y = linspace(period(2)/2, -period(2)/2, 50);
+ x = linspace(0, period(1), 11);
+ y = linspace(period(2), 0, 11);
+
+% x = [0:1:49] * period(1) / 50 - period(1)/2;
+% x = [1:1:50] * period(1) / 50 - period(1)/2;
+% y = [0:1:49] .* period(2) / 50 - period(2)/2
+% y = [1:1:50] .* period(2) / 50 - period(2)/2
+% y = [50:-1:1] .* period(2) / 50 - period(2)/2
+% y = [49:-1:0] .* period(2) / 50 - period(2)/2
+
+ parm.res3.trace=0; %trace automatique % automatic trace
+
+% parm.res3.npts = res3_npts;
+
+ if pol == 1
+ einc = [0, 1];
+ elseif pol == -1
+ einc = [1, 0];
+ else
+ disp('only TE or TM is allowed.');
end
+ [e,z,o]=res3(x,y,aa,profile,einc, parm);
+
+% if pol == 1 % TE
+% top_refl_info = res.TEinc_top_reflected;
+% top_tran_info = res.TEinc_top_transmitted;
+% bottom_refl_info = res.TEinc_bottom_reflected;
+% bottom_tran_info = res.TEinc_bottom_transmitted;
+% else % TM
+% top_refl_info = res.TMinc_top_reflected;
+% top_tran_info = res.TMinc_top_transmitted;
+% bottom_refl_info = res.TMinc_bottom_reflected;
+% bottom_tran_info = res.TMinc_bottom_transmitted;
+% end
+
+ top_refl_info_te = res.TEinc_top_reflected;
+ top_tran_info_te = res.TEinc_top_transmitted;
+ top_refl_info_tm = res.TMinc_top_reflected;
+ top_tran_info_tm = res.TMinc_top_transmitted;
+
+ bottom_refl_info_te = res.TEinc_bottom_reflected;
+ bottom_tran_info_te = res.TEinc_bottom_transmitted;
+ bottom_refl_info_tm = res.TMinc_bottom_reflected;
+ bottom_tran_info_tm = res.TMinc_bottom_transmitted;
end
% Divides the given geometry into rectangles to be used in Reticolo
diff --git a/benchmarks/interface/reticolo_res3.m b/benchmarks/interface/reticolo_res3.m
index e1d8675..5262dbf 100644
--- a/benchmarks/interface/reticolo_res3.m
+++ b/benchmarks/interface/reticolo_res3.m
@@ -1,4 +1,6 @@
-function [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = reticolo_res3(_pol,
+%function [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = reticolo_res3(_pol,
+function [top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm, bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, e] = reticolo_res3(_pol,
+%function [res] = reticolo_res3(_pol,
theta, phi, period, n_inc, nn, _textures, _profile, wavelength, grating_type, matlab_plot_field, res3_npts);
%UNTITLED4 Summary of this function goes here
% Detailed explanation goes here
@@ -41,7 +43,7 @@
res = res2(aa, profile);
end
-x = linspace(0, period(1), 50);
+x = linspace(0, period(1), 11);
%parm.res3.sens=1;
%##parm.res3.gauss_x = 100
@@ -58,10 +60,11 @@
if grating_type == 0
[e,z,o]=res3(x,aa,profile,1,parm);
else
+% y = linspace(period(1), 0, 50);
if grating_type == 1
y=0;
else
- y = linspace(period(1), 0, 50);
+ y = linspace(period(1), 0, 11);
end
if pol == 1
@@ -75,20 +78,30 @@
end
if grating_type == 0
- top_refl_info = res.inc_top_reflected;
- top_tran_info = res.inc_top_transmitted;
- bottom_refl_info = res.inc_bottom_reflected;
- bottom_tran_info = res.inc_bottom_transmitted;
+% top_refl_info = res.inc_top_reflected;
+% top_tran_info = res.inc_top_transmitted;
+% bottom_refl_info = res.inc_bottom_reflected;
+% bottom_tran_info = res.inc_bottom_transmitted;
+
+ top_refl_info_te = res.inc_top_reflected;
+ top_tran_info_te = res.inc_top_transmitted;
+ top_refl_info_tm = res.inc_top_reflected;
+ top_tran_info_tm = res.inc_top_transmitted;
+
+ bottom_refl_info_te = res.inc_bottom_reflected;
+ bottom_tran_info_te = res.inc_bottom_transmitted;
+ bottom_refl_info_tm = res.inc_bottom_reflected;
+ bottom_tran_info_tm = res.inc_bottom_transmitted;
+
else
- if pol == 1 % TE
- top_refl_info = res.TEinc_top_reflected;
- top_tran_info = res.TEinc_top_transmitted;
- bottom_refl_info = res.TEinc_bottom_reflected;
- bottom_tran_info = res.TEinc_bottom_transmitted;
- else % TM
- top_refl_info = res.TMinc_top_reflected;
- top_tran_info = res.TMinc_top_transmitted;
- bottom_refl_info = res.TMinc_bottom_reflected;
- bottom_tran_info = res.TMinc_bottom_transmitted;
- end
+ top_refl_info_te = res.TEinc_top_reflected;
+ top_tran_info_te = res.TEinc_top_transmitted;
+ top_refl_info_tm = res.TMinc_top_reflected;
+ top_tran_info_tm = res.TMinc_top_transmitted;
+
+ bottom_refl_info_te = res.TEinc_bottom_reflected;
+ bottom_tran_info_te = res.TEinc_bottom_transmitted;
+ bottom_refl_info_tm = res.TMinc_bottom_reflected;
+ bottom_tran_info_tm = res.TMinc_bottom_transmitted;
+
end
diff --git a/benchmarks/interface/trashcan/reti_meent_1D.py b/benchmarks/interface/trashcan/reti_meent_1D.py
deleted file mode 100644
index 61362c5..0000000
--- a/benchmarks/interface/trashcan/reti_meent_1D.py
+++ /dev/null
@@ -1,291 +0,0 @@
-import os
-import numpy as np
-import matplotlib.pyplot as plt
-
-import meent
-
-# os.environ['OCTAVE_EXECUTABLE'] = '/opt/homebrew/bin/octave-cli'
-
-
-class Reticolo:
-
- def __init__(self, engine_type='octave', *args, **kwargs):
-
- if engine_type == 'octave':
- try:
- from oct2py import octave
- except Exception as e:
- raise e
- self.eng = octave
-
- elif engine_type == 'matlab':
- try:
- import matlab.engine
- except Exception as e:
- raise e
- self.eng = matlab.engine.start_matlab()
- else:
- raise ValueError
-
- # path that has file to run in octave
- m_path = os.path.dirname(__file__)
- self.eng.addpath(self.eng.genpath(m_path))
-
- def run_res2(self, grating_type, period, fto, ucell, thickness, theta, phi, pol, wavelength, n_I, n_II,
- *args, **kwargs):
- theta *= (180 / np.pi)
- phi *= (180 / np.pi)
-
- if grating_type in (0, 1):
- period = period[0]
-
- fto = fto
- Nx = ucell.shape[2]
- period_x = period
- grid_x = np.linspace(0, period, Nx + 1)[1:]
- grid_x -= period_x / 2
-
- # grid = np.linspace(0, period, Nx)
-
- ucell_new = []
- for z in range(ucell.shape[0]):
- ucell_layer = [grid_x, ucell[z, 0]]
- ucell_new.append(ucell_layer)
-
- textures = [n_I, *ucell_new, n_II]
-
- else:
-
- Nx = ucell.shape[2]
- Ny = ucell.shape[1]
- period_x = period[0]
- period_y = period[1]
-
- unit_x = period_x / Nx
- unit_y = period_y / Ny
-
- grid_x = np.linspace(0, period[0], Nx + 1)[1:]
- grid_y = np.linspace(0, period[1], Ny + 1)[1:]
-
- grid_x -= period_x / 2
- grid_y -= period_y / 2
-
- ucell_new = []
- for z in range(ucell.shape[0]):
- ucell_layer = [10]
- for y, yval in enumerate(grid_y):
- for x, xval in enumerate(grid_x):
- obj = [xval, yval, unit_x, unit_y, ucell[z, y, x], 1]
- ucell_layer.append(obj)
- ucell_new.append(ucell_layer)
- textures = [n_I, *ucell_new, n_II]
-
- profile = np.array([[0, *thickness, 0], range(1, len(thickness) + 3)])
-
- top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info = \
- self._run(pol, theta, phi, period, n_I, fto, textures, profile, wavelength, grating_type,
- cal_field=False)
-
- return top_refl_info.efficiency, top_tran_info.efficiency, bottom_refl_info.efficiency, bottom_tran_info.efficiency
-
- def run_res3(self, grating_type, period, fto, ucell, thickness, theta, phi, pol, wavelength, n_top, n_bot,
- matlab_plot_field=0, res3_npts=0, *args, **kwargs):
-
- # theta *= (180 / np.pi)
- phi *= (180 / np.pi)
-
- if grating_type in (0, 1):
- period = period[0]
-
- fto = fto
- Nx = ucell.shape[2]
- period_x = period
- grid_x = np.linspace(0, period, Nx + 1)[1:]
- grid_x -= period_x / 2
-
- # grid = np.linspace(0, period, Nx)
-
- ucell_new = []
- for z in range(ucell.shape[0]):
- ucell_layer = [grid_x, ucell[z, 0]]
- ucell_new.append(ucell_layer)
-
- textures = [n_top, *ucell_new, n_bot]
-
- else:
-
- Nx = ucell.shape[2]
- Ny = ucell.shape[1]
- period_x = period[0]
- period_y = period[1]
-
- unit_x = period_x / Nx
- unit_y = period_y / Ny
-
- grid_x = np.linspace(0, period[0], Nx + 1)[1:]
- grid_y = np.linspace(0, period[1], Ny + 1)[1:]
-
- grid_x -= period_x / 2
- grid_y -= period_y / 2
-
- ucell_new = []
- for z in range(ucell.shape[0]):
- ucell_layer = [10]
- for y, yval in enumerate(grid_y):
- for x, xval in enumerate(grid_x):
- obj = [xval, yval, unit_x, unit_y, ucell[z, y, x], 1]
- ucell_layer.append(obj)
- ucell_new.append(ucell_layer)
- textures = [n_top, *ucell_new, n_bot]
-
- profile = np.array([[0, *thickness, 0], range(1, len(thickness) + 3)])
-
- top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, field_cell = \
- self._run(pol, theta, phi, period, n_top, fto, textures, profile, wavelength, grating_type,
- cal_field=True, matlab_plot_field=matlab_plot_field, res3_npts=res3_npts)
-
- return top_refl_info.efficiency, top_tran_info.efficiency, bottom_refl_info.efficiency, \
- bottom_tran_info.efficiency, field_cell
-
- def _run(self, pol, theta, phi, period, n_top, fto,
- textures, profile, wavelength, grating_type, cal_field=False, matlab_plot_field=0, res3_npts=0):
-
- if cal_field:
- top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, field_cell = \
- self.eng.reticolo_res3(pol, theta, phi, period, n_top, fto,
- textures, profile, wavelength, grating_type, matlab_plot_field, res3_npts,
- nout=5)
- res = (top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, field_cell)
- else:
- top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info = \
- self.eng.reticolo_res2(pol, theta, phi, period, n_top, fto,
- textures, profile, wavelength, grating_type, nout=4)
- res = (top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info)
- return res
-
-
-if __name__ == '__main__':
-
- factor = 100
- option = {}
- option['grating_type'] = 0 # 0 : just 1D grating, 1 : 1D rotating grating, 2 : 2D grating
- option['pol'] = 0 # 0: TE, 1: TM
- option['n_top'] = 2.2 # n_incidence
- option['n_bot'] = 1 # n_transmission
- option['theta'] = 60 * np.pi / 180
- option['phi'] = 0 * np.pi / 180
- option['fto'] = 1
- option['period'] = [770/factor]
- option['wavelength'] = 777/factor
- option['thickness'] = [100/factor, 100/factor, 100/factor, 100/factor, 100/factor, 100/factor] # final term is for h_substrate
- # option['thickness'] = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1] # final term is for h_substrate
- option['fourier_type'] = 2
-
- ucell = np.array(
- [
- [[3, 3, 3, 3, 3, 1, 1, 1, 1,]],
- ])
-
- option['ucell'] = ucell
- option['thickness'] = [100/factor,] # final term is for h_substrate
-
- res3_npts = 20
- reti = Reticolo()
- reti_de_ri, reti_de_ti, c, d, r_field_cell = reti.run_res3(**option, matlab_plot_field=0, res3_npts=res3_npts)
- print('reti de_ri', np.array(reti_de_ri).flatten())
- print('reti de_ti', np.array(reti_de_ti).flatten())
-
- # Numpy
- backend = 0
- nmee = meent.call_mee(backend=backend, perturbation=1E-30, **option)
- n_de_ri, n_de_ti = nmee.conv_solve()
- n_field_cell = nmee.calculate_field(res_z=20, res_x=ucell.shape[-1])
-
- # n_field_cell = np.roll(n_field_cell, -1, 2)
-
- print('nmeent de_ri', n_de_ri[n_de_ri > 1E-5])
- print('nmeent de_ti', n_de_ti[n_de_ti > 1E-5])
-
-
- if option['pol'] == 0: # TE
- title = ['1D Ey', '1D Hx', '1D Hz', ]
- else: # TM
- title = ['1D Hy', '1D Ex', '1D Ez', ]
-
- for i in range(3):
- a0 = np.flipud(r_field_cell[res3_npts:-res3_npts, :, i])
- b0 = n_field_cell[:, 0, :, i]
-
- res = []
- res.append(np.linalg.norm(a0.conj() - b0).round(3))
- res.append(np.linalg.norm(abs(a0.conj())**2 - abs(b0)**2).round(3))
- res.append(np.linalg.norm(a0.conj().real - b0.real).round(3))
- res.append(np.linalg.norm(a0.conj().imag - b0.imag).round(3))
- print(f'{title[i]}, {res}')
-
- aa = np.angle(a0.conj())
- bb = np.angle(b0)
-
- # print(aa[0][1:] - aa[0][:-1])
- # print(bb[0][1:] - bb[0][:-1])
-
- print(aa[0] - bb[0])
- print(1)
-
- #
- # print('Ey, val diff', np.linalg.norm(a0.conj() - b0))
- # print('Ey, abs2 diff', np.linalg.norm(abs(a0.conj())**2 - abs(b0)**2))
- # print('Ey, real diff', np.linalg.norm(a0.conj().real - b0.real))
- # print('Ey, imag diff', np.linalg.norm(a0.conj().imag - b0.imag))
- #
- # a1 = np.flipud(r_field_cell[res3_npts:-res3_npts, :, 1])
- # b1 = n_field_cell[:, 0, :, 1]
- # print(np.linalg.norm(a1.conj() - b1))
- # print('Hx, val diff', np.linalg.norm(a1.conj() - b1))
- # print('Ey, abs2 diff', np.linalg.norm(abs(a1.conj())**2 - abs(b1)**2))
- # print('Ey, real diff', np.linalg.norm(a1.conj().real - b1.real))
- # print('Ey, imag diff', np.linalg.norm(a1.conj().imag - b1.imag))
- #
- # a2 = np.flipud(r_field_cell[res3_npts:-res3_npts, :, 2])
- # b2 = n_field_cell[:, 0, :, 2]
- # print(np.linalg.norm(a2.conj() - b2))
- # print('Hz, val diff', np.linalg.norm(a2.conj() - b2))
- # print('Ey, abs2 diff', np.linalg.norm(abs(a2.conj())**2 - abs(b2)**2))
- # print('Ey, real diff', np.linalg.norm(a2.conj().real - b2.real))
- # print('Ey, imag diff', np.linalg.norm(a2.conj().imag - b2.imag))
-
- fig, axes = plt.subplots(3, 6, figsize=(10, 5))
-
- for ix in range(len(title)):
- r_data = np.flipud(r_field_cell[res3_npts:-res3_npts, :, ix]).conj()
-
- im = axes[ix, 0].imshow(abs(r_data)**2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 0], shrink=1)
- im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 2], shrink=1)
- im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 4], shrink=1)
-
-
- n_data = n_field_cell[:, 0, :, ix]
-
- im = axes[ix, 1].imshow(abs(n_data)**2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 1], shrink=1)
-
- im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 3], shrink=1)
-
- im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 5], shrink=1)
-
- ix=0
- axes[ix, 0].title.set_text('abs**2 reti')
- axes[ix, 2].title.set_text('Re, reti')
- axes[ix, 4].title.set_text('Im, reti')
- axes[ix, 1].title.set_text('abs**2 meen')
- axes[ix, 3].title.set_text('Re, meen')
- axes[ix, 5].title.set_text('Im, meen')
-
- plt.show()
-
- 1
diff --git a/benchmarks/interface/trashcan/test2d_1.m b/benchmarks/interface/trashcan/test2d_1.m
deleted file mode 100644
index 0c8068b..0000000
--- a/benchmarks/interface/trashcan/test2d_1.m
+++ /dev/null
@@ -1,86 +0,0 @@
-function [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = test2d_1();
-
- warning('off', 'Octave:possible-matlab-short-circuit-operator');
- warning('off', 'Invalid UTF-8 byte sequences have been replaced.');
- warning('off', 'findstr is obsolete; use strfind instead');
-
- factor = 1;
- pol = -1;
- n_top = 1;
- n_bot = 1;
- theta = 20;
- phi = 33;
- nn = [11,11];
- period = [770/factor, 770/factor];
- wavelength = 777/factor;
- thickness = 100/factor;
-
- b = [-period(1)/4, -period(2)/4, period(1)/2, period(2)*5/10, 3, 1];
- c = [-period(1)/10*4, period(2)/10*4, period(1)*2/10, period(2)*2/10, 4, 1];
- d = [-period(1)/10*2, period(2)/10*4, period(1)*2/10, period(2)*2/10, 6, 1];
-
- b = [-period(1)/4+period(1)/2, -period(2)/4+period(2)/2, period(1)/2, period(2)*5/10, 3, 1];
- c = [-period(1)/10*4+period(1)/2, period(2)/10*4+period(2)/2, period(1)*2/10, period(2)*2/10, 4, 1];
- d = [-period(1)/10*2+period(1)/2, period(2)/10*4+period(2)/2, period(1)*2/10, period(2)*2/10, 6, 1];
-
- tt = {1, b, c, d};
-
- retio;
- textures = cell(1,3);
- textures{1} = n_top;
- textures{2} = tt;
- textures{3} = n_bot;
-
- parm = res0;
- parm.res1.champ = 1; % calculate precisely
-% parm.res1.trace = 1;
-% parm.res3.trace = 1; % trace automatique % automatic trace
-
- k_parallel = n_top*sind(theta); % n_air, or whatever the refractive index of the medium where light is coming in.
-
- parm = res0;
-
- parm.not_io = 1; % no write data on hard disk
- parm.res1.champ = 1; % the electromagnetic field is calculated accurately
-% parm.res3.npts=[0,1,0];
- parm.res3.npts=[11,11,11];
-
- profile = {[0, thickness, 0], [1, 2, 3]};
- aa = res1(wavelength,period,textures,nn,k_parallel, phi, parm);
- res = res2(aa, profile);
-
- x = linspace(-period(1)/2, period(1)/2, 50);
- y = linspace(period(2)/2, -period(2)/2, 50);
- x = linspace(0, period(1), 50);
- y = linspace(period(2), 0, 50);
-
-% x = [0:1:49] * period(1) / 50 - period(1)/2;
-% x = [1:1:50] * period(1) / 50 - period(1)/2;
-% y = [0:1:49] .* period(2) / 50 - period(2)/2
-% y = [1:1:50] .* period(2) / 50 - period(2)/2
-% y = [50:-1:1] .* period(2) / 50 - period(2)/2
-% y = [49:-1:0] .* period(2) / 50 - period(2)/2
-
-
- if pol == 1
- einc = [0, 1];
- elseif pol == -1
- einc = [1, 0];
- else
- disp('only TE or TM is allowed.');
- end
- [e,z,o]=res3(x,y,aa,profile,einc, parm);
-
- if pol == 1 % TE
- top_refl_info = res.TEinc_top_reflected;
- top_tran_info = res.TEinc_top_transmitted;
- bottom_refl_info = res.TEinc_bottom_reflected;
- bottom_tran_info = res.TEinc_bottom_transmitted;
- else % TM
- top_refl_info = res.TMinc_top_reflected;
- top_tran_info = res.TMinc_top_transmitted;
- bottom_refl_info = res.TMinc_bottom_reflected;
- bottom_tran_info = res.TMinc_bottom_transmitted;
- end
- disp(1)
-end
diff --git a/benchmarks/interface/trashcan/test2d_2.m b/benchmarks/interface/trashcan/test2d_2.m
deleted file mode 100644
index c80777c..0000000
--- a/benchmarks/interface/trashcan/test2d_2.m
+++ /dev/null
@@ -1,83 +0,0 @@
-function [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = test2d_2();
-
- warning('off', 'Octave:possible-matlab-short-circuit-operator');
- warning('off', 'Invalid UTF-8 byte sequences have been replaced.');
- warning('off', 'findstr is obsolete; use strfind instead');
-
- factor = 1;
- pol = -1;
- n_top = 1;
- n_bot = 1;
- theta = 20;
- phi = 33;
- nn = [11,11];
- period = [770/factor, 770/factor];
- wavelength = 777/factor;
- thickness = 100/factor;
-
- b = [-period(1)/4, -period(2)/4, period(1)/2, period(2)*5/10, 3, 1];
- b = [period(1)/4, period(2)/4, period(1)/2, period(2)*5/10, 3, 1];
-
- tt = {1, b};
-
- retio;
- textures = cell(1,3);
- textures{1} = n_top;
- textures{2} = tt;
- textures{3} = n_bot;
-
- parm = res0;
- parm.res1.champ = 1; % calculate precisely
-% parm.res1.trace = 1;
-% parm.res3.trace = 1; % trace automatique % automatic trace
-
- k_parallel = n_top*sind(theta); % n_air, or whatever the refractive index of the medium where light is coming in.
-
- parm = res0;
-
- parm.not_io = 1; % no write data on hard disk
- parm.res1.champ = 1; % the electromagnetic field is calculated accurately
-% parm.res3.npts=[0,1,0];
- parm.res3.npts=[11,11,11];
-
- profile = {[0, thickness, 0], [1, 2, 3]};
- aa = res1(wavelength,period,textures,nn,k_parallel, phi, parm);
- res = res2(aa, profile);
-
- x = linspace(-period(1)/2, period(1)/2, 50);
- y = linspace(-period(2)/2, period(2)/2, 50);
- y = linspace(period(2)/2, -period(2)/2, 50);
- x = linspace(0, period(1), 50);
- y = linspace(period(2), 0, 50);
-
-% x = [0:1:49] * period(1) / 50 - period(1)/2;
-% x = [1:1:50] * period(1) / 50 - period(1)/2;
-% y = [0:1:49] .* period(2) / 50 - period(2)/2
-% y = [1:1:50] .* period(2) / 50 - period(2)/2
-% y = [50:-1:1] .* period(2) / 50 - period(2)/2
-% y = [49:-1:0] .* period(2) / 50 - period(2)/2
-
- parm.res3.trace=1; %trace automatique % automatic trace
-
- if pol == 1
- einc = [0, 1];
- elseif pol == -1
- einc = [1, 0];
- else
- disp('only TE or TM is allowed.');
- end
- [e,z,o]=res3(x,y,aa,profile,einc, parm);
-
- if pol == 1 % TE
- top_refl_info = res.TEinc_top_reflected;
- top_tran_info = res.TEinc_top_transmitted;
- bottom_refl_info = res.TEinc_bottom_reflected;
- bottom_tran_info = res.TEinc_bottom_transmitted;
- else % TM
- top_refl_info = res.TMinc_top_reflected;
- top_tran_info = res.TMinc_top_transmitted;
- bottom_refl_info = res.TMinc_bottom_reflected;
- bottom_tran_info = res.TMinc_bottom_transmitted;
- end
- disp(1)
-end
diff --git a/benchmarks/interface/trashcan/test2d_3.m b/benchmarks/interface/trashcan/test2d_3.m
deleted file mode 100644
index 201a1cc..0000000
--- a/benchmarks/interface/trashcan/test2d_3.m
+++ /dev/null
@@ -1,80 +0,0 @@
-function [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = test2d_3();
-
- warning('off', 'Octave:possible-matlab-short-circuit-operator');
- warning('off', 'Invalid UTF-8 byte sequences have been replaced.');
- warning('off', 'findstr is obsolete; use strfind instead');
-
- factor = 1;
- pol = -1;
- n_top = 1;
- n_bot = 1;
- theta = 20;
- phi = 33;
- nn = [11,11];
- period = [770/factor, 770/factor];
- wavelength = 777/factor;
- thickness = 100/factor;
-
-
- a = [period(1)/4, period(2)/2, period(1)/2, period(2), 4, 1];
- tt = {1, a};
-
- retio;
- textures = cell(1,3);
- textures{1} = n_top;
- textures{2} = tt;
- textures{3} = n_bot;
-
- parm = res0;
- parm.res1.champ = 1; % calculate precisely
- parm.res1.trace = 1;
-
- k_parallel = n_top*sind(theta); % n_air, or whatever the refractive index of the medium where light is coming in.
-
- parm = res0;
-
- parm.not_io = 1; % no write data on hard disk
- parm.res1.champ = 1; % the electromagnetic field is calculated accurately
- parm.res3.npts=[11,11,11];
-
- profile = {[0, thickness, 0], [1, 2, 3]};
- aa = res1(wavelength,period,textures,nn,k_parallel, phi, parm);
- res = res2(aa, profile);
-
- x = linspace(-period(1)/2, period(1)/2, 50);
- y = linspace(-period(2)/2, period(2)/2, 50);
- y = linspace(period(2)/2, -period(2)/2, 50);
- x = linspace(0, period(1), 50);
- y = linspace(period(2), 0, 50);
-
-% x = [0:1:49] * period(1) / 50 - period(1)/2;
-% x = [1:1:50] * period(1) / 50 - period(1)/2;
-% y = [0:1:49] .* period(2) / 50 - period(2)/2
-% y = [1:1:50] .* period(2) / 50 - period(2)/2
-% y = [50:-1:1] .* period(2) / 50 - period(2)/2
-% y = [49:-1:0] .* period(2) / 50 - period(2)/2
-
- parm.res3.trace=1; %trace automatique % automatic trace
-
- if pol == 1
- einc = [0, 1];
- elseif pol == -1
- einc = [1, 0];
- else
- disp('only TE or TM is allowed.');
- end
- [e,z,o]=res3(x,y,aa,profile,einc, parm);
-
- if pol == 1 % TE
- top_refl_info = res.TEinc_top_reflected;
- top_tran_info = res.TEinc_top_transmitted;
- bottom_refl_info = res.TEinc_bottom_reflected;
- bottom_tran_info = res.TEinc_bottom_transmitted;
- else % TM
- top_refl_info = res.TMinc_top_reflected;
- top_tran_info = res.TMinc_top_transmitted;
- bottom_refl_info = res.TMinc_bottom_reflected;
- bottom_tran_info = res.TMinc_bottom_transmitted;
- end
- disp(1)
-end
diff --git a/benchmarks/interface/trashcan/test2d_4.m b/benchmarks/interface/trashcan/test2d_4.m
deleted file mode 100644
index 103c323..0000000
--- a/benchmarks/interface/trashcan/test2d_4.m
+++ /dev/null
@@ -1,123 +0,0 @@
-function [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = test2d_4();
-
- warning('off', 'Octave:possible-matlab-short-circuit-operator');
- warning('off', 'Invalid UTF-8 byte sequences have been replaced.');
- warning('off', 'findstr is obsolete; use strfind instead');
-
- factor = 1;
- pol = 1;
- n_top = 1;
- n_bot = 1;
- theta = 0;
- phi = 0;
- nn = [11,11];
- period = [480/factor, 480/factor];
- wavelength = 550/factor;
- thickness = 220/factor;
-
-
- a = [0+240, 120+240, 160, 80, 4, 1];
- b = [0+240, -120+240, 160, 80, 4, 1];
- c = [120+240, 0+240, 80, 160, 4, 1];
- d = [-120+240, 0+240, 80, 160, 4, 1];
-
- textures = cell(1,3);
- textures{1} = n_top;
- textures{2} = {1,a, b, c, d};
- textures{3} = n_bot;
-
-
- parm = res0;
- parm.res1.champ = 1; % calculate precisely
- parm.res1.trace = 1;
-
- k_parallel = n_top*sind(theta); % n_air, or whatever the refractive index of the medium where light is coming in.
-
- parm = res0;
-
- parm.not_io = 1; % no write data on hard disk
- parm.res1.champ = 1; % the electromagnetic field is calculated accurately
-% parm.res3.npts=[0,1,0];
- parm.res3.npts=[11,11,11];
-
- %parm.res1.trace = 1; % show the texture
- %
- %textures = cell(1, size(_textures, 2));
- %for i = 1:length(_textures)
- % textures(i) = _textures(i);
- %end
- %
- %profile = cell(1, size(_profile, 1));
- %profile(1) = _profile(1, :);
- %profile(2) = _profile(2, :);
-
- profile = {[0, thickness, 0], [1, 2, 3]};
- aa = res1(wavelength,period,textures,nn,k_parallel, phi, parm);
- res = res2(aa, profile);
-
- %res3(aa)
- %parm.res3.sens=1;
- %##parm.res3.gauss_x = 100
-
- x = linspace(-period(1)/2, period(1)/2, 50);
- y = linspace(-period(2)/2, period(2)/2, 50);
- y = linspace(period(2)/2, -period(2)/2, 50);
- x = linspace(0, period(1), 50);
- y = linspace(period(2), 0, 50);
-
-% x = [0:1:49] * period(1) / 50 - period(1)/2;
-% x = [1:1:50] * period(1) / 50 - period(1)/2;
-% y = [0:1:49] .* period(2) / 50 - period(2)/2
-% y = [1:1:50] .* period(2) / 50 - period(2)/2
-% y = [50:-1:1] .* period(2) / 50 - period(2)/2
-% y = [49:-1:0] .* period(2) / 50 - period(2)/2
-
- parm.res3.trace=1; %trace automatique % automatic trace
-
-% parm.res3.npts = res3_npts;
-
- if pol == 1
- einc = [0, 1];
- elseif pol == -1
- einc = [1, 0];
- else
- disp('only TE or TM is allowed.');
- end
- [e,z,o]=res3(x,y,aa,profile,einc, parm);
-
- if pol == 1 % TE
- top_refl_info = res.TEinc_top_reflected;
- top_tran_info = res.TEinc_top_transmitted;
- bottom_refl_info = res.TEinc_bottom_reflected;
- bottom_tran_info = res.TEinc_bottom_transmitted;
- else % TM
- top_refl_info = res.TMinc_top_reflected;
- top_tran_info = res.TMinc_top_transmitted;
- bottom_refl_info = res.TMinc_bottom_reflected;
- bottom_tran_info = res.TMinc_bottom_transmitted;
- end
- disp(1)
-end
-
-% Divides the given geometry into rectangles to be used in Reticolo
-function GeometryOut = FractureGeom(PatternIn,nLow,nHigh,XGrid,YGrid)
-
- % Acceptable refractive index tolerance in fracturing
-
- % Extract grid parameters
- dX = abs(XGrid(2)-XGrid(1));
- dY = abs(YGrid(2)-YGrid(1));
- [Nx, Ny] = size(PatternIn)
-
- Geometry = {nLow}; %Define background index
-
- % Fracture non binarized pixels
- for i = 1:Nx % Defining texture for patterned layer. Probably could have vectorized this.
- for j = 1:Ny
- if PatternIn(i,j) == 1
- Geometry = [Geometry,{[XGrid(i),YGrid(j),dX,dY,nHigh,1]}];
- end
- end
- end
- GeometryOut = Geometry;
-end
\ No newline at end of file
diff --git a/benchmarks/interface/trashcan/test2d_5.m b/benchmarks/interface/trashcan/test2d_5.m
deleted file mode 100644
index a4cb0e5..0000000
--- a/benchmarks/interface/trashcan/test2d_5.m
+++ /dev/null
@@ -1,141 +0,0 @@
-function [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, e] = test2d_5();
-
- warning('off', 'Octave:possible-matlab-short-circuit-operator');
- warning('off', 'Invalid UTF-8 byte sequences have been replaced.');
- warning('off', 'findstr is obsolete; use strfind instead');
-
- factor = 1;
- pol = 1;
- n_top = 1;
- n_bot = 1;
- theta = 0;
- phi = 0;
- nn = [11,11];
- period = [480/factor, 480/factor];
- wavelength = 550/factor;
- thickness = 220/factor;
-
- PatternIn = [0 0 0 0 0 0; 0 0 1 1 0 0; 0 1 0 0 1 0; 0 1 0 0 1 0; 0 0 1 1 0 0; 0 0 0 0 0 0];
-% PatternIn = [3, 3, 3, 3, 3, 1, 1, 1, 1, 1; 3, 3, 3, 3, 3, 1, 1, 1, 1, 1; 3, 3, 3, 3, 3, 1, 1, 1, 1, 1; 3, 3, 3, 3, 3, 1, 1, 1, 1, 1; 3, 3, 3, 3, 3, 1, 1, 1, 1, 1; 3, 3, 3, 3, 3, 1, 1, 1, 1, 1; 3, 3, 3, 3, 3, 1, 1, 1, 1, 1; 3, 3, 3, 3, 3, 1, 1, 1, 1, 1; 3, 3, 3, 3, 3, 1, 1, 1, 1, 1; 3, 3, 3, 3, 3, 1, 1, 1, 1, 1; ]
-% PatternIn = [3, 3, 3, 3, 3, 1, 1, 1, 1, 1]
-% PatternIn = [1, 1, 1, 1, 1, 0, 0, 0, 0, 0;1, 1, 1, 1, 1, 0, 0, 0, 0, 0;1, 1, 1, 1, 1, 0, 0, 0, 0, 0;1, 1, 1, 1, 1, 0, 0, 0, 0, 0;1, 1, 1, 1, 1, 0, 0, 0, 0, 0;1, 1, 1, 1, 1, 0, 0, 0, 0, 0;1, 1, 1, 1, 1, 0, 0, 0, 0, 0;1, 1, 1, 1, 1, 0, 0, 0, 0, 0;1, 1, 1, 1, 1, 0, 0, 0, 0, 0;1, 1, 1, 1, 1, 0, 0, 0, 0, 0;]
-% PatternIn = [1, 1, 1, 1, 1, 0, 0, 0, 0, 0]
-
- XGrid = [3.5:1:8.5]/6 * period(1);
- YGrid = [3.5:1:8.5]/6 * period(1);
-
- XGrid = linspace(-period(1)/2 + period(1)/12, period(1)/2 - period(1)/12, 6) + period(1)/2;
- YGrid = linspace(-period(2)/2 + period(2)/12, period(2)/2 - period(2)/12, 6) + period(2)/2;
- YGrid = -linspace(-period(2)/2 + period(2)/12, period(2)/2 - period(2)/12, 6) - period(2)/2;
-
-% XGrid = linspace(-period(1)/2 + period(1)/12, period(1)/2 - period(1)/12, 6);
-% YGrid = -linspace(-period(2)/2 + period(2)/12, period(2)/2 - period(2)/12, 6);
-
-% XGrid = [0.5:1:9.5] * period(1) / 10
-% YGrid = [0.5:1:9.5] * period(2) / 10
-
- % RCWA
-
- retio;
- textures = cell(1,3);
- textures{1} = {n_top};
- textures{2} = FractureGeom(PatternIn,1,4,XGrid,YGrid);
- textures{3} = {n_bot};
- profile = {[0, thickness, 0], [1, 2, 3]};
-
- parm = res0;
- parm.res1.champ = 1; % calculate precisely
- parm.res1.trace = 1;
-
- k_parallel = n_top*sind(theta); % n_air, or whatever the refractive index of the medium where light is coming in.
-
- parm = res0;
-
- parm.not_io = 1; % no write data on hard disk
- parm.res1.champ = 1; % the electromagnetic field is calculated accurately
-% parm.res3.npts=[0,1,0];
- parm.res3.npts=[11,11,11];
-
- %parm.res1.trace = 1; % show the texture
- %
- %textures = cell(1, size(_textures, 2));
- %for i = 1:length(_textures)
- % textures(i) = _textures(i);
- %end
- %
- %profile = cell(1, size(_profile, 1));
- %profile(1) = _profile(1, :);
- %profile(2) = _profile(2, :);
-
- profile = {[0, thickness, 0], [1, 2, 3]};
- aa = res1(wavelength,period,textures,nn,k_parallel, phi, parm);
- res = res2(aa, profile);
-
- %res3(aa)
- %parm.res3.sens=1;
- %##parm.res3.gauss_x = 100
-
- x = linspace(-period(1)/2, period(1)/2, 50);
- y = linspace(-period(2)/2, period(2)/2, 50);
- y = linspace(period(2)/2, -period(2)/2, 50);
-
-
- x = linspace(0, period(1), 50);
- y = linspace(period(2), 0, 50);
-
-% x = [0:1:49] * period(1) / 50 - period(1)/2;
-% x = [1:1:50] * period(1) / 50 - period(1)/2;
-% y = [0:1:49] .* period(2) / 50 - period(2)/2
-% y = [1:1:50] .* period(2) / 50 - period(2)/2
-% y = [50:-1:1] .* period(2) / 50 - period(2)/2
-% y = [49:-1:0] .* period(2) / 50 - period(2)/2
-
- parm.res3.trace=1; %trace automatique % automatic trace
-
-% parm.res3.npts = res3_npts;
-
- if pol == 1
- einc = [0, 1];
- elseif pol == -1
- einc = [1, 0];
- else
- disp('only TE or TM is allowed.');
- end
- [e,z,o]=res3(x,y,aa,profile,einc, parm);
-
- if pol == 1 % TE
- top_refl_info = res.TEinc_top_reflected;
- top_tran_info = res.TEinc_top_transmitted;
- bottom_refl_info = res.TEinc_bottom_reflected;
- bottom_tran_info = res.TEinc_bottom_transmitted;
- else % TM
- top_refl_info = res.TMinc_top_reflected;
- top_tran_info = res.TMinc_top_transmitted;
- bottom_refl_info = res.TMinc_bottom_reflected;
- bottom_tran_info = res.TMinc_bottom_transmitted;
- end
- disp(1)
-end
-
-% Divides the given geometry into rectangles to be used in Reticolo
-function GeometryOut = FractureGeom(PatternIn,nLow,nHigh,XGrid,YGrid)
-
- % Acceptable refractive index tolerance in fracturing
-
- % Extract grid parameters
- dX = abs(XGrid(2)-XGrid(1));
- dY = abs(YGrid(2)-YGrid(1));
- [Nx, Ny] = size(PatternIn);
-
- Geometry = {nLow}; %Define background index
-
- % Fracture non binarized pixels
- for i = 1:Nx % Defining texture for patterned layer. Probably could have vectorized this.
- for j = 1:Ny
- if PatternIn(i,j) == 1
- Geometry = [Geometry,{[XGrid(i),YGrid(j),dX,dY,nHigh,1]}];
- end
- end
- end
- GeometryOut = Geometry;
-end
\ No newline at end of file
diff --git a/benchmarks/reti_meent_1D.py b/benchmarks/reti_meent_1D.py
index 6ee0c7f..844fb52 100644
--- a/benchmarks/reti_meent_1D.py
+++ b/benchmarks/reti_meent_1D.py
@@ -14,15 +14,126 @@
from Reticolo import Reticolo
-def test1d_1(plot_figure=False):
+def run_1d(option, plot_figure=False):
+ res_z = 11
+ res_y = 1
+ res_x = 11
+ reti = Reticolo()
+ (top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm,
+ bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, reti_field_cell) \
+ = reti.run_res3(**option, grating_type=0, matlab_plot_field=0, res3_npts=res_z)
+ # print('reti de_ri', np.array(reti_de_ri).flatten())
+ # print('reti de_ti', np.array(reti_de_ti).flatten())
+
+ reti_field_cell = reti_field_cell[:, None, :, :]
+ reti_field_cell = reti_field_cell[res_z:-res_z]
+ reti_field_cell = np.flip(reti_field_cell, 0)
+ reti_field_cell = reti_field_cell.conj()
+
+ # Numpy
+ mee = meent.call_mee(backend=0, **option)
+ res_numpy = mee.conv_solve()
+ field_cell_numpy = mee.calculate_field(res_z=res_z, res_y=res_y, res_x=res_x)
+
+ # JAX
+ mee = meent.call_mee(backend=1, **option) # JAX
+ res_jax = mee.conv_solve()
+ field_cell_jax = mee.calculate_field(res_z=res_z, res_y=res_y, res_x=res_x)
+
+ # Torch
+ mee = meent.call_mee(backend=2, **option) # PyTorch
+ res_torch = mee.conv_solve()
+ field_cell_torch = mee.calculate_field(res_z=res_z, res_y=res_y, res_x=res_x).numpy()
+
+ bds = ['Numpy', 'JAX', 'Torch']
+ fields = [field_cell_numpy, field_cell_jax, field_cell_torch]
+
+ print('Norm of (meent - reti) per backend')
+ for i, res_t in enumerate([res_numpy, res_jax, res_torch]):
+ reti_de_ri_te, reti_de_ti_te = np.array(top_refl_info_te.efficiency).T, np.array(top_tran_info_te.efficiency).T
+ reti_de_ri_tm, reti_de_ti_tm = np.array(top_refl_info_tm.efficiency).T, np.array(top_tran_info_tm.efficiency).T
+
+ # de_ri_te, de_ti_te = np.array(res_t.res_te_inc.de_ri).T, np.array(res_t.res_te_inc.de_ti).T
+ # de_ri_tm, de_ti_tm = np.array(res_t.res_tm_inc.de_ri).T, np.array(res_t.res_tm_inc.de_ti).T
+ #
+ # de_ri_te = de_ri_te[de_ri_te > 1E-5]
+ # de_ti_te = de_ti_te[de_ti_te > 1E-5]
+ # de_ri_tm = de_ri_tm[de_ri_tm > 1E-5]
+ # de_ti_tm = de_ti_tm[de_ti_tm > 1E-5]
+
+ de_ri, de_ti = np.array(res_t.res.de_ri).T, np.array(res_t.res.de_ti).T
+
+ de_ri = de_ri[de_ri > 1E-5]
+ de_ti = de_ti[de_ti > 1E-5]
+
+ # reti_R_s_te = top_refl_info_te.amplitude_TE
+ # reti_T_s_te = top_tran_info_te.amplitude_TE
+ # reti_R_p_tm = top_refl_info_tm.amplitude_TM
+ # reti_T_p_tm = top_tran_info_tm.amplitude_TM
+ #
+ # R_s_te = res_t.res_te_inc.R_s
+ # T_s_te = res_t.res_te_inc.T_s
+ # R_p_tm = res_t.res_tm_inc.R_p
+ # T_p_tm = res_t.res_tm_inc.T_p
+
+ print(bds[i])
+ print('de_ri', np.linalg.norm(de_ri - reti_de_ri_te),
+ 'de_ti', np.linalg.norm(de_ti - reti_de_ti_te),
+ )
+
+ for i_field in range(reti_field_cell.shape[-1]):
+ res_temp = np.linalg.norm(fields[i][i_field] - reti_field_cell[i_field])
+ print(f'field, {i_field+1}th: {res_temp}')
+
+ if plot_figure:
+ if option['pol'] == 0: # TE
+ title = ['1D Ey', '1D Hx', '1D Hz', ]
+ else: # TM
+ title = ['1D Hy', '1D Ex', '1D Ez', ]
+
+ fig, axes = plt.subplots(3, 6, figsize=(10, 5))
+
+ for ix in range(len(title)):
+ r_data = reti_field_cell[:, res_y//2, :, ix]
+
+ im = axes[ix, 0].imshow(abs(r_data) ** 2, cmap='jet', aspect='auto')
+ fig.colorbar(im, ax=axes[ix, 0], shrink=1)
+ im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
+ fig.colorbar(im, ax=axes[ix, 2], shrink=1)
+ im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
+ fig.colorbar(im, ax=axes[ix, 4], shrink=1)
+
+ n_data = fields[i][:, res_y//2, :, ix]
+
+ im = axes[ix, 1].imshow(abs(n_data) ** 2, cmap='jet', aspect='auto')
+ fig.colorbar(im, ax=axes[ix, 1], shrink=1)
+
+ im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
+ fig.colorbar(im, ax=axes[ix, 3], shrink=1)
+
+ im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
+ fig.colorbar(im, ax=axes[ix, 5], shrink=1)
+
+ ix = 0
+ axes[ix, 0].title.set_text('abs**2 reti')
+ axes[ix, 2].title.set_text('Re, reti')
+ axes[ix, 4].title.set_text('Im, reti')
+ axes[ix, 1].title.set_text('abs**2 meen')
+ axes[ix, 3].title.set_text('Re, meen')
+ axes[ix, 5].title.set_text('Im, meen')
+
+ plt.show()
+
+
+def case_1d_1(plot_figure=False):
factor = 1000
option = {}
option['pol'] = 0 # 0: TE, 1: TM
option['n_top'] = 2 # n_incidence
option['n_bot'] = 1 # n_transmission
- option['theta'] = 12 * np.pi / 180
- option['phi'] = 0 * np.pi / 180
+ option['theta'] = 0 * np.pi / 180
+ option['phi'] = None
option['fto'] = 1
option['period'] = [770/factor]
option['wavelength'] = 777/factor
@@ -36,71 +147,10 @@ def test1d_1(plot_figure=False):
option['ucell'] = ucell
- res_z = 11
- reti = Reticolo()
- reti_de_ri, reti_de_ti, c, d, r_field_cell = reti.run_res3(**option, grating_type=0, matlab_plot_field=0, res3_npts=res_z)
- print('reti de_ri', np.array(reti_de_ri).flatten())
- print('reti de_ti', np.array(reti_de_ti).flatten())
-
- # Numpy
- backend = 0
- nmee = meent.call_mee(backend=backend, perturbation=1E-30, **option)
- n_de_ri, n_de_ti = nmee.conv_solve()
- n_field_cell = nmee.calculate_field(res_z=res_z, res_x=50)
-
- print('nmeent de_ri', n_de_ri[n_de_ri > 1E-5])
- print('nmeent de_ti', n_de_ti[n_de_ti > 1E-5])
-
- # r_field_cell = np.moveaxis(r_field_cell, 2, 1)
- r_field_cell = r_field_cell[:, None, :, :]
- r_field_cell = r_field_cell[res_z:-res_z]
- r_field_cell = np.flip(r_field_cell, 0)
- r_field_cell = r_field_cell.conj()
-
- for i in range(r_field_cell.shape[-1]):
- print(i, np.linalg.norm(r_field_cell[:, :, :, i] - n_field_cell[:, :, :, i]))
-
- if plot_figure:
-
- if option['pol'] == 0: # TE
- title = ['1D Ey', '1D Hx', '1D Hz', ]
- else: # TM
- title = ['1D Hy', '1D Ex', '1D Ez', ]
-
- fig, axes = plt.subplots(3, 6, figsize=(10, 5))
-
- for ix in range(len(title)):
- r_data = r_field_cell[:, 0, :, ix]
-
- im = axes[ix, 0].imshow(abs(r_data)**2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 0], shrink=1)
- im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 2], shrink=1)
- im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 4], shrink=1)
-
- n_data = n_field_cell[:, 0, :, ix]
-
- im = axes[ix, 1].imshow(abs(n_data)**2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 1], shrink=1)
-
- im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 3], shrink=1)
+ run_1d(option, plot_figure)
- im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 5], shrink=1)
- axes[0, 0].title.set_text('abs**2 reti')
- axes[0, 2].title.set_text('Re, reti')
- axes[0, 4].title.set_text('Im, reti')
- axes[0, 1].title.set_text('abs**2 meen')
- axes[0, 3].title.set_text('Re, meen')
- axes[0, 5].title.set_text('Im, meen')
-
- plt.show()
-
-
-def test1d_2(plot_figure=False):
+def case_1d_2(plot_figure=False):
factor = 1
option = {}
@@ -108,7 +158,7 @@ def test1d_2(plot_figure=False):
option['n_top'] = 1 # n_incidence
option['n_bot'] = 2.2 # n_transmission
option['theta'] = 0 * np.pi / 180
- option['phi'] = 0 * np.pi / 180
+ option['phi'] = None
option['fto'] = 80
option['period'] = [770/factor]
option['wavelength'] = 777/factor
@@ -122,70 +172,9 @@ def test1d_2(plot_figure=False):
option['ucell'] = ucell
- res_z = 11
- reti = Reticolo()
- reti_de_ri, reti_de_ti, c, d, r_field_cell = reti.run_res3(**option, grating_type=0, matlab_plot_field=0, res3_npts=res_z)
- print('reti de_ri', np.array(reti_de_ri).flatten())
- print('reti de_ti', np.array(reti_de_ti).flatten())
-
- # Numpy
- backend = 0
- nmee = meent.call_mee(backend=backend, perturbation=1E-30, **option)
- n_de_ri, n_de_ti = nmee.conv_solve()
- n_field_cell = nmee.calculate_field(res_z=res_z, res_x=50)
-
- print('nmeent de_ri', n_de_ri[n_de_ri > 1E-5])
- print('nmeent de_ti', n_de_ti[n_de_ti > 1E-5])
-
- # r_field_cell = np.moveaxis(r_field_cell, 2, 1)
- r_field_cell = r_field_cell[:, None, :, :]
- r_field_cell = r_field_cell[res_z:-res_z]
- r_field_cell = np.flip(r_field_cell, 0)
- r_field_cell = r_field_cell.conj()
-
- for i in range(r_field_cell.shape[-1]):
- print(i, np.linalg.norm(r_field_cell[:, :, :, i] - n_field_cell[:, :, :, i]))
-
- if plot_figure:
-
- if option['pol'] == 0: # TE
- title = ['1D Ey', '1D Hx', '1D Hz', ]
- else: # TM
- title = ['1D Hy', '1D Ex', '1D Ez', ]
-
- fig, axes = plt.subplots(3, 6, figsize=(10, 5))
-
- for ix in range(len(title)):
- r_data = r_field_cell[:, 0, :, ix]
-
- im = axes[ix, 0].imshow(abs(r_data)**2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 0], shrink=1)
- im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 2], shrink=1)
- im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 4], shrink=1)
-
- n_data = n_field_cell[:, 0, :, ix]
-
- im = axes[ix, 1].imshow(abs(n_data)**2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 1], shrink=1)
-
- im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 3], shrink=1)
-
- im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 5], shrink=1)
-
- axes[0, 0].title.set_text('abs**2 reti')
- axes[0, 2].title.set_text('Re, reti')
- axes[0, 4].title.set_text('Im, reti')
- axes[0, 1].title.set_text('abs**2 meen')
- axes[0, 3].title.set_text('Re, meen')
- axes[0, 5].title.set_text('Im, meen')
-
- plt.show()
+ run_1d(option, plot_figure)
if __name__ == '__main__':
- test1d_1(False)
- test1d_2(False)
+ case_1d_1(False)
+ case_1d_2(False)
diff --git a/benchmarks/reti_meent_1Dc.py b/benchmarks/reti_meent_1Dc.py
index 11acdf9..a9d6d58 100644
--- a/benchmarks/reti_meent_1Dc.py
+++ b/benchmarks/reti_meent_1Dc.py
@@ -14,99 +14,123 @@
from Reticolo import Reticolo
-def test1dc_1(plot_figure=False):
- factor = 100
- option = {}
- option['pol'] = 0 # 0: TE, 1: TM
- option['n_top'] = 2.2 # n_incidence
- option['n_bot'] = 2 # n_transmission
- option['theta'] = 40 * np.pi / 180
- option['phi'] = 20 * np.pi / 180
- option['fto'] = [40, 1]
- option['period'] = [770 / factor]
- option['wavelength'] = 777 / factor
- option['thickness'] = [100 / factor, ]
- option['fourier_type'] = 1
-
- ucell = np.array(
- [
- [[3, 3, 3, 3, 3, 1, 1, 1, 1, 1]],
- ])
-
- option['ucell'] = ucell
-
+def run_1dc(option, plot_figure=False):
res_z = 11
+ res_y = 11
+ res_x = 11
reti = Reticolo()
- reti_de_ri, reti_de_ti, c, d, r_field_cell = reti.run_res3(**option, grating_type=1, matlab_plot_field=0, res3_npts=res_z)
- print('reti de_ri', np.array(reti_de_ri).flatten())
- print('reti de_ti', np.array(reti_de_ti).flatten())
+ (top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm,
+ bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, reti_field_cell) \
+ = reti.run_res3(**option, grating_type=1, matlab_plot_field=0, res3_npts=res_z)
+ # print('reti de_ri', np.array(reti_de_ri).flatten())
+ # print('reti de_ti', np.array(reti_de_ti).flatten())
+
+ reti_field_cell = reti_field_cell[res_z:-res_z].swapaxes(1, 2)
+ reti_field_cell = np.flip(reti_field_cell, 0)
+ reti_field_cell = reti_field_cell.conj()
# Numpy
- backend = 0
- mee = meent.call_mee(backend=backend, perturbation=1E-30, **option)
- n_de_ri, n_de_ti = mee.conv_solve()
- n_field_cell = mee.calculate_field(res_z=res_z, res_y=1, res_x=50)
+ mee = meent.call_mee(backend=0, **option)
+ res_numpy = mee.conv_solve()
+ field_cell_numpy = mee.calculate_field(res_z=res_z, res_y=res_y, res_x=res_x)
+
+ # JAX
+ mee = meent.call_mee(backend=1, **option) # JAX
+ res_jax = mee.conv_solve()
+ field_cell_jax = mee.calculate_field(res_z=res_z, res_y=res_y, res_x=res_x)
+
+ # Torch
+ mee = meent.call_mee(backend=2, **option) # PyTorch
+ res_torch = mee.conv_solve()
+ field_cell_torch = mee.calculate_field(res_z=res_z, res_y=res_y, res_x=res_x).numpy()
+
+ bds = ['Numpy', 'JAX', 'Torch']
+ fields = [field_cell_numpy, field_cell_jax, field_cell_torch]
+
+ print('Norm of (meent - reti) per backend')
+ for i, res_t in enumerate([res_numpy, res_jax, res_torch]):
+ reti_de_ri_te, reti_de_ti_te = np.array(top_refl_info_te.efficiency).T, np.array(top_tran_info_te.efficiency).T
+ reti_de_ri_tm, reti_de_ti_tm = np.array(top_refl_info_tm.efficiency).T, np.array(top_tran_info_tm.efficiency).T
+
+ de_ri_te, de_ti_te = np.array(res_t.res_te_inc.de_ri).T, np.array(res_t.res_te_inc.de_ti).T
+ de_ri_tm, de_ti_tm = np.array(res_t.res_tm_inc.de_ri).T, np.array(res_t.res_tm_inc.de_ti).T
- print('meent de_ri', n_de_ri[n_de_ri > 1E-5])
- print('meent de_ti', n_de_ti[n_de_ti > 1E-5])
+ de_ri_te = de_ri_te[de_ri_te > 1E-5]
+ de_ti_te = de_ti_te[de_ti_te > 1E-5]
+ de_ri_tm = de_ri_tm[de_ri_tm > 1E-5]
+ de_ti_tm = de_ti_tm[de_ti_tm > 1E-5]
- r_field_cell = np.moveaxis(r_field_cell, 2, 1)
- r_field_cell = r_field_cell[res_z:-res_z]
- r_field_cell = np.flip(r_field_cell, 0)
- r_field_cell = r_field_cell.conj()
+ # reti_R_s_te = top_refl_info_te.amplitude_TE
+ # reti_T_s_te = top_tran_info_te.amplitude_TE
+ # reti_R_p_tm = top_refl_info_tm.amplitude_TM
+ # reti_T_p_tm = top_tran_info_tm.amplitude_TM
+ #
+ # R_s_te = res_t.res_te_inc.R_s
+ # T_s_te = res_t.res_te_inc.T_s
+ # R_p_tm = res_t.res_tm_inc.R_p
+ # T_p_tm = res_t.res_tm_inc.T_p
- for i in range(6):
- print(i, np.linalg.norm(r_field_cell[:, :, :, i] - n_field_cell[:, :, :, i]))
+ print(bds[i])
+ print('de_ri_te', np.linalg.norm(de_ri_te - reti_de_ri_te),
+ 'de_ti_te', np.linalg.norm(de_ti_te - reti_de_ti_te),
+ 'de_ri_tm', np.linalg.norm(de_ri_tm - reti_de_ri_tm),
+ 'de_ti_tm', np.linalg.norm(de_ti_tm - reti_de_ti_tm),
+ )
- if plot_figure:
- title = ['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']
+ for i_field in range(reti_field_cell.shape[-1]):
+ res_temp = np.linalg.norm(fields[i][i_field] - reti_field_cell[i_field])
+ print(f'field, {i_field+1}th: {res_temp}')
- fig, axes = plt.subplots(6, 6, figsize=(10, 5))
+ if plot_figure:
+ title = ['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']
- for ix in range(len(title)):
- r_data = r_field_cell[:, 0, :, ix]
+ fig, axes = plt.subplots(6, 6, figsize=(10, 5))
- im = axes[ix, 0].imshow(abs(r_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 0], shrink=1)
- im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 2], shrink=1)
- im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 4], shrink=1)
+ for ix in range(len(title)):
+ r_data = reti_field_cell[:, res_y//2, :, ix]
- n_data = n_field_cell[:, 0, :, ix]
+ im = axes[ix, 0].imshow(abs(r_data) ** 2, cmap='jet', aspect='auto')
+ fig.colorbar(im, ax=axes[ix, 0], shrink=1)
+ im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
+ fig.colorbar(im, ax=axes[ix, 2], shrink=1)
+ im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
+ fig.colorbar(im, ax=axes[ix, 4], shrink=1)
- im = axes[ix, 1].imshow(abs(n_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 1], shrink=1)
+ n_data = fields[i][:, res_y//2, :, ix]
- im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 3], shrink=1)
+ im = axes[ix, 1].imshow(abs(n_data) ** 2, cmap='jet', aspect='auto')
+ fig.colorbar(im, ax=axes[ix, 1], shrink=1)
- im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 5], shrink=1)
+ im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
+ fig.colorbar(im, ax=axes[ix, 3], shrink=1)
- ix = 0
- axes[ix, 0].title.set_text('abs**2 reti')
- axes[ix, 2].title.set_text('Re, reti')
- axes[ix, 4].title.set_text('Im, reti')
- axes[ix, 1].title.set_text('abs**2 meen')
- axes[ix, 3].title.set_text('Re, meen')
- axes[ix, 5].title.set_text('Im, meen')
+ im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
+ fig.colorbar(im, ax=axes[ix, 5], shrink=1)
- plt.show()
+ ix = 0
+ axes[ix, 0].title.set_text('abs**2 reti')
+ axes[ix, 2].title.set_text('Re, reti')
+ axes[ix, 4].title.set_text('Im, reti')
+ axes[ix, 1].title.set_text('abs**2 meen')
+ axes[ix, 3].title.set_text('Re, meen')
+ axes[ix, 5].title.set_text('Im, meen')
+ plt.show()
-def test1dc_2(plot_figure=False):
- factor = 10
+
+def case_1dc_1(plot_figure=False):
+
+ factor = 1000
option = {}
- option['pol'] = 1 # 0: TE, 1: TM
- option['n_top'] = 1 # n_incidence
- option['n_bot'] = 2 # n_transmission
+ option['pol'] = 0 # 0: TE, 1: TM
+ option['n_top'] = 2 # n_incidence
+ option['n_bot'] = 1 # n_transmission
option['theta'] = 0 * np.pi / 180
- option['phi'] = 90 * np.pi / 180
- option['fto'] = [10, 0]
- option['period'] = [3000 / factor]
- option['wavelength'] = 100 / factor
- option['thickness'] = [400 / factor, ] # final term is for h_substrate
+ option['phi'] = 0 / 180 * np.pi
+ option['fto'] = 1
+ option['period'] = [770/factor]
+ option['wavelength'] = 777/factor
+ option['thickness'] = [100/factor,]
option['fourier_type'] = 1
ucell = np.array(
@@ -116,67 +140,34 @@ def test1dc_2(plot_figure=False):
option['ucell'] = ucell
- res_z = 11
- reti = Reticolo()
- reti_de_ri, reti_de_ti, c, d, r_field_cell = reti.run_res3(**option, grating_type=1, matlab_plot_field=0, res3_npts=res_z)
- print('reti de_ri', np.array(reti_de_ri).flatten())
- print('reti de_ti', np.array(reti_de_ti).flatten())
-
- # Numpy
- backend = 0
- mee = meent.call_mee(backend=backend, perturbation=1E-30, **option)
- n_de_ri, n_de_ti = mee.conv_solve()
- n_field_cell = mee.calculate_field(res_z=res_z, res_y=1, res_x=50)
-
- print('meent de_ri', n_de_ri[n_de_ri > 1E-5])
- print('meent de_ti', n_de_ti[n_de_ti > 1E-5])
-
- r_field_cell = np.moveaxis(r_field_cell, 2, 1)
- r_field_cell = r_field_cell[res_z:-res_z]
- r_field_cell = np.flip(r_field_cell, 0)
- r_field_cell = r_field_cell.conj()
-
- for i in range(6):
- print(i, np.linalg.norm(r_field_cell[:, :, :, i] - n_field_cell[:, :, :, i]))
-
- if plot_figure:
- title = ['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']
-
- fig, axes = plt.subplots(6, 6, figsize=(10, 5))
-
- for ix in range(len(title)):
- r_data = r_field_cell[:, 0, :, ix]
-
- im = axes[ix, 0].imshow(abs(r_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 0], shrink=1)
- im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 2], shrink=1)
- im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 4], shrink=1)
+ run_1dc(option, plot_figure)
- n_data = n_field_cell[:, 0, :, ix]
- im = axes[ix, 1].imshow(abs(n_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 1], shrink=1)
+def case_1dc_2(plot_figure=False):
- im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 3], shrink=1)
+ factor = 1
+ option = {}
+ option['pol'] = 1 # 0: TE, 1: TM
+ option['n_top'] = 1 # n_incidence
+ option['n_bot'] = 2.2 # n_transmission
+ option['theta'] = 0 * np.pi / 180
+ option['phi'] = 30 * np.pi / 180
+ option['fto'] = 80
+ option['period'] = [770/factor]
+ option['wavelength'] = 777/factor
+ option['thickness'] = [100/factor,]
+ option['fourier_type'] = 1
- im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 5], shrink=1)
+ ucell = np.array(
+ [
+ [[3, 3, 3, 3, 3, 1, 1, 1, 1, 1]],
+ ])
- ix = 0
- axes[ix, 0].title.set_text('abs**2 reti')
- axes[ix, 2].title.set_text('Re, reti')
- axes[ix, 4].title.set_text('Im, reti')
- axes[ix, 1].title.set_text('abs**2 meen')
- axes[ix, 3].title.set_text('Re, meen')
- axes[ix, 5].title.set_text('Im, meen')
+ option['ucell'] = ucell
- plt.show()
+ run_1dc(option, plot_figure)
if __name__ == '__main__':
- test1dc_1()
- test1dc_2()
-
+ case_1dc_1(False)
+ case_1dc_2(False)
diff --git a/benchmarks/reti_meent_2D.py b/benchmarks/reti_meent_2D.py
index c83069b..863c542 100644
--- a/benchmarks/reti_meent_2D.py
+++ b/benchmarks/reti_meent_2D.py
@@ -17,13 +17,115 @@
# oct2py.octave.addpath(octave.genpath('E:/funcs/software/octave_calls'))
-def test2d_1(plot_figure=False):
+def run_2d(option, case, plot_figure=False):
+ res_z = 11
+ res_y = 11
+ res_x = 11
reti = Reticolo()
- [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, field_cell] = reti.eng.reti_2d(1, nout=5)
- reti_de_ri, reti_de_ti, c, d, r_field_cell = top_refl_info.efficiency, top_tran_info.efficiency, bottom_refl_info.efficiency, \
- bottom_tran_info.efficiency, field_cell
+ (top_refl_info_te, top_tran_info_te, top_refl_info_tm, top_tran_info_tm,
+ bottom_refl_info_te, bottom_tran_info_te, bottom_refl_info_tm, bottom_tran_info_tm, reti_field_cell)\
+ = reti.eng.reti_2d(case, nout=9)
+
+ reti_field_cell = reti_field_cell[res_z:-res_z].swapaxes(1, 2)
+ reti_field_cell = np.flip(reti_field_cell, 0)
+ reti_field_cell = reti_field_cell.conj()
+ # Numpy
+ mee = meent.call_mee(backend=0, **option)
+ res_numpy = mee.conv_solve()
+ field_cell_numpy = mee.calculate_field(res_z=res_z, res_y=res_y, res_x=res_x)
+
+ # JAX
+ mee = meent.call_mee(backend=1, **option) # JAX
+ res_jax = mee.conv_solve()
+ field_cell_jax = mee.calculate_field(res_z=res_z, res_y=res_y, res_x=res_x)
+
+ # Torch
+ mee = meent.call_mee(backend=2, **option) # PyTorch
+ res_torch = mee.conv_solve()
+ field_cell_torch = mee.calculate_field(res_z=res_z, res_y=res_y, res_x=res_x).numpy()
+
+ bds = ['Numpy', 'JAX', 'Torch']
+ fields = [field_cell_numpy, field_cell_jax, field_cell_torch]
+
+ print('Norm of (meent - reti) per backend')
+ for i, res_t in enumerate([res_numpy, res_jax, res_torch]):
+ reti_de_ri_te, reti_de_ti_te = np.array(top_refl_info_te.efficiency).T, np.array(top_tran_info_te.efficiency).T
+ reti_de_ri_tm, reti_de_ti_tm = np.array(top_refl_info_tm.efficiency).T, np.array(top_tran_info_tm.efficiency).T
+
+ de_ri_te, de_ti_te = np.array(res_t.res_te_inc.de_ri).T, np.array(res_t.res_te_inc.de_ti).T
+ de_ri_tm, de_ti_tm = np.array(res_t.res_tm_inc.de_ri).T, np.array(res_t.res_tm_inc.de_ti).T
+
+ reti_de_ri_te = reti_de_ri_te[reti_de_ri_te > 1E-5]
+ reti_de_ti_te = reti_de_ti_te[reti_de_ti_te > 1E-5]
+ reti_de_ri_tm = reti_de_ri_tm[reti_de_ri_tm > 1E-5]
+ reti_de_ti_tm = reti_de_ti_tm[reti_de_ti_tm > 1E-5]
+
+ de_ri_te = de_ri_te[de_ri_te > 1E-5]
+ de_ti_te = de_ti_te[de_ti_te > 1E-5]
+ de_ri_tm = de_ri_tm[de_ri_tm > 1E-5]
+ de_ti_tm = de_ti_tm[de_ti_tm > 1E-5]
+
+ # reti_R_s_te = top_refl_info_te.amplitude_TE
+ # reti_T_s_te = top_tran_info_te.amplitude_TE
+ # reti_R_p_tm = top_refl_info_tm.amplitude_TM
+ # reti_T_p_tm = top_tran_info_tm.amplitude_TM
+ #
+ # R_s_te = res_t.res_te_inc.R_s
+ # T_s_te = res_t.res_te_inc.T_s
+ # R_p_tm = res_t.res_tm_inc.R_p
+ # T_p_tm = res_t.res_tm_inc.T_p
+
+ print(bds[i])
+ print('de_ri_te', np.linalg.norm(de_ri_te - reti_de_ri_te),
+ 'de_ti_te', np.linalg.norm(de_ti_te - reti_de_ti_te),
+ 'de_ri_tm', np.linalg.norm(de_ri_tm - reti_de_ri_tm),
+ 'de_ti_tm', np.linalg.norm(de_ti_tm - reti_de_ti_tm),
+ )
+
+ for i_field in range(reti_field_cell.shape[-1]):
+ res_temp = np.linalg.norm(fields[i][i_field] - reti_field_cell[i_field])
+ print(f'field, {i_field+1}th: {res_temp}')
+
+ if plot_figure:
+ title = ['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']
+
+ fig, axes = plt.subplots(6, 6, figsize=(10, 5))
+
+ for ix in range(len(title)):
+ r_data = reti_field_cell[:, res_y//2, :, ix]
+
+ im = axes[ix, 0].imshow(abs(r_data) ** 2, cmap='jet', aspect='auto')
+ fig.colorbar(im, ax=axes[ix, 0], shrink=1)
+ im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
+ fig.colorbar(im, ax=axes[ix, 2], shrink=1)
+ im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
+ fig.colorbar(im, ax=axes[ix, 4], shrink=1)
+
+ n_data = fields[i][:, res_y//2, :, ix]
+
+ im = axes[ix, 1].imshow(abs(n_data) ** 2, cmap='jet', aspect='auto')
+ fig.colorbar(im, ax=axes[ix, 1], shrink=1)
+
+ im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
+ fig.colorbar(im, ax=axes[ix, 3], shrink=1)
+
+ im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
+ fig.colorbar(im, ax=axes[ix, 5], shrink=1)
+
+ ix = 0
+ axes[ix, 0].title.set_text('abs**2 reti')
+ axes[ix, 2].title.set_text('Re, reti')
+ axes[ix, 4].title.set_text('Im, reti')
+ axes[ix, 1].title.set_text('abs**2 meen')
+ axes[ix, 3].title.set_text('Re, meen')
+ axes[ix, 5].title.set_text('Im, meen')
+
+ plt.show()
+
+
+def case_2d_1(plot_figure=False):
factor = 1
option = {}
option['pol'] = 1 # 0: TE, 1: TM
@@ -53,108 +155,10 @@ def test2d_1(plot_figure=False):
option['ucell'] = ucell
- print('reti de_ri', np.array(reti_de_ri).flatten())
- print('reti de_ti', np.array(reti_de_ti).flatten())
-
- res_z = 11
-
- # Numpy
- backend = 0
- mee = meent.call_mee(backend=backend, **option)
- n_de_ri, n_de_ti = mee.conv_solve()
- n_field_cell = mee.calculate_field(res_z=res_z, res_y=50, res_x=50)
- # print('meent de_ri', n_de_ri)
- # print('meent de_ti', n_de_ti)
- print('meent de_ri', n_de_ri[n_de_ri > 1E-5])
- print('meent de_ti', n_de_ti[n_de_ti > 1E-5])
-
- r_field_cell = np.moveaxis(r_field_cell, 2, 1)
- r_field_cell = r_field_cell[res_z:-res_z]
- r_field_cell = np.flip(r_field_cell, 0)
- r_field_cell = r_field_cell.conj()
-
- for i in range(6):
- print(i, np.linalg.norm(r_field_cell[:, :, :, i] - n_field_cell[:, :, :, i]))
-
- if plot_figure:
- title = ['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']
- fig, axes = plt.subplots(6, 6, figsize=(10, 5))
-
- for ix in range(len(title)):
- # r_data = np.flipud(r_field_cell[res3_npts:-res3_npts, :, 0, ix]).conj()
- r_data = r_field_cell[:, 0, :, ix]
- im = axes[ix, 0].imshow(abs(r_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 0], shrink=1)
- im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 2], shrink=1)
- im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 4], shrink=1)
-
- n_data = n_field_cell[:, 0, :, ix]
-
- im = axes[ix, 1].imshow(abs(n_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 1], shrink=1)
-
- im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 3], shrink=1)
-
- im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 5], shrink=1)
-
- ix = 0
- axes[ix, 0].title.set_text('abs**2 reti')
- axes[ix, 2].title.set_text('Re, reti')
- axes[ix, 4].title.set_text('Im, reti')
- axes[ix, 1].title.set_text('abs**2 meen')
- axes[ix, 3].title.set_text('Re, meen')
- axes[ix, 5].title.set_text('Im, meen')
-
- plt.show()
-
- fig, axes = plt.subplots(6, 6, figsize=(10, 5))
-
- for ix in range(len(title)):
- # r_data = np.transpose(r_field_cell[2*res3_npts, :, :, ix]).conj()
- r_data = r_field_cell[5, :, :, ix]
-
- im = axes[ix, 0].imshow(abs(r_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 0], shrink=1)
- im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 2], shrink=1)
- im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 4], shrink=1)
-
- n_data = n_field_cell[5, :, :, ix]
-
- im = axes[ix, 1].imshow(abs(n_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 1], shrink=1)
-
- im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 3], shrink=1)
-
- im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 5], shrink=1)
+ run_2d(option, 1, plot_figure)
- ix = 0
- axes[ix, 0].title.set_text('abs**2 reti')
- axes[ix, 2].title.set_text('Re, reti')
- axes[ix, 4].title.set_text('Im, reti')
- axes[ix, 1].title.set_text('abs**2 meen')
- axes[ix, 3].title.set_text('Re, meen')
- axes[ix, 5].title.set_text('Im, meen')
-
- plt.show()
-
- return
-
-
-def test2d_2(plot_figure=False):
- reti = Reticolo()
-
- [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, field_cell] = reti.eng.reti_2d(2, nout=5)
- reti_de_ri, reti_de_ti, c, d, r_field_cell = top_refl_info.efficiency, top_tran_info.efficiency, bottom_refl_info.efficiency, \
- bottom_tran_info.efficiency, field_cell
+def case_2d_2(plot_figure=False):
factor = 1
option = {}
option['pol'] = 1 # 0: TE, 1: TM
@@ -184,110 +188,10 @@ def test2d_2(plot_figure=False):
option['ucell'] = ucell
- # reti = Reticolo()
- # reti_de_ri, reti_de_ti, c, d, r_field_cell = reti.run_res3(**option, res3_npts=res3_npts)
- print('reti de_ri', np.array(reti_de_ri).flatten())
- print('reti de_ti', np.array(reti_de_ti).flatten())
-
- res_z = 11
-
- # Numpy
- backend = 0
- mee = meent.call_mee(backend=backend, **option)
- n_de_ri, n_de_ti = mee.conv_solve()
- n_field_cell = mee.calculate_field(res_z=res_z, res_y=50, res_x=50)
- # print('meent de_ri', n_de_ri)
- # print('meent de_ti', n_de_ti)
- print('meent de_ri', n_de_ri[n_de_ri > 1E-5])
- print('meent de_ti', n_de_ti[n_de_ti > 1E-5])
-
- r_field_cell = np.moveaxis(r_field_cell, 2, 1)
- r_field_cell = r_field_cell[res_z:-res_z]
- r_field_cell = np.flip(r_field_cell, 0)
- r_field_cell = r_field_cell.conj()
-
- for i in range(6):
- print(i, np.linalg.norm(r_field_cell[:, :, :, i] - n_field_cell[:, :, :, i]))
-
- if plot_figure:
- title = ['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']
- fig, axes = plt.subplots(6, 6, figsize=(10, 5))
-
- for ix in range(len(title)):
- # r_data = np.flipud(r_field_cell[res3_npts:-res3_npts, :, 0, ix]).conj()
- r_data = r_field_cell[:, 0, :, ix]
- im = axes[ix, 0].imshow(abs(r_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 0], shrink=1)
- im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 2], shrink=1)
- im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 4], shrink=1)
-
- n_data = n_field_cell[:, 0, :, ix]
-
- im = axes[ix, 1].imshow(abs(n_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 1], shrink=1)
-
- im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 3], shrink=1)
-
- im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 5], shrink=1)
-
- ix = 0
- axes[ix, 0].title.set_text('abs**2 reti')
- axes[ix, 2].title.set_text('Re, reti')
- axes[ix, 4].title.set_text('Im, reti')
- axes[ix, 1].title.set_text('abs**2 meen')
- axes[ix, 3].title.set_text('Re, meen')
- axes[ix, 5].title.set_text('Im, meen')
-
- plt.show()
-
- fig, axes = plt.subplots(6, 6, figsize=(10, 5))
-
- for ix in range(len(title)):
- # r_data = np.transpose(r_field_cell[2*res3_npts, :, :, ix]).conj()
- r_data = r_field_cell[5, :, :, ix]
-
- im = axes[ix, 0].imshow(abs(r_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 0], shrink=1)
- im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 2], shrink=1)
- im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 4], shrink=1)
+ run_2d(option, 2, plot_figure)
- n_data = n_field_cell[5, :, :, ix]
-
- im = axes[ix, 1].imshow(abs(n_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 1], shrink=1)
-
- im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 3], shrink=1)
-
- im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 5], shrink=1)
-
- ix = 0
- axes[ix, 0].title.set_text('abs**2 reti')
- axes[ix, 2].title.set_text('Re, reti')
- axes[ix, 4].title.set_text('Im, reti')
- axes[ix, 1].title.set_text('abs**2 meen')
- axes[ix, 3].title.set_text('Re, meen')
- axes[ix, 5].title.set_text('Im, meen')
-
- plt.show()
-
- return
-
-
-def test2d_3(plot_figure=False):
- reti = Reticolo()
-
- [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, field_cell] = reti.eng.reti_2d(3, nout=5)
- reti_de_ri, reti_de_ti, c, d, r_field_cell = top_refl_info.efficiency, top_tran_info.efficiency, bottom_refl_info.efficiency, \
- bottom_tran_info.efficiency, field_cell
+def case_2d_3(plot_figure=False):
factor = 1
option = {}
option['pol'] = 1 # 0: TE, 1: TM
@@ -316,111 +220,10 @@ def test2d_3(plot_figure=False):
]])
option['ucell'] = ucell
+ run_2d(option, 3, plot_figure)
- # reti = Reticolo()
- # reti_de_ri, reti_de_ti, c, d, r_field_cell = reti.run_res3(**option, res3_npts=res3_npts)
- print('reti de_ri', np.array(reti_de_ri).flatten())
- print('reti de_ti', np.array(reti_de_ti).flatten())
-
- res_z = 11
-
- # Numpy
- backend = 0
- mee = meent.call_mee(backend=backend, **option)
- n_de_ri, n_de_ti = mee.conv_solve()
- n_field_cell = mee.calculate_field(res_z=res_z, res_y=50, res_x=50)
- # print('meent de_ri', n_de_ri)
- # print('meent de_ti', n_de_ti)
- print('meent de_ri', n_de_ri[n_de_ri > 1E-5])
- print('meent de_ti', n_de_ti[n_de_ti > 1E-5])
-
- r_field_cell = np.moveaxis(r_field_cell, 2, 1)
- r_field_cell = r_field_cell[res_z:-res_z]
- r_field_cell = np.flip(r_field_cell, 0)
- r_field_cell = r_field_cell.conj()
-
- for i in range(6):
- print(i, np.linalg.norm(r_field_cell[:, :, :, i] - n_field_cell[:, :, :, i]))
-
- if plot_figure:
- title = ['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']
- fig, axes = plt.subplots(6, 6, figsize=(10, 5))
-
- for ix in range(len(title)):
- # r_data = np.flipud(r_field_cell[res3_npts:-res3_npts, :, 0, ix]).conj()
- r_data = r_field_cell[:, 0, :, ix]
- im = axes[ix, 0].imshow(abs(r_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 0], shrink=1)
- im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 2], shrink=1)
- im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 4], shrink=1)
-
- n_data = n_field_cell[:, 0, :, ix]
-
- im = axes[ix, 1].imshow(abs(n_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 1], shrink=1)
-
- im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 3], shrink=1)
-
- im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 5], shrink=1)
-
- ix = 0
- axes[ix, 0].title.set_text('abs**2 reti')
- axes[ix, 2].title.set_text('Re, reti')
- axes[ix, 4].title.set_text('Im, reti')
- axes[ix, 1].title.set_text('abs**2 meen')
- axes[ix, 3].title.set_text('Re, meen')
- axes[ix, 5].title.set_text('Im, meen')
-
- plt.show()
-
- fig, axes = plt.subplots(6, 6, figsize=(10, 5))
-
- for ix in range(len(title)):
- # r_data = np.transpose(r_field_cell[2*res3_npts, :, :, ix]).conj()
- r_data = r_field_cell[5, :, :, ix]
-
- im = axes[ix, 0].imshow(abs(r_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 0], shrink=1)
- im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 2], shrink=1)
- im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 4], shrink=1)
-
- n_data = n_field_cell[5, :, :, ix]
-
- im = axes[ix, 1].imshow(abs(n_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 1], shrink=1)
-
- im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 3], shrink=1)
-
- im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 5], shrink=1)
-
- ix = 0
- axes[ix, 0].title.set_text('abs**2 reti')
- axes[ix, 2].title.set_text('Re, reti')
- axes[ix, 4].title.set_text('Im, reti')
- axes[ix, 1].title.set_text('abs**2 meen')
- axes[ix, 3].title.set_text('Re, meen')
- axes[ix, 5].title.set_text('Im, meen')
-
- plt.show()
-
- return
-
-
-def test2d_4(plot_figure=False):
- reti = Reticolo()
-
- [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, field_cell] = reti.eng.reti_2d(4, nout=5)
- reti_de_ri, reti_de_ti, c, d, r_field_cell = top_refl_info.efficiency, top_tran_info.efficiency, bottom_refl_info.efficiency, \
- bottom_tran_info.efficiency, field_cell
+def case_2d_4(plot_figure=False):
factor = 1
option = {}
option['pol'] = 0 # 0: TE, 1: TM
@@ -445,113 +248,10 @@ def test2d_4(plot_figure=False):
) * 3 + 1
option['ucell'] = ucell
+ run_2d(option, 4, plot_figure)
- # reti = Reticolo()
- # reti_de_ri, reti_de_ti, c, d, r_field_cell = reti.run_res3(**option, res3_npts=res3_npts)
- # print('reti de_ri', np.array(reti_de_ri))
- # print('reti de_ti', np.array(reti_de_ti))
- print('reti de_ri', np.array(reti_de_ri).flatten())
- print('reti de_ti', np.array(reti_de_ti).flatten())
-
- res_z = 11
-
- # Numpy
- backend = 0
- mee = meent.call_mee(backend=backend, **option)
- n_de_ri, n_de_ti = mee.conv_solve()
- n_field_cell = mee.calculate_field(res_z=res_z, res_y=50, res_x=50)
- # print('meent de_ri', n_de_ri)
- # print('meent de_ti', n_de_ti)
- print('meent de_ri', n_de_ri[n_de_ri > 1E-5])
- print('meent de_ti', n_de_ti[n_de_ti > 1E-5])
-
- r_field_cell = np.moveaxis(r_field_cell, 2, 1)
- r_field_cell = r_field_cell[res_z:-res_z]
- r_field_cell = np.flip(r_field_cell, 0)
- r_field_cell = r_field_cell.conj()
-
- for i in range(6):
- print(i, np.linalg.norm(r_field_cell[:, :, :, i] - n_field_cell[:, :, :, i]))
-
- if plot_figure:
- title = ['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']
- fig, axes = plt.subplots(6, 6, figsize=(10, 5))
-
- for ix in range(len(title)):
- # r_data = np.flipud(r_field_cell[res3_npts:-res3_npts, :, 0, ix]).conj()
- r_data = r_field_cell[:, 0, :, ix]
- im = axes[ix, 0].imshow(abs(r_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 0], shrink=1)
- im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 2], shrink=1)
- im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 4], shrink=1)
-
- n_data = n_field_cell[:, 0, :, ix]
-
- im = axes[ix, 1].imshow(abs(n_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 1], shrink=1)
-
- im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 3], shrink=1)
-
- im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 5], shrink=1)
-
- ix = 0
- axes[ix, 0].title.set_text('abs**2 reti')
- axes[ix, 2].title.set_text('Re, reti')
- axes[ix, 4].title.set_text('Im, reti')
- axes[ix, 1].title.set_text('abs**2 meen')
- axes[ix, 3].title.set_text('Re, meen')
- axes[ix, 5].title.set_text('Im, meen')
-
- plt.show()
-
- fig, axes = plt.subplots(6, 6, figsize=(10, 5))
-
- for ix in range(len(title)):
- # r_data = np.transpose(r_field_cell[2*res3_npts, :, :, ix]).conj()
- r_data = r_field_cell[5, :, :, ix]
-
- im = axes[ix, 0].imshow(abs(r_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 0], shrink=1)
- im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 2], shrink=1)
- im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 4], shrink=1)
-
- n_data = n_field_cell[5, :, :, ix]
-
- im = axes[ix, 1].imshow(abs(n_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 1], shrink=1)
-
- im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 3], shrink=1)
-
- im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 5], shrink=1)
-
- ix = 0
- axes[ix, 0].title.set_text('abs**2 reti')
- axes[ix, 2].title.set_text('Re, reti')
- axes[ix, 4].title.set_text('Im, reti')
- axes[ix, 1].title.set_text('abs**2 meen')
- axes[ix, 3].title.set_text('Re, meen')
- axes[ix, 5].title.set_text('Im, meen')
-
- plt.show()
-
- return
-
-
-def test2d_5(plot_figure=False):
- reti = Reticolo()
-
- [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, field_cell] = reti.eng.reti_2d(5, nout=5)
- reti_de_ri, reti_de_ti, c, d, r_field_cell = top_refl_info.efficiency, top_tran_info.efficiency, bottom_refl_info.efficiency, \
- bottom_tran_info.efficiency, field_cell
+def case_2d_5(plot_figure=False):
factor = 1
option = {}
option['pol'] = 0 # 0: TE, 1: TM
@@ -576,123 +276,10 @@ def test2d_5(plot_figure=False):
) * 3 + 1
option['ucell'] = ucell
+ run_2d(option, 5, plot_figure)
- # reti = Reticolo()
- # reti_de_ri, reti_de_ti, c, d, r_field_cell = reti.run_res3(**option, res3_npts=res3_npts)
- # print('reti de_ri', np.array(reti_de_ri))
- # print('reti de_ti', np.array(reti_de_ti))
- print('reti de_ri', np.array(reti_de_ri).flatten())
- print('reti de_ti', np.array(reti_de_ti).flatten())
-
- res_z = 11
-
- # Numpy
- backend = 0
- mee = meent.call_mee(backend=backend, **option)
- n_de_ri, n_de_ti = mee.conv_solve()
- n_field_cell = mee.calculate_field(res_z=res_z, res_y=50, res_x=50)
- # print('meent de_ri', n_de_ri)
- # print('meent de_ti', n_de_ti)
- print('meent de_ri', n_de_ri[n_de_ri > 1E-5])
- print('meent de_ti', n_de_ti[n_de_ti > 1E-5])
-
- r_field_cell = np.moveaxis(r_field_cell, 2, 1)
- r_field_cell = r_field_cell[res_z:-res_z]
- r_field_cell = np.flip(r_field_cell, 0)
- r_field_cell = r_field_cell.conj()
-
- for i in range(6):
- print(i, np.linalg.norm(r_field_cell[:, :, :, i] - n_field_cell[:, :, :, i]))
-
- if plot_figure:
- title = ['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']
- fig, axes = plt.subplots(6, 6, figsize=(10, 5))
-
- for ix in range(len(title)):
- # r_data = np.flipud(r_field_cell[res3_npts:-res3_npts, :, 0, ix]).conj()
- r_data = r_field_cell[:, 0, :, ix]
- im = axes[ix, 0].imshow(abs(r_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 0], shrink=1)
- im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 2], shrink=1)
- im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 4], shrink=1)
-
- n_data = n_field_cell[:, 0, :, ix]
-
- im = axes[ix, 1].imshow(abs(n_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 1], shrink=1)
-
- im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 3], shrink=1)
-
- im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 5], shrink=1)
-
- axes[0, 0].title.set_text('abs**2 reti')
- axes[0, 2].title.set_text('Re, reti')
- axes[0, 4].title.set_text('Im, reti')
- axes[0, 1].title.set_text('abs**2 meen')
- axes[0, 3].title.set_text('Re, meen')
- axes[0, 5].title.set_text('Im, meen')
-
- plt.show()
-
- fig, axes = plt.subplots(6, 6, figsize=(10, 5))
-
- for ix in range(len(title)):
- # r_data = np.transpose(r_field_cell[2*res3_npts, :, :, ix]).conj()
- r_data = r_field_cell[5, :, :, ix]
-
- im = axes[ix, 0].imshow(abs(r_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 0], shrink=1)
- im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 2], shrink=1)
- im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 4], shrink=1)
-
- n_data = n_field_cell[5, :, :, ix]
-
- im = axes[ix, 1].imshow(abs(n_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 1], shrink=1)
-
- im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 3], shrink=1)
-
- im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 5], shrink=1)
-
- axes[0, 0].title.set_text('abs**2 reti')
- axes[0, 2].title.set_text('Re, reti')
- axes[0, 4].title.set_text('Im, reti')
- axes[0, 1].title.set_text('abs**2 meen')
- axes[0, 3].title.set_text('Re, meen')
- axes[0, 5].title.set_text('Im, meen')
-
- plt.show()
-
- return
-
-
-def test2d_6(plot_figure=False):
-
- res_z = 11
-
- reti = Reticolo()
-
- [top_refl_info, top_tran_info, bottom_refl_info, bottom_tran_info, field_cell] = reti.eng.reti_2d(6, nout=5)
- reti_de_ri, reti_de_ti, c, d, r_field_cell = top_refl_info.efficiency, top_tran_info.efficiency, bottom_refl_info.efficiency, \
- bottom_tran_info.efficiency, field_cell
- # print('reti de_ri', np.array(reti_de_ri))
- # print('reti de_ti', np.array(reti_de_ti))
- print('reti de_ri', np.array(reti_de_ri).flatten())
- print('reti de_ti', np.array(reti_de_ti).flatten())
-
- r_field_cell = np.moveaxis(r_field_cell, 2, 1)
- r_field_cell = r_field_cell[res_z:-res_z]
- r_field_cell = np.flip(r_field_cell, 0)
- r_field_cell = r_field_cell.conj()
+def case_2d_6(plot_figure=False):
factor = 1
option = {}
option['pol'] = 0 # 0: TE, 1: TM
@@ -723,93 +310,49 @@ def test2d_6(plot_figure=False):
]
option['ucell'] = ucell
+ run_2d(option, 6, plot_figure)
- # Numpy
- backend = 0
- mee = meent.call_mee(backend=backend, **option)
- n_de_ri, n_de_ti = mee.conv_solve()
- n_field_cell = mee.calculate_field(res_z=res_z, res_y=50, res_x=50)
- # print('meent de_ri', n_de_ri)
- # print('meent de_ti', n_de_ti)
- print('meent de_ri', n_de_ri[n_de_ri > 1E-5])
- print('meent de_ti', n_de_ti[n_de_ti > 1E-5])
-
- for i in range(6):
- print(i, np.linalg.norm(r_field_cell[:, :, :, i] - n_field_cell[:, :, :, i]))
-
- if plot_figure:
- title = ['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz']
- fig, axes = plt.subplots(6, 6, figsize=(10, 5))
-
- for ix in range(len(title)):
- r_data = r_field_cell[:, 0, :, ix]
- im = axes[ix, 0].imshow(abs(r_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 0], shrink=1)
- im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 2], shrink=1)
- im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 4], shrink=1)
-
- n_data = n_field_cell[:, 0, :, ix]
-
- im = axes[ix, 1].imshow(abs(n_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 1], shrink=1)
-
- im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 3], shrink=1)
-
- im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 5], shrink=1)
- axes[0, 0].title.set_text('abs**2 reti')
- axes[0, 2].title.set_text('Re, reti')
- axes[0, 4].title.set_text('Im, reti')
- axes[0, 1].title.set_text('abs**2 meen')
- axes[0, 3].title.set_text('Re, meen')
- axes[0, 5].title.set_text('Im, meen')
-
- plt.show()
-
- fig, axes = plt.subplots(6, 6, figsize=(10, 5))
-
- for ix in range(len(title)):
- r_data = r_field_cell[5, :, :, ix]
-
- im = axes[ix, 0].imshow(abs(r_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 0], shrink=1)
- im = axes[ix, 2].imshow(r_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 2], shrink=1)
- im = axes[ix, 4].imshow(r_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 4], shrink=1)
-
- n_data = n_field_cell[5, :, :, ix]
-
- im = axes[ix, 1].imshow(abs(n_data) ** 2, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 1], shrink=1)
-
- im = axes[ix, 3].imshow(n_data.real, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 3], shrink=1)
-
- im = axes[ix, 5].imshow(n_data.imag, cmap='jet', aspect='auto')
- fig.colorbar(im, ax=axes[ix, 5], shrink=1)
-
- axes[0, 0].title.set_text('abs**2 reti')
- axes[0, 2].title.set_text('Re, reti')
- axes[0, 4].title.set_text('Im, reti')
- axes[0, 1].title.set_text('abs**2 meen')
- axes[0, 3].title.set_text('Re, meen')
- axes[0, 5].title.set_text('Im, meen')
+def case_2d_7(plot_figure=False):
+ factor = 1
+ option = {}
+ option['pol'] = 0 # 0: TE, 1: TM
+ option['n_top'] = 1 # n_incidence
+ option['n_bot'] = 1 # n_transmission
+ option['theta'] = 10 * np.pi / 180
+ option['phi'] = 20 * np.pi / 180
+ option['fto'] = [2, 2]
+ option['period'] = [480 / factor, 480 / factor]
+ option['wavelength'] = 550 / factor
+ option['thickness'] = [220 / factor, ] # final term is for h_substrate
+ option['fourier_type'] = 1
- plt.show()
+ ucell = [
+ # layer 1
+ [1,
+ [
+ # obj 1
+ ['rectangle', 0+240, 120+240, 160, 80, 4+1j, 0, 0, 0],
+ # obj 2
+ ['rectangle', 0+240, -120+240, 160, 80, 4, 0, 0, 0],
+ # obj 3
+ ['rectangle', 120+240, 0+240, 80, 160, 4-5j, 0, 0, 0],
+ # obj 4
+ ['rectangle', -120+240, 0+240, 80, 160, 4, 0, 0, 0],
+ ],
+ ],
+ ]
- return
+ option['ucell'] = ucell
+ run_2d(option, 7, plot_figure)
if __name__ == '__main__':
- test2d_1()
- test2d_2()
- test2d_3()
- test2d_4()
- test2d_5()
- test2d_6()
+ case_2d_1()
+ case_2d_2()
+ case_2d_3()
+ case_2d_4()
+ case_2d_5()
+ case_2d_6()
+ case_2d_7()
diff --git a/examples/electric-field-fno/data.py b/examples/electric-field-fno/data.py
index ca9d614..864691e 100644
--- a/examples/electric-field-fno/data.py
+++ b/examples/electric-field-fno/data.py
@@ -66,7 +66,7 @@ def get_field(
ucell=ucell_new
)
# Calculate field distribution: OLD
- de_ri, de_ti, field_cell = mee.conv_solve_field(
+ result, field_cell = mee.conv_solve_field(
res_x=field_res[0], res_y=field_res[1], res_z=field_res[2],
)
diff --git a/examples/ocd/arxiv_ocd_optimize.py b/examples/ocd/arxiv_ocd_optimize.py
index 63db6a3..19383e4 100644
--- a/examples/ocd/arxiv_ocd_optimize.py
+++ b/examples/ocd/arxiv_ocd_optimize.py
@@ -119,60 +119,16 @@ def modelling_ref_index(wavelength, rcwa_options, modeling_options, params_name,
ucell.append([a, obj_list_per_layer])
mee.ucell = ucell
- # mee.draw(layer_info_list)
return mee, ucell
-def modelling_ref_index_old(wavelength, rcwa_options, modeling_options, params_name, params_value, instructions):
-
- mee = meent.call_mee(wavelength=wavelength, **rcwa_options)
-
- t = mee.thickness
-
- for i in range(len(t)):
- if f'l{i+1}_thickness' in params_name:
- t[i] = params_value[params_name[f'l{i+1}_thickness']].reshape((1, 1))
- mee.thickness = t
-
- mat_table = read_material_table()
-
- layer_info_list = []
- for i, layer in enumerate(instructions):
- obj_list_per_layer = []
- for j, _ in enumerate(layer):
- instructions_new = []
- instructions_target = instructions[i][j]
- for k, inst in enumerate(instructions_target):
- if k == 0:
- func = getattr(mee, inst)
- elif inst in params_name:
- instructions_new.append(params_value[params_name[inst]])
- elif inst in modeling_options:
- if inst[-7:] == 'n_index' and type(modeling_options[inst]) is str:
- a = find_nk_index(modeling_options[inst], mat_table, wavelength).conj()
- else:
- a = modeling_options[inst]
- instructions_new.append(a)
- else:
- raise ValueError
- obj_list_per_layer += func(*instructions_new)
-
- a = modeling_options[f'l{i+1}_n_base']
- if type(a) is str:
- a = find_nk_index(a, mat_table, wavelength).conj()
-
- layer_info_list.append([a, obj_list_per_layer])
-
- mee.draw(layer_info_list)
-
- return mee, layer_info_list
-
-
def reflectance_mode_00(mee, wavelength):
mee.wavelength = wavelength
- de_ri, de_ti = mee.conv_solve()
+ # de_ri, de_ti = mee.conv_solve()
+ result = mee.conv_solve()
+ de_ri, de_ti = result.de_ri, result.de_ti
x_c, y_c = np.array(de_ti.shape) // 2
reflectance = de_ri[x_c, y_c]
diff --git a/examples/vector_1d.py b/examples/vector_1d.py
index c2f4040..1710c09 100644
--- a/examples/vector_1d.py
+++ b/examples/vector_1d.py
@@ -4,28 +4,20 @@
def run():
- rcwa_options = dict(backend=2, grating_type=2, thickness=[205, 305, 100000], period=[300, 300],
- fourier_order=[3, 3],
- n_I=1, n_II=1,
+ rcwa_options = dict(backend=2, thickness=[205, 100000], period=[300, 300],
+ fto=[3, 0],
+ n_top=1, n_bot=1,
wavelength=900,
- fft_type=2,
+ pol=0.5,
)
- si = 3.638751670074983-0.007498295841854125j
+ # si = 3.638751670074983-0.007498295841854125j
+ si = 3.638751670074983
sio2 = 1.4518-0j
si3n4 = 2.0056-0j
- instructions = [
+ ucell = [
# layer 1
- [sio2,
- [
- # obj 1
- ['ellipse', 75, 225, 101.5, 81.5, si, 20 * torch.pi / 180, 40, 40],
- # obj 2
- ['rectangle', 225, 75, 98.5, 81.5, si, 0, 0, 0],
- ],
- ],
- # layer 2
[si3n4,
[
# obj 1
@@ -34,23 +26,34 @@ def run():
['rectangle', 200, 150, 49.5, 300, si, 0, 0, 0],
],
],
- # layer 3
+ # layer 2
[si,
[]
],
]
mee = meent.call_mee(**rcwa_options)
- mee.modeling_vector_instruction(instructions)
+ mee.ucell = ucell
+
+ result = mee.conv_solve()
+
+ result_given_pol = result.res
+ result_te_incidence = result.res_te_inc
+ result_tm_incidence = result.res_tm_inc
+
+ de_ri, de_ti = result_given_pol.de_ri, result_given_pol.de_ti
+ de_ri1, de_ti1 = result_te_incidence.de_ri, result_te_incidence.de_ti
+ de_ri2, de_ti2 = result_tm_incidence.de_ri, result_tm_incidence.de_ti
- de_ri, de_ti = mee.conv_solve()
- print(de_ri)
+ print(de_ri.sum(), de_ti.sum())
+ print(de_ri1.sum(), de_ti1.sum())
+ print(de_ri2.sum(), de_ti2.sum())
return
if __name__ == '__main__':
- res = run()
+ run()
print(0)
diff --git a/examples/vector_1d_verification.py b/examples/vector_1d_verification.py
index b9a79be..c0637d0 100644
--- a/examples/vector_1d_verification.py
+++ b/examples/vector_1d_verification.py
@@ -8,52 +8,53 @@ def run_vector(rcwa_options, backend):
rcwa_options['backend'] = backend
mee = meent.call_mee(**rcwa_options)
- mee.modeling_vector_instruction(instructions)
+ mee.ucell = ucell_vector
- de_ri, de_ti = mee.conv_solve()
+ res = mee.conv_solve()
- return de_ri, de_ti
+ return res.de_ri, res.de_ti
-def run_raster(rcwa_options, backend, fft_type):
+def run_raster(rcwa_options, backend, fourier_type):
- # ucell = ucell.numpy()
+ # ucell_raster = ucell_raster.numpy()
rcwa_options['backend'] = backend
- rcwa_options['fourier_type'] = fft_type
+ rcwa_options['fourier_type'] = fourier_type
# 0: Discrete Fourier series; 1 is for Continuous FS which is used in vector modeling.
-
if backend == 0:
- ucell = np.asarray(rcwa_options['ucell'])
+ ucell_raster_1 = np.asarray(ucell_raster)
elif backend == 1:
- ucell = np.asarray(rcwa_options['ucell'])
+ ucell_raster_1 = np.asarray(ucell_raster)
elif backend == 2:
- ucell = torch.as_tensor(rcwa_options['ucell'])
+ ucell_raster_1 = torch.as_tensor(ucell_raster)
else:
raise ValueError
- rcwa_options['ucell'] = ucell
+ rcwa_options['ucell'] = ucell_raster_1
mee = meent.call_mee(**rcwa_options)
- de_ri, de_ti = mee.conv_solve()
+ res = mee.conv_solve()
+ de_ri, de_ti = res.de_ri, res.de_ti
+
return de_ri, de_ti
if __name__ == '__main__':
- rcwa_options = dict(backend=0, grating_type=2, thickness=[205, 100000], period=[300, 300],
- fourier_order=[3, 0],
- n_I=1, n_II=1,
+ rcwa_options = dict(backend=0, thickness=[205, 100000], period=[300, 300],
+ fto=[3, 0],
+ n_top=1, n_bot=1,
wavelength=900,
- fft_type=2,
)
- si = 3.638751670074983-0.007498295841854125j
+ # si = 3.638751670074983-0.007498295841854125j
+ si = 3.638751670074983
sio2 = 1.4518
si3n4 = 2.0056
- instructions = [
+ ucell_vector = [
# layer 1
[si3n4,
[
@@ -73,7 +74,7 @@ def run_raster(rcwa_options, backend, fft_type):
b = si
c = si
- ucell = [
+ ucell_raster = [
[
[c,c,c,c,c,c,c,c,c,c] + [a,a,a,a,a,a,a,a,c,c] + [c,c,a,a,a,a,a,a,a,a],
[c,c,c,c,c,c,c,c,c,c] + [a,a,a,a,a,a,a,a,c,c] + [c,c,a,a,a,a,a,a,a,a],
@@ -139,7 +140,6 @@ def run_raster(rcwa_options, backend, fft_type):
[b,b,b,b,b,b,b,b,b,b] + [b,b,b,b,b,b,b,b,b,b] + [b,b,b,b,b,b,b,b,b,b],
],
]
- # ucell = np.array(ucell)
de_ri_v_0, de_ti_v_0 = run_vector(rcwa_options, 0) # NumPy
de_ri_v_1, de_ti_v_1 = run_vector(rcwa_options, 1) # JAX
@@ -159,7 +159,6 @@ def run_raster(rcwa_options, backend, fft_type):
print(f'Norm of difference JAX and Torch; R: {np.linalg.norm(de_ri_v_1-de_ri_v_2)}, T: {np.linalg.norm(de_ti_v_1-de_ti_v_2)}')
print(f'Norm of difference Torch and NumPy; R: {np.linalg.norm(de_ri_v_1-de_ri_v_2)}, T: {np.linalg.norm(de_ti_v_1-de_ti_v_2)}')
- rcwa_options['ucell'] = ucell
de_ri_r_0_dfs, de_ti_r_0_dfs = run_raster(rcwa_options, 0, 0) # NumPy
de_ri_r_0_cfs, de_ti_r_0_cfs = run_raster(rcwa_options, 0, 1) # NumPy
de_ri_r_1_dfs, de_ti_r_1_dfs = run_raster(rcwa_options, 1, 0) # JAX
diff --git a/examples/vector_2d.py b/examples/vector_2d.py
index 5a11568..c2b7ef8 100644
--- a/examples/vector_2d.py
+++ b/examples/vector_2d.py
@@ -4,18 +4,19 @@
def run():
- rcwa_options = dict(backend=1, grating_type=2, thickness=[205, 305, 100000], period=[300, 300],
- fourier_order=[3, 3],
- n_I=1, n_II=1,
+ rcwa_options = dict(backend=1, thickness=[205, 305, 100000], period=[300, 300],
+ fto=[3, 3],
+ n_top=1, n_bot=1,
wavelength=900,
- fft_type=2,
+ pol=0.5,
)
- si = 3.638751670074983-0.007498295841854125j
+ # si = 3.638751670074983-0.007498295841854125j
+ si = 3.638751670074983
sio2 = 1.4518-0j
si3n4 = 2.0056-0j
- instructions = [
+ ucell = [
# layer 1
[sio2,
[
@@ -41,16 +42,27 @@ def run():
]
mee = meent.call_mee(**rcwa_options)
- mee.modeling_vector_instruction(instructions)
+ mee.ucell = ucell
- de_ri, de_ti = mee.conv_solve()
- print(de_ri)
+ result = mee.conv_solve()
+
+ result_given_pol = result.res
+ result_te_incidence = result.res_te_inc
+ result_tm_incidence = result.res_tm_inc
+
+ de_ri, de_ti = result_given_pol.de_ri, result_given_pol.de_ti
+ de_ri1, de_ti1 = result_te_incidence.de_ri, result_te_incidence.de_ti
+ de_ri2, de_ti2 = result_tm_incidence.de_ri, result_tm_incidence.de_ti
+
+ print(de_ri.sum(), de_ti.sum())
+ print(de_ri1.sum(), de_ti1.sum())
+ print(de_ri2.sum(), de_ti2.sum())
return
if __name__ == '__main__':
- res = run()
+ run()
print(0)
diff --git a/examples/vector_2d_verification.py b/examples/vector_2d_verification.py
index ecc425b..8d565d7 100644
--- a/examples/vector_2d_verification.py
+++ b/examples/vector_2d_verification.py
@@ -8,52 +8,53 @@ def run_vector(rcwa_options, backend):
rcwa_options['backend'] = backend
mee = meent.call_mee(**rcwa_options)
- mee.modeling_vector_instruction(instructions)
+ mee.ucell = ucell_vector
- de_ri, de_ti = mee.conv_solve()
+ res = mee.conv_solve()
- return de_ri, de_ti
+ return res.de_ri, res.de_ti
-def run_raster(rcwa_options, backend, fft_type):
+def run_raster(rcwa_options, backend, fourier_type):
- # ucell = ucell.numpy()
+ # ucell_raster = ucell_raster.numpy()
rcwa_options['backend'] = backend
- rcwa_options['fourier_type'] = fft_type
+ rcwa_options['fourier_type'] = fourier_type
# 0: Discrete Fourier series; 1 is for Continuous FS which is used in vector modeling.
-
if backend == 0:
- ucell = np.asarray(rcwa_options['ucell'])
+ ucell_raster_1 = np.asarray(ucell_raster)
elif backend == 1:
- ucell = np.asarray(rcwa_options['ucell'])
+ ucell_raster_1 = np.asarray(ucell_raster)
elif backend == 2:
- ucell = torch.as_tensor(rcwa_options['ucell'])
+ ucell_raster_1 = torch.as_tensor(ucell_raster)
else:
raise ValueError
- rcwa_options['ucell'] = ucell
+ rcwa_options['ucell'] = ucell_raster_1
mee = meent.call_mee(**rcwa_options)
- de_ri, de_ti = mee.conv_solve()
+ res = mee.conv_solve()
+ de_ri, de_ti = res.de_ri, res.de_ti
+
return de_ri, de_ti
if __name__ == '__main__':
- rcwa_options = dict(backend=0, grating_type=2, thickness=[205, 100000], period=[300, 300],
- fourier_order=[3, 3],
- n_I=1, n_II=1,
+ rcwa_options = dict(backend=0, thickness=[205, 100000], period=[300, 300],
+ fto=[3, 3],
+ n_top=1, n_bot=1,
wavelength=900,
- fft_type=2,
)
- si = 3.638751670074983-0.007498295841854125j
+ # si = 3.638751670074983-0.007498295841854125j
+ si = 3.638751670074983
sio2 = 1.4518
si3n4 = 2.0056
- instructions = [
+ ucell_vector = [
# layer 1
[si3n4,
[
@@ -73,7 +74,7 @@ def run_raster(rcwa_options, backend, fft_type):
b = si
c = si
- ucell = [
+ ucell_raster = [
[
[c,c,c,c,c,c,c,c,c,c] + [a,a,a,a,a,a,a,a,a,a] + [a,a,a,a,a,a,a,a,a,a],
[c,c,c,c,c,c,c,c,c,c] + [a,a,a,a,a,a,a,a,a,a] + [a,a,a,a,a,a,a,a,a,a],
@@ -142,7 +143,6 @@ def run_raster(rcwa_options, backend, fft_type):
[b,b,b,b,b,b,b,b,b,b] + [b,b,b,b,b,b,b,b,b,b] + [b,b,b,b,b,b,b,b,b,b],
],
]
- # ucell = np.array(ucell)
de_ri_v_0, de_ti_v_0 = run_vector(rcwa_options, 0) # NumPy
de_ri_v_1, de_ti_v_1 = run_vector(rcwa_options, 1) # JAX
@@ -162,7 +162,6 @@ def run_raster(rcwa_options, backend, fft_type):
print(f'Norm of difference JAX and Torch; R: {np.linalg.norm(de_ri_v_1-de_ri_v_2)}, T: {np.linalg.norm(de_ti_v_1-de_ti_v_2)}')
print(f'Norm of difference Torch and NumPy; R: {np.linalg.norm(de_ri_v_1-de_ri_v_2)}, T: {np.linalg.norm(de_ti_v_1-de_ti_v_2)}')
- rcwa_options['ucell'] = ucell
de_ri_r_0_dfs, de_ti_r_0_dfs = run_raster(rcwa_options, 0, 0) # NumPy
de_ri_r_0_cfs, de_ti_r_0_cfs = run_raster(rcwa_options, 0, 1) # NumPy
de_ri_r_1_dfs, de_ti_r_1_dfs = run_raster(rcwa_options, 1, 0) # JAX
diff --git a/meent/on_jax/emsolver/_base.py b/meent/on_jax/emsolver/_base.py
index 4f1c7cd..ac7c77f 100644
--- a/meent/on_jax/emsolver/_base.py
+++ b/meent/on_jax/emsolver/_base.py
@@ -6,7 +6,8 @@
from .scattering_method import (scattering_1d_1, scattering_1d_2, scattering_1d_3,
scattering_2d_1, scattering_2d_wv, scattering_2d_2, scattering_2d_3)
-from .transfer_method import (transfer_1d_1, transfer_1d_2, transfer_1d_3, transfer_1d_4,
+from .transfer_method import (transfer_1d_1, transfer_1d_2, transfer_1d_3, transfer_1d_4, transfer_1d_conical_1,
+ transfer_1d_conical_2, transfer_1d_conical_3, transfer_1d_conical_4,
transfer_2d_1, transfer_2d_2, transfer_2d_3, transfer_2d_4)
@@ -23,10 +24,10 @@ def wrap(*args, **kwargs):
class _BaseRCWA:
- def __init__(self, n_top=1., n_bot=1., theta=0., phi=0., psi=None, pol=0., fto=(2, 0),
- period=(100., 100.), wavelength=1.,
+ def __init__(self, n_top=1., n_bot=1., theta=0., phi=None, psi=None, pol=0., fto=(0, 0),
+ period=(1., 1.), wavelength=1.,
thickness=(0.,), connecting_algo='TMM', perturbation=1E-20,
- device=0, type_complex=jnp.complex128):
+ device=0, type_complex=jnp.complex128, use_pinv=False):
self.device = device
@@ -51,16 +52,16 @@ def __init__(self, n_top=1., n_bot=1., theta=0., phi=0., psi=None, pol=0., fto=(
self.phi = phi
self.pol = pol
self.psi = psi
- # self._psi = jnp.array((jnp.pi / 2 * (1 - pol)), dtype=self.type_float)
self.fto = fto
self.period = period
self.wavelength = wavelength
self.thickness = thickness
self.connecting_algo = connecting_algo
+ self.use_pinv = use_pinv
+
self.layer_info_list = []
self.T1 = None
- # self.kx = None # only kx, not ky, because kx is always used while ky is 2D only.
@property
def device(self):
@@ -108,33 +109,17 @@ def type_float(self):
def type_int(self):
return self._type_int
- @property
- def pol(self):
- return self._pol
-
- @pol.setter
- def pol(self, pol):
- room = 1E-6
- if 1 < pol < 1 + room:
- pol = 1
- elif 0 - room < pol < 0:
- pol = 0
-
- if not 0 <= pol <= 1:
- raise ValueError
-
- self._pol = pol
- psi = jnp.pi / 2 * (1 - self.pol)
- self._psi = jnp.array(psi, dtype=self.type_float)
-
@property
def theta(self):
return self._theta
@theta.setter
def theta(self, theta):
- self._theta = jnp.array(theta, dtype=self.type_float)
- self._theta = jnp.where(self._theta == 0, self.perturbation, self._theta) # perturbation
+ if theta is None:
+ self._theta = None
+ else:
+ self._theta = jnp.array(theta, dtype=self.type_complex)
+ self._theta = jnp.where(self._theta == 0, self.perturbation, self._theta) # perturbation
@property
def phi(self):
@@ -142,7 +127,10 @@ def phi(self):
@phi.setter
def phi(self, phi):
- self._phi = jnp.array(phi, dtype=self.type_float)
+ if phi is None:
+ self._phi = None
+ else:
+ self._phi = jnp.array(phi, dtype=self.type_complex)
@property
def psi(self):
@@ -151,10 +139,35 @@ def psi(self):
@psi.setter
def psi(self, psi):
if psi is not None:
- self._psi = jnp.array(psi, dtype=self.type_float)
+ self._psi = jnp.array(psi, dtype=self.type_complex)
pol = -(2 * psi / jnp.pi - 1)
self._pol = pol
+ @property
+ def pol(self):
+ """
+ portion of TM. 0: full TE, 1: full TM
+
+ Returns: polarization ratio
+
+ """
+ return self._pol
+
+ @pol.setter
+ def pol(self, pol):
+ room = 1E-6
+ if 1 < pol < 1 + room:
+ pol = 1
+ elif 0 - room < pol < 0:
+ pol = 0
+
+ if not 0 <= pol <= 1:
+ raise ValueError
+
+ self._pol = pol
+ psi = jnp.array(jnp.pi / 2 * (1 - self.pol), dtype=self.type_complex)
+ self._psi = psi
+
@property
def fto(self):
return self._fto
@@ -241,15 +254,28 @@ def get_kx_ky_vector(self, wavelength):
fto_x_range = jnp.arange(-self.fto[0], self.fto[0] + 1)
fto_y_range = jnp.arange(-self.fto[1], self.fto[1] + 1)
- kx_vector = (self.n_top * jnp.sin(self.theta) * jnp.cos(self.phi) + fto_x_range * (
- wavelength / self.period[0])).astype(self.type_complex)
+ def adjust_theta():
+ # https://github.com/numpy/numpy/issues/27306
+ check = self.theta.real >= jnp.float32(jnp.pi / 2)
+ sin_theta_true_case = jnp.sin(
+ jnp.nextafter(jnp.float32(jnp.pi / 2), jnp.float32(0)) + self.theta.imag * jnp.complex64(1j))
+ sin_theta_false_case = jnp.sin(self.theta)
+ return jnp.where(check, sin_theta_true_case, sin_theta_false_case)
+
+ sin_theta = adjust_theta()
+
+ phi = 0 if self.phi is None else self.phi # phi is None -> 1D TE TM case
+
+ kx = (self.n_top * sin_theta * jnp.cos(phi) + fto_x_range * (
+ wavelength / self.period[0])).astype(self.type_complex).conj()
- ky_vector = (self.n_top * jnp.sin(self.theta) * jnp.sin(self.phi) + fto_y_range * (
- wavelength / self.period[1])).astype(self.type_complex)
+ ky = (self.n_top * sin_theta * jnp.sin(phi) + fto_y_range * (
+ wavelength / self.period[1])).astype(self.type_complex).conj()
- return kx_vector, ky_vector
+ return kx, ky
@jax_device_set
+ # @jax.jit # TODO: make optional
def solve_1d(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
self.layer_info_list = []
self.T1 = None
@@ -261,11 +287,13 @@ def solve_1d(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
if self.connecting_algo == 'TMM':
kz_top, kz_bot, F, G, T \
- = transfer_1d_1(self.pol, ff_x, kx, self.n_top, self.n_bot, type_complex=self.type_complex)
+ = transfer_1d_1(self.pol, kx, self.n_top, self.n_bot, type_complex=self.type_complex)
elif self.connecting_algo == 'SMM':
- Kx, Wg, Vg, Kzg, Wr, Vr, Kzr, Wt, Vt, Kzt, Ar, Br, Sg \
- = scattering_1d_1(k0, self.n_top, self.n_bot, self.theta, self.phi, self.period,
- self.pol, wl=wavelength)
+ raise ValueError
+
+ # Kx, Wg, Vg, Kzg, Wr, Vr, Kzr, Wt, Vt, Kzt, Ar, Br, Sg \
+ # = scattering_1d_1(k0, self.n_top, self.n_bot, self.theta, self.phi, self.period,
+ # self.pol, wl=wavelength)
else:
raise ValueError
@@ -279,98 +307,108 @@ def solve_1d(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
d = self.thickness[layer_index]
if self.connecting_algo == 'TMM':
- W, V, q = transfer_1d_2(self.pol, kx, epx_conv, epy_conv, epz_conv_i, self.type_complex)
+ W, V, q = transfer_1d_2(self.pol, kx, epx_conv, epy_conv, epz_conv_i, self.type_complex,
+ self.perturbation, use_pinv=self.use_pinv)
- X, F, G, T, A_i, B = transfer_1d_3(k0, W, V, q, d, F, G, T, type_complex=self.type_complex)
+ X, F, G, T, A_i, B = transfer_1d_3(k0, W, V, q, d, F, G, T, type_complex=self.type_complex,
+ use_pinv=self.use_pinv)
layer_info = [epz_conv_i, W, V, q, d, A_i, B]
self.layer_info_list.append(layer_info)
elif self.connecting_algo == 'SMM':
- A, B, S_dict, Sg = scattering_1d_2(W, Wg, V, Vg, d, k0, Q, Sg)
+ raise ValueError
+ # A, B, S_dict, Sg = scattering_1d_2(W, Wg, V, Vg, d, k0, Q, Sg)
+ else:
+ raise ValueError
+
+ if self.connecting_algo == 'TMM':
+ result, T1 = transfer_1d_4(self.pol, ff_x, F, G, T, kz_top, kz_bot, self.theta, self.n_top, self.n_bot,
+ type_complex=self.type_complex, use_pinv=self.use_pinv)
+ self.T1 = T1 # Hurdle for jitting. This is not saved.
+
+ elif self.connecting_algo == 'SMM':
+ raise ValueError
+ # de_ri, de_ti = scattering_1d_3(Wt, Wg, Vt, Vg, Sg, ff, Wr, self.fto, Kzr, Kzt,
+ # self.n_top, self.n_bot, self.theta, self.pol)
+ else:
+ raise ValueError
+
+ # return de_ri, de_ti, self.layer_info_list, self.T1
+ return result
+
+ @jax_device_set
+ def solve_1d_conical(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
+
+ self.layer_info_list = []
+ self.T1 = None
+
+ ff_x = self.fto[0] * 2 + 1
+ ff_y = 1
+
+ k0 = 2 * jnp.pi / wavelength
+ kx, ky = self.get_kx_ky_vector(wavelength)
+
+ if self.connecting_algo == 'TMM':
+ # Kx, ky, k_I_z, k_II_z, varphi, Y_I, Y_II, Z_I, Z_II, big_F, big_G, big_T \
+ # = transfer_1d_conical_1(ff, k0, self.n_top, self.n_bot, self.kx, self.theta, self.phi,
+ # type_complex=self.type_complex)
+ kz_top, kz_bot, varphi, big_F, big_G, big_T \
+ = transfer_1d_conical_1(kx, ky, self.n_top, self.n_bot, type_complex=self.type_complex)
+
+ elif self.connecting_algo == 'SMM':
+ print('SMM for 1D conical is not implemented')
+ return jnp.nan, jnp.nan
+ else:
+ raise ValueError
+
+ for layer_index in range(len(self.thickness))[::-1]:
+
+ epx_conv = epx_conv_all[layer_index]
+ epy_conv = epy_conv_all[layer_index]
+ epz_conv_i = epz_conv_i_all[layer_index]
+
+ d = self.thickness[layer_index]
+
+ if self.connecting_algo == 'TMM':
+ # big_X, big_F, big_G, big_T, big_A_i, big_B, W_1, W_2, V_11, V_12, V_21, V_22, q_1, q_2 \
+ # = transfer_1d_conical_2(k0, Kx, ky, E_conv, E_conv_i, o_E_conv_i, ff, d,
+ # varphi, big_F, big_G, big_T,
+ # type_complex=self.type_complex, device=self.device)
+ W, V, q = transfer_1d_conical_2(kx, ky, epx_conv, epy_conv, epz_conv_i, type_complex=self.type_complex,
+ perturbation=self.perturbation, device=self.device,
+ use_pinv=self.use_pinv)
+
+ big_X, big_F, big_G, big_T, big_A_i, big_B, \
+ = transfer_1d_conical_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_complex=self.type_complex,
+ use_pinv=self.use_pinv)
+
+ layer_info = [epz_conv_i, W, V, q, d, big_A_i, big_B]
+ self.layer_info_list.append(layer_info)
+
+ elif self.connecting_algo == 'SMM':
+ raise ValueError
else:
raise ValueError
if self.connecting_algo == 'TMM':
- de_ri, de_ti, T1 = transfer_1d_4(self.pol, F, G, T, kz_top, kz_bot, self.theta, self.n_top, self.n_bot,
- type_complex=self.type_complex)
- self.T1 = T1
+ # de_ri, de_ti, big_T1 = transfer_1d_conical_3(big_F, big_G, big_T, Z_I, Y_I, self.psi, self.theta, ff,
+ # delta_i0, k_I_z, k0, self.n_top, self.n_bot, k_II_z,
+ # type_complex=self.type_complex)
+ result, big_T1 = transfer_1d_conical_4(ff_x, ff_y, big_F, big_G, big_T, kz_top, kz_bot, self.psi,
+ self.theta, self.n_top, self.n_bot, type_complex=self.type_complex,
+ use_pinv=self.use_pinv)
+ self.T1 = big_T1
elif self.connecting_algo == 'SMM':
- de_ri, de_ti = scattering_1d_3(Wt, Wg, Vt, Vg, Sg, ff, Wr, self.fto, Kzr, Kzt,
- self.n_top, self.n_bot, self.theta, self.pol)
+ raise ValueError
else:
raise ValueError
- return de_ri, de_ti, self.layer_info_list, self.T1
- # @jax_device_set
- # def solve_1d_conical(self, wavelength, E_conv_all, o_E_conv_all):
- #
- # self.layer_info_list = []
- # self.T1 = None
- #
- # # fourier_indices = jnp.arange(-self.fto, self.fto + 1)
- # ff = self.fto[0] * 2 + 1
- #
- # delta_i0 = jnp.zeros(ff, dtype=self.type_complex)
- # delta_i0 = delta_i0.at[self.fto[0]].set(1)
- #
- # k0 = 2 * jnp.pi / wavelength
- #
- # if self.connecting_algo == 'TMM':
- # Kx, ky, k_I_z, k_II_z, varphi, Y_I, Y_II, Z_I, Z_II, big_F, big_G, big_T \
- # = transfer_1d_conical_1(ff, k0, self.n_top, self.n_bot, self.kx, self.theta, self.phi,
- # type_complex=self.type_complex)
- # elif self.connecting_algo == 'SMM':
- # print('SMM for 1D conical is not implemented')
- # return jnp.nan, jnp.nan
- # else:
- # raise ValueError
- #
- # # for E_conv, o_E_conv, d in zip(E_conv_all[::-1], o_E_conv_all[::-1], self.thickness[::-1]):
- # count = min(len(E_conv_all), len(o_E_conv_all), len(self.thickness))
- #
- # # From the last layer
- # for layer_index in range(count)[::-1]:
- #
- # E_conv = E_conv_all[layer_index]
- # # o_E_conv = o_E_conv_all[layer_index]
- # o_E_conv = None
- #
- # d = self.thickness[layer_index]
- #
- # E_conv_i = jnp.linalg.inv(E_conv)
- # # o_E_conv_i = jnp.linalg.inv(o_E_conv)
- # o_E_conv_i = None
- #
- # if self.connecting_algo == 'TMM':
- # big_X, big_F, big_G, big_T, big_A_i, big_B, W_1, W_2, V_11, V_12, V_21, V_22, q_1, q_2 \
- # = transfer_1d_conical_2(k0, Kx, ky, E_conv, E_conv_i, o_E_conv_i, ff, d,
- # varphi, big_F, big_G, big_T,
- # type_complex=self.type_complex, device=self.device)
- #
- # layer_info = [E_conv_i, q_1, q_2, W_1, W_2, V_11, V_12, V_21, V_22, big_X, big_A_i, big_B, d]
- # self.layer_info_list.append(layer_info)
- #
- # elif self.connecting_algo == 'SMM':
- # raise ValueError
- # else:
- # raise ValueError
- #
- # if self.connecting_algo == 'TMM':
- # de_ri, de_ti, big_T1 = transfer_1d_conical_3(big_F, big_G, big_T, Z_I, Y_I, self.psi, self.theta, ff,
- # delta_i0, k_I_z, k0, self.n_top, self.n_bot, k_II_z,
- # type_complex=self.type_complex)
- # self.T1 = big_T1
- #
- # elif self.connecting_algo == 'SMM':
- # raise ValueError
- # else:
- # raise ValueError
- #
- # return de_ri, de_ti, self.layer_info_list, self.T1
+ return result
@jax_device_set
+ # @jax.jit
def solve_2d(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
self.layer_info_list = []
@@ -384,11 +422,12 @@ def solve_2d(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
if self.connecting_algo == 'TMM':
kz_top, kz_bot, varphi, big_F, big_G, big_T \
- = transfer_2d_1(ff_x, ff_y, kx, ky, self.n_top, self.n_bot, type_complex=self.type_complex)
+ = transfer_2d_1(kx, ky, self.n_top, self.n_bot, type_complex=self.type_complex)
elif self.connecting_algo == 'SMM':
- Kx, Ky, kz_inc, Wg, Vg, Kzg, Wr, Vr, Kzr, Wt, Vt, Kzt, Ar, Br, Sg \
- = scattering_2d_1(self.n_top, self.n_bot, self.theta, self.phi, k0, self.period, self.fto)
+ raise ValueError
+ # Kx, Ky, kz_inc, Wg, Vg, Kzg, Wr, Vr, Kzr, Wt, Vt, Kzt, Ar, Br, Sg \
+ # = scattering_2d_1(self.n_top, self.n_bot, self.theta, self.phi, k0, self.period, self.fto)
else:
raise ValueError
@@ -402,32 +441,37 @@ def solve_2d(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
d = self.thickness[layer_index]
if self.connecting_algo == 'TMM':
- W, V, q = transfer_2d_2(kx, ky, epx_conv, epy_conv, epz_conv_i, type_complex=self.type_complex)
+ W, V, q = transfer_2d_2(kx, ky, epx_conv, epy_conv, epz_conv_i, self.type_complex, self.perturbation,
+ use_pinv=self.use_pinv)
big_X, big_F, big_G, big_T, big_A_i, big_B, \
- = transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_complex=self.type_complex)
+ = transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_complex=self.type_complex,
+ use_pinv=self.use_pinv)
layer_info = [epz_conv_i, W, V, q, d, big_A_i, big_B]
self.layer_info_list.append(layer_info)
elif self.connecting_algo == 'SMM':
- W, V, q = scattering_2d_wv(ff_xy, Kx, Ky, E_conv, o_E_conv, o_E_conv_i, E_conv_i)
- A, B, Sl_dict, Sg_matrix, Sg = scattering_2d_2(W, Wg, V, Vg, d, k0, Sg, q)
+ raise ValueError
+ # W, V, q = scattering_2d_wv(ff_xy, Kx, Ky, E_conv, o_E_conv, o_E_conv_i, E_conv_i)
+ # A, B, Sl_dict, Sg_matrix, Sg = scattering_2d_2(W, Wg, V, Vg, d, k0, Sg, q)
else:
raise ValueError
if self.connecting_algo == 'TMM':
- de_ri, de_ti, big_T1 = transfer_2d_4(big_F, big_G, big_T, kz_top, kz_bot, self.psi, self.theta,
- self.n_top, self.n_bot, type_complex=self.type_complex)
+ result, big_T1 = transfer_2d_4(ff_x, ff_y, big_F, big_G, big_T, kz_top, kz_bot, self.psi, self.theta,
+ self.n_top, self.n_bot, type_complex=self.type_complex,
+ use_pinv=self.use_pinv)
self.T1 = big_T1
elif self.connecting_algo == 'SMM':
- de_ri, de_ti = scattering_2d_3(ff_xy, Wt, Wg, Vt, Vg, Sg, Wr, Kx, Ky, Kzr, Kzt, kz_inc, self.n_top,
- self.pol, self.theta, self.phi, self.fto)
+ raise ValueError
+ # de_ri, de_ti = scattering_2d_3(ff_xy, Wt, Wg, Vt, Vg, Sg, Wr, Kx, Ky, Kzr, Kzt, kz_inc, self.n_top,
+ # self.pol, self.theta, self.phi, self.fto)
else:
raise ValueError
- de_ri = de_ri.reshape((ff_y, ff_x)).T
- de_ti = de_ti.reshape((ff_y, ff_x)).T
-
- return de_ri, de_ti, self.layer_info_list, self.T1
+ # de_ri = de_ri.reshape((ff_y, ff_x)).T
+ # de_ti = de_ti.reshape((ff_y, ff_x)).T
+ # return de_ri, de_ti, self.layer_info_list, self.T1
+ return result
diff --git a/meent/on_jax/emsolver/convolution_matrix.py b/meent/on_jax/emsolver/convolution_matrix.py
index ad93838..e83bfb8 100644
--- a/meent/on_jax/emsolver/convolution_matrix.py
+++ b/meent/on_jax/emsolver/convolution_matrix.py
@@ -4,6 +4,7 @@
from functools import partial
from .fourier_analysis import dfs2d, cfs2d
+from .primitives import meeinv
def cell_compression(cell, type_complex=jnp.complex128):
@@ -47,60 +48,7 @@ def cell_compression(cell, type_complex=jnp.complex128):
return cell_comp, x, y
-
-# @partial(jax.jit, static_argnums=(1,2 ))
-# def fft_piecewise_constant(cell, x, y, fto_x, fto_y, type_complex=jnp.complex128):
-#
-# period_x, period_y = x[-1], y[-1]
-#
-# # X axis
-# cell_next_x = jnp.roll(cell, -1, axis=1)
-# cell_diff_x = cell_next_x - cell
-#
-# modes_x = jnp.arange(-2 * fto_x, 2 * fto_x + 1, 1)
-#
-# f_coeffs_x = cell_diff_x @ jnp.exp(-1j * 2 * jnp.pi * x @ modes_x[None, :] / period_x).astype(type_complex)
-# c = f_coeffs_x.shape[1] // 2
-#
-# x_next = jnp.vstack((jnp.roll(x, -1, axis=0)[:-1], period_x)) - x
-#
-# assign_index = (jnp.arange(len(f_coeffs_x)), jnp.array([c]))
-# assign_value = (cell @ jnp.vstack((x[0], x_next[:-1])) / period_x).flatten().astype(type_complex)
-# f_coeffs_x = f_coeffs_x.at[assign_index].set(assign_value)
-#
-# mask = jnp.hstack([jnp.arange(c), jnp.arange(c+1, f_coeffs_x.shape[1])])
-# assign_index = mask
-# assign_value = f_coeffs_x[:, mask] / (1j * 2 * jnp.pi * modes_x[mask])
-# f_coeffs_x = f_coeffs_x.at[:, assign_index].set(assign_value)
-#
-# # Y axis
-# f_coeffs_x_next_y = jnp.roll(f_coeffs_x, -1, axis=0)
-# f_coeffs_x_diff_y = f_coeffs_x_next_y - f_coeffs_x
-#
-# modes_y = jnp.arange(-2 * fto_y, 2 * fto_y + 1, 1)
-#
-# f_coeffs_xy = f_coeffs_x_diff_y.T @ jnp.exp(-1j * 2 * jnp.pi * y @ modes_y[None, :] / period_y).astype(type_complex)
-# c = f_coeffs_xy.shape[1] // 2
-#
-# y_next = jnp.vstack((jnp.roll(y, -1, axis=0)[:-1], period_y)) - y
-#
-# assign_index = [c]
-# assign_value = (f_coeffs_x.T @ jnp.vstack((y[0], y_next[:-1])) / period_y).astype(type_complex)
-# f_coeffs_xy = f_coeffs_xy.at[:, assign_index].set(assign_value)
-#
-# if c:
-# mask = jnp.hstack([jnp.arange(c), jnp.arange(c + 1, f_coeffs_x.shape[1])])
-#
-# assign_index = mask
-# assign_value = f_coeffs_xy[:, mask] / (1j * 2 * jnp.pi * modes_y[mask])
-#
-# f_coeffs_xy = f_coeffs_xy.at[:, assign_index].set(assign_value)
-#
-# return f_coeffs_xy.T
-
-
-def to_conv_mat_vector(ucell_info_list, fto_x, fto_y, device=None,
- type_complex=jnp.complex128):
+def to_conv_mat_vector(ucell_info_list, fto_x, fto_y, device=None, type_complex=jnp.complex128, use_pinv=False):
ff_xy = (2 * fto_x + 1) * (2 * fto_y + 1)
@@ -116,39 +64,14 @@ def to_conv_mat_vector(ucell_info_list, fto_x, fto_y, device=None,
epy_conv = cfs2d(eps_matrix, x_list, y_list, 1, 0, fto_x, fto_y, type_complex)
epx_conv = cfs2d(eps_matrix, x_list, y_list, 0, 1, fto_x, fto_y, type_complex)
- # epx_conv_all[i] = epx_conv
- # epy_conv_all[i] = epy_conv
- # epz_conv_i_all[i] = jnp.linalg.inv(epz_conv)
-
epx_conv_all = epx_conv_all.at[i].set(epx_conv)
epy_conv_all = epy_conv_all.at[i].set(epy_conv)
- epz_conv_i_all = epz_conv_i_all.at[i].set(jnp.linalg.inv(epz_conv))
-
- # f_coeffs = fft_piecewise_constant(ucell_layer, x_list, y_list,
- # fto_x, fto_y, type_complex=type_complex)
- # o_f_coeffs = fft_piecewise_constant(1/ucell_layer, x_list, y_list,
- # fto_x, fto_y, type_complex=type_complex)
- # center = jnp.array(f_coeffs.shape) // 2
- #
- # conv_idx_y = jnp.arange(-ff_y + 1, ff_y, 1)
- # conv_idx_y = circulant(conv_idx_y)
- # conv_i = jnp.repeat(conv_idx_y, ff_x, axis=1)
- # conv_i = jnp.repeat(conv_i, jnp.array([ff_x] * ff_y), axis=0, total_repeat_length=ff_x * ff_y)
- #
- # conv_idx_x = jnp.arange(-ff_x + 1, ff_x, 1)
- # conv_idx_x = circulant(conv_idx_x)
- # conv_j = jnp.tile(conv_idx_x, (ff_y, ff_y))
- #
- # e_conv = f_coeffs[center[0] + conv_i, center[1] + conv_j]
- # o_e_conv = o_f_coeffs[center[0] + conv_i, center[1] + conv_j]
- #
- # e_conv_all = e_conv_all.at[i].set(e_conv)
- # o_e_conv_all = o_e_conv_all.at[i].set(o_e_conv)
+ epz_conv_i_all = epz_conv_i_all.at[i].set(meeinv(epz_conv, use_pinv))
return epx_conv_all, epy_conv_all, epz_conv_i_all
-def to_conv_mat_raster_continuous(ucell, fto_x, fto_y, device=None, type_complex=jnp.complex128):
+def to_conv_mat_raster_continuous(ucell, fto_x, fto_y, device=None, type_complex=jnp.complex128, use_pinv=False):
ff_xy = (2 * fto_x + 1) * (2 * fto_y + 1)
@@ -164,20 +87,17 @@ def to_conv_mat_raster_continuous(ucell, fto_x, fto_y, device=None, type_complex
epy_conv = cfs2d(eps_matrix, x_list, y_list, 1, 0, fto_x, fto_y, type_complex)
epx_conv = cfs2d(eps_matrix, x_list, y_list, 0, 1, fto_x, fto_y, type_complex)
- # epx_conv_all[i] = epx_conv
- # epy_conv_all[i] = epy_conv
- # epz_conv_i_all[i] = jnp.linalg.inv(epz_conv)
-
epx_conv_all = epx_conv_all.at[i].set(epx_conv)
epy_conv_all = epy_conv_all.at[i].set(epy_conv)
- epz_conv_i_all = epz_conv_i_all.at[i].set(jnp.linalg.inv(epz_conv))
+ epz_conv_i_all = epz_conv_i_all.at[i].set(meeinv(epz_conv, use_pinv))
return epx_conv_all, epy_conv_all, epz_conv_i_all
# @partial(jax.jit, static_argnums=(1, 2, 3, 4, 5))
-def to_conv_mat_raster_discrete(ucell, fto_x, fto_y, device=None, type_complex=jnp.complex128,
- enhanced_dfs=True):
+def to_conv_mat_raster_discrete(ucell, fto_x, fto_y, device=None, type_complex=jnp.complex128, enhanced_dfs=True,
+ use_pinv=False):
+
ff_xy = (2 * fto_x + 1) * (2 * fto_y + 1)
epx_conv_all = jnp.zeros((ucell.shape[0], ff_xy, ff_xy)).astype(type_complex)
@@ -206,13 +126,9 @@ def to_conv_mat_raster_discrete(ucell, fto_x, fto_y, device=None, type_complex=j
epy_conv = dfs2d(eps_matrix, 1, 0, fto_x, fto_y, type_complex)
epx_conv = dfs2d(eps_matrix, 0, 1, fto_x, fto_y, type_complex)
- # epx_conv_all[i] = epx_conv
- # epy_conv_all[i] = epy_conv
- # epz_conv_i_all[i] = jnp.linalg.inv(epz_conv)
-
epx_conv_all = epx_conv_all.at[i].set(epx_conv)
epy_conv_all = epy_conv_all.at[i].set(epy_conv)
- epz_conv_i_all = epz_conv_i_all.at[i].set(jnp.linalg.inv(epz_conv))
+ epz_conv_i_all = epz_conv_i_all.at[i].set(meeinv(epz_conv, use_pinv=False))
return epx_conv_all, epy_conv_all, epz_conv_i_all
diff --git a/meent/on_jax/emsolver/field_distribution.py b/meent/on_jax/emsolver/field_distribution.py
index 72f1ff6..fa27598 100644
--- a/meent/on_jax/emsolver/field_distribution.py
+++ b/meent/on_jax/emsolver/field_distribution.py
@@ -25,8 +25,8 @@ def field_dist_1d(wavelength, kx, T1, layer_info_list, period, pol, res_x=20, re
# z_1d = jnp.arange(res_z, dtype=type_float).reshape((-1, 1, 1)) / res_z * d
z_1d = jnp.linspace(0, res_z, res_z).reshape((-1, 1, 1)) / res_z * d
- My = W @ (diag_exp_batch(-k0 * Q * z_1d) @ c1 + diag_exp_batch(k0 * Q * (z_1d - d)) @ c2)
- Mx = V @ (-diag_exp_batch(-k0 * Q * z_1d) @ c1 + diag_exp_batch(k0 * Q * (z_1d - d)) @ c2)
+ My = W @ (d_exp(-k0 * Q * z_1d) @ c1 + d_exp(k0 * Q * (z_1d - d)) @ c2)
+ Mx = V @ (-d_exp(-k0 * Q * z_1d) @ c1 + d_exp(k0 * Q * (z_1d - d)) @ c2)
if pol == 0:
Mz = -1j * Kx @ My
@@ -64,9 +64,100 @@ def field_dist_1d(wavelength, kx, T1, layer_info_list, period, pol, res_x=20, re
return field_cell
+# @partial(jax.jit, static_argnums=(5, 6, 10, 11, 12, 13))
+def field_dist_1d_conical(wavelength, kx, ky, T1, layer_info_list, period,
+ res_x=20, res_y=20, res_z=20, type_complex=jnp.complex128):
+
+ k0 = 2 * jnp.pi / wavelength
+
+ ff_x = len(kx)
+ ff_y = len(ky)
+ ff_xy = ff_x * ff_y
+
+ Kx = jnp.diag(jnp.tile(kx, ff_y).flatten())
+ Ky = jnp.diag(jnp.tile(ky.reshape((-1, 1)), ff_x).flatten())
+
+ field_cell = jnp.zeros((res_z * len(layer_info_list), res_y, res_x, 6), dtype=type_complex)
+
+ T_layer = T1
+
+ big_I = jnp.eye((len(T1))).astype(type_complex)
+ O = jnp.zeros((ff_xy, ff_xy), dtype=type_complex)
+
+ # From the first layer
+ for idx_layer, (epz_conv_i, W, V, q, d, big_A_i, big_B) in enumerate(layer_info_list[::-1]):
+ W_1 = W[:, :ff_xy]
+ W_2 = W[:, ff_xy:]
+
+ V_11 = V[:ff_xy, :ff_xy]
+ V_12 = V[:ff_xy, ff_xy:]
+ V_21 = V[ff_xy:, :ff_xy]
+ V_22 = V[ff_xy:, ff_xy:]
+
+ q_1 = q[:ff_xy]
+ q_2 = q[ff_xy:]
+
+ X_1 = jnp.diag(jnp.exp(-k0 * q_1 * d))
+ X_2 = jnp.diag(jnp.exp(-k0 * q_2 * d))
+
+ big_X = jnp.block([[X_1, O], [O, X_2]])
+
+ c = jnp.block([[big_I], [big_B @ big_A_i @ big_X]]) @ T_layer
+ # z_1d = np.arange(0, res_z, res_z).reshape((-1, 1, 1)) / res_z * d
+ z_1d = jnp.linspace(0, res_z, res_z).reshape((-1, 1, 1)) / res_z * d
+
+ c1_plus = c[0 * ff_xy:1 * ff_xy]
+ c2_plus = c[1 * ff_xy:2 * ff_xy]
+ c1_minus = c[2 * ff_xy:3 * ff_xy]
+ c2_minus = c[3 * ff_xy:4 * ff_xy]
+
+ big_Q1 = jnp.diag(q_1)
+ big_Q2 = jnp.diag(q_2)
+
+ Sx = W_2 @ (d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Sy = V_11 @ (d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
+ + V_12 @ (d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Ux = W_1 @ (-d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus)
+ Uy = V_21 @ (-d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
+ + V_22 @ (-d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Sz = -1j * epz_conv_i @ (Kx @ Uy - Ky @ Ux)
+ Uz = -1j * (Kx @ Sy - Ky @ Sx)
+
+ # x_1d = jnp.arange(res_x).reshape((1, -1, 1)) * period[0] / res_x
+ x_1d = jnp.linspace(0, period[0], res_x).reshape((1, -1, 1))
+ x_2d = jnp.tile(x_1d, (res_y, 1, 1))
+ x_2d = x_2d * kx * k0
+ x_2d = x_2d.reshape((res_y, res_x, 1, len(kx)))
+
+ # y_1d = jnp.arange(res_y-1, -1, -1).reshape((-1, 1, 1)) * period[1] / res_y
+ y_1d = jnp.linspace(0, period[1], res_y)[::-1].reshape((-1, 1, 1))
+ y_2d = jnp.tile(y_1d, (1, res_x, 1))
+ y_2d = y_2d * ky * k0
+ y_2d = y_2d.reshape((res_y, res_x, len(ky), 1))
+
+ inv_fourier = jnp.exp(-1j * x_2d) * jnp.exp(-1j * y_2d)
+ inv_fourier = inv_fourier.reshape((res_y, res_x, -1))
+
+ Ex = inv_fourier[:, :, None, :] @ Sx[:, None, None, :, :]
+ Ey = inv_fourier[:, :, None, :] @ Sy[:, None, None, :, :]
+ Ez = inv_fourier[:, :, None, :] @ Sz[:, None, None, :, :]
+ Hx = 1j * inv_fourier[:, :, None, :] @ Ux[:, None, None, :, :]
+ Hy = 1j * inv_fourier[:, :, None, :] @ Uy[:, None, None, :, :]
+ Hz = 1j * inv_fourier[:, :, None, :] @ Uz[:, None, None, :, :]
+
+ val = jnp.concatenate(
+ (Ex.squeeze(-1), Ey.squeeze(-1), Ez.squeeze(-1), Hx.squeeze(-1), Hy.squeeze(-1), Hz.squeeze(-1)), -1)
+
+ field_cell = field_cell.at[res_z * idx_layer:res_z * (idx_layer + 1)].set(val)
+
+ T_layer = big_A_i @ big_X @ T_layer
+
+ return field_cell
+
+
# @partial(jax.jit, static_argnums=(5, 6, 10, 11, 12, 13))
def field_dist_2d(wavelength, kx, ky, T1, layer_info_list, period,
- res_x=20, res_y=20, res_z=20, type_complex=jnp.complex128, type_float=jnp.float64):
+ res_x=20, res_y=20, res_z=20, type_complex=jnp.complex128):
k0 = 2 * jnp.pi / wavelength
@@ -112,25 +203,14 @@ def field_dist_2d(wavelength, kx, ky, T1, layer_info_list, period,
big_Q1 = jnp.diag(q1)
big_Q2 = jnp.diag(q2)
- Sx = W_11 @ (diag_exp_batch(-k0 * big_Q1 * z_1d) @ c1_plus + diag_exp_batch(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
- + W_12 @ (diag_exp_batch(-k0 * big_Q2 * z_1d) @ c2_plus + diag_exp_batch(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
-
- Sy = W_21 @ (diag_exp_batch(-k0 * big_Q1 * z_1d) @ c1_plus + diag_exp_batch(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
- + W_22 @ (diag_exp_batch(-k0 * big_Q2 * z_1d) @ c2_plus + diag_exp_batch(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
-
- # Ux = -V_11 @ (diag_exp_batch(-k0 * big_Q1 * z_1d) @ c1_plus + diag_exp_batch(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
- # - V_12 @ (diag_exp_batch(-k0 * big_Q2 * z_1d) @ c2_plus + diag_exp_batch(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
- # Uy = -V_21 @ (diag_exp_batch(-k0 * big_Q1 * z_1d) @ c1_plus + diag_exp_batch(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
- # - V_22 @ (diag_exp_batch(-k0 * big_Q2 * z_1d) @ c2_plus + diag_exp_batch(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
-
- Ux = V_11 @ (-diag_exp_batch(-k0 * big_Q1 * z_1d) @ c1_plus + diag_exp_batch(
- k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
- + V_12 @ (-diag_exp_batch(-k0 * big_Q2 * z_1d) @ c2_plus + diag_exp_batch(
- k0 * big_Q2 * (z_1d - d)) @ c2_minus)
- Uy = V_21 @ (-diag_exp_batch(-k0 * big_Q1 * z_1d) @ c1_plus + diag_exp_batch(
- k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
- + V_22 @ (-diag_exp_batch(-k0 * big_Q2 * z_1d) @ c2_plus + diag_exp_batch(
- k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Sx = W_11 @ (d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
+ + W_12 @ (d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Sy = W_21 @ (d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
+ + W_22 @ (d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Ux = V_11 @ (-d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
+ + V_12 @ (-d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Uy = V_21 @ (-d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
+ + V_22 @ (-d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
Sz = -1j * epz_conv_i @ (Kx @ Uy - Ky @ Ux)
Uz = -1j * (Kx @ Sy - Ky @ Sx)
@@ -207,11 +287,7 @@ def field_plot(field_cell, pol=0, plot_indices=(1, 1, 1, 1, 1, 1), y_slice=0, z_
plt.show()
-def diag_exp(x):
- return jnp.diag(jnp.exp(jnp.diag(x)))
-
-
-def diag_exp_batch(x):
+def d_exp(x):
res = jnp.zeros(x.shape, dtype=x.dtype)
ix = jnp.diag_indices_from(x[0])
res = res.at[:, ix[0], ix[1]].set(jnp.exp(x[:, ix[0], ix[1]]))
diff --git a/meent/on_jax/emsolver/primitives.py b/meent/on_jax/emsolver/primitives.py
index 8a46c45..22b3fe4 100644
--- a/meent/on_jax/emsolver/primitives.py
+++ b/meent/on_jax/emsolver/primitives.py
@@ -10,8 +10,8 @@ def conj(arr):
@partial(jax.custom_vjp, nondiff_argnums=(1, 2, 3))
-def eig(x, type_complex=jnp.complex128, perturbation=1E-10, device='cpu'):
-
+def eig(x, type_complex=jnp.complex128, perturbation=1E-20, device='cpu'):
+ # TODO: check perturbation in backprop
_eig = jax.jit(jnp.linalg.eig, device=jax.devices('cpu')[0])
eigenvalues_shape = jax.ShapeDtypeStruct(x.shape[:-1], type_complex)
@@ -68,3 +68,12 @@ def eig_bwd(type_complex, perturbation, device, res, g):
eig.defvjp(eig_fwd, eig_bwd)
+
+
+def meeinv(x, use_pinv=False):
+ if use_pinv:
+ res = jnp.linalg.pinv(x)
+ else:
+ res = jnp.linalg.inv(x)
+
+ return res
diff --git a/meent/on_jax/emsolver/rcwa.py b/meent/on_jax/emsolver/rcwa.py
index d3d2711..8188d60 100644
--- a/meent/on_jax/emsolver/rcwa.py
+++ b/meent/on_jax/emsolver/rcwa.py
@@ -7,7 +7,44 @@
from ._base import _BaseRCWA, jax_device_set
from .convolution_matrix import to_conv_mat_raster_discrete, to_conv_mat_raster_continuous, to_conv_mat_vector
-from .field_distribution import field_dist_1d, field_dist_2d, field_plot
+from .field_distribution import field_dist_1d, field_dist_1d_conical, field_dist_2d, field_plot
+
+
+class ResultJax:
+ def __init__(self, res=None, res_te_inc=None, res_tm_inc=None):
+
+ self.res = res
+ self.res_te_inc = res_te_inc
+ self.res_tm_inc = res_tm_inc
+
+ @property
+ def de_ri(self):
+ if self.res is not None:
+ return self.res.de_ri
+ else:
+ return None
+
+ @property
+ def de_ti(self):
+ if self.res is not None:
+ return self.res.de_ti
+ else:
+ return None
+
+
+class ResultSubJax:
+ def __init__(self, R_s, R_p, T_s, T_p, de_ri, de_ri_s, de_ri_p, de_ti, de_ti_s, de_ti_p):
+ self.R_s = R_s
+ self.R_p = R_p
+ self.T_s = T_s
+ self.T_p = T_p
+ self.de_ri = de_ri
+ self.de_ri_s = de_ri_s
+ self.de_ri_p = de_ri_p
+
+ self.de_ti = de_ti
+ self.de_ti_s = de_ti_s
+ self.de_ti_p = de_ti_p
class RCWAJax(_BaseRCWA):
@@ -15,29 +52,32 @@ def __init__(self,
n_top=1.,
n_bot=1.,
theta=0.,
- phi=0.,
+ phi=None,
psi=None,
- period=(100., 100.),
- wavelength=900.,
+ period=(1., 1.),
+ wavelength=1.,
ucell=None,
thickness=(0., ),
- backend=0,
+ backend=1,
pol=0.,
fto=(0, 0),
ucell_materials=None,
connecting_algo='TMM',
perturbation=1E-20,
device='cpu',
- type_complex=np.complex128,
+ type_complex=jnp.complex128,
fourier_type=0, # 0 DFS, 1 CFS
enhanced_dfs=True,
- # **kwargs,
+ use_pinv=False,
):
super().__init__(n_top=n_top, n_bot=n_bot, theta=theta, phi=phi, psi=psi, pol=pol,
fto=fto, period=period, wavelength=wavelength,
thickness=thickness, connecting_algo=connecting_algo, perturbation=perturbation,
- device=device, type_complex=type_complex)
+ device=device, type_complex=type_complex, use_pinv=use_pinv)
+
+ self._modeling_type_assigned = None
+ self._grating_type_assigned = None
self.ucell = ucell
self.ucell_materials = ucell_materials
@@ -45,8 +85,7 @@ def __init__(self,
self.backend = backend
self.fourier_type = fourier_type
self.enhanced_dfs = enhanced_dfs
- self._modeling_type_assigned = None
- self._grating_type_assigned = None
+ self.use_pinv = use_pinv
@property
def ucell(self):
@@ -56,6 +95,7 @@ def ucell(self):
def ucell(self, ucell):
if isinstance(ucell, jnp.ndarray): # Raster
+ self._modeling_type_assigned = 0
if ucell.dtype in (jnp.float64, jnp.float32, jnp.int64, jnp.int32):
dtype = self.type_float
self._ucell = ucell.astype(dtype)
@@ -64,6 +104,7 @@ def ucell(self, ucell):
self._ucell = ucell.astype(dtype)
elif isinstance(ucell, np.ndarray): # Raster
+ self._modeling_type_assigned = 0
if ucell.dtype in (np.int64, np.float64, np.int32, np.float32):
dtype = self.type_float
self._ucell = jnp.array(ucell, dtype=dtype)
@@ -72,6 +113,7 @@ def ucell(self, ucell):
self._ucell = jnp.array(ucell, dtype=dtype)
elif type(ucell) is list: # Vector
+ self._modeling_type_assigned = 1
self._ucell = ucell
elif ucell is None:
self._ucell = ucell
@@ -82,30 +124,45 @@ def ucell(self, ucell):
def modeling_type_assigned(self):
return self._modeling_type_assigned
- @modeling_type_assigned.setter
- def modeling_type_assigned(self, modeling_type_assigned):
- self._modeling_type_assigned = modeling_type_assigned
+ # @modeling_type_assigned.setter
+ # def modeling_type_assigned(self, modeling_type_assigned):
+ # self._modeling_type_assigned = modeling_type_assigned
+
+ def _assign_grating_type(self):
+ """
+ Select the grating type for RCWA simulation. This decides the efficient formulation for given case.
- def _assign_modeling_type(self):
- if isinstance(self.ucell, (np.ndarray, jnp.ndarray)): # Raster
- self.modeling_type_assigned = 0
- if (self.ucell.shape[1] == 1) and (self.pol in (0, 1)):
+ `_grating_type_assigned` == 0(1D TETM) is for 1D grating, no rotation (phi or azimuth), and either TE or TM.
+ `_grating_type_assigned` == 1(1D conical) is for 1D grating with generality.
+ `_grating_type_assigned` == 2(2D) is for 2D grating with generality.
- def false_fun(): return 0 # 1D TE and TM only
- def true_fun(): return 1
+ Note that no rotation means 'phi' is `None`. If phi is given as '0', then it takes 1D conical form
+ even though when the case itself is 1D TETM.
- gear = jax.lax.cond(self.phi % (2 * np.pi) + self.fto[1], true_fun, false_fun)
+ 1D conical is under implementation.
- self._grating_type_assigned = gear
+ Returns:
- # if (self.ucell.shape[1] == 1) and (self.pol in (0, 1)) and (self.phi % (2 * np.pi) == 0):
- # self._grating_type_assigned = 0 # 1D TE and TM only
+ """
+ if self.modeling_type_assigned == 0: # Raster
+ if self.ucell.shape[1] == 1:
+ if (self.pol in (0, 1)) and (self.phi is None) and (self.fto[1] == 0):
+ self._grating_type_assigned = 0
+ else:
+ self._grating_type_assigned = 1
+
+ # TODO: jit
+ # def false_fun(): return 0 # 1D TE and TM only
+ # def true_fun(): return 1
+ #
+ # gear = jax.lax.cond(self.phi % (2 * np.pi) + self.fto[1], true_fun, false_fun)
+ #
+ # self._grating_type_assigned = gear
else:
- self._grating_type_assigned = 1 # else
+ self._grating_type_assigned = 2
- elif isinstance(self.ucell, list): # Vector
- self.modeling_type_assigned = 1
- self.grating_type_assigned = 1
+ elif self.modeling_type_assigned == 1: # Vector
+ self.grating_type_assigned = 2
@property
def grating_type_assigned(self):
@@ -115,7 +172,8 @@ def grating_type_assigned(self):
def grating_type_assigned(self, grating_type_assigned):
self._grating_type_assigned = grating_type_assigned
- def _solve(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
+ @jax_device_set
+ def solve_for_conv(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
# def false_fun(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
# de_ri, de_ti, layer_info_list, T1 = self.solve_1d(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
@@ -127,82 +185,91 @@ def _solve(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
#
# de_ri, de_ti, layer_info_list, T1 = jax.lax.cond(self._grating_type_assigned, true_fun, false_fun, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
- # if self._grating_type_assigned == 0:
- # de_ri, de_ti, layer_info_list, T1 = self.solve_1d(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
- # else:
- # de_ri, de_ti, layer_info_list, T1 = self.solve_2d(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
+ self._assign_grating_type()
- # In JAXMeent, 1D TE TM are turned off for jit compilation.
- de_ri, de_ti, layer_info_list, T1 = self.solve_2d(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
+ if self._grating_type_assigned == 0:
+ result_dict = self.solve_1d(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
+ elif self._grating_type_assigned == 1:
+ result_dict = self.solve_1d_conical(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
+ else:
+ result_dict = self.solve_2d(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
- return de_ri, de_ti, layer_info_list, T1
+ # TODO: In JAXMeent, 1D TE TM are turned off for jit compilation.
- @jax_device_set
- def solve(self, wavelength, e_conv_all, o_e_conv_all):
- de_ri, de_ti, layer_info_list, T1, kx_vector = jax.jit(self._solve)(wavelength, e_conv_all, o_e_conv_all)
+ res_psi = ResultSubJax(**result_dict['res']) if 'res' in result_dict else None
+ res_te_inc = ResultSubJax(**result_dict['res_te_inc']) if 'res_te_inc' in result_dict else None
+ res_tm_inc = ResultSubJax(**result_dict['res_tm_inc']) if 'res_tm_inc' in result_dict else None
- self.layer_info_list = layer_info_list
- self.T1 = T1
+ result = ResultJax(res_psi, res_te_inc, res_tm_inc)
- return de_ri, de_ti
+ return result
+
+ # @jax_device_set
+ # def solve(self, wavelength, e_conv_all, o_e_conv_all):
+ # de_ri, de_ti, layer_info_list, T1, kx_vector = jax.jit(self._solve)(wavelength, e_conv_all, o_e_conv_all)
+ #
+ # self.layer_info_list = layer_info_list
+ # self.T1 = T1
+ #
+ # return de_ri, de_ti
- def _conv_solve(self, **kwargs):
- self._assign_modeling_type()
+ @jax_device_set
+ def conv_solve(self, **kwargs):
+ [setattr(self, k, v) for k, v in kwargs.items()] # needed for optimization
if self._modeling_type_assigned == 0: # Raster
if self.fourier_type == 0:
epx_conv_all, epy_conv_all, epz_conv_i_all = to_conv_mat_raster_discrete(
self.ucell, self.fto[0], self.fto[1], type_complex=self.type_complex,
- enhanced_dfs=self.enhanced_dfs)
+ enhanced_dfs=self.enhanced_dfs, use_pinv=self.use_pinv)
elif self.fourier_type == 1:
epx_conv_all, epy_conv_all, epz_conv_i_all = to_conv_mat_raster_continuous(
- self.ucell, self.fto[0], self.fto[1], type_complex=self.type_complex)
+ self.ucell, self.fto[0], self.fto[1], type_complex=self.type_complex, use_pinv=self.use_pinv)
else:
raise ValueError("Check 'modeling_type' and 'fourier_type' in 'conv_solve'.")
elif self._modeling_type_assigned == 1: # Vector
ucell_vector = self.modeling_vector_instruction(self.ucell)
epx_conv_all, epy_conv_all, epz_conv_i_all = to_conv_mat_vector(
- ucell_vector, self.fto[0], self.fto[1], type_complex=self.type_complex)
+ ucell_vector, self.fto[0], self.fto[1], type_complex=self.type_complex, use_pinv=self.use_pinv)
else:
raise ValueError("Check 'modeling_type' and 'fourier_type' in 'conv_solve'.")
- de_ri, de_ti, layer_info_list, T1 = self._solve(self.wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
+ result = self.solve_for_conv(self.wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
- self.layer_info_list = layer_info_list
- self.T1 = T1
+ return result
- return de_ri, de_ti, layer_info_list, T1
+ # @jax.jit
+ # def _conv_solve_jit(self):
+ # return self._conv_solve()
- @jax.jit
- def _conv_solve_jit(self):
- return self._conv_solve()
-
- @jax_device_set
- def conv_solve(self, **kwargs):
- [setattr(self, k, v) for k, v in kwargs.items()] # needed for optimization
- if self.fourier_type == 1:
- # print('CFT (fourier_type=1) is not supported for jit-compilation. Using non-jit-compiled method.')
- de_ri, de_ti, layer_info_list, T1 = self._conv_solve()
-
- else:
- de_ri, de_ti, layer_info_list, T1 = self._conv_solve()
- # de_ri, de_ti, layer_info_list, T1 = self._conv_solve_jit()
-
- return de_ri, de_ti
+ # @jax_device_set
+ # def conv_solve(self, **kwargs):
+ # [setattr(self, k, v) for k, v in kwargs.items()] # needed for optimization
+ # if self.fourier_type == 1:
+ # # print('CFT (fourier_type=1) is not supported for jit-compilation. Using non-jit-compiled method.')
+ # de_ri, de_ti, layer_info_list, T1 = self._conv_solve()
+ #
+ # else:
+ # de_ri, de_ti, layer_info_list, T1 = self._conv_solve()
+ # # de_ri, de_ti, layer_info_list, T1 = self._conv_solve_jit()
+ #
+ # return de_ri, de_ti
@jax_device_set
def calculate_field(self, res_x=20, res_y=20, res_z=20):
-
kx, ky = self.get_kx_ky_vector(wavelength=self.wavelength)
if self._grating_type_assigned == 0:
res_y = 1
field_cell = field_dist_1d(self.wavelength, kx, self.T1, self.layer_info_list, self.period, self.pol,
res_x=res_x, res_y=res_y, res_z=res_z, type_complex=self.type_complex)
+ elif self._grating_type_assigned == 1:
+ field_cell = field_dist_1d_conical(self.wavelength, kx, ky, self.T1, self.layer_info_list, self.period,
+ res_x=res_x, res_y=res_y, res_z=res_z, type_complex=self.type_complex)
else:
field_cell = field_dist_2d(self.wavelength, kx, ky, self.T1, self.layer_info_list, self.period,
res_x=res_x, res_y=res_y, res_z=res_z, type_complex=self.type_complex)
diff --git a/meent/on_jax/emsolver/transfer_method.py b/meent/on_jax/emsolver/transfer_method.py
index 41710ef..ca1d9f7 100644
--- a/meent/on_jax/emsolver/transfer_method.py
+++ b/meent/on_jax/emsolver/transfer_method.py
@@ -1,12 +1,11 @@
import jax
import jax.numpy as jnp
-from .primitives import eig, conj
+from .primitives import eig, conj, meeinv
-def transfer_1d_1(pol, ff_x, kx, n_top, n_bot, type_complex=jnp.complex128):
-
- ff_xy = ff_x * 1
+def transfer_1d_1(pol, kx, n_top, n_bot, type_complex=jnp.complex128):
+ ff_x = len(kx)
kz_top = (n_top ** 2 - kx ** 2) ** 0.5
kz_bot = (n_bot ** 2 - kx ** 2) ** 0.5
@@ -14,7 +13,7 @@ def transfer_1d_1(pol, ff_x, kx, n_top, n_bot, type_complex=jnp.complex128):
kz_top = kz_top.conjugate()
kz_bot = kz_bot.conjugate()
- F = jnp.eye(ff_xy, dtype=type_complex)
+ F = jnp.eye(ff_x, dtype=type_complex)
def false_fun(kz_bot):
Kz_bot = jnp.diag(kz_bot)
@@ -26,9 +25,9 @@ def true_fun(kz_bot):
G = 1j * Kz_bot
return Kz_bot, G
- Kz_bot, G = jax.lax.cond(pol, true_fun, false_fun, kz_bot)
+ Kz_bot, G = jax.lax.cond(pol.real, true_fun, false_fun, kz_bot)
- T = jnp.eye(ff_xy, dtype=type_complex)
+ T = jnp.eye(ff_x, dtype=type_complex)
return kz_top, kz_bot, F, G, T
@@ -50,13 +49,13 @@ def true_fun(kz_bot):
# return kz_top, kz_bot, F, G, T
-def transfer_1d_2(pol, kx, epx_conv, epy_conv, epz_conv_i, type_complex=jnp.complex128):
-
+def transfer_1d_2(pol, kx, epx_conv, epy_conv, epz_conv_i, type_complex=jnp.complex128,
+ perturbation=1E-20, use_pinv=False):
Kx = jnp.diag(kx)
def false_fun(Kx, epy_conv): # TE
A = Kx ** 2 - epy_conv
- eigenvalues, W = eig(A)
+ eigenvalues, W = eig(A, type_complex, perturbation)
eigenvalues += 0j # to get positive square root
q = eigenvalues ** 0.5
Q = jnp.diag(q)
@@ -66,16 +65,17 @@ def false_fun(Kx, epy_conv): # TE
def true_fun(Kx, epy_conv): # TM
B = Kx @ epz_conv_i @ Kx - jnp.eye(epy_conv.shape[0], dtype=type_complex)
- eigenvalues, W = eig(epx_conv @ B)
+ eigenvalues, W = eig(epx_conv @ B, type_complex, perturbation)
eigenvalues += 0j # to get positive square root
q = eigenvalues ** 0.5
Q = jnp.diag(q)
- V = jnp.linalg.inv(epx_conv) @ W @ Q
+ # V = jnp.linalg.inv(epx_conv) @ W @ Q
+ V = meeinv(epx_conv, use_pinv) @ W @ Q
return W, V, q
- W, V, q = jax.lax.cond(pol, true_fun, false_fun, Kx, epy_conv)
+ W, V, q = jax.lax.cond(pol.real, true_fun, false_fun, Kx, epy_conv)
return W, V, q
# if pol == 0:
@@ -103,21 +103,23 @@ def true_fun(Kx, epy_conv): # TM
# return W, V, q
-def transfer_1d_3(k0, W, V, q, d, F, G, T, type_complex=jnp.complex128):
-
+def transfer_1d_3(k0, W, V, q, d, F, G, T, type_complex=jnp.complex128, use_pinv=False):
ff_x = len(q)
I = jnp.eye(ff_x, dtype=type_complex)
X = jnp.diag(jnp.exp(-k0 * q * d))
- W_i = jnp.linalg.inv(W)
- V_i = jnp.linalg.inv(V)
+ # W_i = jnp.linalg.inv(W)
+ # V_i = jnp.linalg.inv(V)
+ W_i = meeinv(W, use_pinv)
+ V_i = meeinv(V, use_pinv)
A = 0.5 * (W_i @ F + V_i @ G)
B = 0.5 * (W_i @ F - V_i @ G)
- A_i = jnp.linalg.inv(A)
+ # A_i = jnp.linalg.inv(A)
+ A_i = meeinv(A, use_pinv)
F = W @ (I + X @ B @ A_i @ X)
G = V @ (I - X @ B @ A_i @ X)
@@ -126,65 +128,359 @@ def transfer_1d_3(k0, W, V, q, d, F, G, T, type_complex=jnp.complex128):
return X, F, G, T, A_i, B
-def transfer_1d_4(pol, F, G, T, kz_top, kz_bot, theta, n_top, n_bot, type_complex=jnp.complex128):
+def transfer_1d_4(pol, ff_x, F, G, T, kz_top, kz_bot, theta, n_top, n_bot, type_complex=jnp.complex128, use_pinv=False):
+ Kz_top = jnp.diag(kz_top)
+ kz_top = kz_top.reshape((1, ff_x))
+ kz_bot = kz_bot.reshape((1, ff_x))
- ff_xy = len(kz_top)
+ delta_i0 = jnp.zeros(ff_x, dtype=type_complex)
+ delta_i0 = delta_i0.at[ff_x // 2].set(1)
- Kz_top = jnp.diag(kz_top)
+ def false_fun(): # TE
+ inc_term = 1j * n_top * jnp.cos(theta) * delta_i0
+ T1 = meeinv(G + 1j * Kz_top @ F, use_pinv) @ (1j * Kz_top @ delta_i0 + inc_term)
- delta_i0 = jnp.zeros(ff_xy, dtype=type_complex)
- delta_i0 = delta_i0.at[ff_xy // 2].set(1)
+ R = (F @ T1 - delta_i0).reshape((1, ff_x))
+ _T = (T @ T1).reshape((1, ff_x))
- # if pol == 0: # TE
- # inc_term = 1j * n_top * jnp.cos(theta) * delta_i0
- # T1 = jnp.linalg.inv(G + 1j * Kz_top @ F) @ (1j * Kz_top @ delta_i0 + inc_term)
- #
- # elif pol == 1: # TM
- # inc_term = 1j * delta_i0 * jnp.cos(theta) / n_top
- # T1 = jnp.linalg.inv(G + 1j * Kz_top/(n_top ** 2) @ F) @ (1j * Kz_top/(n_top ** 2) @ delta_i0 + inc_term)
+ de_ri = (R * R.conj() * (kz_top / (n_top * jnp.cos(theta))).real).real
+ de_ti = (_T * _T.conj() * (kz_bot / (n_top * jnp.cos(theta))).real).real
- def false_fun(n_top, theta, delta_i0, G, Kz_top, T): # TE
- inc_term = 1j * n_top * jnp.cos(theta) * delta_i0
- T1 = jnp.linalg.inv(G + 1j * Kz_top @ F) @ (1j * Kz_top @ delta_i0 + inc_term)
- R = F @ T1 - delta_i0
- T = T @ T1
+ R_s = R
+ R_p = jnp.zeros(R.shape, dtype=R.dtype)
+ T_s = _T
+ T_p = jnp.zeros(_T.shape, dtype=_T.dtype)
+ de_ri_s = de_ri
+ de_ri_p = jnp.zeros(de_ri.shape, dtype=de_ri.dtype)
+ de_ti_s = de_ti
+ de_ti_p = jnp.zeros(de_ri.shape, dtype=de_ti.dtype)
+ res = {'R_s': R_s, 'R_p': R_p, 'T_s': T_s, 'T_p': T_p,
+ 'de_ri': de_ri, 'de_ri_s': de_ri_s, 'de_ri_p': de_ri_p,
+ 'de_ti': de_ti, 'de_ti_s': de_ti_s, 'de_ti_p': de_ti_p,
+ }
- de_ri = jnp.real(R * jnp.conj(R) * kz_top / (n_top * jnp.cos(theta)))
- de_ti = T * jnp.conj(T) * jnp.real(kz_bot / (n_top * jnp.cos(theta)))
+ _result = {'res': res}
- return de_ri, de_ti, T1
+ return _result, T1
- def true_fun(n_top, theta, delta_i0, G, Kz_top, T): # TM
+ def true_fun(): # TM
inc_term = 1j * delta_i0 * jnp.cos(theta) / n_top
- T1 = jnp.linalg.inv(G + 1j * Kz_top / (n_top ** 2) @ F) @ (1j * Kz_top / (n_top ** 2) @ delta_i0 + inc_term)
+ T1 = meeinv(G + 1j * Kz_top / (n_top ** 2) @ F, use_pinv) @ (1j * Kz_top / (n_top ** 2) @ delta_i0 + inc_term)
+
+ R = (F @ T1 - delta_i0).reshape((1, ff_x))
+ _T = (T @ T1).reshape((1, ff_x))
+
+ de_ri = (R * R.conj() * (kz_top / (n_top * jnp.cos(theta))).real).real
+ de_ti = (_T * _T.conj() * (kz_bot / n_bot ** 2 / (jnp.cos(theta) / n_top)).real).real
+
+ R_s = jnp.zeros(R.shape, dtype=R.dtype)
+ R_p = R
+ T_s = jnp.zeros(_T.shape, dtype=_T.dtype)
+ T_p = _T
+ de_ri_s = jnp.zeros(de_ri.shape, dtype=de_ri.dtype)
+ de_ri_p = de_ri
+ de_ti_s = jnp.zeros(de_ri.shape, dtype=de_ti.dtype)
+ de_ti_p = de_ti
+
+ res = {'R_s': R_s, 'R_p': R_p, 'T_s': T_s, 'T_p': T_p,
+ 'de_ri': de_ri, 'de_ri_s': de_ri_s, 'de_ri_p': de_ri_p,
+ 'de_ti': de_ti, 'de_ti_s': de_ti_s, 'de_ti_p': de_ti_p,
+ }
+
+ _result = {'res': res}
+
+ return _result, T1
+
+ result, T1 = jax.lax.cond(pol.real, true_fun, false_fun)
+
+ return result, T1
+
+
+def transfer_1d_conical_1(kx, ky, n_top, n_bot, type_complex=jnp.complex128):
+
+ ff_x = len(kx)
+ ff_y = len(ky)
+ ff_xy = ff_x * ff_y
- R = F @ T1 - delta_i0
- T = T @ T1
+ I = jnp.eye(ff_xy).astype(type_complex)
+ O = jnp.zeros((ff_xy, ff_xy)).astype(type_complex)
- de_ri = jnp.real(R * jnp.conj(R) * kz_top / (n_top * jnp.cos(theta)))
- de_ti = T * jnp.conj(T) * jnp.real(kz_bot / n_bot ** 2) / (jnp.cos(theta) / n_top)
+ kz_top = (n_top ** 2 - kx ** 2 - ky.reshape((-1, 1)) ** 2) ** 0.5
+ kz_bot = (n_bot ** 2 - kx ** 2 - ky.reshape((-1, 1)) ** 2) ** 0.5
- return de_ri, de_ti, T1
+ kz_top = kz_top.flatten().conj()
+ kz_bot = kz_bot.flatten().conj()
- de_ri, de_ti, T1 = jax.lax.cond(pol, true_fun, false_fun, n_top, theta, delta_i0, G, Kz_top, T)
+ varphi = jnp.arctan(ky.reshape((-1, 1)) / kx).flatten()
+ Kz_bot = jnp.diag(kz_bot)
+
+ big_F = jnp.block([[I, O], [O, 1j * Kz_bot / (n_bot ** 2)]])
+ big_G = jnp.block([[1j * Kz_bot, O], [O, I]])
+ big_T = jnp.eye(2 * ff_xy, dtype=type_complex)
+
+ return kz_top, kz_bot, varphi, big_F, big_G, big_T
- # R = F @ T1 - delta_i0
- # T = T @ T1
#
- # de_ri = jnp.real(R * jnp.conj(R) * kz_top / (n_top * jnp.cos(theta)))
+ # ky = k0 * n_I * jnp.sin(theta) * jnp.sin(phi)
#
- # if pol == 0:
- # de_ti = T * jnp.conj(T) * jnp.real(kz_bot / (n_top * jnp.cos(theta)))
- # elif pol == 1:
- # de_ti = T * jnp.conj(T) * jnp.real(kz_bot / n_bot ** 2) / (jnp.cos(theta) / n_top)
- # else:
- # raise ValueError
+ # k_I_z = (k0 ** 2 * n_I ** 2 - kx_vector ** 2 - ky ** 2) ** 0.5
+ # k_II_z = (k0 ** 2 * n_II ** 2 - kx_vector ** 2 - ky ** 2) ** 0.5
+ #
+ # # conj() is not allowed with grad x jit
+ # # k_I_z = k_I_z.conjugate()
+ # # k_II_z = k_II_z.conjugate()
+ #
+ # k_I_z = conj(k_I_z) # manual conjugate
+ # k_II_z = conj(k_II_z) # manual conjugate
+ #
+ # Kx = jnp.diag(kx_vector / k0)
+ # varphi = jnp.arctan(ky / kx_vector)
+ #
+ # Y_I = jnp.diag(k_I_z / k0)
+ # Y_II = jnp.diag(k_II_z / k0)
+ #
+ # Z_I = jnp.diag(k_I_z / (k0 * n_I ** 2))
+ # Z_II = jnp.diag(k_II_z / (k0 * n_II ** 2))
+ #
+ # big_F = jnp.block([[I, O], [O, 1j * Z_II]])
+ # big_G = jnp.block([[1j * Y_II, O], [O, I]])
+ #
+ # big_T = jnp.eye(2 * ff).astype(type_complex)
+ #
+ # return Kx, ky, k_I_z, k_II_z, varphi, Y_I, Y_II, Z_I, Z_II, big_F, big_G, big_T
- return de_ri.real, de_ti.real, T1
+
+# def transfer_1d_conical_2(k0, Kx, ky, E_conv, E_conv_i, o_E_conv_i, ff, d, varphi, big_F, big_G, big_T,
+# type_complex=jnp.complex128, perturbation=1E-10, device='cpu'):
+def transfer_1d_conical_2(kx, ky, epx_conv, epy_conv, epz_conv_i, type_complex=jnp.complex128, perturbation=1E-20,
+ device='cpu', use_pinv=False):
+
+ ff_x = len(kx)
+ ff_y = len(ky)
+ ff_xy = ff_x * ff_y
+
+ I = jnp.eye(ff_xy).astype(type_complex)
+
+ Kx = jnp.diag(jnp.tile(kx, ff_y).flatten())
+ Ky = jnp.diag(jnp.tile(ky.reshape((-1, 1)), ff_x).flatten())
+
+ A = Kx ** 2 - epy_conv
+ B = Kx @ epz_conv_i @ Kx - I
+
+ Omega2_RL = Ky ** 2 + A
+ Omega2_LR = Ky ** 2 + B @ epx_conv
+
+ eigenvalues_1, W_1 = eig(Omega2_RL, type_complex=type_complex, perturbation=perturbation, device=device)
+ eigenvalues_2, W_2 = eig(Omega2_LR, type_complex=type_complex, perturbation=perturbation, device=device)
+
+ eigenvalues_1 += 0j # to get positive square root
+ eigenvalues_2 += 0j # to get positive square root
+
+ q_1 = eigenvalues_1 ** 0.5
+ q_2 = eigenvalues_2 ** 0.5
+
+ Q_1 = jnp.diag(q_1)
+ Q_2 = jnp.diag(q_2)
+
+ A_i = meeinv(A, use_pinv)
+ B_i = meeinv(B, use_pinv)
+
+ V_11 = A_i @ W_1 @ Q_1
+ V_12 = Ky @ A_i @ Kx @ W_2
+ V_21 = Ky @ B_i @ Kx @ epz_conv_i @ W_1
+ V_22 = B_i @ W_2 @ Q_2
+
+ W = jnp.block([W_1, W_2])
+ V = jnp.block([[V_11, V_12],
+ [V_21, V_22]])
+ q = jnp.hstack([q_1, q_2])
+
+ return W, V, q
+
+
+def transfer_1d_conical_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_complex=jnp.complex128, use_pinv=False):
+ ff_xy = len(q) // 2
+ I = jnp.eye(ff_xy, dtype=type_complex)
+ O = jnp.zeros((ff_xy, ff_xy), dtype=type_complex)
+
+ q_1 = q[:ff_xy]
+ q_2 = q[ff_xy:]
+
+ W_1 = W[:, :ff_xy]
+ W_2 = W[:, ff_xy:]
+
+ V_11 = V[:ff_xy, :ff_xy]
+ V_12 = V[:ff_xy, ff_xy:]
+ V_21 = V[ff_xy:, :ff_xy]
+ V_22 = V[ff_xy:, ff_xy:]
+
+ X_1 = jnp.diag(jnp.exp(-k0 * q_1 * d))
+ X_2 = jnp.diag(jnp.exp(-k0 * q_2 * d))
+
+ F_c = jnp.diag(jnp.cos(varphi))
+ F_s = jnp.diag(jnp.sin(varphi))
+
+ V_ss = F_c @ V_11
+ V_sp = F_c @ V_12 - F_s @ W_2
+ W_ss = F_c @ W_1 + F_s @ V_21
+ W_sp = F_s @ V_22
+ W_ps = F_s @ V_11
+ W_pp = F_c @ W_2 + F_s @ V_12
+ V_ps = F_c @ V_21 - F_s @ W_1
+ V_pp = F_c @ V_22
+
+ big_I = jnp.eye(2 * (len(I)), dtype=type_complex)
+ big_X = jnp.block([[X_1, O], [O, X_2]])
+ big_W = jnp.block([[V_ss, V_sp], [W_ps, W_pp]])
+ big_V = jnp.block([[W_ss, W_sp], [V_ps, V_pp]])
+
+ big_W_i = meeinv(big_W, use_pinv)
+ big_V_i = meeinv(big_V, use_pinv)
+
+ big_A = 0.5 * (big_W_i @ big_F + big_V_i @ big_G)
+ big_B = 0.5 * (big_W_i @ big_F - big_V_i @ big_G)
+
+ big_A_i = meeinv(big_A, use_pinv)
+
+ big_F = big_W @ (big_I + big_X @ big_B @ big_A_i @ big_X)
+ big_G = big_V @ (big_I - big_X @ big_B @ big_A_i @ big_X)
+
+ big_T = big_T @ big_A_i @ big_X
+
+ return big_X, big_F, big_G, big_T, big_A_i, big_B
-def transfer_2d_1(ff_x, ff_y, kx, ky, n_top, n_bot, type_complex=jnp.complex128):
+# def transfer_1d_conical_4(big_F, big_G, big_T, Z_I, Y_I, psi, theta, ff, delta_i0, k_I_z, k0, n_I, n_II, k_II_z,
+# type_complex=jnp.complex128):
+def transfer_1d_conical_4(ff_x, ff_y, big_F, big_G, big_T, kz_top, kz_bot, psi, theta, n_top, n_bot,
+ type_complex=jnp.complex128, use_pinv=False):
+
+ ff_xy = ff_x * ff_y
+
+ Kz_top = jnp.diag(kz_top)
+ kz_top = kz_top.reshape((ff_y, ff_x))
+ kz_bot = kz_bot.reshape((ff_y, ff_x))
+
+ I = jnp.eye(ff_xy, dtype=type_complex)
+ O = jnp.zeros((ff_xy, ff_xy), dtype=type_complex)
+
+
+ big_F_11 = big_F[:ff_xy, :ff_xy]
+ big_F_12 = big_F[:ff_xy, ff_xy:]
+ big_F_21 = big_F[ff_xy:, :ff_xy]
+ big_F_22 = big_F[ff_xy:, ff_xy:]
+
+ big_G_11 = big_G[:ff_xy, :ff_xy]
+ big_G_12 = big_G[:ff_xy, ff_xy:]
+ big_G_21 = big_G[ff_xy:, :ff_xy]
+ big_G_22 = big_G[ff_xy:, ff_xy:]
+
+ delta_i0 = jnp.zeros((ff_xy, 1), dtype=type_complex)
+ delta_i0 = delta_i0.at[ff_xy // 2, 0].set(1)
+
+ # Final Equation in form of AX=B
+ final_A = jnp.block(
+ [
+ [I, O, -big_F_11, -big_F_12],
+ [O, -1j * Kz_top / (n_top ** 2), -big_F_21, -big_F_22],
+ [-1j * Kz_top, O, -big_G_11, -big_G_12],
+ [O, I, -big_G_21, -big_G_22],
+ ]
+ )
+ final_B = jnp.block(
+ [
+ [-jnp.sin(psi) * delta_i0],
+ [jnp.cos(psi) * jnp.cos(theta) * delta_i0],
+ [-1j * jnp.sin(psi) * n_top * jnp.cos(theta) * delta_i0],
+ [-1j * n_top * jnp.cos(psi) * delta_i0]
+ ]
+ )
+
+ final_A_inv = meeinv(final_A, use_pinv)
+ final_RT = final_A_inv @ final_B
+
+ R_s = final_RT[:ff_xy, :].reshape((ff_y, ff_x))
+ R_p = final_RT[ff_xy: 2 * ff_xy, :].reshape((ff_y, ff_x))
+
+ big_T1 = final_RT[2 * ff_xy:, :]
+ big_T_tetm = big_T.copy()
+ big_T = big_T @ big_T1
+
+ T_s = big_T[:ff_xy, :].reshape((ff_y, ff_x))
+ T_p = big_T[ff_xy:, :].reshape((ff_y, ff_x))
+
+ de_ri_s = (R_s * R_s.conj() * (kz_top / (n_top * jnp.cos(theta))).real).real
+ de_ri_p = (R_p * R_p.conj() * (kz_top / n_top ** 2 / (n_top * jnp.cos(theta))).real).real
+
+ de_ti_s = (T_s * T_s.conj() * (kz_bot / (n_top * jnp.cos(theta))).real).real
+ de_ti_p = (T_p * T_p.conj() * (kz_bot / n_bot ** 2 / (n_top * jnp.cos(theta))).real).real
+
+ de_ri = de_ri_s + de_ri_p
+ de_ti = de_ti_s + de_ti_p
+
+ res = {'R_s': R_s, 'R_p': R_p, 'T_s': T_s, 'T_p': T_p,
+ 'de_ri_s': de_ri_s, 'de_ri_p': de_ri_p, 'de_ri': de_ri,
+ 'de_ti_s': de_ti_s, 'de_ti_p': de_ti_p, 'de_ti': de_ti}
+
+ # TE TM incidence
+ psi_tm = jnp.array(0, dtype=type_complex)
+ final_B_tm = jnp.block(
+ [
+ [-jnp.sin(psi_tm) * delta_i0],
+ [jnp.cos(psi_tm) * jnp.cos(theta) * delta_i0],
+ [-1j * jnp.sin(psi_tm) * n_top * jnp.cos(theta) * delta_i0],
+ [-1j * n_top * jnp.cos(psi_tm) * delta_i0]
+ ]
+ )
+
+ psi_te = jnp.array(jnp.pi / 2, dtype=type_complex)
+ final_B_te = jnp.block(
+ [
+ [-jnp.sin(psi_te) * delta_i0],
+ [jnp.cos(psi_te) * jnp.cos(theta) * delta_i0],
+ [-1j * jnp.sin(psi_te) * n_top * jnp.cos(theta) * delta_i0],
+ [-1j * n_top * jnp.cos(psi_te) * delta_i0]
+ ]
+ )
+
+ final_B_tetm = jnp.hstack([final_B_te, final_B_tm])
+ final_RT_tetm = final_A_inv @ final_B_tetm
+
+ R_s_tetm = final_RT_tetm[:ff_xy, :].T.reshape((2, ff_y, ff_x))
+ R_p_tetm = final_RT_tetm[ff_xy: 2 * ff_xy, :].T.reshape((2, ff_y, ff_x))
+
+ big_T1_tetm = final_RT_tetm[2 * ff_xy:, :]
+ big_T_tetm = big_T_tetm @ big_T1_tetm
+
+ T_s_tetm = big_T_tetm[:ff_xy, :].T.reshape((2, ff_y, ff_x))
+ T_p_tetm = big_T_tetm[ff_xy:, :].T.reshape((2, ff_y, ff_x))
+
+ de_ri_s_tetm = (R_s_tetm * R_s_tetm.conj() * (kz_top / (n_top * jnp.cos(theta))).real).real
+ de_ri_p_tetm = (R_p_tetm * R_p_tetm.conj() * (kz_top / n_top ** 2 / (n_top * jnp.cos(theta))).real).real
+
+ de_ti_s_tetm = (T_s_tetm * T_s_tetm.conj() * (kz_bot / (n_top * jnp.cos(theta))).real).real
+ de_ti_p_tetm = (T_p_tetm * T_p_tetm.conj() * (kz_bot / n_bot ** 2 / (n_top * jnp.cos(theta))).real).real
+
+ de_ri_tetm = de_ri_s_tetm + de_ri_p_tetm
+ de_ti_tetm = de_ti_s_tetm + de_ti_p_tetm
+
+ res_te_inc = {'R_s': R_s_tetm[0], 'R_p': R_p_tetm[0], 'T_s': T_s_tetm[0], 'T_p': T_p_tetm[0],
+ 'de_ri_s': de_ri_s_tetm[0], 'de_ri_p': de_ri_p_tetm[0], 'de_ri': de_ri_tetm[0],
+ 'de_ti_s': de_ti_s_tetm[0], 'de_ti_p': de_ti_p_tetm[0], 'de_ti': de_ti_tetm[0]}
+
+ res_tm_inc = {'R_s': R_s_tetm[1], 'R_p': R_p_tetm[1], 'T_s': T_s_tetm[1], 'T_p': T_p_tetm[1],
+ 'de_ri_s': de_ri_s_tetm[1], 'de_ri_p': de_ri_p_tetm[1], 'de_ri': de_ri_tetm[1],
+ 'de_ti_s': de_ti_s_tetm[1], 'de_ti_p': de_ti_p_tetm[1], 'de_ti': de_ti_tetm[1]}
+
+ result = {'res': res, 'res_tm_inc': res_tm_inc, 'res_te_inc': res_te_inc}
+
+ return result, big_T1
+
+
+def transfer_2d_1(kx, ky, n_top, n_bot, type_complex=jnp.complex128):
+ ff_x = len(kx)
+ ff_y = len(ky)
ff_xy = ff_x * ff_y
I = jnp.eye(ff_xy, dtype=type_complex)
@@ -193,11 +489,10 @@ def transfer_2d_1(ff_x, ff_y, kx, ky, n_top, n_bot, type_complex=jnp.complex128)
kz_top = (n_top ** 2 - kx ** 2 - ky.reshape((-1, 1)) ** 2) ** 0.5
kz_bot = (n_bot ** 2 - kx ** 2 - ky.reshape((-1, 1)) ** 2) ** 0.5
- kz_top = kz_top.flatten().conjugate()
- kz_bot = kz_bot.flatten().conjugate()
+ kz_top = kz_top.flatten().conj()
+ kz_bot = kz_bot.flatten().conj()
varphi = jnp.arctan(ky.reshape((-1, 1)) / kx).flatten()
-
Kz_bot = jnp.diag(kz_bot)
big_F = jnp.block([[I, O], [O, 1j * Kz_bot / (n_bot ** 2)]])
@@ -207,8 +502,8 @@ def transfer_2d_1(ff_x, ff_y, kx, ky, n_top, n_bot, type_complex=jnp.complex128)
return kz_top, kz_bot, varphi, big_F, big_G, big_T
-def transfer_2d_2(kx, ky, epx_conv, epy_conv, epz_conv_i, type_complex=jnp.complex128):
-
+def transfer_2d_2(kx, ky, epx_conv, epy_conv, epz_conv_i, type_complex=jnp.complex128,
+ perturbation=1E-20, use_pinv=False):
ff_x = len(kx)
ff_y = len(ky)
@@ -227,12 +522,12 @@ def transfer_2d_2(kx, ky, epx_conv, epy_conv, epz_conv_i, type_complex=jnp.compl
])
# eigenvalues, W = jnp.linalg.eig(Omega2_LR)
- eigenvalues, W = eig(Omega2_LR)
+ eigenvalues, W = eig(Omega2_LR, type_complex, perturbation)
eigenvalues += 0j # to get positive square root
q = eigenvalues ** 0.5
Q = jnp.diag(q)
- Q_i = jnp.linalg.inv(Q)
+ Q_i = meeinv(Q, use_pinv)
Omega_R = jnp.block(
[
@@ -246,15 +541,14 @@ def transfer_2d_2(kx, ky, epx_conv, epy_conv, epz_conv_i, type_complex=jnp.compl
return W, V, q
-def transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_complex=jnp.complex128):
-
+def transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_complex=jnp.complex128, use_pinv=False):
ff_xy = len(q)//2
I = jnp.eye(ff_xy, dtype=type_complex)
O = jnp.zeros((ff_xy, ff_xy), dtype=type_complex)
- q1 = q[:ff_xy]
- q2 = q[ff_xy:]
+ q_1 = q[:ff_xy]
+ q_2 = q[ff_xy:]
W_11 = W[:ff_xy, :ff_xy]
W_12 = W[:ff_xy, ff_xy:]
@@ -266,8 +560,8 @@ def transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_complex=jnp.
V_21 = V[ff_xy:, :ff_xy]
V_22 = V[ff_xy:, ff_xy:]
- X_1 = jnp.diag(jnp.exp(-k0 * q1 * d))
- X_2 = jnp.diag(jnp.exp(-k0 * q2 * d))
+ X_1 = jnp.diag(jnp.exp(-k0 * q_1 * d))
+ X_2 = jnp.diag(jnp.exp(-k0 * q_2 * d))
F_c = jnp.diag(jnp.cos(varphi))
F_s = jnp.diag(jnp.sin(varphi))
@@ -287,13 +581,13 @@ def transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_complex=jnp.
big_W = jnp.block([[W_ss, W_sp], [W_ps, W_pp]])
big_V = jnp.block([[V_ss, V_sp], [V_ps, V_pp]])
- big_W_i = jnp.linalg.inv(big_W)
- big_V_i = jnp.linalg.inv(big_V)
+ big_W_i = meeinv(big_W, use_pinv)
+ big_V_i = meeinv(big_V, use_pinv)
big_A = 0.5 * (big_W_i @ big_F + big_V_i @ big_G)
big_B = 0.5 * (big_W_i @ big_F - big_V_i @ big_G)
- big_A_i = jnp.linalg.inv(big_A)
+ big_A_i = meeinv(big_A, use_pinv)
big_F = big_W @ (big_I + big_X @ big_B @ big_A_i @ big_X)
big_G = big_V @ (big_I - big_X @ big_B @ big_A_i @ big_X)
@@ -303,12 +597,15 @@ def transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_complex=jnp.
return big_X, big_F, big_G, big_T, big_A_i, big_B
-def transfer_2d_4(big_F, big_G, big_T, kz_top, kz_bot, psi, theta, n_top, n_bot, type_complex=jnp.complex128):
-
- ff_xy = len(big_F) // 2
+def transfer_2d_4(ff_x, ff_y, big_F, big_G, big_T, kz_top, kz_bot, psi, theta, n_top, n_bot,
+ type_complex=jnp.complex128, use_pinv=False):
+ ff_xy = ff_x * ff_y
Kz_top = jnp.diag(kz_top)
+ kz_top = kz_top.reshape((ff_y, ff_x))
+ kz_bot = kz_bot.reshape((ff_y, ff_x))
+
I = jnp.eye(ff_xy, dtype=type_complex)
O = jnp.zeros((ff_xy, ff_xy), dtype=type_complex)
@@ -344,22 +641,83 @@ def transfer_2d_4(big_F, big_G, big_T, kz_top, kz_bot, psi, theta, n_top, n_bot,
]
)
- final_RT = jnp.linalg.inv(final_A) @ final_B
+ final_A_inv = meeinv(final_A, use_pinv)
+ final_RT = final_A_inv @ final_B
- R_s = final_RT[:ff_xy, :].flatten()
- R_p = final_RT[ff_xy: 2 * ff_xy, :].flatten()
+ R_s = final_RT[:ff_xy, :].reshape((ff_y, ff_x))
+ R_p = final_RT[ff_xy: 2 * ff_xy, :].reshape((ff_y, ff_x))
big_T1 = final_RT[2 * ff_xy:, :]
+ big_T_tetm = big_T.copy()
big_T = big_T @ big_T1
- T_s = big_T[:ff_xy, :].flatten()
- T_p = big_T[ff_xy:, :].flatten()
+ T_s = big_T[:ff_xy, :].reshape((ff_y, ff_x))
+ T_p = big_T[ff_xy:, :].reshape((ff_y, ff_x))
+
+ de_ri_s = (R_s * R_s.conj() * (kz_top / (n_top * jnp.cos(theta))).real).real
+ de_ri_p = (R_p * R_p.conj() * (kz_top / n_top ** 2 / (n_top * jnp.cos(theta))).real).real
+
+ de_ti_s = (T_s * T_s.conj() * (kz_bot / (n_top * jnp.cos(theta))).real).real
+ de_ti_p = (T_p * T_p.conj() * (kz_bot / n_bot ** 2 / (n_top * jnp.cos(theta))).real).real
+
+ de_ri = de_ri_s + de_ri_p
+ de_ti = de_ti_s + de_ti_p
+
+ res = {'R_s': R_s, 'R_p': R_p, 'T_s': T_s, 'T_p': T_p,
+ 'de_ri_s': de_ri_s, 'de_ri_p': de_ri_p, 'de_ri': de_ri,
+ 'de_ti_s': de_ti_s, 'de_ti_p': de_ti_p, 'de_ti': de_ti}
+
+ # TE TM incidence
+ psi_tm = jnp.array(0, dtype=type_complex)
+ final_B_tm = jnp.block(
+ [
+ [-jnp.sin(psi_tm) * delta_i0],
+ [jnp.cos(psi_tm) * jnp.cos(theta) * delta_i0],
+ [-1j * jnp.sin(psi_tm) * n_top * jnp.cos(theta) * delta_i0],
+ [-1j * n_top * jnp.cos(psi_tm) * delta_i0]
+ ]
+ )
+ psi_te = jnp.array(jnp.pi/2, dtype=type_complex)
+ final_B_te = jnp.block(
+ [
+ [-jnp.sin(psi_te) * delta_i0],
+ [jnp.cos(psi_te) * jnp.cos(theta) * delta_i0],
+ [-1j * jnp.sin(psi_te) * n_top * jnp.cos(theta) * delta_i0],
+ [-1j * n_top * jnp.cos(psi_te) * delta_i0]
+ ]
+ )
+
+ final_B_tetm = jnp.hstack([final_B_te, final_B_tm])
+ final_RT_tetm = final_A_inv @ final_B_tetm
+
+ R_s_tetm = final_RT_tetm[:ff_xy, :].T.reshape((2, ff_y, ff_x))
+ R_p_tetm = final_RT_tetm[ff_xy: 2 * ff_xy, :].T.reshape((2, ff_y, ff_x))
+
+ big_T1_tetm = final_RT_tetm[2 * ff_xy:, :]
+ big_T_tetm = big_T_tetm @ big_T1_tetm
+
+ T_s_tetm = big_T_tetm[:ff_xy, :].T.reshape((2, ff_y, ff_x))
+ T_p_tetm = big_T_tetm[ff_xy:, :].T.reshape((2, ff_y, ff_x))
+
+ de_ri_s_tetm = (R_s_tetm * R_s_tetm.conj() * (kz_top / (n_top * jnp.cos(theta))).real).real
+ de_ri_p_tetm = (R_p_tetm * R_p_tetm.conj() * (kz_top / n_top ** 2 / (n_top * jnp.cos(theta))).real).real
+
+ de_ti_s_tetm = (T_s_tetm * T_s_tetm.conj() * (kz_bot / (n_top * jnp.cos(theta))).real).real
+ de_ti_p_tetm = (T_p_tetm * T_p_tetm.conj() * (kz_bot / n_bot ** 2 / (n_top * jnp.cos(theta))).real).real
+
+ de_ri_tetm = de_ri_s_tetm + de_ri_p_tetm
+ de_ti_tetm = de_ti_s_tetm + de_ti_p_tetm
+
+ res_te_inc = {'R_s': R_s_tetm[0], 'R_p': R_p_tetm[0], 'T_s': T_s_tetm[0], 'T_p': T_p_tetm[0],
+ 'de_ri_s': de_ri_s_tetm[0], 'de_ri_p': de_ri_p_tetm[0], 'de_ri': de_ri_tetm[0],
+ 'de_ti_s': de_ti_s_tetm[0], 'de_ti_p': de_ti_p_tetm[0], 'de_ti': de_ti_tetm[0]}
+
+ res_tm_inc = {'R_s': R_s_tetm[1], 'R_p': R_p_tetm[1], 'T_s': T_s_tetm[1], 'T_p': T_p_tetm[1],
+ 'de_ri_s': de_ri_s_tetm[1], 'de_ri_p': de_ri_p_tetm[1], 'de_ri': de_ri_tetm[1],
+ 'de_ti_s': de_ti_s_tetm[1], 'de_ti_p': de_ti_p_tetm[1], 'de_ti': de_ti_tetm[1]}
- de_ri = R_s * jnp.conj(R_s) * jnp.real(kz_top / (n_top * jnp.cos(theta))) \
- + R_p * jnp.conj(R_p) * jnp.real(kz_top / n_top ** 2 / (n_top * jnp.cos(theta)))
+ result = {'res': res, 'res_tm_inc': res_tm_inc, 'res_te_inc': res_te_inc}
- de_ti = T_s * jnp.conj(T_s) * jnp.real(kz_bot / (n_top * jnp.cos(theta))) \
- + T_p * jnp.conj(T_p) * jnp.real(kz_bot / n_bot ** 2 / (n_top * jnp.cos(theta)))
+ return result, big_T1
- return de_ri.real, de_ti.real, big_T1
diff --git a/meent/on_jax/optimizer/loss.py b/meent/on_jax/optimizer/loss.py
deleted file mode 100644
index e24d125..0000000
--- a/meent/on_jax/optimizer/loss.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import jax.numpy as jnp
-
-
-class LossDeflector:
- def __init__(self, x_order=0, y_order=0):
- self.x_order = x_order
- self.y_order = y_order
-
- def __call__(self, value, *args, **kwargs):
- de_ri, de_ti = value
-
- if len(de_ti.shape) == 1:
- c_x = de_ti.shape[0] // 2
- res = de_ti[c_x + self.x_order]
- elif len(de_ti.shape) == 2:
- c_x = de_ti.shape[0] // 2
- c_y = de_ti.shape[1] // 2
- res = de_ti[c_x + self.x_order, c_y + self.y_order]
- else:
- raise ValueError
-
- return res
-
-
-class LossSpectrumL2:
- def __init__(self):
- pass
-
- def __call__(self, pred, target, *args, **kwargs):
- gap = jnp.linalg.norm(pred, target)
- return gap
diff --git a/meent/on_numpy/emsolver/_base.py b/meent/on_numpy/emsolver/_base.py
index afa8a63..230a482 100644
--- a/meent/on_numpy/emsolver/_base.py
+++ b/meent/on_numpy/emsolver/_base.py
@@ -2,15 +2,16 @@
from .scattering_method import scattering_1d_1, scattering_1d_2, scattering_1d_3, scattering_2d_1, scattering_2d_wv, \
scattering_2d_2, scattering_2d_3
-from .transfer_method import (transfer_1d_1, transfer_1d_2, transfer_1d_3, transfer_1d_4,
+from .transfer_method import (transfer_1d_1, transfer_1d_2, transfer_1d_3, transfer_1d_4, transfer_1d_conical_1,
+ transfer_1d_conical_2, transfer_1d_conical_3, transfer_1d_conical_4,
transfer_2d_1, transfer_2d_2, transfer_2d_3, transfer_2d_4)
class _BaseRCWA:
- def __init__(self, n_top=1., n_bot=1., theta=0., phi=0., psi=None, pol=0., fto=(0, 0),
- period=(100., 100.), wavelength=1.,
- thickness=(0., ), connecting_algo='TMM', perturbation=1E-20,
- type_complex=np.complex128, *args, **kwargs): # TODO: delete args and kwargs?
+ def __init__(self, n_top=1., n_bot=1., theta=0., phi=None, psi=None, pol=0., fto=(0, 0),
+ period=(1., 1.), wavelength=1.,
+ thickness=(0.,), connecting_algo='TMM', perturbation=1E-20,
+ device=0, type_complex=np.complex128, use_pinv=False):
self._device = 0
@@ -40,6 +41,8 @@ def __init__(self, n_top=1., n_bot=1., theta=0., phi=0., psi=None, pol=0., fto=(
self.wavelength = wavelength
self.thickness = thickness
self.connecting_algo = connecting_algo
+ self.use_pinv = use_pinv
+
self.layer_info_list = []
self.T1 = None
@@ -83,33 +86,17 @@ def type_float(self):
def type_int(self):
return self._type_int
- @property
- def pol(self):
- return self._pol
-
- @pol.setter
- def pol(self, pol):
- room = 1E-6
- if 1 < pol < 1 + room:
- pol = 1
- elif 0 - room < pol < 0:
- pol = 0
-
- if not 0 <= pol <= 1:
- raise ValueError
-
- self._pol = pol
- psi = np.pi / 2 * (1 - self.pol)
- self._psi = np.array(psi, dtype=self.type_float)
-
@property
def theta(self):
return self._theta
@theta.setter
def theta(self, theta):
- self._theta = np.array(theta, dtype=self.type_float)
- self._theta = np.where(self._theta == 0, self.perturbation, self._theta) # perturbation
+ if theta is None:
+ self._theta = None
+ else:
+ self._theta = np.array(theta, dtype=self.type_complex)
+ self._theta = np.where(self._theta == 0, self.perturbation, self._theta) # perturbation
@property
def phi(self):
@@ -117,7 +104,11 @@ def phi(self):
@phi.setter
def phi(self, phi):
- self._phi = np.array(phi, dtype=self.type_float)
+ if phi is None:
+ self._phi = None
+ else:
+ self._phi = np.array(phi, dtype=self.type_complex)
+ # self._phi = np.array(phi, dtype=self.type_complex) if phi is not None else None
@property
def psi(self):
@@ -126,10 +117,29 @@ def psi(self):
@psi.setter
def psi(self, psi):
if psi is not None:
- self._psi = np.array(psi, dtype=self.type_float)
+ self._psi = np.array(psi, dtype=self.type_complex) # TODO: complex, QA
pol = -(2 * psi / np.pi - 1)
self._pol = pol
+ @property
+ def pol(self):
+ """
+ portion of TM. 0: full TE, 1: full TM
+
+ Returns: polarization ratio
+
+ """
+ return self._pol
+
+ @pol.setter
+ def pol(self, pol):
+ if not 0 <= pol <= 1:
+ raise ValueError
+
+ self._pol = pol
+ psi = np.array(np.pi / 2 * (1 - self.pol), dtype=self.type_complex)
+ self._psi = psi
+
@property
def fto(self):
return self._fto
@@ -195,11 +205,19 @@ def get_kx_ky_vector(self, wavelength):
fto_x_range = np.arange(-self.fto[0], self.fto[0] + 1)
fto_y_range = np.arange(-self.fto[1], self.fto[1] + 1)
- kx = (self.n_top * np.sin(self.theta) * np.cos(self.phi) + fto_x_range * (
- wavelength / self.period[0])).astype(self.type_complex)
+ if self.theta.real >= np.float32(np.pi / 2):
+ # https://github.com/numpy/numpy/issues/27306
+ sin_theta = np.sin(np.nextafter(np.float32(np.pi / 2), np.float32(0)) + self.theta.imag * np.complex64(1j))
+ else:
+ sin_theta = np.sin(self.theta)
- ky = (self.n_top * np.sin(self.theta) * np.sin(self.phi) + fto_y_range * (
- wavelength / self.period[1])).astype(self.type_complex)
+ phi = 0 if self.phi is None else self.phi # phi is None -> 1D TE TM case
+
+ kx = (self.n_top * sin_theta * np.cos(phi) + fto_x_range * (
+ wavelength / self.period[0])).astype(self.type_complex).conj()
+
+ ky = (self.n_top * sin_theta * np.sin(phi) + fto_y_range * (
+ wavelength / self.period[1])).astype(self.type_complex).conj()
return kx, ky
@@ -214,11 +232,13 @@ def solve_1d(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
if self.connecting_algo == 'TMM':
kz_top, kz_bot, F, G, T \
- = transfer_1d_1(self.pol, ff_x, kx, self.n_top, self.n_bot, type_complex=self.type_complex)
+ = transfer_1d_1(self.pol, kx, self.n_top, self.n_bot, type_complex=self.type_complex)
elif self.connecting_algo == 'SMM':
- Kx, Wg, Vg, Kzg, Wr, Vr, Kzr, Wt, Vt, Kzt, Ar, Br, Sg \
- = scattering_1d_1(k0, self.n_top, self.n_bot, self.theta, self.phi, self.period,
- self.pol, wl=wavelength)
+ raise ValueError
+
+ # Kx, Wg, Vg, Kzg, Wr, Vr, Kzr, Wt, Vt, Kzt, Ar, Br, Sg \
+ # = scattering_1d_1(k0, self.n_top, self.n_bot, self.theta, self.phi, self.period,
+ # self.pol, wl=wavelength)
else:
raise ValueError
@@ -232,33 +252,95 @@ def solve_1d(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
d = self.thickness[layer_index]
if self.connecting_algo == 'TMM':
- W, V, q = transfer_1d_2(self.pol, kx, epx_conv, epy_conv, epz_conv_i, self.type_complex)
+ W, V, q = transfer_1d_2(self.pol, kx, epx_conv, epy_conv, epz_conv_i, self.type_complex,
+ use_pinv=self.use_pinv)
- X, F, G, T, A_i, B = transfer_1d_3(k0, W, V, q, d, F, G, T, type_complex=self.type_complex)
+ X, F, G, T, A_i, B = transfer_1d_3(k0, W, V, q, d, F, G, T, type_complex=self.type_complex,
+ use_pinv=self.use_pinv)
layer_info = [epz_conv_i, W, V, q, d, A_i, B]
self.layer_info_list.append(layer_info)
elif self.connecting_algo == 'SMM':
- A, B, S_dict, Sg = scattering_1d_2(W, Wg, V, Vg, d, k0, Q, Sg)
+ raise ValueError
+
+ # A, B, S_dict, Sg = scattering_1d_2(W, Wg, V, Vg, d, k0, Q, Sg)
else:
raise ValueError
if self.connecting_algo == 'TMM':
- de_ri, de_ti, T1 = transfer_1d_4(self.pol, F, G, T, kz_top, kz_bot, self.theta, self.n_top, self.n_bot,
- type_complex=self.type_complex)
+ result, T1 = transfer_1d_4(self.pol, ff_x, F, G, T, kz_top, kz_bot, self.theta, self.n_top, self.n_bot,
+ type_complex=self.type_complex, use_pinv=self.use_pinv)
self.T1 = T1
elif self.connecting_algo == 'SMM':
- de_ri, de_ti = scattering_1d_3(Wt, Wg, Vt, Vg, Sg, ff, Wr, self.fto, Kzr, Kzt,
- self.n_top, self.n_bot, self.theta, self.pol)
+ raise ValueError
+
+ # de_ri, de_ti = scattering_1d_3(Wt, Wg, Vt, Vg, Sg, ff, Wr, self.fto, Kzr, Kzt,
+ # self.n_top, self.n_bot, self.theta, self.pol)
else:
raise ValueError
- return de_ri, de_ti, self.layer_info_list, self.T1
+ return result
- def solve_2d(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
+ def solve_1d_conical(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
+ self.layer_info_list = []
+ self.T1 = None
+
+ ff_x = self.fto[0] * 2 + 1
+ ff_y = 1
+
+ k0 = 2 * np.pi / wavelength
+ kx, ky = self.get_kx_ky_vector(wavelength)
+ if self.connecting_algo == 'TMM':
+ kz_top, kz_bot, varphi, big_F, big_G, big_T \
+ = transfer_1d_conical_1(kx, ky, self.n_top, self.n_bot, type_complex=self.type_complex)
+
+ elif self.connecting_algo == 'SMM':
+ print('SMM for 1D conical is not implemented')
+ return np.nan, np.nan
+ else:
+ raise ValueError
+
+ for layer_index in range(len(self.thickness))[::-1]:
+
+ epx_conv = epx_conv_all[layer_index]
+ epy_conv = epy_conv_all[layer_index]
+ epz_conv_i = epz_conv_i_all[layer_index]
+
+ d = self.thickness[layer_index]
+
+ if self.connecting_algo == 'TMM':
+ W, V, q = transfer_1d_conical_2(kx, ky, epx_conv, epy_conv, epz_conv_i, type_complex=self.type_complex,
+ use_pinv=self.use_pinv)
+
+ big_X, big_F, big_G, big_T, big_A_i, big_B, \
+ = transfer_1d_conical_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_complex=self.type_complex,
+ use_pinv=self.use_pinv)
+
+ layer_info = [epz_conv_i, W, V, q, d, big_A_i, big_B]
+ self.layer_info_list.append(layer_info)
+
+ elif self.connecting_algo == 'SMM':
+ raise ValueError
+ else:
+ raise ValueError
+
+ if self.connecting_algo == 'TMM':
+ result, big_T1 = transfer_1d_conical_4(ff_x, ff_y, big_F, big_G, big_T, kz_top, kz_bot, self.psi,
+ self.theta, self.n_top, self.n_bot, type_complex=self.type_complex,
+ use_pinv=self.use_pinv)
+ self.T1 = big_T1
+
+ elif self.connecting_algo == 'SMM':
+ raise ValueError
+ else:
+ raise ValueError
+
+ return result
+
+ def solve_2d(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
self.layer_info_list = []
self.T1 = None
@@ -270,11 +352,13 @@ def solve_2d(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
if self.connecting_algo == 'TMM':
kz_top, kz_bot, varphi, big_F, big_G, big_T \
- = transfer_2d_1(ff_x, ff_y, kx, ky, self.n_top, self.n_bot, type_complex=self.type_complex)
+ = transfer_2d_1(kx, ky, self.n_top, self.n_bot, type_complex=self.type_complex)
elif self.connecting_algo == 'SMM':
- Kx, Ky, kz_inc, Wg, Vg, Kzg, Wr, Vr, Kzr, Wt, Vt, Kzt, Ar, Br, Sg \
- = scattering_2d_1(self.n_top, self.n_bot, self.theta, self.phi, k0, self.period, self.fto)
+ raise ValueError
+
+ # Kx, Ky, kz_inc, Wg, Vg, Kzg, Wr, Vr, Kzr, Wt, Vt, Kzt, Ar, Br, Sg, kz_top, kz_bot \
+ # = scattering_2d_1(self.n_top, self.n_bot, self.theta, self.phi, k0, self.period, self.fto, kx, ky)
else:
raise ValueError
@@ -288,32 +372,47 @@ def solve_2d(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
d = self.thickness[layer_index]
if self.connecting_algo == 'TMM':
- W, V, q = transfer_2d_2(kx, ky, epx_conv, epy_conv, epz_conv_i, type_complex=self.type_complex)
+ W, V, q = transfer_2d_2(kx, ky, epx_conv, epy_conv, epz_conv_i, type_complex=self.type_complex,
+ use_pinv=self.use_pinv)
big_X, big_F, big_G, big_T, big_A_i, big_B, \
- = transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_complex=self.type_complex)
+ = transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_complex=self.type_complex,
+ use_pinv=self.use_pinv)
layer_info = [epz_conv_i, W, V, q, d, big_A_i, big_B]
self.layer_info_list.append(layer_info)
elif self.connecting_algo == 'SMM':
- W, V, q = scattering_2d_wv(ff_xy, Kx, Ky, E_conv, o_E_conv, o_E_conv_i, E_conv_i)
- A, B, Sl_dict, Sg_matrix, Sg = scattering_2d_2(W, Wg, V, Vg, d, k0, Sg, q)
+ raise ValueError
+
+ # W, V, q = scattering_2d_wv(ff_xy, Kx, Ky, E_conv, o_E_conv, o_E_conv_i, E_conv_i)
+ # A, B, Sl_dict, Sg_matrix, Sg = scattering_2d_2(W, Wg, V, Vg, d, k0, Sg, q)
+
+ # W, V, q = scattering_2d_wv(Kx, Ky, E_conv, o_E_conv, o_E_conv_i, E_conv_i)
+ # W, V, q = scattering_2d_wv(Kx, Ky, epx_conv, epy_conv, epz_conv_i)
+ # A, B, Sl_dict, Sg_matrix, Sg = scattering_2d_2(W, Wg, V, Vg, d, k0, Sg, q)
else:
raise ValueError
if self.connecting_algo == 'TMM':
- de_ri, de_ti, big_T1 = transfer_2d_4(big_F, big_G, big_T, kz_top, kz_bot, self.psi, self.theta,
- self.n_top, self.n_bot, type_complex=self.type_complex)
+ result, big_T1 = transfer_2d_4(ff_x, ff_y, big_F, big_G, big_T, kz_top, kz_bot, self.psi, self.theta,
+ self.n_top, self.n_bot, type_complex=self.type_complex,
+ use_pinv=self.use_pinv)
self.T1 = big_T1
elif self.connecting_algo == 'SMM':
- de_ri, de_ti = scattering_2d_3(ff_xy, Wt, Wg, Vt, Vg, Sg, Wr, Kx, Ky, Kzr, Kzt, kz_inc, self.n_top,
- self.pol, self.theta, self.phi, self.fto)
+ raise ValueError
+
+ # de_ri, de_ti = scattering_2d_3(ff_xy, Wt, Wg, Vt, Vg, Sg, Wr, Kx, Ky, Kzr, Kzt, kz_inc, self.n_top,
+ # self.pol, self.theta, self.phi, self.fto)
+
+ # de_ri_s, de_ri_p, de_ti_s, de_ti_p, R_s, R_p, T_s, T_p =\
+ # scattering_2d_3(Wt, Wg, Vt, Vg, Sg, Wr, Kx, Ky, Kzr, Kzt, kz_top, kz_bot, self.n_top, self.n_bot,
+ # self.pol, self.theta, self.phi, self.fto)
else:
raise ValueError
- de_ri = de_ri.reshape((ff_y, ff_x)).T
- de_ti = de_ti.reshape((ff_y, ff_x)).T
- return de_ri, de_ti, self.layer_info_list, self.T1
+ # de_ri = de_ri.reshape((ff_y, ff_x)).T # TODO: check benchmarks codes
+ # de_ti = de_ti.reshape((ff_y, ff_x)).T
+ return result
diff --git a/meent/on_numpy/emsolver/convolution_matrix.py b/meent/on_numpy/emsolver/convolution_matrix.py
index ba44100..9d8426c 100644
--- a/meent/on_numpy/emsolver/convolution_matrix.py
+++ b/meent/on_numpy/emsolver/convolution_matrix.py
@@ -1,5 +1,6 @@
import numpy as np
from .fourier_analysis import dfs2d, cfs2d
+from .primitives import meeinv
def cell_compression(cell, type_complex=np.complex128):
@@ -43,7 +44,7 @@ def cell_compression(cell, type_complex=np.complex128):
return cell_comp, x, y
-def to_conv_mat_vector(ucell_info_list, fto_x, fto_y, device=None, type_complex=np.complex128):
+def to_conv_mat_vector(ucell_info_list, fto_x, fto_y, device=None, type_complex=np.complex128, use_pinv=False):
ff_xy = (2 * fto_x + 1) * (2 * fto_y + 1)
@@ -61,12 +62,12 @@ def to_conv_mat_vector(ucell_info_list, fto_x, fto_y, device=None, type_complex=
epx_conv_all[i] = epx_conv
epy_conv_all[i] = epy_conv
- epz_conv_i_all[i] = np.linalg.inv(epz_conv)
+ epz_conv_i_all[i] = meeinv(epz_conv, use_pinv=use_pinv)
return epx_conv_all, epy_conv_all, epz_conv_i_all
-def to_conv_mat_raster_continuous(ucell, fto_x, fto_y, device=None, type_complex=np.complex128):
+def to_conv_mat_raster_continuous(ucell, fto_x, fto_y, device=None, type_complex=np.complex128, use_pinv=False):
ff_xy = (2 * fto_x + 1) * (2 * fto_y + 1)
@@ -84,13 +85,13 @@ def to_conv_mat_raster_continuous(ucell, fto_x, fto_y, device=None, type_complex
epx_conv_all[i] = epx_conv
epy_conv_all[i] = epy_conv
- epz_conv_i_all[i] = np.linalg.inv(epz_conv)
+ epz_conv_i_all[i] = meeinv(epz_conv, use_pinv=use_pinv)
return epx_conv_all, epy_conv_all, epz_conv_i_all
def to_conv_mat_raster_discrete(ucell, fto_x, fto_y, device=None, type_complex=np.complex128,
- enhanced_dfs=True):
+ enhanced_dfs=True, use_pinv=False):
ff_xy = (2 * fto_x + 1) * (2 * fto_y + 1)
@@ -123,7 +124,7 @@ def to_conv_mat_raster_discrete(ucell, fto_x, fto_y, device=None, type_complex=n
epx_conv_all[i] = epx_conv
epy_conv_all[i] = epy_conv
- epz_conv_i_all[i] = np.linalg.inv(epz_conv)
+ epz_conv_i_all[i] = meeinv(epz_conv, use_pinv=use_pinv)
return epx_conv_all, epy_conv_all, epz_conv_i_all
diff --git a/meent/on_numpy/emsolver/field_distribution.py b/meent/on_numpy/emsolver/field_distribution.py
index a0ad4c7..aa37549 100644
--- a/meent/on_numpy/emsolver/field_distribution.py
+++ b/meent/on_numpy/emsolver/field_distribution.py
@@ -3,7 +3,6 @@
def field_dist_1d(wavelength, kx, T1, layer_info_list, period,
pol, res_x=20, res_y=1, res_z=20, type_complex=np.complex128):
-
k0 = 2 * np.pi / wavelength
Kx = np.diag(kx)
@@ -21,8 +20,8 @@ def field_dist_1d(wavelength, kx, T1, layer_info_list, period,
z_1d = np.linspace(0, res_z, res_z).reshape((-1, 1, 1)) / res_z * d
- My = W @ (diag_exp_batch(-k0 * Q * z_1d) @ c1 + diag_exp_batch(k0 * Q * (z_1d - d)) @ c2)
- Mx = V @ (-diag_exp_batch(-k0 * Q * z_1d) @ c1 + diag_exp_batch(k0 * Q * (z_1d - d)) @ c2)
+ My = W @ (d_exp(-k0 * Q * z_1d) @ c1 + d_exp(k0 * Q * (z_1d - d)) @ c2)
+ Mx = V @ (-d_exp(-k0 * Q * z_1d) @ c1 + d_exp(k0 * Q * (z_1d - d)) @ c2)
if pol == 0:
Mz = -1j * Kx @ My
@@ -59,9 +58,108 @@ def field_dist_1d(wavelength, kx, T1, layer_info_list, period,
return field_cell
+def field_dist_1d_conical(wavelength, kx, ky, T1, layer_info_list, period,
+ res_x=20, res_y=20, res_z=20, type_complex=np.complex128):
+ k0 = 2 * np.pi / wavelength
+
+ ff_x = len(kx)
+ ff_y = len(ky)
+ ff_xy = ff_x * ff_y
+
+ Kx = np.diag(np.tile(kx, ff_y).flatten())
+ Ky = np.diag(np.tile(ky.reshape((-1, 1)), ff_x).flatten())
+
+ field_cell = np.zeros((res_z * len(layer_info_list), res_y, res_x, 6), dtype=type_complex)
+
+ T_layer = T1
+
+ big_I = np.eye((len(T1))).astype(type_complex)
+ O = np.zeros((ff_xy, ff_xy), dtype=type_complex)
+
+ # From the first layer
+ for idx_layer, (epz_conv_i, W, V, q, d, big_A_i, big_B) in enumerate(layer_info_list[::-1]):
+ W_1 = W[:, :ff_xy]
+ W_2 = W[:, ff_xy:]
+
+ V_11 = V[:ff_xy, :ff_xy]
+ V_12 = V[:ff_xy, ff_xy:]
+ V_21 = V[ff_xy:, :ff_xy]
+ V_22 = V[ff_xy:, ff_xy:]
+
+ q_1 = q[:ff_xy]
+ q_2 = q[ff_xy:]
+
+ X_1 = np.diag(np.exp(-k0 * q_1 * d))
+ X_2 = np.diag(np.exp(-k0 * q_2 * d))
+
+ big_X = np.block([[X_1, O], [O, X_2]])
+
+ c = np.block([[big_I], [big_B @ big_A_i @ big_X]]) @ T_layer
+ # z_1d = np.arange(res_z).reshape((-1, 1, 1)) / res_z * d
+ z_1d = np.linspace(0, res_z, res_z).reshape((-1, 1, 1)) / res_z * d
+
+ c1_plus = c[0 * ff_xy:1 * ff_xy]
+ c2_plus = c[1 * ff_xy:2 * ff_xy]
+ c1_minus = c[2 * ff_xy:3 * ff_xy]
+ c2_minus = c[3 * ff_xy:4 * ff_xy]
+
+ big_Q1 = np.diag(q_1)
+ big_Q2 = np.diag(q_2)
+
+ Sx = W_2 @ (d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Sy = V_11 @ (d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
+ + V_12 @ (d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Ux = W_1 @ (-d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus)
+ Uy = V_21 @ (-d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
+ + V_22 @ (-d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Sz = -1j * epz_conv_i @ (Kx @ Uy - Ky @ Ux)
+ Uz = -1j * (Kx @ Sy - Ky @ Sx)
+
+ # x_1d = np.arange(res_x).reshape((1, -1, 1))
+ # x_1d = -1j * x_1d * period[0] / res_x
+ x_1d = np.linspace(0, period[0], res_x).reshape((1, -1, 1))
+ x_2d = np.tile(x_1d, (res_y, 1, 1))
+ x_2d = x_2d * kx * k0
+ x_2d = x_2d.reshape((res_y, res_x, 1, len(kx)))
+
+ y_1d = np.linspace(0, period[1], res_y)[::-1].reshape((-1, 1, 1))
+ y_2d = np.tile(y_1d, (1, res_x, 1))
+ y_2d = y_2d * ky * k0
+ y_2d = y_2d.reshape((res_y, res_x, len(ky), 1))
+
+ # exp_K = np.exp(x_2d)
+ # exp_K = exp_K.reshape((res_y, res_x, -1))
+
+ inv_fourier = np.exp(-1j * x_2d) * np.exp(-1j * y_2d)
+ inv_fourier = inv_fourier.reshape((res_y, res_x, -1))
+
+ # Ex = exp_K[:, :, None, :] @ Sx[:, None, None, :, :]
+ # Ey = exp_K[:, :, None, :] @ Sy[:, None, None, :, :]
+ # Ez = exp_K[:, :, None, :] @ Sz[:, None, None, :, :]
+ #
+ # Hx = -1j * exp_K[:, :, None, :] @ Ux[:, None, None, :, :]
+ # Hy = -1j * exp_K[:, :, None, :] @ Uy[:, None, None, :, :]
+ # Hz = -1j * exp_K[:, :, None, :] @ Uz[:, None, None, :, :]
+
+ Ex = inv_fourier[:, :, None, :] @ Sx[:, None, None, :, :]
+ Ey = inv_fourier[:, :, None, :] @ Sy[:, None, None, :, :]
+ Ez = inv_fourier[:, :, None, :] @ Sz[:, None, None, :, :]
+ Hx = 1j * inv_fourier[:, :, None, :] @ Ux[:, None, None, :, :]
+ Hy = 1j * inv_fourier[:, :, None, :] @ Uy[:, None, None, :, :]
+ Hz = 1j * inv_fourier[:, :, None, :] @ Uz[:, None, None, :, :]
+
+ val = np.concatenate(
+ (Ex.squeeze(-1), Ey.squeeze(-1), Ez.squeeze(-1), Hx.squeeze(-1), Hy.squeeze(-1), Hz.squeeze(-1)), -1)
+
+ field_cell[res_z * idx_layer:res_z * (idx_layer + 1)] = val
+
+ T_layer = big_A_i @ big_X @ T_layer
+
+ return field_cell
+
+
def field_dist_2d(wavelength, kx, ky, T1, layer_info_list, period,
res_x=20, res_y=20, res_z=20, type_complex=np.complex128):
-
k0 = 2 * np.pi / wavelength
ff_x = len(kx)
@@ -79,7 +177,6 @@ def field_dist_2d(wavelength, kx, ky, T1, layer_info_list, period,
# From the first layer
for idx_layer, (epz_conv_i, W, V, q, d, big_A_i, big_B) in enumerate(layer_info_list[::-1]):
-
W_11 = W[:ff_xy, :ff_xy]
W_12 = W[:ff_xy, ff_xy:]
W_21 = W[ff_xy:, :ff_xy]
@@ -90,6 +187,9 @@ def field_dist_2d(wavelength, kx, ky, T1, layer_info_list, period,
V_21 = V[ff_xy:, :ff_xy]
V_22 = V[ff_xy:, ff_xy:]
+ q_1 = q[:ff_xy]
+ q_2 = q[ff_xy:]
+
big_X = np.diag(np.exp(-k0 * q * d))
c = np.block([[big_I], [big_B @ big_A_i @ big_X]]) @ T_layer
@@ -101,25 +201,17 @@ def field_dist_2d(wavelength, kx, ky, T1, layer_info_list, period,
c1_minus = c[2 * ff_xy:3 * ff_xy]
c2_minus = c[3 * ff_xy:4 * ff_xy]
- q1 = q[:len(q) // 2]
- q2 = q[len(q) // 2:]
- big_Q1 = np.diag(q1)
- big_Q2 = np.diag(q2)
+ big_Q1 = np.diag(q_1)
+ big_Q2 = np.diag(q_2)
- Sx = W_11 @ (diag_exp_batch(-k0 * big_Q1 * z_1d) @ c1_plus + diag_exp_batch(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
- + W_12 @ (diag_exp_batch(-k0 * big_Q2 * z_1d) @ c2_plus + diag_exp_batch(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
- Sy = W_21 @ (diag_exp_batch(-k0 * big_Q1 * z_1d) @ c1_plus + diag_exp_batch(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
- + W_22 @ (diag_exp_batch(-k0 * big_Q2 * z_1d) @ c2_plus + diag_exp_batch(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
-
- # Ux = -V_11 @ (diag_exp_batch(-k0 * big_Q1 * z_1d) @ c1_plus + diag_exp_batch(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
- # - V_12 @ (diag_exp_batch(-k0 * big_Q2 * z_1d) @ c2_plus + diag_exp_batch(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
- # Uy = -V_21 @ (diag_exp_batch(-k0 * big_Q1 * z_1d) @ c1_plus + diag_exp_batch(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
- # - V_22 @ (diag_exp_batch(-k0 * big_Q2 * z_1d) @ c2_plus + diag_exp_batch(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
-
- Ux = V_11 @ (-diag_exp_batch(-k0 * big_Q1 * z_1d) @ c1_plus + diag_exp_batch(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
- + V_12 @ (-diag_exp_batch(-k0 * big_Q2 * z_1d) @ c2_plus + diag_exp_batch(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
- Uy = V_21 @ (-diag_exp_batch(-k0 * big_Q1 * z_1d) @ c1_plus + diag_exp_batch(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
- + V_22 @ (-diag_exp_batch(-k0 * big_Q2 * z_1d) @ c2_plus + diag_exp_batch(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Sx = W_11 @ (d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
+ + W_12 @ (d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Sy = W_21 @ (d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
+ + W_22 @ (d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Ux = V_11 @ (-d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
+ + V_12 @ (-d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Uy = V_21 @ (-d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
+ + V_22 @ (-d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
Sz = -1j * epz_conv_i @ (Kx @ Uy - Ky @ Ux)
Uz = -1j * (Kx @ Sy - Ky @ Sx)
@@ -196,11 +288,7 @@ def field_plot(field_cell, pol=0, plot_indices=(1, 1, 1, 1, 1, 1), y_slice=0, z_
plt.show()
-def diag_exp(x):
- return np.diag(np.exp(np.diag(x)))
-
-
-def diag_exp_batch(x):
+def d_exp(x):
res = np.zeros(x.shape).astype(x.dtype)
ix = np.arange(x.shape[-1])
res[:, ix, ix] = np.exp(x[:, ix, ix])
diff --git a/meent/on_numpy/emsolver/fourier_analysis.py b/meent/on_numpy/emsolver/fourier_analysis.py
index ab89b9e..47e3156 100644
--- a/meent/on_numpy/emsolver/fourier_analysis.py
+++ b/meent/on_numpy/emsolver/fourier_analysis.py
@@ -28,7 +28,7 @@ def cfs2d(cell, x, y, conti_x, conti_y, fto_x, fto_y, type_complex=np.complex128
ff_x = 2 * fto_x + 1
ff_y = 2 * fto_y + 1
- period_x, period_y = x[-1], y[-1] # TODO: needed? for vector modeling?
+ period_x, period_y = x[-1], y[-1] # needed for vector modeling?
cell = cell.T
diff --git a/meent/on_numpy/emsolver/primitives.py b/meent/on_numpy/emsolver/primitives.py
new file mode 100644
index 0000000..14976a3
--- /dev/null
+++ b/meent/on_numpy/emsolver/primitives.py
@@ -0,0 +1,10 @@
+import numpy as np
+
+
+def meeinv(x, use_pinv=False):
+ if use_pinv:
+ res = np.linalg.pinv(x)
+ else:
+ res = np.linalg.inv(x)
+
+ return res
diff --git a/meent/on_numpy/emsolver/rcwa.py b/meent/on_numpy/emsolver/rcwa.py
index 8cf6c27..5447780 100644
--- a/meent/on_numpy/emsolver/rcwa.py
+++ b/meent/on_numpy/emsolver/rcwa.py
@@ -2,7 +2,43 @@
from ._base import _BaseRCWA
from .convolution_matrix import to_conv_mat_raster_continuous, to_conv_mat_raster_discrete, to_conv_mat_vector
-from .field_distribution import field_plot, field_dist_1d, field_dist_2d
+from .field_distribution import field_plot, field_dist_1d, field_dist_1d_conical, field_dist_2d
+
+
+class ResultNumpy:
+ def __init__(self, res=None, res_te_inc=None, res_tm_inc=None):
+ self.res = res
+ self.res_te_inc = res_te_inc
+ self.res_tm_inc = res_tm_inc
+
+ @property
+ def de_ri(self):
+ if self.res is not None:
+ return self.res.de_ri
+ else:
+ return None
+
+ @property
+ def de_ti(self):
+ if self.res is not None:
+ return self.res.de_ti
+ else:
+ return None
+
+
+class ResultSubNumpy:
+ def __init__(self, R_s, R_p, T_s, T_p, de_ri, de_ri_s, de_ri_p, de_ti, de_ti_s, de_ti_p):
+ self.R_s = R_s
+ self.R_p = R_p
+ self.T_s = T_s
+ self.T_p = T_p
+ self.de_ri = de_ri
+ self.de_ri_s = de_ri_s
+ self.de_ri_p = de_ri_p
+
+ self.de_ti = de_ti
+ self.de_ti_s = de_ti_s
+ self.de_ti_p = de_ti_p
class RCWANumpy(_BaseRCWA):
@@ -10,12 +46,12 @@ def __init__(self,
n_top=1.,
n_bot=1.,
theta=0.,
- phi=0.,
+ phi=None,
psi=None,
- period=(100., 100.),
- wavelength=900.,
+ period=(1., 1.),
+ wavelength=1.,
ucell=None,
- thickness=(0., ),
+ thickness=(0.,),
backend=0,
pol=0.,
fto=(0, 0),
@@ -26,13 +62,16 @@ def __init__(self,
type_complex=np.complex128,
fourier_type=0, # 0 DFS, 1 CFS
enhanced_dfs=True,
- # **kwargs,
+ use_pinv=False,
):
super().__init__(n_top=n_top, n_bot=n_bot, theta=theta, phi=phi, psi=psi, pol=pol,
fto=fto, period=period, wavelength=wavelength,
thickness=thickness, connecting_algo=connecting_algo, perturbation=perturbation,
- device=device, type_complex=type_complex, )
+ device=device, type_complex=type_complex, use_pinv=use_pinv)
+
+ self._modeling_type_assigned = None
+ self._grating_type_assigned = None
self.ucell = ucell
self.ucell_materials = ucell_materials
@@ -40,8 +79,7 @@ def __init__(self,
self.backend = backend
self.fourier_type = fourier_type
self.enhanced_dfs = enhanced_dfs
- self.modeling_type_assigned = None
- self.grating_type_assigned = None
+ self.use_pinv = use_pinv
@property
def ucell(self):
@@ -51,16 +89,18 @@ def ucell(self):
def ucell(self, ucell):
if isinstance(ucell, np.ndarray): # Raster
+ self._modeling_type_assigned = 0
if ucell.dtype in (np.int64, np.float64, np.int32, np.float32):
dtype = self.type_float
elif ucell.dtype in (np.complex128, np.complex64):
dtype = self.type_complex
else:
raise ValueError
- self._ucell = np.array(ucell, dtype=dtype)
+ # self._ucell = np.array(ucell, dtype=dtype)
self._ucell = ucell.astype(dtype)
elif type(ucell) is list: # Vector
+ self._modeling_type_assigned = 1
self._ucell = ucell
elif ucell is None:
self._ucell = ucell
@@ -71,22 +111,38 @@ def ucell(self, ucell):
def modeling_type_assigned(self):
return self._modeling_type_assigned
- @modeling_type_assigned.setter
- def modeling_type_assigned(self, modeling_type_assigned):
- self._modeling_type_assigned = modeling_type_assigned
+ # @modeling_type_assigned.setter
+ # def modeling_type_assigned(self, modeling_type_assigned):
+ # self._modeling_type_assigned = modeling_type_assigned
+
+ def _assign_grating_type(self):
+ """
+ Select the grating type for RCWA simulation. This decides the efficient formulation for given case.
+
+ `_grating_type_assigned` == 0(1D TETM) is for 1D grating, no rotation (phi or azimuth), and either TE or TM.
+ `_grating_type_assigned` == 1(1D conical) is for 1D grating with generality.
+ `_grating_type_assigned` == 2(2D) is for 2D grating with generality.
+
+ Note that no rotation means 'phi' is `None`. If phi is given as '0', then it takes 1D conical form
+ even though when the case itself is 1D TETM.
- def _assign_modeling_type(self):
+ 1D conical is under implementation.
- if isinstance(self.ucell, np.ndarray): # Raster
- self.modeling_type_assigned = 0
- if (self.ucell.shape[1] == 1) and (self.pol in (0, 1)) and (self.phi % (2 * np.pi) == 0) and (self.fto[1] == 0):
- self._grating_type_assigned = 0 # 1D TE and TM only
+ Returns:
+
+ """
+
+ if self.modeling_type_assigned == 0: # Raster
+ if self.ucell.shape[1] == 1:
+ if (self.pol in (0, 1)) and (self.phi is None) and (self.fto[1] == 0):
+ self._grating_type_assigned = 0 # 1D TE and TM only
+ else:
+ self._grating_type_assigned = 1 # 1D conical
else:
- self._grating_type_assigned = 1 # else
+ self._grating_type_assigned = 2 # else
- elif isinstance(self.ucell, list): # Vector
- self.modeling_type_assigned = 1
- self.grating_type_assigned = 1
+ elif self.modeling_type_assigned == 1: # Vector
+ self.grating_type_assigned = 2 # TODO: 1d conical case
@property
def grating_type_assigned(self):
@@ -96,55 +152,51 @@ def grating_type_assigned(self):
def grating_type_assigned(self, grating_type_assigned):
self._grating_type_assigned = grating_type_assigned
- def _solve(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
+ def solve_for_conv(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
+ self._assign_grating_type()
- if self._grating_type_assigned == 0:
- de_ri, de_ti, layer_info_list, T1 = self.solve_1d(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
+ if self.grating_type_assigned == 0:
+ result_dict = self.solve_1d(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
+ elif self._grating_type_assigned == 1:
+ result_dict = self.solve_1d_conical(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
else:
- de_ri, de_ti, layer_info_list, T1 = self.solve_2d(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
-
- return de_ri, de_ti, layer_info_list, T1
-
- def solve(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
+ result_dict = self.solve_2d(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
- de_ri, de_ti, layer_info_list, T1 = self._solve(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
+ res_psi = ResultSubNumpy(**result_dict['res']) if 'res' in result_dict else None
+ res_te_inc = ResultSubNumpy(**result_dict['res_te_inc']) if 'res_te_inc' in result_dict else None
+ res_tm_inc = ResultSubNumpy(**result_dict['res_tm_inc']) if 'res_tm_inc' in result_dict else None
- self.layer_info_list = layer_info_list
- self.T1 = T1
+ result = ResultNumpy(res_psi, res_te_inc, res_tm_inc)
- return de_ri, de_ti
+ return result
def conv_solve(self, **kwargs):
# [setattr(self, k, v) for k, v in kwargs.items()] # no need in npmeent
- self._assign_modeling_type()
- if self._modeling_type_assigned == 0: # Raster
+ if self.modeling_type_assigned == 0: # Raster
if self.fourier_type == 0:
epx_conv_all, epy_conv_all, epz_conv_i_all = to_conv_mat_raster_discrete(
self.ucell, self.fto[0], self.fto[1], type_complex=self.type_complex,
- enhanced_dfs=self.enhanced_dfs)
+ enhanced_dfs=self.enhanced_dfs, use_pinv=self.use_pinv)
elif self.fourier_type == 1:
epx_conv_all, epy_conv_all, epz_conv_i_all = to_conv_mat_raster_continuous(
- self.ucell, self.fto[0], self.fto[1], type_complex=self.type_complex)
+ self.ucell, self.fto[0], self.fto[1], type_complex=self.type_complex, use_pinv=self.use_pinv)
else:
raise ValueError("Check 'modeling_type' and 'fourier_type' in 'conv_solve'.")
- elif self._modeling_type_assigned == 1: # Vector
+ elif self.modeling_type_assigned == 1: # Vector
ucell_vector = self.modeling_vector_instruction(self.ucell)
epx_conv_all, epy_conv_all, epz_conv_i_all = to_conv_mat_vector(
- ucell_vector, self.fto[0], self.fto[1], type_complex=self.type_complex)
+ ucell_vector, self.fto[0], self.fto[1], type_complex=self.type_complex, use_pinv=self.use_pinv)
else:
raise ValueError("Check 'modeling_type' and 'fourier_type' in 'conv_solve'.")
- # print(epz_conv_i_all)
- de_ri, de_ti, layer_info_list, T1 = self._solve(self.wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
- self.layer_info_list = layer_info_list
- self.T1 = T1
+ result = self.solve_for_conv(self.wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
- return de_ri, de_ti
+ return result
def calculate_field(self, res_x=20, res_y=20, res_z=20):
# TODO: change res_ to accept array of points.
@@ -154,6 +206,10 @@ def calculate_field(self, res_x=20, res_y=20, res_z=20):
res_y = 1
field_cell = field_dist_1d(self.wavelength, kx, self.T1, self.layer_info_list, self.period, self.pol,
res_x=res_x, res_y=res_y, res_z=res_z, type_complex=self.type_complex)
+ elif self._grating_type_assigned == 1:
+ # TODO other bds
+ field_cell = field_dist_1d_conical(self.wavelength, kx, ky, self.T1, self.layer_info_list, self.period,
+ res_x=res_x, res_y=res_y, res_z=res_z, type_complex=self.type_complex)
else:
field_cell = field_dist_2d(self.wavelength, kx, ky, self.T1, self.layer_info_list, self.period,
res_x=res_x, res_y=res_y, res_z=res_z, type_complex=self.type_complex)
@@ -161,9 +217,9 @@ def calculate_field(self, res_x=20, res_y=20, res_z=20):
return field_cell
def conv_solve_field(self, res_x=20, res_y=20, res_z=20):
- de_ri, de_ti = self.conv_solve()
+ res = self.conv_solve()
field_cell = self.calculate_field(res_x, res_y, res_z)
- return de_ri, de_ti, field_cell
+ return res, field_cell
def field_plot(self, field_cell):
field_plot(field_cell, self.pol)
diff --git a/meent/on_numpy/emsolver/scattering_method.py b/meent/on_numpy/emsolver/scattering_method.py
index 9726a5b..6f3459b 100644
--- a/meent/on_numpy/emsolver/scattering_method.py
+++ b/meent/on_numpy/emsolver/scattering_method.py
@@ -73,13 +73,13 @@ def scattering_1d_3(Wt, Wg, Vt, Vg, Sg, ff, Wr, fourier_order, Kzr, Kzt, n_I, n_
return de_ri.flatten(), de_ti.flatten()
-def scattering_2d_1(n_I, n_II, theta, phi, k0, period, fourier_order):
+def scattering_2d_1(n_I, n_II, theta, phi, k0, period, fourier_order, kx, ky):
kx_inc = n_I * np.sin(theta) * np.cos(phi)
ky_inc = n_I * np.sin(theta) * np.sin(phi)
kz_inc = np.sqrt(n_I ** 2 * 1 - kx_inc ** 2 - ky_inc ** 2)
Kx, Ky = K_matrix_cubic_2D(kx_inc, ky_inc, k0, period[0], period[1], fourier_order[0], fourier_order[1])
-
+ print(Kx.shape, Ky.shape)
# specify gap media (this is an LHI so no eigenvalue problem should be solved
e_h = 1
Wg, Vg, Kzg = homogeneous_module(Kx, Ky, e_h)
@@ -99,7 +99,24 @@ def scattering_2d_1(n_I, n_II, theta, phi, k0, period, fourier_order):
_, Sr_dict = S_RT(Ar, Br, ref_mode=True) # scatter matrix for the reflection region
Sg = Sr_dict
- return Kx, Ky, kz_inc, Wg, Vg, Kzg, Wr, Vr, Kzr, Wt, Vt, Kzt, Ar, Br, Sg
+ ff_x, ff_y = fourier_order
+ ff_xy = ff_x * ff_y
+
+ # I = np.eye(ff_xy, dtype=type_complex)
+ # O = np.zeros((ff_xy, ff_xy), dtype=type_complex)
+ I = np.eye(ff_xy)
+ O = np.zeros((ff_xy, ff_xy))
+
+ # kz_top = (n_I ** 2 - Kx.diagonal() ** 2 - Ky.diagonal().reshape((-1, 1)) ** 2) ** 0.5
+ # kz_bot = (n_II ** 2 - Kx.diagonal() ** 2 - Ky.diagonal().reshape((-1, 1)) ** 2) ** 0.5
+
+ kz_top = (n_I ** 2 - kx ** 2 - ky.reshape((-1, 1)) ** 2) ** 0.5
+ kz_bot = (n_II ** 2 - kx ** 2 - ky.reshape((-1, 1)) ** 2) ** 0.5
+
+ kz_top = kz_top.flatten().conjugate()
+ kz_bot = kz_bot.flatten().conjugate()
+
+ return Kx, Ky, kz_inc, Wg, Vg, Kzg, Wr, Vr, Kzr, Wt, Vt, Kzt, Ar, Br, Sg, kz_top, kz_bot
def scattering_2d_2(W, Wg, V, Vg, d, k0, Sg, LAMBDA):
@@ -111,7 +128,7 @@ def scattering_2d_2(W, Wg, V, Vg, d, k0, Sg, LAMBDA):
return A, B, Sl_dict, Sg_matrix, Sg
-def scattering_2d_3(ff, Wt, Wg, Vt, Vg, Sg, Wr, Kx, Ky, Kzr, Kzt, kz_inc, n_I, pol, theta,
+def scattering_2d_3(Wt, Wg, Vt, Vg, Sg, Wr, Kx, Ky, Kzr, Kzt, kz_top, kz_bot, n_top, n_bot, pol, theta,
phi, fourier_order):
normal_vector = np.array([0, 0, 1]) # positive z points down;
# amplitude of the te vs tm modes (which are decoupled)
@@ -126,8 +143,9 @@ def scattering_2d_3(ff, Wt, Wg, Vt, Vg, Sg, Wr, Kx, Ky, Kzr, Kzt, kz_inc, n_I, p
raise ValueError
M, N = fourier_order
- NM = ff ** 2
- NM = ff
+ # NM = ff ** 2
+ NM = ((2*M+1)*(2*N+1))
+
# get At, Bt
# since transmission is the same as gap, order does not matter
At, Bt = A_B_matrices_half_space(Vt, Vg)
@@ -138,7 +156,7 @@ def scattering_2d_3(ff, Wt, Wg, Vt, Vg, Sg, Wr, Kx, Ky, Kzr, Kzt, kz_inc, n_I, p
# finally CONVERT THE GLOBAL SCATTERING MATRIX BACK TO A MATRIX
- K_inc_vector = n_I * np.array([np.sin(theta) * np.cos(phi), np.sin(theta) * np.sin(phi), np.cos(theta)])
+ K_inc_vector = n_top * np.array([np.sin(theta) * np.cos(phi), np.sin(theta) * np.sin(phi), np.cos(theta)])
_, e_src, _ = initial_conditions(K_inc_vector, theta, normal_vector, pte, ptm, N, M)
@@ -147,32 +165,49 @@ def scattering_2d_3(ff, Wt, Wg, Vt, Vg, Sg, Wr, Kx, Ky, Kzr, Kzt, kz_inc, n_I, p
reflected = Wr @ Sg['S11'] @ c_inc
transmitted = Wt @ Sg['S21'] @ c_inc
- rx = reflected[0:NM, :] # rx is the Ex component.
- ry = reflected[NM:, :]
- tx = transmitted[0:NM, :]
- ty = transmitted[NM:, :]
+ R_s = np.array(reflected[0:NM, :]).flatten() # rx is the Ex component.
+ R_p = np.array(reflected[NM:, :]).flatten()
+ T_s = np.array(transmitted[0:NM, :]).flatten()
+ T_p = np.array(transmitted[NM:, :]).flatten()
+ # R_s = reflected[0:NM, 0] # rx is the Ex component.
+ # R_p = reflected[NM:, 0]
+ # T_s = transmitted[0:NM, 0]
+ # T_p = transmitted[NM:, 0]
+
+ # rz = np.linalg.inv(Kzr) @ (Kx @ R_s + Ky @ R_p)
+ # tz = np.linalg.inv(Kzt) @ (Kx @ T_s + Ky @ T_p)
+ #
+ # rsq = np.square(np.abs(R_s)) + np.square(np.abs(R_p)) + np.square(np.abs(rz))
+ # tsq = np.square(np.abs(T_s)) + np.square(np.abs(T_p)) + np.square(np.abs(tz))
+ #
+ # de_ri = np.real(Kzr)@rsq/np.real(K_inc_vector[2]) # real because we only want propagating components
+ # de_ti = np.real(Kzt)@tsq/np.real(K_inc_vector[2])
- rz = np.linalg.inv(Kzr) @ (Kx @ rx + Ky @ ry)
- tz = np.linalg.inv(Kzt) @ (Kx @ tx + Ky @ ty)
+ # return de_ri, de_ti
- rsq = np.square(np.abs(rx)) + np.square(np.abs(ry)) + np.square(np.abs(rz))
- tsq = np.square(np.abs(tx)) + np.square(np.abs(ty)) + np.square(np.abs(tz))
+ print(R_s.shape, kz_top.shape)
+ de_ri_s = R_s * np.conj(R_s) * np.real(kz_top / (n_top * np.cos(theta)))
+ de_ri_p = R_p * np.conj(R_p) * np.real(kz_top / n_top ** 2 / (n_top * np.cos(theta)))
- de_ri = np.real(Kzr)@rsq/np.real(K_inc_vector[2]) # real because we only want propagating components
- de_ti = np.real(Kzt)@tsq/np.real(K_inc_vector[2])
+ de_ti_s = T_s * np.conj(T_s) * np.real(kz_bot / (n_top * np.cos(theta)))
+ de_ti_p = T_p * np.conj(T_p) * np.real(kz_bot / n_bot ** 2 / (n_top * np.cos(theta)))
- return de_ri, de_ti
+ # return de_ri.real, de_ti.real, big_T1
+ return de_ri_s.real, de_ri_p.real, de_ti_s.real, de_ti_p.real, R_s, R_p, T_s, T_p
-def scattering_2d_wv(ff, Kx, Ky, E_conv, oneover_E_conv, oneover_E_conv_i, E_i, mu_conv=None):
+def scattering_2d_wv(Kx, Ky, epx_conv, epy_conv, epz_conv_i, mu_conv=None):
# -------------------------
# W and V from SMM method.
- NM = ff ** 2
- NM = ff
+ # NM = ff ** 2
+ # M, N = fourier_order
+ # print(N,M)
+ # NM = ((2*M+1)*(2*N+1))
+ NM = len(Kx)
if mu_conv is None:
mu_conv = np.identity(NM)
- P, Q, _ = P_Q_kz(Kx, Ky, E_conv, mu_conv, oneover_E_conv, oneover_E_conv_i, E_i)
+ P, Q, _ = P_Q_kz(Kx, Ky, epx_conv, epy_conv, epz_conv_i, mu_conv)
GAMMA = P @ Q
Lambda, W = np.linalg.eig(GAMMA) # LAMBDa is effectively refractive index
diff --git a/meent/on_numpy/emsolver/smm_util.py b/meent/on_numpy/emsolver/smm_util.py
index c754de9..9d06d1e 100644
--- a/meent/on_numpy/emsolver/smm_util.py
+++ b/meent/on_numpy/emsolver/smm_util.py
@@ -202,7 +202,7 @@ def K_matrix_cubic_2D(beta_x, beta_y, k0, a_x, a_y, N_p, N_q):
return Kx, Ky
-def P_Q_kz(Kx, Ky, e_conv, mu_conv, oneover_E_conv, oneover_E_conv_i, E_i):
+def P_Q_kz(Kx, Ky, epx_conv, epy_conv, epz_conv_i, mu_conv):
'''
r is for relative so do not put epsilon_0 or mu_0 here
:param Kx: NM x NM matrix
@@ -211,20 +211,20 @@ def P_Q_kz(Kx, Ky, e_conv, mu_conv, oneover_E_conv, oneover_E_conv_i, E_i):
:param mu_r:
:return:
'''
- argument = e_conv - Kx ** 2 - Ky ** 2
+ argument = np.linalg.inv(epz_conv_i) - Kx ** 2 - Ky ** 2
Kz = np.conj(np.sqrt(argument.astype('complex')))
# Kz = np.sqrt(argument.astype('complex')) # CHECK: conjugate?
# CHECK: confirm whether oneonver_E_conv is indeed not used
# CHECK: Check sign of P and Q
P = np.block([
- [Kx @ E_i @ Ky, -Kx @ E_i @ Kx + mu_conv],
- [Ky @ E_i @ Ky - mu_conv, -Ky @ E_i @ Kx]
+ [Kx @ epz_conv_i @ Ky, -Kx @ epz_conv_i @ Kx + mu_conv],
+ [Ky @ epz_conv_i @ Ky - mu_conv, -Ky @ epz_conv_i @ Kx]
])
Q = np.block([
- [Kx @ inv(mu_conv) @ Ky, -Kx @ inv(mu_conv) @ Kx + e_conv],
- [-oneover_E_conv_i + Ky @ inv(mu_conv) @ Ky, -Ky @ inv(mu_conv) @ Kx]
+ [Kx @ inv(mu_conv) @ Ky, -Kx @ inv(mu_conv) @ Kx + epy_conv],
+ [-epx_conv + Ky @ inv(mu_conv) @ Ky, -Ky @ inv(mu_conv) @ Kx]
])
return P, Q, Kz
diff --git a/meent/on_numpy/emsolver/transfer_method.py b/meent/on_numpy/emsolver/transfer_method.py
index e50c68f..6b616c7 100644
--- a/meent/on_numpy/emsolver/transfer_method.py
+++ b/meent/on_numpy/emsolver/transfer_method.py
@@ -1,38 +1,34 @@
import numpy as np
+from .primitives import meeinv
-def transfer_1d_1(pol, ff_x, kx, n_top, n_bot, type_complex=np.complex128):
- ff_xy = ff_x * 1
+def transfer_1d_1(pol, kx, n_top, n_bot, type_complex=np.complex128):
+ ff_x = len(kx)
kz_top = (n_top ** 2 - kx ** 2) ** 0.5
kz_bot = (n_bot ** 2 - kx ** 2) ** 0.5
- kz_top = kz_top.conjugate()
- kz_bot = kz_bot.conjugate()
+ kz_top = kz_top.conj()
+ kz_bot = kz_bot.conj()
- F = np.eye(ff_xy, dtype=type_complex)
+ F = np.eye(ff_x, dtype=type_complex)
if pol == 0: # TE
Kz_bot = np.diag(kz_bot)
-
G = 1j * Kz_bot
-
elif pol == 1: # TM
Kz_bot = np.diag(kz_bot / (n_bot ** 2))
-
G = 1j * Kz_bot
-
else:
raise ValueError
- T = np.eye(ff_xy, dtype=type_complex)
+ T = np.eye(ff_x, dtype=type_complex)
return kz_top, kz_bot, F, G, T
-def transfer_1d_2(pol, kx, epx_conv, epy_conv, epz_conv_i, type_complex=np.complex128):
-
+def transfer_1d_2(pol, kx, epx_conv, epy_conv, epz_conv_i, type_complex=np.complex128, use_pinv=False):
Kx = np.diag(kx)
if pol == 0:
@@ -52,7 +48,7 @@ def transfer_1d_2(pol, kx, epx_conv, epy_conv, epz_conv_i, type_complex=np.compl
q = eigenvalues ** 0.5
Q = np.diag(q)
- V = np.linalg.inv(epx_conv) @ W @ Q
+ V = meeinv(epx_conv, use_pinv) @ W @ Q
else:
raise ValueError
@@ -60,21 +56,20 @@ def transfer_1d_2(pol, kx, epx_conv, epy_conv, epz_conv_i, type_complex=np.compl
return W, V, q
-def transfer_1d_3(k0, W, V, q, d, F, G, T, type_complex=np.complex128):
-
+def transfer_1d_3(k0, W, V, q, d, F, G, T, type_complex=np.complex128, use_pinv=False):
ff_x = len(q)
I = np.eye(ff_x, dtype=type_complex)
X = np.diag(np.exp(-k0 * q * d))
- W_i = np.linalg.inv(W)
- V_i = np.linalg.inv(V)
+ W_i = meeinv(W, use_pinv)
+ V_i = meeinv(V, use_pinv)
A = 0.5 * (W_i @ F + V_i @ G)
B = 0.5 * (W_i @ F - V_i @ G)
- A_i = np.linalg.inv(A)
+ A_i = meeinv(A, use_pinv)
F = W @ (I + X @ B @ A_i @ X)
G = V @ (I - X @ B @ A_i @ X)
@@ -83,54 +78,78 @@ def transfer_1d_3(k0, W, V, q, d, F, G, T, type_complex=np.complex128):
return X, F, G, T, A_i, B
-def transfer_1d_4(pol, F, G, T, kz_top, kz_bot, theta, n_top, n_bot, type_complex=np.complex128):
-
- ff_xy = len(kz_top)
-
+def transfer_1d_4(pol, ff_x, F, G, T, kz_top, kz_bot, theta, n_top, n_bot, type_complex=np.complex128, use_pinv=False):
Kz_top = np.diag(kz_top)
+ kz_top = kz_top.reshape((1, ff_x))
+ kz_bot = kz_bot.reshape((1, ff_x))
- delta_i0 = np.zeros(ff_xy, dtype=type_complex)
- delta_i0[ff_xy // 2] = 1
+ delta_i0 = np.zeros(ff_x, dtype=type_complex)
+ delta_i0[ff_x // 2] = 1
if pol == 0: # TE
inc_term = 1j * n_top * np.cos(theta) * delta_i0
- T1 = np.linalg.inv(G + 1j * Kz_top @ F) @ (1j * Kz_top @ delta_i0 + inc_term)
+ T1 = meeinv(G + 1j * Kz_top @ F, use_pinv) @ (1j * Kz_top @ delta_i0 + inc_term)
elif pol == 1: # TM
inc_term = 1j * delta_i0 * np.cos(theta) / n_top
- T1 = np.linalg.inv(G + 1j * Kz_top / (n_top ** 2) @ F) @ (1j * Kz_top / (n_top ** 2) @ delta_i0 + inc_term)
+ T1 = meeinv(G + 1j * Kz_top / (n_top ** 2) @ F, use_pinv) @ (1j * Kz_top / (n_top ** 2) @ delta_i0 + inc_term)
+ else:
+ raise ValueError
- # T1 = np.linalg.inv(G + 1j * YZ_I @ F) @ (1j * YZ_I @ delta_i0 + inc_term)
- R = F @ T1 - delta_i0
- T = T @ T1
+ R = (F @ T1 - delta_i0).reshape((1, ff_x))
+ T = (T @ T1).reshape((1, ff_x))
- de_ri = np.real(R * np.conj(R) * kz_top / (n_top * np.cos(theta)))
+ de_ri = (R * R.conj() * (kz_top / (n_top * np.cos(theta))).real).real
if pol == 0:
- de_ti = T * np.conj(T) * np.real(kz_bot / (n_top * np.cos(theta)))
+ de_ti = (T * T.conj() * (kz_bot / (n_top * np.cos(theta))).real).real
+ R_s = R
+ R_p = np.zeros(R.shape)
+ T_s = T
+ T_p = np.zeros(T.shape)
+ de_ri_s = de_ri
+ de_ri_p = np.zeros(de_ri.shape)
+ de_ti_s = de_ti
+ de_ti_p = np.zeros(de_ri.shape)
+
elif pol == 1:
- de_ti = T * np.conj(T) * np.real(kz_bot / n_bot ** 2) / (np.cos(theta) / n_top)
+ de_ti = (T * T.conj() * (kz_bot / n_bot ** 2 / (np.cos(theta) / n_top)).real).real
+ R_s = np.zeros(R.shape)
+ R_p = R
+ T_s = np.zeros(T.shape)
+ T_p = T
+ de_ri_s = np.zeros(de_ri.shape)
+ de_ri_p = de_ri
+ de_ti_s = np.zeros(de_ri.shape)
+ de_ti_p = de_ti
else:
raise ValueError
- return de_ri.real, de_ti.real, T1
+ res = {'R_s': R_s, 'R_p': R_p, 'T_s': T_s, 'T_p': T_p,
+ 'de_ri': de_ri, 'de_ri_s': de_ri_s, 'de_ri_p': de_ri_p,
+ 'de_ti': de_ti, 'de_ti_s': de_ti_s, 'de_ti_p': de_ti_p,
+ }
+ result = {'res': res}
-def transfer_1d_conical_1(ff_x, ff_y, kx_vector, ky_vector, n_top, n_bot, type_complex=np.complex128):
+ return result, T1
+
+def transfer_1d_conical_1(kx, ky, n_top, n_bot, type_complex=np.complex128):
+ ff_x = len(kx)
+ ff_y = len(ky)
ff_xy = ff_x * ff_y
I = np.eye(ff_xy, dtype=type_complex)
O = np.zeros((ff_xy, ff_xy), dtype=type_complex)
- kz_top = (n_top ** 2 - kx_vector ** 2 - ky_vector ** 2) ** 0.5
- kz_bot = (n_bot ** 2 - kx_vector ** 2 - ky_vector ** 2) ** 0.5
-
- kz_top = kz_top.conjugate()
- kz_bot = kz_bot.conjugate()
+ kz_top = (n_top ** 2 - kx ** 2 - ky.reshape((-1, 1)) ** 2) ** 0.5
+ kz_bot = (n_bot ** 2 - kx ** 2 - ky.reshape((-1, 1)) ** 2) ** 0.5
- varphi = np.arctan(ky_vector / kx_vector)
+ kz_top = kz_top.flatten().conj()
+ kz_bot = kz_bot.flatten().conj()
+ varphi = np.arctan(ky.reshape((-1, 1)) / kx).flatten()
Kz_bot = np.diag(kz_bot)
big_F = np.block([[I, O], [O, 1j * Kz_bot / (n_bot ** 2)]])
@@ -140,26 +159,25 @@ def transfer_1d_conical_1(ff_x, ff_y, kx_vector, ky_vector, n_top, n_bot, type_c
return kz_top, kz_bot, varphi, big_F, big_G, big_T
-def transfer_1d_conical_2(kx, ky, epx_conv, epy_conv, epz_conv_i, type_complex=np.complex128):
-
+def transfer_1d_conical_2(kx, ky, epx_conv, epy_conv, epz_conv_i, type_complex=np.complex128, use_pinv=False):
ff_x = len(kx)
ff_y = len(ky)
+ ff_xy = ff_x * ff_y
- I = np.eye(ff_y * ff_x, dtype=type_complex)
+ I = np.eye(ff_xy, dtype=type_complex)
Kx = np.diag(np.tile(kx, ff_y).flatten())
Ky = np.diag(np.tile(ky.reshape((-1, 1)), ff_x).flatten())
A = Kx ** 2 - epy_conv
- # A = Kx ** 2 - np.linalg.inv(epz_conv_i)
B = Kx @ epz_conv_i @ Kx - I
Omega2_RL = Ky ** 2 + A
Omega2_LR = Ky ** 2 + B @ epx_conv
- # Omega2_LR = Ky ** 2 + B @ np.linalg.inv(epz_conv_i)
eigenvalues_1, W_1 = np.linalg.eig(Omega2_RL)
eigenvalues_2, W_2 = np.linalg.eig(Omega2_LR)
+
eigenvalues_1 += 0j # to get positive square root
eigenvalues_2 += 0j # to get positive square root
@@ -169,40 +187,37 @@ def transfer_1d_conical_2(kx, ky, epx_conv, epy_conv, epz_conv_i, type_complex=n
Q_1 = np.diag(q_1)
Q_2 = np.diag(q_2)
- A_i = np.linalg.inv(A)
- B_i = np.linalg.inv(B)
+ A_i = meeinv(A, use_pinv)
+ B_i = meeinv(B, use_pinv)
V_11 = A_i @ W_1 @ Q_1
- V_12 = Ky * A_i @ Kx @ W_2
- V_21 = Ky * B_i @ Kx @ epz_conv_i @ W_1
- # V_21 = Ky * B_i @ Kx @ np.linalg.inv(epy_conv) @ W_1
+ V_12 = Ky @ A_i @ Kx @ W_2
+ V_21 = Ky @ B_i @ Kx @ epz_conv_i @ W_1
V_22 = B_i @ W_2 @ Q_2
W = np.block([W_1, W_2])
V = np.block([[V_11, V_12],
[V_21, V_22]])
- q = np.block([q_1, q_2])
+ q = np.hstack([q_1, q_2])
return W, V, q
-def transfer_1d_conical_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_complex=np.complex128):
-
- ff_x = len(W)
-
- I = np.eye(ff_x, dtype=type_complex)
- O = np.zeros((ff_x, ff_x), dtype=type_complex)
+def transfer_1d_conical_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_complex=np.complex128, use_pinv=False):
+ ff_xy = len(q) // 2
+ I = np.eye(ff_xy, dtype=type_complex)
+ O = np.zeros((ff_xy, ff_xy), dtype=type_complex)
- W_1 = W[:, :ff_x]
- W_2 = W[:, ff_x:]
+ q_1 = q[:ff_xy]
+ q_2 = q[ff_xy:]
- V_11 = V[:ff_x, :ff_x]
- V_12 = V[:ff_x, ff_x:]
- V_21 = V[ff_x:, :ff_x]
- V_22 = V[ff_x:, ff_x:]
+ W_1 = W[:, :ff_xy]
+ W_2 = W[:, ff_xy:]
- q_1 = q[:ff_x]
- q_2 = q[ff_x:]
+ V_11 = V[:ff_xy, :ff_xy]
+ V_12 = V[:ff_xy, ff_xy:]
+ V_21 = V[ff_xy:, :ff_xy]
+ V_22 = V[ff_xy:, ff_xy:]
X_1 = np.diag(np.exp(-k0 * q_1 * d))
X_2 = np.diag(np.exp(-k0 * q_2 * d))
@@ -224,13 +239,13 @@ def transfer_1d_conical_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_comp
big_W = np.block([[V_ss, V_sp], [W_ps, W_pp]])
big_V = np.block([[W_ss, W_sp], [V_ps, V_pp]])
- big_W_i = np.linalg.inv(big_W)
- big_V_i = np.linalg.inv(big_V)
+ big_W_i = meeinv(big_W, use_pinv)
+ big_V_i = meeinv(big_V, use_pinv)
big_A = 0.5 * (big_W_i @ big_F + big_V_i @ big_G)
big_B = 0.5 * (big_W_i @ big_F - big_V_i @ big_G)
- big_A_i = np.linalg.inv(big_A)
+ big_A_i = meeinv(big_A, use_pinv)
big_F = big_W @ (big_I + big_X @ big_B @ big_A_i @ big_X)
big_G = big_V @ (big_I - big_X @ big_B @ big_A_i @ big_X)
@@ -240,11 +255,14 @@ def transfer_1d_conical_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_comp
return big_X, big_F, big_G, big_T, big_A_i, big_B
-def transfer_1d_conical_4(big_F, big_G, big_T, kz_top, kz_bot, psi, theta, n_top, n_bot, type_complex=np.complex128):
+def transfer_1d_conical_4(ff_x, ff_y, big_F, big_G, big_T, kz_top, kz_bot, psi, theta, n_top, n_bot,
+ type_complex=np.complex128, use_pinv=False):
- ff_xy = len(big_F) // 2
+ ff_xy = ff_x * ff_y
Kz_top = np.diag(kz_top)
+ kz_top = kz_top.reshape((ff_y, ff_x))
+ kz_bot = kz_bot.reshape((ff_y, ff_x))
I = np.eye(ff_xy, dtype=type_complex)
O = np.zeros((ff_xy, ff_xy), dtype=type_complex)
@@ -259,9 +277,6 @@ def transfer_1d_conical_4(big_F, big_G, big_T, kz_top, kz_bot, psi, theta, n_top
big_G_21 = big_G[ff_xy:, :ff_xy]
big_G_22 = big_G[ff_xy:, ff_xy:]
- # delta_i0 = np.zeros(ff_xy, dtype=type_complex)
- # delta_i0[ff_xy // 2] = 1
-
delta_i0 = np.zeros((ff_xy, 1), dtype=type_complex)
delta_i0[ff_xy // 2, 0] = 1
@@ -274,36 +289,99 @@ def transfer_1d_conical_4(big_F, big_G, big_T, kz_top, kz_bot, psi, theta, n_top
[O, I, -big_G_21, -big_G_22],
]
)
+ final_B = np.block(
+ [
+ [-np.sin(psi) * delta_i0],
+ [np.cos(psi) * np.cos(theta) * delta_i0],
+ [-1j * np.sin(psi) * n_top * np.cos(theta) * delta_i0],
+ [-1j * n_top * np.cos(psi) * delta_i0]
+ ]
+ )
- final_B = np.block([
- [-np.sin(psi) * delta_i0],
- [-np.cos(psi) * np.cos(theta) * delta_i0],
- [-1j * np.sin(psi) * n_top * np.cos(theta) * delta_i0],
- [1j * n_top * np.cos(psi) * delta_i0]
- ])
-
- final_RT = np.linalg.inv(final_A) @ final_B
+ final_A_inv = meeinv(final_A, use_pinv)
+ final_RT = final_A_inv @ final_B
- R_s = final_RT[:ff_xy, :].flatten()
- R_p = final_RT[ff_xy:2 * ff_xy, :].flatten()
+ R_s = final_RT[:ff_xy, :].reshape((ff_y, ff_x))
+ R_p = final_RT[ff_xy: 2 * ff_xy, :].reshape((ff_y, ff_x))
big_T1 = final_RT[2 * ff_xy:, :]
+ big_T_tetm = big_T.copy()
big_T = big_T @ big_T1
- T_s = big_T[:ff_xy, :].flatten()
- T_p = big_T[ff_xy:, :].flatten()
+ T_s = big_T[:ff_xy, :].reshape((ff_y, ff_x))
+ T_p = big_T[ff_xy:, :].reshape((ff_y, ff_x))
- de_ri = R_s * np.conj(R_s) * np.real(kz_top / (n_top * np.cos(theta))) \
- + R_p * np.conj(R_p) * np.real(kz_top / n_top ** 2 / (n_top * np.cos(theta)))
+ de_ri_s = (R_s * R_s.conj() * (kz_top / (n_top * np.cos(theta))).real).real
+ de_ri_p = (R_p * R_p.conj() * (kz_top / n_top ** 2 / (n_top * np.cos(theta))).real).real
- de_ti = T_s * np.conj(T_s) * np.real(kz_bot / (n_top * np.cos(theta))) \
- + T_p * np.conj(T_p) * np.real(kz_bot / n_bot ** 2 / (n_top * np.cos(theta)))
+ de_ti_s = (T_s * T_s.conj() * (kz_bot / (n_top * np.cos(theta))).real).real
+ de_ti_p = (T_p * T_p.conj() * (kz_bot / n_bot ** 2 / (n_top * np.cos(theta))).real).real
- return de_ri.real, de_ti.real, big_T1
+ de_ri = de_ri_s + de_ri_p
+ de_ti = de_ti_s + de_ti_p
+ res = {'R_s': R_s, 'R_p': R_p, 'T_s': T_s, 'T_p': T_p,
+ 'de_ri_s': de_ri_s, 'de_ri_p': de_ri_p, 'de_ri': de_ri,
+ 'de_ti_s': de_ti_s, 'de_ti_p': de_ti_p, 'de_ti': de_ti}
-def transfer_2d_1(ff_x, ff_y, kx, ky, n_top, n_bot, type_complex=np.complex128):
+ # TE TM incidence
+ psi_tm = np.array(0, dtype=type_complex)
+ final_B_tm = np.block(
+ [
+ [-np.sin(psi_tm) * delta_i0],
+ [np.cos(psi_tm) * np.cos(theta) * delta_i0],
+ [-1j * np.sin(psi_tm) * n_top * np.cos(theta) * delta_i0],
+ [-1j * n_top * np.cos(psi_tm) * delta_i0]
+ ]
+ )
+ psi_te = np.array(np.pi / 2, dtype=type_complex)
+ final_B_te = np.block(
+ [
+ [-np.sin(psi_te) * delta_i0],
+ [np.cos(psi_te) * np.cos(theta) * delta_i0],
+ [-1j * np.sin(psi_te) * n_top * np.cos(theta) * delta_i0],
+ [-1j * n_top * np.cos(psi_te) * delta_i0]
+ ]
+ )
+
+ final_B_tetm = np.hstack([final_B_te, final_B_tm])
+ final_RT_tetm = final_A_inv @ final_B_tetm
+
+ R_s_tetm = final_RT_tetm[:ff_xy, :].T.reshape((2, ff_y, ff_x))
+ R_p_tetm = final_RT_tetm[ff_xy: 2 * ff_xy, :].T.reshape((2, ff_y, ff_x))
+
+ big_T1_tetm = final_RT_tetm[2 * ff_xy:, :]
+ big_T_tetm = big_T_tetm @ big_T1_tetm
+
+ T_s_tetm = big_T_tetm[:ff_xy, :].T.reshape((2, ff_y, ff_x))
+ T_p_tetm = big_T_tetm[ff_xy:, :].T.reshape((2, ff_y, ff_x))
+
+ de_ri_s_tetm = (R_s_tetm * R_s_tetm.conj() * (kz_top / (n_top * np.cos(theta))).real).real
+ de_ri_p_tetm = (R_p_tetm * R_p_tetm.conj() * (kz_top / n_top ** 2 / (n_top * np.cos(theta))).real).real
+
+ de_ti_s_tetm = (T_s_tetm * T_s_tetm.conj() * (kz_bot / (n_top * np.cos(theta))).real).real
+ de_ti_p_tetm = (T_p_tetm * T_p_tetm.conj() * (kz_bot / n_bot ** 2 / (n_top * np.cos(theta))).real).real
+
+ de_ri_tetm = de_ri_s_tetm + de_ri_p_tetm
+ de_ti_tetm = de_ti_s_tetm + de_ti_p_tetm
+
+ res_te_inc = {'R_s': R_s_tetm[0], 'R_p': R_p_tetm[0], 'T_s': T_s_tetm[0], 'T_p': T_p_tetm[0],
+ 'de_ri_s': de_ri_s_tetm[0], 'de_ri_p': de_ri_p_tetm[0], 'de_ri': de_ri_tetm[0],
+ 'de_ti_s': de_ti_s_tetm[0], 'de_ti_p': de_ti_p_tetm[0], 'de_ti': de_ti_tetm[0]}
+
+ res_tm_inc = {'R_s': R_s_tetm[1], 'R_p': R_p_tetm[1], 'T_s': T_s_tetm[1], 'T_p': T_p_tetm[1],
+ 'de_ri_s': de_ri_s_tetm[1], 'de_ri_p': de_ri_p_tetm[1], 'de_ri': de_ri_tetm[1],
+ 'de_ti_s': de_ti_s_tetm[1], 'de_ti_p': de_ti_p_tetm[1], 'de_ti': de_ti_tetm[1]}
+
+ result = {'res': res, 'res_tm_inc': res_tm_inc, 'res_te_inc': res_te_inc}
+
+ return result, big_T1
+
+
+def transfer_2d_1(kx, ky, n_top, n_bot, type_complex=np.complex128):
+ ff_x = len(kx)
+ ff_y = len(ky)
ff_xy = ff_x * ff_y
I = np.eye(ff_xy, dtype=type_complex)
@@ -312,11 +390,10 @@ def transfer_2d_1(ff_x, ff_y, kx, ky, n_top, n_bot, type_complex=np.complex128):
kz_top = (n_top ** 2 - kx ** 2 - ky.reshape((-1, 1)) ** 2) ** 0.5
kz_bot = (n_bot ** 2 - kx ** 2 - ky.reshape((-1, 1)) ** 2) ** 0.5
- kz_top = kz_top.flatten().conjugate()
- kz_bot = kz_bot.flatten().conjugate()
+ kz_top = kz_top.flatten().conj()
+ kz_bot = kz_bot.flatten().conj()
varphi = np.arctan(ky.reshape((-1, 1)) / kx).flatten()
-
Kz_bot = np.diag(kz_bot)
big_F = np.block([[I, O], [O, 1j * Kz_bot / (n_bot ** 2)]])
@@ -326,8 +403,7 @@ def transfer_2d_1(ff_x, ff_y, kx, ky, n_top, n_bot, type_complex=np.complex128):
return kz_top, kz_bot, varphi, big_F, big_G, big_T
-def transfer_2d_2(kx, ky, epx_conv, epy_conv, epz_conv_i, type_complex=np.complex128):
-
+def transfer_2d_2(kx, ky, epx_conv, epy_conv, epz_conv_i, type_complex=np.complex128, use_pinv=False):
ff_x = len(kx)
ff_y = len(ky)
ff_xy = ff_x * ff_y
@@ -347,11 +423,12 @@ def transfer_2d_2(kx, ky, epx_conv, epy_conv, epz_conv_i, type_complex=np.comple
])
eigenvalues, W = np.linalg.eig(Omega2_LR)
+
eigenvalues += 0j # to get positive square root
q = eigenvalues ** 0.5
Q = np.diag(q)
- Q_i = np.linalg.inv(Q)
+ Q_i = meeinv(Q, use_pinv)
Omega_R = np.block(
[
@@ -365,15 +442,14 @@ def transfer_2d_2(kx, ky, epx_conv, epy_conv, epz_conv_i, type_complex=np.comple
return W, V, q
-def transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_complex=np.complex128):
-
- ff_xy = len(q)//2
+def transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_complex=np.complex128, use_pinv=False):
+ ff_xy = len(q) // 2
I = np.eye(ff_xy, dtype=type_complex)
O = np.zeros((ff_xy, ff_xy), dtype=type_complex)
- q1 = q[:ff_xy]
- q2 = q[ff_xy:]
+ q_1 = q[:ff_xy]
+ q_2 = q[ff_xy:]
W_11 = W[:ff_xy, :ff_xy]
W_12 = W[:ff_xy, ff_xy:]
@@ -385,8 +461,8 @@ def transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_complex=np.c
V_21 = V[ff_xy:, :ff_xy]
V_22 = V[ff_xy:, ff_xy:]
- X_1 = np.diag(np.exp(-k0 * q1 * d))
- X_2 = np.diag(np.exp(-k0 * q2 * d))
+ X_1 = np.diag(np.exp(-k0 * q_1 * d))
+ X_2 = np.diag(np.exp(-k0 * q_2 * d))
F_c = np.diag(np.cos(varphi))
F_s = np.diag(np.sin(varphi))
@@ -406,13 +482,13 @@ def transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_complex=np.c
big_W = np.block([[W_ss, W_sp], [W_ps, W_pp]])
big_V = np.block([[V_ss, V_sp], [V_ps, V_pp]])
- big_W_i = np.linalg.inv(big_W)
- big_V_i = np.linalg.inv(big_V)
+ big_W_i = meeinv(big_W, use_pinv)
+ big_V_i = meeinv(big_V, use_pinv)
big_A = 0.5 * (big_W_i @ big_F + big_V_i @ big_G)
big_B = 0.5 * (big_W_i @ big_F - big_V_i @ big_G)
- big_A_i = np.linalg.inv(big_A)
+ big_A_i = meeinv(big_A, use_pinv)
big_F = big_W @ (big_I + big_X @ big_B @ big_A_i @ big_X)
big_G = big_V @ (big_I - big_X @ big_B @ big_A_i @ big_X)
@@ -422,11 +498,14 @@ def transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, type_complex=np.c
return big_X, big_F, big_G, big_T, big_A_i, big_B
-def transfer_2d_4(big_F, big_G, big_T, kz_top, kz_bot, psi, theta, n_top, n_bot, type_complex=np.complex128):
+def transfer_2d_4(ff_x, ff_y, big_F, big_G, big_T, kz_top, kz_bot, psi, theta, n_top, n_bot,
+ type_complex=np.complex128, use_pinv=False):
- ff_xy = len(big_F) // 2
+ ff_xy = ff_x * ff_y
Kz_top = np.diag(kz_top)
+ kz_top = kz_top.reshape((ff_y, ff_x))
+ kz_bot = kz_bot.reshape((ff_y, ff_x))
I = np.eye(ff_xy, dtype=type_complex)
O = np.zeros((ff_xy, ff_xy), dtype=type_complex)
@@ -463,21 +542,82 @@ def transfer_2d_4(big_F, big_G, big_T, kz_top, kz_bot, psi, theta, n_top, n_bot,
]
)
- final_RT = np.linalg.inv(final_A) @ final_B
+ final_A_inv = meeinv(final_A, use_pinv)
+ final_RT = final_A_inv @ final_B
- R_s = final_RT[:ff_xy, :].flatten()
- R_p = final_RT[ff_xy: 2 * ff_xy, :].flatten()
+ R_s = final_RT[:ff_xy, :].reshape((ff_y, ff_x))
+ R_p = final_RT[ff_xy: 2 * ff_xy, :].reshape((ff_y, ff_x))
big_T1 = final_RT[2 * ff_xy:, :]
+ big_T_tetm = big_T.copy()
big_T = big_T @ big_T1
- T_s = big_T[:ff_xy, :].flatten()
- T_p = big_T[ff_xy:, :].flatten()
+ T_s = big_T[:ff_xy, :].reshape((ff_y, ff_x))
+ T_p = big_T[ff_xy:, :].reshape((ff_y, ff_x))
+
+ de_ri_s = (R_s * R_s.conj() * (kz_top / (n_top * np.cos(theta))).real).real
+ de_ri_p = (R_p * R_p.conj() * (kz_top / n_top ** 2 / (n_top * np.cos(theta))).real).real
+
+ de_ti_s = (T_s * T_s.conj() * (kz_bot / (n_top * np.cos(theta))).real).real
+ de_ti_p = (T_p * T_p.conj() * (kz_bot / n_bot ** 2 / (n_top * np.cos(theta))).real).real
+
+ de_ri = de_ri_s + de_ri_p
+ de_ti = de_ti_s + de_ti_p
+
+ res = {'R_s': R_s, 'R_p': R_p, 'T_s': T_s, 'T_p': T_p,
+ 'de_ri_s': de_ri_s, 'de_ri_p': de_ri_p, 'de_ri': de_ri,
+ 'de_ti_s': de_ti_s, 'de_ti_p': de_ti_p, 'de_ti': de_ti}
+
+ # TE TM incidence
+ psi_tm = np.array(0, dtype=type_complex)
+ final_B_tm = np.block(
+ [
+ [-np.sin(psi_tm) * delta_i0],
+ [np.cos(psi_tm) * np.cos(theta) * delta_i0],
+ [-1j * np.sin(psi_tm) * n_top * np.cos(theta) * delta_i0],
+ [-1j * n_top * np.cos(psi_tm) * delta_i0]
+ ]
+ )
+
+ psi_te = np.array(np.pi/2, dtype=type_complex)
+ final_B_te = np.block(
+ [
+ [-np.sin(psi_te) * delta_i0],
+ [np.cos(psi_te) * np.cos(theta) * delta_i0],
+ [-1j * np.sin(psi_te) * n_top * np.cos(theta) * delta_i0],
+ [-1j * n_top * np.cos(psi_te) * delta_i0]
+ ]
+ )
+
+ final_B_tetm = np.hstack([final_B_te, final_B_tm])
+ final_RT_tetm = final_A_inv @ final_B_tetm
+
+ R_s_tetm = final_RT_tetm[:ff_xy, :].T.reshape((2, ff_y, ff_x))
+ R_p_tetm = final_RT_tetm[ff_xy: 2 * ff_xy, :].T.reshape((2, ff_y, ff_x))
+
+ big_T1_tetm = final_RT_tetm[2 * ff_xy:, :]
+ big_T_tetm = big_T_tetm @ big_T1_tetm
+
+ T_s_tetm = big_T_tetm[:ff_xy, :].T.reshape((2, ff_y, ff_x))
+ T_p_tetm = big_T_tetm[ff_xy:, :].T.reshape((2, ff_y, ff_x))
+
+ de_ri_s_tetm = (R_s_tetm * R_s_tetm.conj() * (kz_top / (n_top * np.cos(theta))).real).real
+ de_ri_p_tetm = (R_p_tetm * R_p_tetm.conj() * (kz_top / n_top ** 2 / (n_top * np.cos(theta))).real).real
+
+ de_ti_s_tetm = (T_s_tetm * T_s_tetm.conj() * (kz_bot / (n_top * np.cos(theta))).real).real
+ de_ti_p_tetm = (T_p_tetm * T_p_tetm.conj() * (kz_bot / n_bot ** 2 / (n_top * np.cos(theta))).real).real
+
+ de_ri_tetm = de_ri_s_tetm + de_ri_p_tetm
+ de_ti_tetm = de_ti_s_tetm + de_ti_p_tetm
+
+ res_te_inc = {'R_s': R_s_tetm[0], 'R_p': R_p_tetm[0], 'T_s': T_s_tetm[0], 'T_p': T_p_tetm[0],
+ 'de_ri_s': de_ri_s_tetm[0], 'de_ri_p': de_ri_p_tetm[0], 'de_ri': de_ri_tetm[0],
+ 'de_ti_s': de_ti_s_tetm[0], 'de_ti_p': de_ti_p_tetm[0], 'de_ti': de_ti_tetm[0]}
- de_ri = R_s * np.conj(R_s) * np.real(kz_top / (n_top * np.cos(theta))) \
- + R_p * np.conj(R_p) * np.real(kz_top / n_top ** 2 / (n_top * np.cos(theta)))
+ res_tm_inc = {'R_s': R_s_tetm[1], 'R_p': R_p_tetm[1], 'T_s': T_s_tetm[1], 'T_p': T_p_tetm[1],
+ 'de_ri_s': de_ri_s_tetm[1], 'de_ri_p': de_ri_p_tetm[1], 'de_ri': de_ri_tetm[1],
+ 'de_ti_s': de_ti_s_tetm[1], 'de_ti_p': de_ti_p_tetm[1], 'de_ti': de_ti_tetm[1]}
- de_ti = T_s * np.conj(T_s) * np.real(kz_bot / (n_top * np.cos(theta))) \
- + T_p * np.conj(T_p) * np.real(kz_bot / n_bot ** 2 / (n_top * np.cos(theta)))
+ result = {'res': res, 'res_tm_inc': res_tm_inc, 'res_te_inc': res_te_inc}
- return de_ri.real, de_ti.real, big_T1
+ return result, big_T1
diff --git a/meent/on_numpy/mee.py b/meent/on_numpy/mee.py
index 1a8f148..410e751 100644
--- a/meent/on_numpy/mee.py
+++ b/meent/on_numpy/mee.py
@@ -10,17 +10,17 @@ def __init__(self, *args, **kwargs):
ModelingNumpy.__init__(self, *args, **kwargs)
RCWANumpy.__init__(self, *args, **kwargs)
- def spectrum(self, wavelength_list):
- if self.grating_type in (0, 1):
- de_ri_list = np.zeros((len(wavelength_list), self.fourier_order))
- de_ti_list = np.zeros((len(wavelength_list), self.fourier_order))
- else:
- de_ri_list = np.zeros((len(wavelength_list), self.ff, self.ff))
- de_ti_list = np.zeros((len(wavelength_list), self.ff, self.ff))
-
- for i, wavelength in enumerate(wavelength_list):
- de_ri, de_ti = self.conv_solve(wavelength=wavelength)
- de_ri_list[i] = de_ri
- de_ti_list[i] = de_ti
-
- return de_ri_list, de_ti_list
+ # def spectrum(self, wavelength_list):
+ # if self.grating_type in (0, 1):
+ # de_ri_list = np.zeros((len(wavelength_list), self.fourier_order))
+ # de_ti_list = np.zeros((len(wavelength_list), self.fourier_order))
+ # else:
+ # de_ri_list = np.zeros((len(wavelength_list), self.ff, self.ff))
+ # de_ti_list = np.zeros((len(wavelength_list), self.ff, self.ff))
+ #
+ # for i, wavelength in enumerate(wavelength_list):
+ # de_ri, de_ti = self.conv_solve(wavelength=wavelength)
+ # de_ri_list[i] = de_ri
+ # de_ti_list[i] = de_ti
+ #
+ # return de_ri_list, de_ti_list
diff --git a/meent/on_numpy/modeler/modeling.py b/meent/on_numpy/modeler/modeling.py
index 97c536d..fb6f4f9 100644
--- a/meent/on_numpy/modeler/modeling.py
+++ b/meent/on_numpy/modeler/modeling.py
@@ -160,34 +160,6 @@ def rectangle(self, cx, cy, lx, ly, n_index, angle=0, n_split_triangle=2, n_spli
length = length_top12 / np.sin(angle_inside)
top3_cp = [top3[0] - length, top3[1]]
- # for i in range(n_split_triangle + 1):
- # x = top1[0] - (top1[0] - top2[0]) / n_split_triangle * i
- # y = top1[1] - (top1[1] - top2[1]) / n_split_parallelogram * i
- # xxx.append(x)
- # yyy.append(y)
- #
- # xxx_cp.append(x + length / n_split_triangle * i)
- # yyy_cp.append(y)
- #
- # for i in range(n_split_parallelogram + 1):
- #
- # x = top2[0] + (top3_cp[0] - top2[0]) / n_split_triangle * i
- # y = top2[1] - (top2[1] - top3_cp[1]) / n_split_parallelogram * i
- # xxx.append(x)
- # yyy.append(y)
- #
- # xxx_cp.append(x + length)
- # yyy_cp.append(y)
- #
- # for i in range(n_split_triangle + 1):
- # x = top3_cp[0] + (top4[0] - top3_cp[0]) / n_split_triangle * i
- # y = top3_cp[1] - (top3_cp[1] - top4[1]) / n_split_parallelogram * i
- # xxx.append(x)
- # yyy.append(y)
- #
- # xxx_cp.append(x + length / n_split_triangle * (n_split_triangle - i))
- # yyy_cp.append(y)
-
# 1: Upper triangle
xxx1 = top1[0] - (top1[0] - top2[0]) / n_split_triangle * np.arange(n_split_triangle+1).reshape((-1, 1))
yyy1 = top1[1] - (top1[1] - top2[1]) / n_split_parallelogram * np.arange(n_split_triangle+1).reshape((-1, 1))
@@ -501,8 +473,11 @@ def ellipse(self, cx, cy, lx, ly, n_index, angle=0, n_split_w=2, n_split_h=2, an
ax, ay = arr[:, i]
bx, by = arr_roll[:, i]
+ # LL = [min(ay.real, by.real), min(ax.real, bx.real)]
+ # UR = [max(ay.real, by.real), max(ax.real, bx.real)]
LL = [min(ay.real, by.real)+0j, min(ax.real, bx.real)+0j]
UR = [max(ay.real, by.real)+0j, max(ax.real, bx.real)+0j]
+ # What is 0j for?
res.append([LL, UR, n_index])
diff --git a/meent/on_torch/emsolver/_base.py b/meent/on_torch/emsolver/_base.py
index ca72c35..1443db2 100644
--- a/meent/on_torch/emsolver/_base.py
+++ b/meent/on_torch/emsolver/_base.py
@@ -5,15 +5,16 @@
from .scattering_method import scattering_1d_1, scattering_1d_2, scattering_1d_3, scattering_2d_1, scattering_2d_wv, \
scattering_2d_2, scattering_2d_3
-from .transfer_method import (transfer_1d_1, transfer_1d_2, transfer_1d_3, transfer_1d_4,
+from .transfer_method import (transfer_1d_1, transfer_1d_2, transfer_1d_3, transfer_1d_4, transfer_1d_conical_1,
+ transfer_1d_conical_2, transfer_1d_conical_3, transfer_1d_conical_4,
transfer_2d_1, transfer_2d_2, transfer_2d_3, transfer_2d_4)
class _BaseRCWA:
- def __init__(self, n_top=1., n_bot=1., theta=0., phi=0., psi=None, pol=0., fto=(0, 0),
- period=(100., 100.), wavelength=1.,
+ def __init__(self, n_top=1., n_bot=1., theta=0., phi=None, psi=None, pol=0., fto=(0, 0),
+ period=(1., 1.), wavelength=1.,
thickness=(0.,), connecting_algo='TMM', perturbation=1E-20,
- device='cpu', type_complex=torch.complex128):
+ device='cpu', type_complex=torch.complex128, use_pinv=False):
# device
if device in (0, 'cpu'):
@@ -43,7 +44,6 @@ def __init__(self, n_top=1., n_bot=1., theta=0., phi=0., psi=None, pol=0., fto=(
self.theta = theta
self.phi = phi
self.pol = pol
- # self._psi = torch.tensor((torch.pi / 2 * (1 - pol)), device=self.device, dtype=self.type_float)
self.psi = psi
self.fto = fto
@@ -51,12 +51,11 @@ def __init__(self, n_top=1., n_bot=1., theta=0., phi=0., psi=None, pol=0., fto=(
self.wavelength = wavelength
self.thickness = thickness
self.connecting_algo = connecting_algo
+ self.use_pinv = use_pinv
+
self.layer_info_list = []
self.T1 = None
- self.rayleigh_R = None # TODO: other bds
- self.rayleigh_T = None
-
@property
def device(self):
return self._device
@@ -85,6 +84,15 @@ def type_complex(self, type_complex):
else:
raise ValueError('type_complex')
+ self._type_float = torch.float64 if self.type_complex is not torch.complex64 else torch.float32
+ self._type_int = torch.int64 if self.type_complex is not torch.complex64 else torch.int32
+ self.theta = self.theta
+ self.phi = self.phi
+ self._psi = self.psi
+
+ self.fto = self.fto
+ self.thickness = self.thickness
+
@property
def type_float(self):
return self._type_float
@@ -93,33 +101,17 @@ def type_float(self):
def type_int(self):
return self._type_int
- @property
- def pol(self):
- return self._pol
-
- @pol.setter
- def pol(self, pol):
- room = 1E-6
- if 1 < pol < 1 + room:
- pol = 1
- elif 0 - room < pol < 0:
- pol = 0
-
- if not 0 <= pol <= 1:
- raise ValueError
-
- self._pol = pol
- psi = torch.pi / 2 * (1 - self.pol)
- self._psi = torch.tensor(psi, device=self.device, dtype=self.type_float)
-
@property
def theta(self):
return self._theta
@theta.setter
def theta(self, theta):
- self._theta = torch.tensor(theta, device=self.device, dtype=self.type_float)
- self._theta = torch.where(self._theta == 0, self.perturbation, self._theta) # perturbation
+ if theta is None:
+ self._theta = None
+ else:
+ self._theta = torch.tensor(theta, device=self.device, dtype=self.type_complex)
+ self._theta = torch.where(self._theta == 0, self.perturbation, self._theta) # perturbation
@property
def phi(self):
@@ -127,7 +119,10 @@ def phi(self):
@phi.setter
def phi(self, phi):
- self._phi = torch.tensor(phi, device=self.device, dtype=self.type_float)
+ if phi is None:
+ self._phi = None
+ else:
+ self._phi = torch.tensor(phi, device=self.device, dtype=self.type_complex)
@property
def psi(self):
@@ -136,10 +131,29 @@ def psi(self):
@psi.setter
def psi(self, psi):
if psi is not None:
- self._psi = torch.tensor(psi, dtype=self.type_float)
+ self._psi = torch.tensor(psi, dtype=self.type_complex)
pol = -(2 * psi / torch.pi - 1)
self._pol = pol
+ @property
+ def pol(self):
+ """
+ portion of TM. 0: full TE, 1: full TM
+
+ Returns: polarization ratio
+
+ """
+ return self._pol
+
+ @pol.setter
+ def pol(self, pol):
+ if not 0 <= pol <= 1:
+ raise ValueError
+
+ self._pol = pol
+ psi = torch.tensor(torch.pi / 2 * (1 - self.pol), device=self.device, dtype=self.type_complex)
+ self._psi = psi
+
@property
def fto(self):
return self._fto
@@ -172,13 +186,6 @@ def fto(self, fto):
else:
raise ValueError('Torch fto')
- # if type(fto) in (int, float):
- # self._fto = torch.tensor([int(fto), 0], device=self.device)
- # elif len(fto) == 1:
- # self._fto = torch.tensor([int(fto[0]), 0], device=self.device)
- # else:
- # self._fto = torch.tensor([int(v) for v in fto], device=self.device)
-
@property
def period(self):
return self._period
@@ -216,19 +223,29 @@ def get_kx_ky_vector(self, wavelength):
fto_y_range = torch.arange(-self.fto[1], self.fto[1] + 1, device=self.device,
dtype=self.type_float)
- kx = (self.n_top * torch.sin(self.theta) * torch.cos(self.phi) + fto_x_range * (
- wavelength / self.period[0])).type(self.type_complex)
+ if self.theta.real >= torch.tensor(torch.pi/2, dtype=torch.float32):
+ # https://github.com/numpy/numpy/issues/27306
+ sin_theta = torch.sin(
+ torch.nextafter(torch.float32(torch.pi / 2), torch.float32(0)) + self.theta.imag * np.complex64(1j))
+ else:
+ sin_theta = np.sin(self.theta)
+
+ if self.phi is None:
+ phi = torch.tensor(0, device=self.device, dtype=self.type_complex)
+ else:
+ phi = self.phi
+
+ kx = (self.n_top * sin_theta * torch.cos(phi) + fto_x_range * (
+ wavelength / self.period[0])).type(self.type_complex).conj()
- ky = (self.n_top * torch.sin(self.theta) * torch.sin(self.phi) + fto_y_range * (
- wavelength / self.period[1])).type(self.type_complex)
+ ky = (self.n_top * sin_theta * torch.sin(phi) + fto_y_range * (
+ wavelength / self.period[1])).type(self.type_complex).conj()
return kx, ky
def solve_1d(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
-
self.layer_info_list = []
self.T1 = None
- self.rayleigh_R, self.rayleigh_T = [], []
ff_x = self.fto[0] * 2 + 1
@@ -237,12 +254,14 @@ def solve_1d(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
if self.connecting_algo == 'TMM':
kz_top, kz_bot, F, G, T \
- = transfer_1d_1(self.pol, ff_x, kx, self.n_top, self.n_bot, device=self.device, type_complex=self.type_complex)
+ = transfer_1d_1(self.pol, kx, self.n_top, self.n_bot, device=self.device,
+ type_complex=self.type_complex)
elif self.connecting_algo == 'SMM':
- Kx, Wg, Vg, Kzg, Wr, Vr, Kzr, Wt, Vt, Kzt, Ar, Br, Sg \
- = scattering_1d_1(k0, self.n_top, self.n_bot, self.theta, self.phi, fourier_indices, self.period,
- self.pol, wl=wavelength)
+ raise ValueError
+ # Kx, Wg, Vg, Kzg, Wr, Vr, Kzr, Wt, Vt, Kzt, Ar, Br, Sg \
+ # = scattering_1d_1(k0, self.n_top, self.n_bot, self.theta, self.phi, fourier_indices, self.period,
+ # self.pol, wl=wavelength)
else:
raise ValueError
@@ -256,38 +275,101 @@ def solve_1d(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
d = self.thickness[layer_index]
if self.connecting_algo == 'TMM':
- W, V, q = transfer_1d_2(self.pol, kx, epx_conv, epy_conv, epz_conv_i, device=self.device, type_complex=self.type_complex)
+ W, V, q = transfer_1d_2(self.pol, kx, epx_conv, epy_conv, epz_conv_i, device=self.device,
+ type_complex=self.type_complex, perturbation=self.perturbation,
+ use_pinv=self.use_pinv)
- X, F, G, T, A_i, B = transfer_1d_3(k0, W, V, q, d, F, G, T, device=self.device, type_complex=self.type_complex)
+ X, F, G, T, A_i, B = transfer_1d_3(k0, W, V, q, d, F, G, T, device=self.device,
+ type_complex=self.type_complex, use_pinv=self.use_pinv)
layer_info = [epz_conv_i, W, V, q, d, A_i, B]
self.layer_info_list.append(layer_info)
elif self.connecting_algo == 'SMM':
- A, B, S_dict, Sg = scattering_1d_2(W, Wg, V, Vg, d, k0, Q, Sg)
+ raise ValueError
+ # A, B, S_dict, Sg = scattering_1d_2(W, Wg, V, Vg, d, k0, Q, Sg)
else:
raise ValueError
if self.connecting_algo == 'TMM':
- de_ri, de_ti, T1, [R], [T] = transfer_1d_4(self.pol, F, G, T, kz_top, kz_bot, self.theta, self.n_top, self.n_bot,
- device=self.device, type_complex=self.type_complex)
+ result, T1 = transfer_1d_4(self.pol, ff_x, F, G, T, kz_top, kz_bot, self.theta, self.n_top, self.n_bot,
+ device=self.device, type_complex=self.type_complex, use_pinv=self.use_pinv)
self.T1 = T1
- self.rayleigh_R = [R]
- self.rayleigh_T = [T]
elif self.connecting_algo == 'SMM':
- de_ri, de_ti = scattering_1d_3(Wt, Wg, Vt, Vg, Sg, self.ff, Wr, self.fto, Kzr, Kzt,
- self.n_top, self.n_bot, self.theta, self.pol)
+ raise ValueError
+ # de_ri, de_ti = scattering_1d_3(Wt, Wg, Vt, Vg, Sg, self.ff, Wr, self.fto, Kzr, Kzt,
+ # self.n_top, self.n_bot, self.theta, self.pol)
else:
raise ValueError
- return de_ri, de_ti, self.rayleigh_R, self.rayleigh_T, self.layer_info_list, self.T1
+ # return de_ri, de_ti, self.rayleigh_R, self.rayleigh_T, self.layer_info_list, self.T1
+ return result
+
+ def solve_1d_conical(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
+ self.layer_info_list = []
+ self.T1 = None
+
+ ff_x = self.fto[0] * 2 + 1
+ ff_y = 1
+
+ k0 = 2 * torch.pi / wavelength
+ kx, ky = self.get_kx_ky_vector(wavelength)
+
+ if self.connecting_algo == 'TMM':
+ kz_top, kz_bot, varphi, big_F, big_G, big_T \
+ = transfer_1d_conical_1(kx, ky, self.n_top, self.n_bot, device=self.device,
+ type_complex=self.type_complex)
+
+ elif self.connecting_algo == 'SMM':
+ print('SMM for 1D conical is not implemented')
+ return np.nan, np.nan
+ else:
+ raise ValueError
+
+ for layer_index in range(len(self.thickness))[::-1]:
+
+ epx_conv = epx_conv_all[layer_index]
+ epy_conv = epy_conv_all[layer_index]
+ epz_conv_i = epz_conv_i_all[layer_index]
+
+ d = self.thickness[layer_index]
+
+ if self.connecting_algo == 'TMM':
+ W, V, q = transfer_1d_conical_2(kx, ky, epx_conv, epy_conv, epz_conv_i, device=self.device,
+ type_complex=self.type_complex, perturbation=self.perturbation,
+ use_pinv=self.use_pinv)
+
+ big_X, big_F, big_G, big_T, big_A_i, big_B, \
+ = transfer_1d_conical_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, device=self.device,
+ type_complex=self.type_complex, use_pinv=self.use_pinv)
+
+ layer_info = [epz_conv_i, W, V, q, d, big_A_i, big_B]
+ self.layer_info_list.append(layer_info)
+
+ elif self.connecting_algo == 'SMM':
+ raise ValueError
+ else:
+ raise ValueError
+
+ if self.connecting_algo == 'TMM':
+ result, big_T1 = transfer_1d_conical_4(ff_x, ff_y, big_F, big_G, big_T, kz_top, kz_bot, self.psi,
+ self.theta, self.n_top, self.n_bot, device=self.device,
+ type_complex=self.type_complex,
+ use_pinv=self.use_pinv)
+ self.T1 = big_T1
+
+ elif self.connecting_algo == 'SMM':
+ raise ValueError
+ else:
+ raise ValueError
+
+ return result
def solve_2d(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
self.layer_info_list = []
self.T1 = None
- self.rayleigh_R, self.rayleigh_T = [], []
ff_x = self.fto[0] * 2 + 1
ff_y = self.fto[1] * 2 + 1
@@ -296,16 +378,13 @@ def solve_2d(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
kx, ky = self.get_kx_ky_vector(wavelength)
if self.connecting_algo == 'TMM':
- # kx, ky, Kx, Ky, k_I_z, k_II_z, varphi, Y_I, Y_II, Z_I, Z_II, big_F, big_G, big_T \
- # = transfer_2d_1(ff_x, ff_y, ff_xy, k0, self.n_top, self.n_bot, self.kx, self.period, fourier_indices_y,
- # self.theta, self.phi, wavelength, device=self.device, type_complex=self.type_complex)
kz_top, kz_bot, varphi, big_F, big_G, big_T \
- = transfer_2d_1(ff_x, ff_y, kx, ky, self.n_top, self.n_bot, device=self.device,
- type_complex=self.type_complex)
+ = transfer_2d_1(kx, ky, self.n_top, self.n_bot, device=self.device, type_complex=self.type_complex)
elif self.connecting_algo == 'SMM':
- Kx, Ky, kz_inc, Wg, Vg, Kzg, Wr, Vr, Kzr, Wt, Vt, Kzt, Ar, Br, Sg \
- = scattering_2d_1(self.n_top, self.n_bot, self.theta, self.phi, k0, self.period, self.fto)
+ raise ValueError
+ # Kx, Ky, kz_inc, Wg, Vg, Kzg, Wr, Vr, Kzr, Wt, Vt, Kzt, Ar, Br, Sg \
+ # = scattering_2d_1(self.n_top, self.n_bot, self.theta, self.phi, k0, self.period, self.fto)
else:
raise ValueError
@@ -319,38 +398,51 @@ def solve_2d(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
d = self.thickness[layer_index]
if self.connecting_algo == 'TMM':
-
W, V, q = transfer_2d_2(kx, ky, epx_conv, epy_conv, epz_conv_i, device=self.device,
- type_complex=self.type_complex)
+ type_complex=self.type_complex, perturbation=self.perturbation,
+ use_pinv=self.use_pinv)
big_X, big_F, big_G, big_T, big_A_i, big_B, \
= transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, device=self.device,
- type_complex=self.type_complex)
+ type_complex=self.type_complex, use_pinv=self.use_pinv)
layer_info = [epz_conv_i, W, V, q, d, big_A_i, big_B]
self.layer_info_list.append(layer_info)
elif self.connecting_algo == 'SMM':
- W, V, LAMBDA = scattering_2d_wv(ff_xy, Kx, Ky, E_conv, o_E_conv, o_E_conv_i, E_conv_i)
- A, B, Sl_dict, Sg_matrix, Sg = scattering_2d_2(W, Wg, V, Vg, d, k0, Sg, LAMBDA)
+ raise ValueError
+ # W, V, LAMBDA = scattering_2d_wv(ff_xy, Kx, Ky, E_conv, o_E_conv, o_E_conv_i, E_conv_i)
+ # A, B, Sl_dict, Sg_matrix, Sg = scattering_2d_2(W, Wg, V, Vg, d, k0, Sg, LAMBDA)
else:
raise ValueError
if self.connecting_algo == 'TMM':
- de_ri, de_ti, big_T1, [R_s, R_p], [T_s, T_p], = transfer_2d_4(big_F, big_G, big_T, kz_top, kz_bot, self.psi, self.theta,
- self.n_top, self.n_bot, device=self.device,
- type_complex=self.type_complex)
+ # de_ri, de_ti, big_T1, [R_s, R_p], [T_s, T_p], = transfer_2d_4(big_F, big_G, big_T, kz_top, kz_bot, self.psi, self.theta,
+ # self.n_top, self.n_bot, device=self.device,
+ # type_complex=self.type_complex)
+ # self.T1 = big_T1
+ # self.rayleigh_R = [R_s, R_p]
+ # self.rayleigh_T = [T_s, T_p]
+
+ # de_ri_s, de_ri_p, de_ti_s, de_ti_p, big_T1, R_s, R_p, T_s, T_p = transfer_2d_4(big_F, big_G, big_T, kz_top,
+ # kz_bot, self.psi, self.theta,
+ # self.n_top, self.n_bot,
+ # type_complex=self.type_complex)
+ result, big_T1 = transfer_2d_4(ff_x, ff_y, big_F, big_G, big_T, kz_top, kz_bot, self.psi, self.theta,
+ self.n_top, self.n_bot, device=self.device, type_complex=self.type_complex,
+ use_pinv=self.use_pinv)
self.T1 = big_T1
- self.rayleigh_R = [R_s, R_p]
- self.rayleigh_T = [T_s, T_p]
elif self.connecting_algo == 'SMM':
- de_ri, de_ti = scattering_2d_3(Wt, Wg, Vt, Vg, Sg, Wr, Kx, Ky, Kzr, Kzt, kz_inc, self.n_top,
- self.pol, self.theta, self.phi, self.fto)
+ raise ValueError
+ # de_ri, de_ti = scattering_2d_3(Wt, Wg, Vt, Vg, Sg, Wr, Kx, Ky, Kzr, Kzt, kz_inc, self.n_top,
+ # self.pol, self.theta, self.phi, self.fto)
else:
raise ValueError
- de_ri = de_ri.reshape((ff_y, ff_x)).T
- de_ti = de_ti.reshape((ff_y, ff_x)).T
-
- return de_ri, de_ti, self.rayleigh_R, self.rayleigh_T, self.layer_info_list, self.T1
+ # de_ri = de_ri.reshape((ff_y, ff_x)).T
+ # de_ti = de_ti.reshape((ff_y, ff_x)).T
+ #
+ # return de_ri, de_ti, self.rayleigh_R, self.rayleigh_T, self.layer_info_list, self.T1
+ # return de_ri_s, de_ri_p, de_ti_s, de_ti_p, self.layer_info_list, self.T1, R_s, R_p, T_s, T_p
+ return result
diff --git a/meent/on_torch/emsolver/convolution_matrix.py b/meent/on_torch/emsolver/convolution_matrix.py
index c5a74e8..a3ffad4 100644
--- a/meent/on_torch/emsolver/convolution_matrix.py
+++ b/meent/on_torch/emsolver/convolution_matrix.py
@@ -1,5 +1,6 @@
import torch
from .fourier_analysis import dfs2d, cfs2d
+from .primitives import meeinv
def cell_compression(cell, device=torch.device('cpu'), type_complex=torch.complex128):
@@ -43,6 +44,7 @@ def cell_compression(cell, device=torch.device('cpu'), type_complex=torch.comple
return cell_comp, x, y
+# TODO: delete
def fft_piecewise_constant(cell, x, y, fourier_order_x, fourier_order_y, device=torch.device('cpu'),
type_complex=torch.complex128):
@@ -88,8 +90,8 @@ def fft_piecewise_constant(cell, x, y, fourier_order_x, fourier_order_y, device=
return f_coeffs_xy.T
-def to_conv_mat_vector(ucell_info_list, fto_x, fto_y, device=torch.device('cpu'),
- type_complex=torch.complex128):
+def to_conv_mat_vector(ucell_info_list, fto_x, fto_y, device=torch.device('cpu'), type_complex=torch.complex128,
+ use_pinv=False):
ff_xy = (2 * fto_x + 1) * (2 * fto_y + 1)
@@ -107,13 +109,13 @@ def to_conv_mat_vector(ucell_info_list, fto_x, fto_y, device=torch.device('cpu')
epx_conv_all[i] = epx_conv
epy_conv_all[i] = epy_conv
- epz_conv_i_all[i] = torch.linalg.inv(epz_conv)
+ epz_conv_i_all[i] = meeinv(epz_conv, use_pinv=use_pinv)
return epx_conv_all, epy_conv_all, epz_conv_i_all
-def to_conv_mat_raster_continuous(ucell, fto_x, fto_y, device=torch.device('cpu'),
- type_complex=torch.complex128):
+def to_conv_mat_raster_continuous(ucell, fto_x, fto_y, device=torch.device('cpu'), type_complex=torch.complex128,
+ use_pinv=False):
ff_xy = (2 * fto_x + 1) * (2 * fto_y + 1)
epx_conv_all = torch.zeros((ucell.shape[0], ff_xy, ff_xy), device=device, dtype=type_complex)
@@ -130,13 +132,13 @@ def to_conv_mat_raster_continuous(ucell, fto_x, fto_y, device=torch.device('cpu'
epx_conv_all[i] = epx_conv
epy_conv_all[i] = epy_conv
- epz_conv_i_all[i] = torch.linalg.inv(epz_conv)
+ epz_conv_i_all[i] = meeinv(epz_conv, use_pinv=use_pinv)
return epx_conv_all, epy_conv_all, epz_conv_i_all
-def to_conv_mat_raster_discrete(ucell, fto_x, fto_y, device=None, type_complex=torch.complex128,
- enhanced_dfs=True):
+def to_conv_mat_raster_discrete(ucell, fto_x, fto_y, device=torch.device('cpu'), type_complex=torch.complex128,
+ enhanced_dfs=True, use_pinv=False):
ff_xy = (2 * fto_x + 1) * (2 * fto_y + 1)
@@ -170,6 +172,6 @@ def to_conv_mat_raster_discrete(ucell, fto_x, fto_y, device=None, type_complex=t
epx_conv_all[i] = epx_conv
epy_conv_all[i] = epy_conv
- epz_conv_i_all[i] = torch.linalg.inv(epz_conv)
+ epz_conv_i_all[i] = meeinv(epz_conv, use_pinv=use_pinv)
return epx_conv_all, epy_conv_all, epz_conv_i_all
diff --git a/meent/on_torch/emsolver/field_distribution.py b/meent/on_torch/emsolver/field_distribution.py
index b4bf02c..46ba0d3 100644
--- a/meent/on_torch/emsolver/field_distribution.py
+++ b/meent/on_torch/emsolver/field_distribution.py
@@ -22,8 +22,8 @@ def field_dist_1d(wavelength, kx, T1, layer_info_list, period,
z_1d = torch.linspace(0, res_z, res_z, device=device, dtype=type_complex).reshape((-1, 1, 1)) / res_z * d
- My = W @ (diag_exp_batch(-k0 * Q * z_1d) @ c1 + diag_exp_batch(k0 * Q * (z_1d - d)) @ c2)
- Mx = V @ (-diag_exp_batch(-k0 * Q * z_1d) @ c1 + diag_exp_batch(k0 * Q * (z_1d - d)) @ c2)
+ My = W @ (d_exp(-k0 * Q * z_1d) @ c1 + d_exp(k0 * Q * (z_1d - d)) @ c2)
+ Mx = V @ (-d_exp(-k0 * Q * z_1d) @ c1 + d_exp(k0 * Q * (z_1d - d)) @ c2)
if pol == 0:
Mz = -1j * Kx @ My
else:
@@ -61,7 +61,7 @@ def field_dist_1d(wavelength, kx, T1, layer_info_list, period,
return field_cell
-def field_dist_2d(wavelength, kx, ky, T1, layer_info_list, period,
+def field_dist_1d_conical(wavelength, kx, ky, T1, layer_info_list, period,
res_x=20, res_y=20, res_z=20, device='cpu', type_complex=torch.complex128, type_float=torch.float64):
k0 = 2 * torch.pi / wavelength
@@ -77,6 +77,100 @@ def field_dist_2d(wavelength, kx, ky, T1, layer_info_list, period,
T_layer = T1
+ big_I = torch.eye((len(T1)), device=device, dtype=type_complex)
+ O = torch.zeros((ff_xy, ff_xy), device=device, dtype=type_complex)
+
+ # From the first layer
+ for idx_layer, (epz_conv_i, W, V, q, d, big_A_i, big_B) in enumerate(layer_info_list[::-1]):
+ W_1 = W[:, :ff_xy]
+ W_2 = W[:, ff_xy:]
+
+ V_11 = V[:ff_xy, :ff_xy]
+ V_12 = V[:ff_xy, ff_xy:]
+ V_21 = V[ff_xy:, :ff_xy]
+ V_22 = V[ff_xy:, ff_xy:]
+
+ q_1 = q[:ff_xy]
+ q_2 = q[ff_xy:]
+
+ X_1 = torch.diag(torch.exp(-k0 * q_1 * d))
+ X_2 = torch.diag(torch.exp(-k0 * q_2 * d))
+
+ big_X = torch.cat([
+ torch.cat([X_1, O], dim=1),
+ torch.cat([O, X_2], dim=1)])
+
+ c = torch.cat([big_I, big_B @ big_A_i @ big_X]) @ T_layer
+
+ # z_1d = np.arange(0, res_z, res_z).reshape((-1, 1, 1)) / res_z * d
+ z_1d = torch.linspace(0, res_z, res_z, device=device, dtype=type_complex).reshape((-1, 1, 1)) / res_z * d
+
+ c1_plus = c[0 * ff_xy:1 * ff_xy]
+ c2_plus = c[1 * ff_xy:2 * ff_xy]
+ c1_minus = c[2 * ff_xy:3 * ff_xy]
+ c2_minus = c[3 * ff_xy:4 * ff_xy]
+
+ big_Q1 = torch.diag(q_1)
+ big_Q2 = torch.diag(q_2)
+
+ Sx = W_2 @ (d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Sy = V_11 @ (d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
+ + V_12 @ (d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Ux = W_1 @ (-d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus)
+ Uy = V_21 @ (-d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
+ + V_22 @ (-d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Sz = -1j * epz_conv_i @ (Kx @ Uy - Ky @ Ux)
+ Uz = -1j * (Kx @ Sy - Ky @ Sx)
+
+ # x_1d = np.arange(res_x).reshape((1, -1, 1)) * period[0] / res_x
+ x_1d = torch.linspace(0, period[0], res_x, device=device, dtype=type_complex).reshape((1, -1, 1))
+ x_2d = torch.tile(x_1d, (res_y, 1, 1))
+ x_2d = x_2d * kx * k0
+ x_2d = x_2d.reshape((res_y, res_x, 1, len(kx)))
+
+ # y_1d = np.arange(res_y-1, -1, -1).reshape((-1, 1, 1)) * period[1] / res_y
+ # y_1d = torch.linspace(0, period[1], res_y, device=device, dtype=type_complex)[::-1].reshape((-1, 1, 1))
+ y_1d = torch.flip(torch.linspace(0, period[1], res_y, device=device, dtype=type_complex), dims=(0,)).reshape((-1, 1, 1))
+ y_2d = torch.tile(y_1d, (1, res_x, 1))
+ y_2d = y_2d * ky * k0
+ y_2d = y_2d.reshape((res_y, res_x, len(ky), 1))
+
+ inv_fourier = torch.exp(-1j * x_2d) * torch.exp(-1j * y_2d)
+ inv_fourier = inv_fourier.reshape((res_y, res_x, -1))
+
+ Ex = inv_fourier[:, :, None, :] @ Sx[:, None, None, :, :]
+ Ey = inv_fourier[:, :, None, :] @ Sy[:, None, None, :, :]
+ Ez = inv_fourier[:, :, None, :] @ Sz[:, None, None, :, :]
+ Hx = 1j * inv_fourier[:, :, None, :] @ Ux[:, None, None, :, :]
+ Hy = 1j * inv_fourier[:, :, None, :] @ Uy[:, None, None, :, :]
+ Hz = 1j * inv_fourier[:, :, None, :] @ Uz[:, None, None, :, :]
+
+ val = torch.cat(
+ (Ex.squeeze(-1), Ey.squeeze(-1), Ez.squeeze(-1), Hx.squeeze(-1), Hy.squeeze(-1), Hz.squeeze(-1)), -1)
+
+ field_cell[res_z * idx_layer:res_z * (idx_layer + 1)] = val
+
+ T_layer = big_A_i @ big_X @ T_layer
+
+ return field_cell
+
+
+def field_dist_2d(wavelength, kx, ky, T1, layer_info_list, period,
+ res_x=20, res_y=20, res_z=20, device='cpu', type_complex=torch.complex128):
+
+ k0 = 2 * torch.pi / wavelength
+
+ ff_x = len(kx)
+ ff_y = len(ky)
+ ff_xy = ff_x * ff_y
+
+ Kx = torch.diag(torch.tile(kx, (ff_y, )).flatten())
+ Ky = torch.diag(torch.tile(ky.reshape((-1, 1)), (ff_x, )).flatten())
+
+ field_cell = torch.zeros((res_z * len(layer_info_list), res_y, res_x, 6), device=device, dtype=type_complex)
+
+ T_layer = T1
+
big_I = torch.eye((len(T1)), device=device, dtype=type_complex)
# From the first layer
@@ -92,6 +186,9 @@ def field_dist_2d(wavelength, kx, ky, T1, layer_info_list, period,
V_21 = V[ff_xy:, :ff_xy]
V_22 = V[ff_xy:, ff_xy:]
+ q_1 = q[:ff_xy]
+ q_2 = q[ff_xy:]
+
big_X = torch.diag(torch.exp(-k0 * q * d))
c = torch.cat([big_I, big_B @ big_A_i @ big_X]) @ T_layer
@@ -104,20 +201,18 @@ def field_dist_2d(wavelength, kx, ky, T1, layer_info_list, period,
c1_minus = c[2 * ff_xy:3 * ff_xy]
c2_minus = c[3 * ff_xy:4 * ff_xy]
- q1 = q[:len(q) // 2]
- q2 = q[len(q) // 2:]
- big_Q1 = torch.diag(q1)
- big_Q2 = torch.diag(q2)
+ big_Q1 = torch.diag(q_1)
+ big_Q2 = torch.diag(q_2)
- Sx = W_11 @ (diag_exp_batch(-k0 * big_Q1 * z_1d) @ c1_plus + diag_exp_batch(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
- + W_12 @ (diag_exp_batch(-k0 * big_Q2 * z_1d) @ c2_plus + diag_exp_batch(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
- Sy = W_21 @ (diag_exp_batch(-k0 * big_Q1 * z_1d) @ c1_plus + diag_exp_batch(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
- + W_22 @ (diag_exp_batch(-k0 * big_Q2 * z_1d) @ c2_plus + diag_exp_batch(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Sx = W_11 @ (d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
+ + W_12 @ (d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Sy = W_21 @ (d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
+ + W_22 @ (d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
- Ux = V_11 @ (-diag_exp_batch(-k0 * big_Q1 * z_1d) @ c1_plus + diag_exp_batch(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
- + V_12 @ (-diag_exp_batch(-k0 * big_Q2 * z_1d) @ c2_plus + diag_exp_batch(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
- Uy = V_21 @ (-diag_exp_batch(-k0 * big_Q1 * z_1d) @ c1_plus + diag_exp_batch(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
- + V_22 @ (-diag_exp_batch(-k0 * big_Q2 * z_1d) @ c2_plus + diag_exp_batch(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Ux = V_11 @ (-d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
+ + V_12 @ (-d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
+ Uy = V_21 @ (-d_exp(-k0 * big_Q1 * z_1d) @ c1_plus + d_exp(k0 * big_Q1 * (z_1d - d)) @ c1_minus) \
+ + V_22 @ (-d_exp(-k0 * big_Q2 * z_1d) @ c2_plus + d_exp(k0 * big_Q2 * (z_1d - d)) @ c2_minus)
Sz = -1j * epz_conv_i @ (Kx @ Uy - Ky @ Ux)
Uz = -1j * (Kx @ Sy - Ky @ Sx)
@@ -195,11 +290,7 @@ def field_plot(field_cell, pol=0, plot_indices=(1, 1, 1, 1, 1, 1), y_slice=0, z_
plt.show()
-def diag_exp(x):
- return torch.diag(torch.exp(torch.diag(x)))
-
-
-def diag_exp_batch(x):
+def d_exp(x):
res = torch.zeros(x.shape, device=x.device, dtype=x.dtype)
ix = torch.arange(x.shape[-1], device=x.device)
res[:, ix, ix] = torch.exp(x[:, ix, ix])
diff --git a/meent/on_torch/emsolver/primitives.py b/meent/on_torch/emsolver/primitives.py
index 73dae28..d8089b9 100644
--- a/meent/on_torch/emsolver/primitives.py
+++ b/meent/on_torch/emsolver/primitives.py
@@ -44,3 +44,12 @@ def backward(ctx, grad_eigval, grad_eigvec):
grad = grad.real
return grad
+
+
+def meeinv(x, use_pinv=False):
+ if use_pinv:
+ res = torch.linalg.pinv(x)
+ else:
+ res = torch.linalg.inv(x)
+
+ return res
diff --git a/meent/on_torch/emsolver/rcwa.py b/meent/on_torch/emsolver/rcwa.py
index 7e84c25..ea01ed6 100644
--- a/meent/on_torch/emsolver/rcwa.py
+++ b/meent/on_torch/emsolver/rcwa.py
@@ -1,11 +1,47 @@
-import time
import torch
import numpy as np
from ._base import _BaseRCWA
from .convolution_matrix import to_conv_mat_raster_discrete, to_conv_mat_raster_continuous, to_conv_mat_vector
-from .field_distribution import field_dist_1d, field_dist_2d, field_plot
+from .field_distribution import field_dist_1d, field_dist_1d_conical, field_dist_2d, field_plot
+
+
+class ResultTorch:
+ def __init__(self, res=None, res_te_inc=None, res_tm_inc=None):
+
+ self.res = res
+ self.res_te_inc = res_te_inc
+ self.res_tm_inc = res_tm_inc
+
+ @property
+ def de_ri(self):
+ if self.res is not None:
+ return self.res.de_ri
+ else:
+ return None
+
+ @property
+ def de_ti(self):
+ if self.res is not None:
+ return self.res.de_ti
+ else:
+ return None
+
+
+class ResultSubTorch:
+ def __init__(self, R_s, R_p, T_s, T_p, de_ri, de_ri_s, de_ri_p, de_ti, de_ti_s, de_ti_p):
+ self.R_s = R_s
+ self.R_p = R_p
+ self.T_s = T_s
+ self.T_p = T_p
+ self.de_ri = de_ri
+ self.de_ri_s = de_ri_s
+ self.de_ri_p = de_ri_p
+
+ self.de_ti = de_ti
+ self.de_ti_s = de_ti_s
+ self.de_ti_p = de_ti_p
class RCWATorch(_BaseRCWA):
@@ -13,10 +49,10 @@ def __init__(self,
n_top=1.,
n_bot=1.,
theta=0.,
- phi=0.,
+ phi=None,
psi=None,
- period=(100., 100.),
- wavelength=900.,
+ period=(1., 1.),
+ wavelength=1.,
ucell=None,
thickness=(0., ),
backend=2,
@@ -29,13 +65,16 @@ def __init__(self,
type_complex=torch.complex128,
fourier_type=0,
enhanced_dfs=True,
- # **kwargs,
+ use_pinv=False,
):
super().__init__(n_top=n_top, n_bot=n_bot, theta=theta, phi=phi, psi=psi, pol=pol,
fto=fto, period=period, wavelength=wavelength,
thickness=thickness, connecting_algo=connecting_algo, perturbation=perturbation,
- device=device, type_complex=type_complex)
+ device=device, type_complex=type_complex, use_pinv=use_pinv)
+
+ self._modeling_type_assigned = None
+ self._grating_type_assigned = None
self.ucell = ucell
self.ucell_materials = ucell_materials
@@ -43,8 +82,7 @@ def __init__(self,
self.backend = backend
self.fourier_type = fourier_type
self.enhanced_dfs = enhanced_dfs
- self._modeling_type_assigned = None
- self._grating_type_assigned = None
+ self.use_pinv = use_pinv
@property
def ucell(self):
@@ -54,6 +92,7 @@ def ucell(self):
def ucell(self, ucell):
if isinstance(ucell, (torch.Tensor, np.ndarray)): # Raster
+ self._modeling_type_assigned = 0
if ucell.dtype in (torch.complex128, torch.complex64):
dtype = self.type_complex
self._ucell = ucell.to(device=self.device, dtype=dtype)
@@ -70,6 +109,7 @@ def ucell(self, ucell):
raise ValueError
elif type(ucell) is list: # Vector
+ self._modeling_type_assigned = 1
self._ucell = ucell
elif ucell is None:
self._ucell = ucell
@@ -80,22 +120,25 @@ def ucell(self, ucell):
def modeling_type_assigned(self):
return self._modeling_type_assigned
- @modeling_type_assigned.setter
- def modeling_type_assigned(self, modeling_type_assigned):
- self._modeling_type_assigned = modeling_type_assigned
+ # @modeling_type_assigned.setter
+ # def modeling_type_assigned(self, modeling_type_assigned):
+ # self._modeling_type_assigned = modeling_type_assigned
- def _assign_modeling_type(self):
+ def _assign_grating_type(self):
+ # self.modeling_type_assigned = 0
+ # self._grating_type_assigned = 1 # else
- if isinstance(self.ucell, torch.Tensor): # Raster
- self.modeling_type_assigned = 0
- if (self.ucell.shape[1] == 1) and (self.pol in (0, 1)) and (self.phi % (2 * np.pi) == 0) and (self.fto[1] == 0):
- self._grating_type_assigned = 0 # 1D TE and TM only
+ if self.modeling_type_assigned == 0:
+ if self.ucell.shape[1] == 1:
+ if (self.pol in (0, 1)) and (self.phi is None) and (self.fto[1] == 0):
+ self._grating_type_assigned = 0 # 1D TE and TM only
+ else:
+ self._grating_type_assigned = 1 # 1D conical
else:
- self._grating_type_assigned = 1 # else
+ self._grating_type_assigned = 2 # else
- elif isinstance(self.ucell, list): # Vector
- self.modeling_type_assigned = 1
- self.grating_type_assigned = 1
+ elif self.modeling_type_assigned == 1:
+ self.grating_type_assigned = 2
@property
def grating_type_assigned(self):
@@ -105,57 +148,53 @@ def grating_type_assigned(self):
def grating_type_assigned(self, grating_type_assigned):
self._grating_type_assigned = grating_type_assigned
- def _solve(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
+ def solve_for_conv(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
+ self._assign_grating_type()
if self._grating_type_assigned == 0:
- de_ri, de_ti, rayleigh_R, rayleigh_T, layer_info_list, T1 = self.solve_1d(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
+ result_dict = self.solve_1d(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
+ elif self._grating_type_assigned == 1:
+ result_dict = self.solve_1d_conical(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
else:
- de_ri, de_ti, rayleigh_R, rayleigh_T, layer_info_list, T1 = self.solve_2d(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
+ result_dict = self.solve_2d(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
- return de_ri, de_ti, rayleigh_R, rayleigh_T, layer_info_list, T1
+ res_psi = ResultSubTorch(**result_dict['res']) if 'res' in result_dict else None
+ res_te_inc = ResultSubTorch(**result_dict['res_te_inc']) if 'res_te_inc' in result_dict else None
+ res_tm_inc = ResultSubTorch(**result_dict['res_tm_inc']) if 'res_tm_inc' in result_dict else None
- def solve(self, wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all):
+ result = ResultTorch(res_psi, res_te_inc, res_tm_inc)
- de_ri, de_ti, rayleigh_R, rayleigh_T, layer_info_list, T1 = self._solve(wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
-
- self.rayleigh_R = rayleigh_R
- self.rayleigh_T = rayleigh_T
- self.layer_info_list = layer_info_list
- self.T1 = T1
-
- return de_ri, de_ti
+ return result
def conv_solve(self, **kwargs):
[setattr(self, k, v) for k, v in kwargs.items()] # needed for optimization
- self._assign_modeling_type()
- if self._modeling_type_assigned == 0: # Raster
+ if self.modeling_type_assigned == 0: # Raster
if self.fourier_type == 0:
epx_conv_all, epy_conv_all, epz_conv_i_all = to_conv_mat_raster_discrete(
self.ucell, self.fto[0], self.fto[1], device=self.device, type_complex=self.type_complex,
- enhanced_dfs=self.enhanced_dfs)
+ enhanced_dfs=self.enhanced_dfs, use_pinv=self.use_pinv)
elif self.fourier_type == 1:
epx_conv_all, epy_conv_all, epz_conv_i_all = to_conv_mat_raster_continuous(
- self.ucell, self.fto[0], self.fto[1], device=self.device, type_complex=self.type_complex)
+ self.ucell, self.fto[0], self.fto[1], device=self.device, type_complex=self.type_complex,
+ use_pinv=self.use_pinv)
else:
raise ValueError("Check 'modeling_type' and 'fourier_type' in 'conv_solve'.")
- elif self._modeling_type_assigned == 1: # Vector
+ elif self.modeling_type_assigned == 1: # Vector
ucell_vector = self.modeling_vector_instruction(self.ucell)
epx_conv_all, epy_conv_all, epz_conv_i_all = to_conv_mat_vector(
- ucell_vector, self.fto[0], self.fto[1], device=self.device, type_complex=self.type_complex)
+ ucell_vector, self.fto[0], self.fto[1], device=self.device, type_complex=self.type_complex,
+ use_pinv=self.use_pinv)
else:
raise ValueError("Check 'modeling_type' and 'fourier_type' in 'conv_solve'.")
- de_ri, de_ti, rayleigh_r, rayleigh_t, layer_info_list, T1 = self._solve(self.wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
+ result = self.solve_for_conv(self.wavelength, epx_conv_all, epy_conv_all, epz_conv_i_all)
- self.layer_info_list = layer_info_list
- self.T1 = T1
-
- return de_ri, de_ti
+ return result
def calculate_field(self, res_x=20, res_y=20, res_z=20):
kx, ky = self.get_kx_ky_vector(wavelength=self.wavelength)
@@ -164,6 +203,9 @@ def calculate_field(self, res_x=20, res_y=20, res_z=20):
res_y = 1
field_cell = field_dist_1d(self.wavelength, kx, self.T1, self.layer_info_list, self.period, self.pol,
res_x=res_x, res_y=res_y, res_z=res_z, device=self.device, type_complex=self.type_complex)
+ elif self._grating_type_assigned == 1:
+ field_cell = field_dist_1d_conical(self.wavelength, kx, ky, self.T1, self.layer_info_list, self.period,
+ res_x=res_x, res_y=res_y, res_z=res_z, device=self.device, type_complex=self.type_complex)
else:
field_cell = field_dist_2d(self.wavelength, kx, ky, self.T1, self.layer_info_list, self.period,
res_x=res_x, res_y=res_y, res_z=res_z, device=self.device, type_complex=self.type_complex)
diff --git a/meent/on_torch/emsolver/transfer_method.py b/meent/on_torch/emsolver/transfer_method.py
index 982bb48..f03ff38 100644
--- a/meent/on_torch/emsolver/transfer_method.py
+++ b/meent/on_torch/emsolver/transfer_method.py
@@ -1,18 +1,19 @@
import torch
-from .primitives import Eig
+from .primitives import Eig, meeinv
-def transfer_1d_1(pol, ff_x, kx, n_top, n_bot, device=torch.device('cpu'), type_complex=torch.complex128):
-
- ff_xy = ff_x * 1
+def transfer_1d_1(pol, kx, n_top, n_bot, device=torch.device('cpu'), type_complex=torch.complex128):
+ ff_x = len(kx)
kz_top = (n_top ** 2 - kx ** 2) ** 0.5
kz_bot = (n_bot ** 2 - kx ** 2) ** 0.5
- kz_top = torch.conj(kz_top)
- kz_bot = torch.conj(kz_bot)
+ # kz_top = torch.conj(kz_top)
+ # kz_bot = torch.conj(kz_bot)
+ kz_top = kz_top.conj()
+ kz_bot = kz_bot.conj()
- F = torch.eye(ff_xy, device=device, dtype=type_complex)
+ F = torch.eye(ff_x, device=device, dtype=type_complex)
if pol == 0: # TE
Kz_bot = torch.diag(kz_bot)
@@ -23,12 +24,13 @@ def transfer_1d_1(pol, ff_x, kx, n_top, n_bot, device=torch.device('cpu'), type_
else:
raise ValueError
- T = torch.eye(ff_xy, device=device, dtype=type_complex)
+ T = torch.eye(ff_x, device=device, dtype=type_complex)
return kz_top, kz_bot, F, G, T
-def transfer_1d_2(pol, kx, epx_conv, epy_conv, epz_conv_i, device=torch.device('cpu'), type_complex=torch.complex128, perturbation=1E-10):
+def transfer_1d_2(pol, kx, epx_conv, epy_conv, epz_conv_i, device=torch.device('cpu'), type_complex=torch.complex128,
+ perturbation=1E-20, use_pinv=False):
Kx = torch.diag(kx)
@@ -53,7 +55,7 @@ def transfer_1d_2(pol, kx, epx_conv, epy_conv, epz_conv_i, device=torch.device('
q = eigenvalues ** 0.5
Q = torch.diag(q)
- V = torch.linalg.inv(epx_conv) @ W @ Q
+ V = meeinv(epx_conv, use_pinv) @ W @ Q
else:
raise ValueError
@@ -61,40 +63,20 @@ def transfer_1d_2(pol, kx, epx_conv, epy_conv, epz_conv_i, device=torch.device('
return W, V, q
-def transfer_1d_2_(k0, q, d, W, V, f, g, fourier_order, T, device=torch.device('cpu'), type_complex=torch.complex128):
-
- X = torch.diag(torch.exp(-k0 * q * d))
-
- W_i = torch.linalg.inv(W)
- V_i = torch.linalg.inv(V)
-
- a = 0.5 * (W_i @ f + V_i @ g)
- b = 0.5 * (W_i @ f - V_i @ g)
-
- a_i = torch.linalg.inv(a)
-
- f = W @ (torch.eye(2 * fourier_order[0] + 1, device=device, dtype=type_complex) + X @ b @ a_i @ X)
- g = V @ (torch.eye(2 * fourier_order[0] + 1, device=device, dtype=type_complex) - X @ b @ a_i @ X)
- T = T @ a_i @ X
-
- return X, f, g, T, a_i, b
-
-
-def transfer_1d_3(k0, W, V, q, d, F, G, T, device=torch.device('cpu'), type_complex=torch.complex128):
-
+def transfer_1d_3(k0, W, V, q, d, F, G, T, device=torch.device('cpu'), type_complex=torch.complex128, use_pinv=False):
ff_x = len(q)
I = torch.eye(ff_x, device=device, dtype=type_complex)
X = torch.diag(torch.exp(-k0 * q * d))
- W_i = torch.linalg.inv(W)
- V_i = torch.linalg.inv(V)
+ W_i = meeinv(W, use_pinv)
+ V_i = meeinv(V, use_pinv)
A = 0.5 * (W_i @ F + V_i @ G)
B = 0.5 * (W_i @ F - V_i @ G)
- A_i = torch.linalg.inv(A)
+ A_i = meeinv(A, use_pinv)
F = W @ (I + X @ B @ A_i @ X)
G = V @ (I - X @ B @ A_i @ X)
@@ -103,54 +85,387 @@ def transfer_1d_3(k0, W, V, q, d, F, G, T, device=torch.device('cpu'), type_comp
return X, F, G, T, A_i, B
-def transfer_1d_4(pol, F, G, T, kz_top, kz_bot, theta, n_top, n_bot, device=torch.device('cpu'), type_complex=torch.complex128):
-
- ff_xy = len(kz_top)
+def transfer_1d_4(pol, ff_x, F, G, T, kz_top, kz_bot, theta, n_top, n_bot, device=torch.device('cpu'),
+ type_complex=torch.complex128, use_pinv=False):
Kz_top = torch.diag(kz_top)
+ kz_top = kz_top.reshape((1, ff_x))
+ kz_bot = kz_bot.reshape((1, ff_x))
- delta_i0 = torch.zeros(ff_xy, device=device, dtype=type_complex)
- delta_i0[ff_xy // 2] = 1
+ delta_i0 = torch.zeros(ff_x, device=device, dtype=type_complex)
+ delta_i0[ff_x // 2] = 1
if pol == 0: # TE
inc_term = 1j * n_top * torch.cos(theta) * delta_i0
- T1 = torch.linalg.inv(G + 1j * Kz_top @ F) @ (1j * Kz_top @ delta_i0 + inc_term)
+ T1 = meeinv(G + 1j * Kz_top @ F, use_pinv) @ (1j * Kz_top @ delta_i0 + inc_term)
elif pol == 1: # TM
inc_term = 1j * delta_i0 * torch.cos(theta) / n_top
- T1 = torch.linalg.inv(G + 1j * Kz_top / (n_top ** 2) @ F) @ (1j * Kz_top / (n_top ** 2) @ delta_i0 + inc_term)
+ T1 = meeinv(G + 1j * Kz_top / (n_top ** 2) @ F, use_pinv) @ (1j * Kz_top / (n_top ** 2) @ delta_i0 + inc_term)
+ else:
+ raise ValueError
- # T1 = np.linalg.inv(G + 1j * YZ_I @ F) @ (1j * YZ_I @ delta_i0 + inc_term)
- R = F @ T1 - delta_i0
- T = T @ T1
+ # T1 = np.linalg.pinv(G + 1j * YZ_I @ F) @ (1j * YZ_I @ delta_i0 + inc_term)
+ R = (F @ T1 - delta_i0).reshape((1, ff_x))
+ T = (T @ T1).reshape((1, ff_x))
- de_ri = torch.real(R * torch.conj(R) * kz_top / (n_top * torch.cos(theta)))
+ # de_ri = np.real(np.real(R * np.conj(R) * kz_top / (n_top * np.cos(theta))))
+ # de_ri = np.real(R * np.conj(R) * np.real(kz_top / (n_top * np.cos(theta))))
+ de_ri = (R * R.conj() * (kz_top / (n_top * torch.cos(theta))).real).real
if pol == 0:
- de_ti = T * torch.conj(T) * torch.real(kz_bot / (n_top * torch.cos(theta)))
+ # de_ti = np.real(T * np.conj(T) * np.real(kz_bot / (n_top * np.cos(theta))))
+ # de_ti = np.real(T * np.conj(T) * np.real(kz_bot / (n_top * np.cos(theta))))
+ de_ti = (T * T.conj() * (kz_bot / (n_top * torch.cos(theta))).real).real
+ R_s = R
+ R_p = torch.zeros(R.shape)
+ T_s = T
+ T_p = torch.zeros(T.shape)
+ de_ri_s = de_ri
+ de_ri_p = torch.zeros(de_ri.shape)
+ de_ti_s = de_ti
+ de_ti_p = torch.zeros(de_ri.shape)
+
elif pol == 1:
- de_ti = T * torch.conj(T) * torch.real(kz_bot / n_bot ** 2) / (torch.cos(theta) / n_top)
+ # de_ti = np.real(T * np.conj(T) * np.real(kz_bot / n_bot ** 2) / (np.cos(theta) / n_top))
+ # de_ti = np.real(T * np.conj(T) * np.real(kz_bot / n_bot ** 2 / (np.cos(theta) / n_top)))
+ de_ti = (T * T.conj() * (kz_bot / n_bot ** 2 / (torch.cos(theta) / n_top)).real).real
+ R_s = torch.zeros(R.shape)
+ R_p = R
+ T_s = torch.zeros(T.shape)
+ T_p = T
+ de_ri_s = torch.zeros(de_ri.shape)
+ de_ri_p = de_ri
+ de_ti_s = torch.zeros(de_ri.shape)
+ de_ti_p = de_ti
else:
raise ValueError
- return de_ri.real, de_ti.real, T1, [R], [T]
+ res = {'R_s': R_s, 'R_p': R_p, 'T_s': T_s, 'T_p': T_p,
+ 'de_ri': de_ri, 'de_ri_s': de_ri_s, 'de_ri_p': de_ri_p,
+ 'de_ti': de_ti, 'de_ti_s': de_ti_s, 'de_ti_p': de_ti_p,
+ }
+ result = {'res': res}
-def transfer_2d_1(ff_x, ff_y, kx, ky, n_top, n_bot, device=torch.device('cpu'), type_complex=torch.complex128):
+ return result, T1
+
+def transfer_1d_conical_1(kx, ky, n_top, n_bot, device='cpu', type_complex=torch.complex128):
+ ff_x = len(kx)
+ ff_y = len(ky)
ff_xy = ff_x * ff_y
I = torch.eye(ff_xy, device=device, dtype=type_complex)
O = torch.zeros((ff_xy, ff_xy), device=device, dtype=type_complex)
+ # TODO: cleaning
+ # ky = k0 * n_I * torch.sin(theta) * torch.sin(phi)
+ #
+ # k_I_z = (k0 ** 2 * n_I ** 2 - kx_vector ** 2 - ky ** 2) ** 0.5
+ # k_II_z = (k0 ** 2 * n_II ** 2 - kx_vector ** 2 - ky ** 2) ** 0.5
+ #
+ # k_I_z = torch.conj(k_I_z.flatten())
+ # k_II_z = torch.conj(k_II_z.flatten())
+ #
+ # Kx = torch.diag(kx_vector / k0)
+
+
kz_top = (n_top ** 2 - kx ** 2 - ky.reshape((-1, 1)) ** 2) ** 0.5
kz_bot = (n_bot ** 2 - kx ** 2 - ky.reshape((-1, 1)) ** 2) ** 0.5
- kz_top = torch.conj(kz_top).flatten()
- kz_bot = torch.conj(kz_bot).flatten()
+ kz_top = kz_top.flatten().conj()
+ kz_bot = kz_bot.flatten().conj()
+
+
+ # varphi = torch.arctan(ky / kx_vector)
varphi = torch.arctan(ky.reshape((-1, 1)) / kx).flatten()
+ Kz_bot = torch.diag(kz_bot)
+
+
+ # Y_I = torch.diag(k_I_z / k0)
+ # Y_II = torch.diag(k_II_z / k0)
+ #
+ # Z_I = torch.diag(k_I_z / (k0 * n_I ** 2))
+ # Z_II = torch.diag(k_II_z / (k0 * n_II ** 2))
+
+ big_F = torch.cat(
+ [
+ torch.cat([I, O], dim=1),
+ torch.cat([O, 1j * Kz_bot / (n_bot ** 2)], dim=1),
+ ]
+ )
+
+ big_G = torch.cat(
+ [
+ torch.cat([1j * Kz_bot, O], dim=1),
+ torch.cat([O, I], dim=1),
+ ]
+ )
+
+ big_T = torch.eye(2*ff_xy, device=device, dtype=type_complex)
+ return kz_top, kz_bot, varphi, big_F, big_G, big_T
+
+ # return Kx, ky, k_I_z, k_II_z, varphi, Y_I, Y_II, Z_I, Z_II, big_F, big_G, big_T
+
+
+# def transfer_1d_conical_2(k0, Kx, ky, E_conv, E_i, o_E_conv_i, ff, d, varphi, big_F, big_G, big_T,
+# device='cpu', type_complex=torch.complex128, perturbation=1E-10):
+def transfer_1d_conical_2(kx, ky, epx_conv, epy_conv, epz_conv_i, device='cpu', type_complex=torch.complex128,
+ perturbation=1E-20, use_pinv=False):
+
+ ff_x = len(kx)
+ ff_y = len(ky)
+ ff_xy = ff_x * ff_y
+
+ I = torch.eye(ff_xy, device=device, dtype=type_complex)
+
+ Kx = torch.diag(kx.tile(ff_y).flatten())
+ Ky = torch.diag(ky.reshape((-1, 1)).tile(ff_x).flatten())
+
+ A = Kx ** 2 - epy_conv
+ B = Kx @ epz_conv_i @ Kx - I
+
+ Omega2_RL = Ky ** 2 + A
+ Omega2_LR = Ky ** 2 + B @ epx_conv
+
+ Eig.perturbation = perturbation
+ eigenvalues_1, W_1 = Eig.apply(Omega2_RL)
+ eigenvalues_2, W_2 = Eig.apply(Omega2_LR)
+
+ q_1 = eigenvalues_1 ** 0.5
+ q_2 = eigenvalues_2 ** 0.5
+
+ Q_1 = torch.diag(q_1)
+ Q_2 = torch.diag(q_2)
+
+ A_i = meeinv(A, use_pinv)
+ B_i = meeinv(B, use_pinv)
+
+ V_11 = A_i @ W_1 @ Q_1
+ V_12 = Ky @ A_i @ Kx @ W_2
+ V_21 = Ky @ B_i @ Kx @ epz_conv_i @ W_1
+ V_22 = B_i @ W_2 @ Q_2
+
+ W = torch.cat([W_1, W_2], dim=1)
+ V = torch.cat(
+ [
+ torch.cat([V_11, V_12], dim=1),
+ torch.cat([V_21, V_22], dim=1),
+ ])
+
+ q = torch.hstack([q_1, q_2])
+
+ return W, V, q
+
+
+# def transfer_1d_conical_3(big_F, big_G, big_T, Z_I, Y_I, psi, theta, ff, delta_i0, k_I_z, k0, n_I, n_II, k_II_z,
+# device='cpu', type_complex=torch.complex128):
+def transfer_1d_conical_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, device='cpu', type_complex=torch.complex128,
+ use_pinv=False):
+
+ ff_xy = len(q) // 2
+ I = torch.eye(ff_xy, device=device, dtype=type_complex)
+ O = torch.zeros((ff_xy, ff_xy), device=device, dtype=type_complex)
+
+ q_1 = q[:ff_xy]
+ q_2 = q[ff_xy:]
+
+ W_1 = W[:, :ff_xy]
+ W_2 = W[:, ff_xy:]
+
+ V_11 = V[:ff_xy, :ff_xy]
+ V_12 = V[:ff_xy, ff_xy:]
+ V_21 = V[ff_xy:, :ff_xy]
+ V_22 = V[ff_xy:, ff_xy:]
+
+
+ X_1 = torch.diag(torch.exp(-k0 * q_1 * d))
+ X_2 = torch.diag(torch.exp(-k0 * q_2 * d))
+
+ F_c = torch.diag(torch.cos(varphi))
+ F_s = torch.diag(torch.sin(varphi))
+
+ V_ss = F_c @ V_11
+ V_sp = F_c @ V_12 - F_s @ W_2
+ W_ss = F_c @ W_1 + F_s @ V_21
+ W_sp = F_s @ V_22
+ W_ps = F_s @ V_11
+ W_pp = F_c @ W_2 + F_s @ V_12
+ V_ps = F_c @ V_21 - F_s @ W_1
+ V_pp = F_c @ V_22
+
+ big_I = torch.eye(2 * (len(I)), device=device, dtype=type_complex)
+
+ big_X = torch.cat([
+ torch.cat([X_1, O], dim=1),
+ torch.cat([O, X_2], dim=1)])
+
+ big_W = torch.cat([
+ torch.cat([V_ss, V_sp], dim=1),
+ torch.cat([W_ps, W_pp], dim=1)])
+
+ big_V = torch.cat([
+ torch.cat([W_ss, W_sp], dim=1),
+ torch.cat([V_ps, V_pp], dim=1)])
+
+ big_W_i = meeinv(big_W, use_pinv)
+ big_V_i = meeinv(big_V, use_pinv)
+
+ big_A = 0.5 * (big_W_i @ big_F + big_V_i @ big_G)
+ big_B = 0.5 * (big_W_i @ big_F - big_V_i @ big_G)
+
+ big_A_i = meeinv(big_A, use_pinv)
+
+ big_F = big_W @ (big_I + big_X @ big_B @ big_A_i @ big_X)
+ big_G = big_V @ (big_I - big_X @ big_B @ big_A_i @ big_X)
+
+ big_T = big_T @ big_A_i @ big_X
+
+ return big_X, big_F, big_G, big_T, big_A_i, big_B
+
+
+def transfer_1d_conical_4(ff_x, ff_y, big_F, big_G, big_T, kz_top, kz_bot, psi, theta, n_top, n_bot, device='cpu',
+ type_complex=torch.complex128, use_pinv=False):
+
+ ff_xy = ff_x * ff_y
+
+ Kz_top = torch.diag(kz_top)
+ kz_top = kz_top.reshape((ff_y, ff_x))
+ kz_bot = kz_bot.reshape((ff_y, ff_x))
+
+ I = torch.eye(ff_xy, device=device, dtype=type_complex)
+ O = torch.zeros((ff_xy, ff_xy), device=device, dtype=type_complex)
+
+ big_F_11 = big_F[:ff_xy, :ff_xy]
+ big_F_12 = big_F[:ff_xy, ff_xy:]
+ big_F_21 = big_F[ff_xy:, :ff_xy]
+ big_F_22 = big_F[ff_xy:, ff_xy:]
+
+ big_G_11 = big_G[:ff_xy, :ff_xy]
+ big_G_12 = big_G[:ff_xy, ff_xy:]
+ big_G_21 = big_G[ff_xy:, :ff_xy]
+ big_G_22 = big_G[ff_xy:, ff_xy:]
+
+ delta_i0 = torch.zeros((ff_xy, 1), device=device, dtype=type_complex)
+ delta_i0[ff_xy // 2, 0] = 1
+
+ # Final Equation in form of AX=B
+ final_A = torch.cat(
+ [
+ torch.cat([I, O, -big_F_11, -big_F_12], dim=1),
+ torch.cat([O, -1j * Kz_top / (n_top ** 2), -big_F_21, -big_F_22], dim=1),
+ torch.cat([-1j * Kz_top, O, -big_G_11, -big_G_12], dim=1),
+ torch.cat([O, I, -big_G_21, -big_G_22], dim=1),
+ ]
+ )
+
+ final_B = torch.cat(
+ [
+ torch.cat([-torch.sin(psi) * delta_i0], dim=1),
+ torch.cat([torch.cos(psi) * torch.cos(theta) * delta_i0], dim=1),
+ torch.cat([-1j * torch.sin(psi) * n_top * torch.cos(theta) * delta_i0], dim=1),
+ torch.cat([-1j * n_top * torch.cos(psi) * delta_i0], dim=1),
+ ]
+ )
+
+ final_A_inv = meeinv(final_A, use_pinv)
+ final_RT = final_A_inv @ final_B
+
+ R_s = final_RT[:ff_xy, :].reshape((ff_y, ff_x))
+ R_p = final_RT[ff_xy: 2 * ff_xy, :].reshape((ff_y, ff_x))
+
+ big_T1 = final_RT[2 * ff_xy:, :]
+ big_T_tetm = big_T.clone().detach()
+ big_T = big_T @ big_T1
+
+ T_s = big_T[:ff_xy, :].reshape((ff_y, ff_x))
+ T_p = big_T[ff_xy:, :].reshape((ff_y, ff_x))
+
+ de_ri_s = (R_s * R_s.conj() * (kz_top / (n_top * torch.cos(theta))).real).real
+ de_ri_p = (R_p * R_p.conj() * (kz_top / n_top ** 2 / (n_top * torch.cos(theta))).real).real
+
+ de_ti_s = (T_s * T_s.conj() * (kz_bot / (n_top * torch.cos(theta))).real).real
+ de_ti_p = (T_p * T_p.conj() * (kz_bot / n_bot ** 2 / (n_top * torch.cos(theta))).real).real
+
+ de_ri = de_ri_s + de_ri_p
+ de_ti = de_ti_s + de_ti_p
+
+ res = {'R_s': R_s, 'R_p': R_p, 'T_s': T_s, 'T_p': T_p,
+ 'de_ri_s': de_ri_s, 'de_ri_p': de_ri_p, 'de_ri': de_ri,
+ 'de_ti_s': de_ti_s, 'de_ti_p': de_ti_p, 'de_ti': de_ti}
+ # TE TM incidence
+ psi_tm = torch.tensor(0, dtype=type_complex)
+ final_B_tm = torch.cat(
+ [
+ torch.cat([-torch.sin(psi_tm) * delta_i0], dim=1),
+ torch.cat([torch.cos(psi_tm) * torch.cos(theta) * delta_i0], dim=1),
+ torch.cat([-1j * torch.sin(psi_tm) * n_top * torch.cos(theta) * delta_i0], dim=1),
+ torch.cat([-1j * n_top * torch.cos(psi_tm) * delta_i0], dim=1),
+ ]
+ )
+
+ psi_te = torch.tensor(torch.pi / 2, dtype=type_complex)
+ final_B_te = torch.cat(
+ [
+ torch.cat([-torch.sin(psi_te) * delta_i0], dim=1),
+ torch.cat([torch.cos(psi_te) * torch.cos(theta) * delta_i0], dim=1),
+ torch.cat([-1j * torch.sin(psi_te) * n_top * torch.cos(theta) * delta_i0], dim=1),
+ torch.cat([-1j * n_top * torch.cos(psi_te) * delta_i0], dim=1),
+ ]
+ )
+
+ final_B_tetm = torch.hstack([final_B_te, final_B_tm])
+ final_RT_tetm = final_A_inv @ final_B_tetm
+
+ R_s_tetm = final_RT_tetm[:ff_xy, :].T.reshape((2, ff_y, ff_x))
+ R_p_tetm = final_RT_tetm[ff_xy: 2 * ff_xy, :].T.reshape((2, ff_y, ff_x))
+
+ big_T1_tetm = final_RT_tetm[2 * ff_xy:, :]
+ big_T_tetm = big_T_tetm @ big_T1_tetm
+
+ T_s_tetm = big_T_tetm[:ff_xy, :].T.reshape((2, ff_y, ff_x))
+ T_p_tetm = big_T_tetm[ff_xy:, :].T.reshape((2, ff_y, ff_x))
+
+ de_ri_s_tetm = (R_s_tetm * R_s_tetm.conj() * (kz_top / (n_top * torch.cos(theta))).real).real
+ de_ri_p_tetm = (R_p_tetm * R_p_tetm.conj() * (kz_top / n_top ** 2 / (n_top * torch.cos(theta))).real).real
+
+ de_ti_s_tetm = (T_s_tetm * T_s_tetm.conj() * (kz_bot / (n_top * torch.cos(theta))).real).real
+ de_ti_p_tetm = (T_p_tetm * T_p_tetm.conj() * (kz_bot / n_bot ** 2 / (n_top * torch.cos(theta))).real).real
+
+ de_ri_tetm = de_ri_s_tetm + de_ri_p_tetm
+ de_ti_tetm = de_ti_s_tetm + de_ti_p_tetm
+
+ res_te_inc = {'R_s': R_s_tetm[0], 'R_p': R_p_tetm[0], 'T_s': T_s_tetm[0], 'T_p': T_p_tetm[0],
+ 'de_ri_s': de_ri_s_tetm[0], 'de_ri_p': de_ri_p_tetm[0], 'de_ri': de_ri_tetm[0],
+ 'de_ti_s': de_ti_s_tetm[0], 'de_ti_p': de_ti_p_tetm[0], 'de_ti': de_ti_tetm[0]}
+
+ res_tm_inc = {'R_s': R_s_tetm[1], 'R_p': R_p_tetm[1], 'T_s': T_s_tetm[1], 'T_p': T_p_tetm[1],
+ 'de_ri_s': de_ri_s_tetm[1], 'de_ri_p': de_ri_p_tetm[1], 'de_ri': de_ri_tetm[1],
+ 'de_ti_s': de_ti_s_tetm[1], 'de_ti_p': de_ti_p_tetm[1], 'de_ti': de_ti_tetm[1]}
+
+ result = {'res': res, 'res_tm_inc': res_tm_inc, 'res_te_inc': res_te_inc}
+
+ return result, big_T1
+
+
+def transfer_2d_1(kx, ky, n_top, n_bot, device=torch.device('cpu'), type_complex=torch.complex128):
+ ff_x = len(kx)
+ ff_y = len(ky)
+ ff_xy = ff_x * ff_y
+
+ I = torch.eye(ff_xy, device=device, dtype=type_complex)
+ O = torch.zeros((ff_xy, ff_xy), device=device, dtype=type_complex)
+
+ kz_top = (n_top ** 2 - kx ** 2 - ky.reshape((-1, 1)) ** 2) ** 0.5
+ kz_bot = (n_bot ** 2 - kx ** 2 - ky.reshape((-1, 1)) ** 2) ** 0.5
+
+ kz_top = kz_top.flatten().conj()
+ kz_bot = kz_bot.flatten().conj()
+
+ varphi = torch.arctan(ky.reshape((-1, 1)) / kx).flatten()
Kz_bot = torch.diag(kz_bot)
big_F = torch.cat(
@@ -173,18 +488,14 @@ def transfer_2d_1(ff_x, ff_y, kx, ky, n_top, n_bot, device=torch.device('cpu'),
def transfer_2d_2(kx, ky, epx_conv, epy_conv, epz_conv_i, device=torch.device('cpu'), type_complex=torch.complex128,
- perturbation=1E-10):
+ perturbation=1E-20, use_pinv=False):
ff_x = len(kx)
ff_y = len(ky)
ff_xy = ff_x * ff_y
- # I = np.eye(ff_y * ff_x, dtype=type_complex)
I = torch.eye(ff_xy, device=device, dtype=type_complex)
- # Kx = torch.diag(torch.tile(kx, ff_y).flatten())
- # Ky = torch.diag(torch.tile(ky.reshape((-1, 1)), ff_x).flatten())
-
Kx = torch.diag(kx.tile(ff_y).flatten())
Ky = torch.diag(ky.reshape((-1, 1)).tile(ff_x).flatten())
@@ -202,7 +513,7 @@ def transfer_2d_2(kx, ky, epx_conv, epy_conv, epz_conv_i, device=torch.device('c
q = eigenvalues ** 0.5
Q = torch.diag(q)
- Q_i = torch.linalg.inv(Q)
+ Q_i = meeinv(Q, use_pinv)
Omega_R = torch.cat(
[
torch.cat([-Kx @ Ky, Kx ** 2 - epy_conv], dim=1),
@@ -214,14 +525,15 @@ def transfer_2d_2(kx, ky, epx_conv, epy_conv, epz_conv_i, device=torch.device('c
return W, V, q
-def transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, device=torch.device('cpu'), type_complex=torch.complex128):
+def transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, device=torch.device('cpu'),
+ type_complex=torch.complex128, use_pinv=False):
ff_xy = len(q)//2
I = torch.eye(ff_xy, device=device, dtype=type_complex)
O = torch.zeros((ff_xy, ff_xy), device=device, dtype=type_complex)
- q1 = q[:ff_xy]
- q2 = q[ff_xy:]
+ q_1 = q[:ff_xy]
+ q_2 = q[ff_xy:]
W_11 = W[:ff_xy, :ff_xy]
W_12 = W[:ff_xy, ff_xy:]
@@ -233,8 +545,8 @@ def transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, device=torch.devi
V_21 = V[ff_xy:, :ff_xy]
V_22 = V[ff_xy:, ff_xy:]
- X_1 = torch.diag(torch.exp(-k0 * q1 * d))
- X_2 = torch.diag(torch.exp(-k0 * q2 * d))
+ X_1 = torch.diag(torch.exp(-k0 * q_1 * d))
+ X_2 = torch.diag(torch.exp(-k0 * q_2 * d))
F_c = torch.diag(torch.cos(varphi))
F_s = torch.diag(torch.sin(varphi))
@@ -263,13 +575,13 @@ def transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, device=torch.devi
torch.cat([V_ss, V_sp], dim=1),
torch.cat([V_ps, V_pp], dim=1)])
- big_W_i = torch.linalg.inv(big_W)
- big_V_i = torch.linalg.inv(big_V)
+ big_W_i = meeinv(big_W, use_pinv)
+ big_V_i = meeinv(big_V, use_pinv)
big_A = 0.5 * (big_W_i @ big_F + big_V_i @ big_G)
big_B = 0.5 * (big_W_i @ big_F - big_V_i @ big_G)
- big_A_i = torch.linalg.inv(big_A)
+ big_A_i = meeinv(big_A, use_pinv)
big_F = big_W @ (big_I + big_X @ big_B @ big_A_i @ big_X)
big_G = big_V @ (big_I - big_X @ big_B @ big_A_i @ big_X)
@@ -279,12 +591,14 @@ def transfer_2d_3(k0, W, V, q, d, varphi, big_F, big_G, big_T, device=torch.devi
return big_X, big_F, big_G, big_T, big_A_i, big_B
-def transfer_2d_4(big_F, big_G, big_T, kz_top, kz_bot, psi, theta, n_top, n_bot,
- device=torch.device('cpu'), type_complex=torch.complex128):
+def transfer_2d_4(ff_x, ff_y, big_F, big_G, big_T, kz_top, kz_bot, psi, theta, n_top, n_bot,
+ device=torch.device('cpu'), type_complex=torch.complex128, use_pinv=False):
- ff_xy = len(big_F) // 2
+ ff_xy = ff_x * ff_y
Kz_top = torch.diag(kz_top)
+ kz_top = kz_top.reshape((ff_y, ff_x))
+ kz_bot = kz_bot.reshape((ff_y, ff_x))
I = torch.eye(ff_xy, device=device, dtype=type_complex)
O = torch.zeros((ff_xy, ff_xy), device=device, dtype=type_complex)
@@ -321,21 +635,82 @@ def transfer_2d_4(big_F, big_G, big_T, kz_top, kz_bot, psi, theta, n_top, n_bot,
]
)
- final_RT = torch.linalg.inv(final_A) @ final_B
+ final_A_inv = meeinv(final_A, use_pinv)
+ final_RT = final_A_inv @ final_B
- R_s = final_RT[:ff_xy, :].flatten() # TODO: why flatten?
- R_p = final_RT[ff_xy:2 * ff_xy, :].flatten()
+ R_s = final_RT[:ff_xy, :].reshape((ff_y, ff_x))
+ R_p = final_RT[ff_xy: 2 * ff_xy, :].reshape((ff_y, ff_x))
big_T1 = final_RT[2 * ff_xy:, :]
+ big_T_tetm = big_T.clone().detach()
big_T = big_T @ big_T1
- T_s = big_T[:ff_xy, :].flatten()
- T_p = big_T[ff_xy:, :].flatten()
+ T_s = big_T[:ff_xy, :].reshape((ff_y, ff_x))
+ T_p = big_T[ff_xy:, :].reshape((ff_y, ff_x))
+
+ de_ri_s = (R_s * R_s.conj() * (kz_top / (n_top * torch.cos(theta))).real).real
+ de_ri_p = (R_p * R_p.conj() * (kz_top / n_top ** 2 / (n_top * torch.cos(theta))).real).real
+
+ de_ti_s = (T_s * T_s.conj() * (kz_bot / (n_top * torch.cos(theta))).real).real
+ de_ti_p = (T_p * T_p.conj() * (kz_bot / n_bot ** 2 / (n_top * torch.cos(theta))).real).real
+
+ de_ri = de_ri_s + de_ri_p
+ de_ti = de_ti_s + de_ti_p
+
+ res = {'R_s': R_s, 'R_p': R_p, 'T_s': T_s, 'T_p': T_p,
+ 'de_ri_s': de_ri_s, 'de_ri_p': de_ri_p, 'de_ri': de_ri,
+ 'de_ti_s': de_ti_s, 'de_ti_p': de_ti_p, 'de_ti': de_ti}
+
+ # TE TM incidence
+ psi_tm = torch.tensor(0, dtype=type_complex)
+ final_B_tm = torch.cat(
+ [
+ torch.cat([-torch.sin(psi_tm) * delta_i0], dim=1),
+ torch.cat([torch.cos(psi_tm) * torch.cos(theta) * delta_i0], dim=1),
+ torch.cat([-1j * torch.sin(psi_tm) * n_top * torch.cos(theta) * delta_i0], dim=1),
+ torch.cat([-1j * n_top * torch.cos(psi_tm) * delta_i0], dim=1),
+ ]
+ )
+
+ psi_te = torch.tensor(torch.pi/2, dtype=type_complex)
+ final_B_te = torch.cat(
+ [
+ torch.cat([-torch.sin(psi_te) * delta_i0], dim=1),
+ torch.cat([torch.cos(psi_te) * torch.cos(theta) * delta_i0], dim=1),
+ torch.cat([-1j * torch.sin(psi_te) * n_top * torch.cos(theta) * delta_i0], dim=1),
+ torch.cat([-1j * n_top * torch.cos(psi_te) * delta_i0], dim=1),
+ ]
+ )
+
+ final_B_tetm = torch.hstack([final_B_te, final_B_tm])
+ final_RT_tetm = final_A_inv @ final_B_tetm
+
+ R_s_tetm = final_RT_tetm[:ff_xy, :].T.reshape((2, ff_y, ff_x))
+ R_p_tetm = final_RT_tetm[ff_xy: 2 * ff_xy, :].T.reshape((2, ff_y, ff_x))
+
+ big_T1_tetm = final_RT_tetm[2 * ff_xy:, :]
+ big_T_tetm = big_T_tetm @ big_T1_tetm
+
+ T_s_tetm = big_T_tetm[:ff_xy, :].T.reshape((2, ff_y, ff_x))
+ T_p_tetm = big_T_tetm[ff_xy:, :].T.reshape((2, ff_y, ff_x))
+
+ de_ri_s_tetm = (R_s_tetm * R_s_tetm.conj() * (kz_top / (n_top * torch.cos(theta))).real).real
+ de_ri_p_tetm = (R_p_tetm * R_p_tetm.conj() * (kz_top / n_top ** 2 / (n_top * torch.cos(theta))).real).real
+
+ de_ti_s_tetm = (T_s_tetm * T_s_tetm.conj() * (kz_bot / (n_top * torch.cos(theta))).real).real
+ de_ti_p_tetm = (T_p_tetm * T_p_tetm.conj() * (kz_bot / n_bot ** 2 / (n_top * torch.cos(theta))).real).real
+
+ de_ri_tetm = de_ri_s_tetm + de_ri_p_tetm
+ de_ti_tetm = de_ti_s_tetm + de_ti_p_tetm
+
+ res_te_inc = {'R_s': R_s_tetm[0], 'R_p': R_p_tetm[0], 'T_s': T_s_tetm[0], 'T_p': T_p_tetm[0],
+ 'de_ri_s': de_ri_s_tetm[0], 'de_ri_p': de_ri_p_tetm[0], 'de_ri': de_ri_tetm[0],
+ 'de_ti_s': de_ti_s_tetm[0], 'de_ti_p': de_ti_p_tetm[0], 'de_ti': de_ti_tetm[0]}
- de_ri = R_s * torch.conj(R_s) * torch.real(kz_top / (n_top * torch.cos(theta))) \
- + R_p * torch.conj(R_p) * torch.real((kz_top / n_top ** 2) / (n_top * torch.cos(theta)))
+ res_tm_inc = {'R_s': R_s_tetm[1], 'R_p': R_p_tetm[1], 'T_s': T_s_tetm[1], 'T_p': T_p_tetm[1],
+ 'de_ri_s': de_ri_s_tetm[1], 'de_ri_p': de_ri_p_tetm[1], 'de_ri': de_ri_tetm[1],
+ 'de_ti_s': de_ti_s_tetm[1], 'de_ti_p': de_ti_p_tetm[1], 'de_ti': de_ti_tetm[1]}
- de_ti = T_s * torch.conj(T_s) * torch.real(kz_bot / (n_top * torch.cos(theta))) \
- + T_p * torch.conj(T_p) * torch.real((kz_bot / n_bot ** 2) / (n_top * torch.cos(theta)))
+ result = {'res': res, 'res_tm_inc': res_tm_inc, 'res_te_inc': res_te_inc}
- return de_ri.real, de_ti.real, big_T1, [R_s, R_p], [T_s, T_p]
+ return result, big_T1
diff --git a/meent/on_torch/mee.py b/meent/on_torch/mee.py
index 44d9b87..1a24f1b 100644
--- a/meent/on_torch/mee.py
+++ b/meent/on_torch/mee.py
@@ -19,7 +19,7 @@ def __init__(self, device=0, type_complex=0, *args, **kwargs):
self._device = device
else:
raise ValueError('device')
-
+ #
# type_complex
if type_complex in (0, torch.complex128, np.complex128):
self._type_complex = torch.complex128
@@ -27,13 +27,13 @@ def __init__(self, device=0, type_complex=0, *args, **kwargs):
self._type_complex = torch.complex64
else:
raise ValueError('Torch type_complex')
-
+ #
self._type_float = torch.float64 if self._type_complex is not torch.complex64 else torch.float32
self._type_int = torch.int64 if self._type_complex is not torch.complex64 else torch.int32
# self.perturbation = perturbation
-
+ #
self.device = device
- self.type_complex = type_complex
+ # self.type_complex = type_complex
ModelingTorch.__init__(self, device=device, type_complex=type_complex, *args, **kwargs)
RCWATorch.__init__(self, device=device, type_complex=type_complex, *args, **kwargs)
diff --git a/meent/on_torch/modeler/modeling.py b/meent/on_torch/modeler/modeling.py
index d3d2ca0..978abe8 100644
--- a/meent/on_torch/modeler/modeling.py
+++ b/meent/on_torch/modeler/modeling.py
@@ -38,7 +38,7 @@ def __init__(self, period=None, *args, **kwargs):
self.mat_table = None
self.ucell_info_list = None
self.period = period
- self.type_complex = torch.complex128
+ # self.type_complex = torch.complex128
# self.type_float = torch.float64
self.film_layer = None
diff --git a/meent/on_torch/optimizer/loss.py b/meent/on_torch/optimizer/loss.py
deleted file mode 100644
index d1a4ca2..0000000
--- a/meent/on_torch/optimizer/loss.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import torch
-
-
-class LossDeflector:
- def __init__(self, x_order=0, y_order=0):
- self.x_order = x_order
- self.y_order = y_order
-
- def __call__(self, value, *args, **kwargs):
- de_ri, de_ti = value
-
- if len(de_ti.shape) == 1:
- c_x = de_ti.shape[0] // 2
- res = de_ti[c_x + self.x_order]
- elif len(de_ti.shape) == 2:
- c_x = de_ti.shape[0] // 2
- c_y = de_ti.shape[1] // 2
- res = de_ti[c_x + self.x_order, c_y + self.y_order]
- else:
- raise ValueError
-
- return res
-
-
-class LossSpectrumL2:
- def __init__(self):
- pass
-
- def __call__(self, pred, target, *args, **kwargs):
- gap = torch.linalg.norm(pred, target)
- return gap
diff --git a/setup.py b/setup.py
index 35ca149..6c4c752 100644
--- a/setup.py
+++ b/setup.py
@@ -1,3 +1,5 @@
+import os
+
from setuptools import setup, find_packages
extras = {
@@ -10,9 +12,14 @@
'tqdm>=4.64.1',
],
}
+# Read in README.md for our long_description
+cwd = os.path.dirname(os.path.abspath(__file__))
+with open(os.path.join(cwd, "README.md"), encoding="utf-8") as f:
+ long_description = f.read()
+
setup(
name='meent',
- version='0.10.0',
+ version='0.11.0',
url='https://github.com/kc-ml2/meent',
author='KC ML2',
author_email='yongha@kc-ml2.com',
@@ -23,6 +30,10 @@
],
extras_require=extras,
python_requires='>=3.8',
+ description=(
+ "Electromagnetic simulation (RCWA) & optimization package in Python"
+ ),
+ long_description=long_description,
long_description_content_type="text/markdown",
package_data={
'meent': ['nk_data/filmetrics/*.txt', 'nk_data/matlab/*.mat'],
diff --git a/tutorials/01-modeling-and-emsolver.ipynb b/tutorials/01-modeling-and-emsolver.ipynb
index 043b7f6..250efce 100644
--- a/tutorials/01-modeling-and-emsolver.ipynb
+++ b/tutorials/01-modeling-and-emsolver.ipynb
@@ -296,11 +296,14 @@
},
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
- "mee = meent.call_mee(backend=0, pol=pol, n_top=n_top, n_bot=n_bot, theta=theta, phi=phi, fto=fto, wavelength=wavelength, period=period, ucell=ucell_1d_s, thickness=thickness, type_complex=type_complex)"
+ "mee = meent.call_mee(backend=0, \n",
+ " pol=pol, n_top=n_top, n_bot=n_bot, theta=theta, fto=fto, \n",
+ " wavelength=wavelength, period=period, ucell=ucell_1d_s, thickness=thickness,\n",
+ " type_complex=type_complex)"
]
},
{
@@ -321,49 +324,34 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "### Diffraction Efficiency"
+ "### Case 1: 1D TE/TM\n",
+ "For 1D TE or TM case, fast calculation can be achieved.\n",
+ "Setting phi as `None` (which is default). \n",
+ "\n",
+ "This returns result of either TE or TM while the general case does both."
]
},
{
- "cell_type": "markdown",
+ "cell_type": "code",
+ "execution_count": 8,
"metadata": {},
+ "outputs": [],
"source": [
- "#### Diffraction"
+ "mee = meent.call_mee(backend=0, pol=pol, n_top=n_top, n_bot=n_bot, theta=theta, fto=fto, \n",
+ " wavelength=wavelength, period=period, ucell=ucell_1d_s, thickness=thickness,\n",
+ " type_complex=type_complex)\n",
+ "mee.fto = [200]\n",
+ "mee.pol = 0 # 0 or 1. Other values will be operated in General form.\n",
+ "mee.phi = None # None is by default. This explicit assign is for demo.\n",
+ "\n",
+ "result = mee.conv_solve()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "\n",
- " Incidence Backward Diffraction \n",
- " (Reflected)\n",
- " || \n",
- " || -1th 0th +1th\n",
- " || order order order\n",
- " || \\ | /\n",
- " || ... \\ | / ...\n",
- " || \\ | / \n",
- " || \\ | / n_top:refractive index of superstrate\n",
- " ____________________________________\n",
- " | Layer 1 |\n",
- " |____________________________________|\n",
- " . z-axis \n",
- " . |\n",
- " . |\n",
- " ____________________________________ |_____ x-axis \n",
- " | Layer N |\n",
- " |____________________________________|\n",
- " n_bot:refractive index of substrate\n",
- " / | \\ \n",
- " / | \\\n",
- " ... / | \\ ...\n",
- " / | \\\n",
- " -2nd -1th 0th +1th +2th\n",
- " order order order order order\n",
- " \n",
- " Forward Diffraction \n",
- " (Transmitted) "
+ "For 1D TE/TM, the result is in `res` in `result`."
]
},
{
@@ -375,82 +363,294 @@
"name": "stdout",
"output_type": "stream",
"text": [
- "time: 0.11336421966552734\n"
+ "Diffraction efficiency: 0.8028173479774741 0.19718265202260613\n"
]
}
],
"source": [
- "t0 = time.time()\n",
- "de_ri, de_ti = mee.conv_solve()\n",
- "print(f'time: ', time.time() - t0)"
+ "res = result.res\n",
+ "de_ri = res.de_ri\n",
+ "de_ti = res.de_ti\n",
+ "print('Diffraction efficiency: ', de_ri.sum(), de_ti.sum())\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Case 2: General\n",
+ "General form includes 1D grating with non TE or TM input, 1D conical and 2D gratings.\n",
+ "\n",
+ "This returns 3 results: result from given polarization(or psi), result from TE incidence, and result from TM incidence."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
+ "outputs": [],
+ "source": [
+ "mee = meent.call_mee(backend=0, pol=pol, n_top=n_top, n_bot=n_bot, theta=theta, phi=phi, fto=fto, \n",
+ " wavelength=wavelength, period=period, ucell=ucell_1d_s, thickness=thickness,\n",
+ " type_complex=type_complex)\n",
+ "mee.fto = [200]\n",
+ "mee.pol = 0.5 \n",
+ "mee.phi = 30\n",
+ "\n",
+ "result = mee.conv_solve()\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The return `result` has 3 sub-result classes each of which contains: result for given polarization (or psi), for TE incidence and for TM incidence."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "Diffraction Efficiency of Reflection: [[0. ]\n",
- " [0.287]\n",
- " [0. ]]\n",
- "Diffraction Efficiency of Transmission: [[0. ]\n",
- " [0.713]\n",
- " [0. ]]\n"
+ "Diffraction efficiency: 0.6154849015525773 0.3845150984544151\n",
+ "Diffraction efficiency: 0.13853479697001853 0.8614652030395099\n",
+ "Diffraction efficiency: 0.7254065902537097 0.2745934097456641\n"
]
}
],
"source": [
- "center = de_ri.shape[0] // 2\n",
+ "res = result.res\n",
+ "res_te_inc = result.res_te_inc\n",
+ "res_tm_inc = result.res_tm_inc\n",
+ "\n",
+ "de_ri, de_ti = res.de_ri, res.de_ti\n",
+ "de_ri_te, de_ti_te = res_te_inc.de_ri, res_te_inc.de_ti\n",
+ "de_ri_tm, de_ti_tm = res_tm_inc.de_ri, res_tm_inc.de_ti\n",
"\n",
- "print('Diffraction Efficiency of Reflection:', np.round(de_ri[center-1:center+2], 3))\n",
- "print('Diffraction Efficiency of Transmission:', np.round(de_ti[center-1:center+2], 3))"
+ "print('Diffraction efficiency: ', de_ri.sum(), de_ti.sum())\n",
+ "print('Diffraction efficiency: ', de_ri_te.sum(), de_ti_te.sum())\n",
+ "print('Diffraction efficiency: ', de_ri_tm.sum(), de_ti_tm.sum())\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "### Field Distribution"
+ "call meent operator by `meent.call_mee`.\n",
+ "Here, backend can be selected with keyword `backend`\n",
+ "\n",
+ "```python\n",
+ "backend = 0 # Numpy backend\n",
+ "backend = 1 # JAX backend\n",
+ "backend = 2 # PyTorch backend\n",
+ "```\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 1.3 RCWA Result"
]
},
{
"cell_type": "code",
- "execution_count": 11,
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "fto = [4, 2]\n",
+ "thickness = [100, 200, 400, 245]\n",
+ "period = [1000, 2000]\n",
+ "\n",
+ "ucell_2d_m = np.array([\n",
+ " [\n",
+ " [0, 1, 0, 1, 1, 0, 1, 0, 1, 1, ],\n",
+ " [1, 1, 1, 1, 1, 1, 1, 0, 1, 1, ],\n",
+ " ],\n",
+ " [\n",
+ " [1, 1, 0, 1, 1, 0, 1, 0, 2, 1, ],\n",
+ " [0, 1, 1, 1, 2, 4, 1, 0, 1, 1, ],\n",
+ " ],\n",
+ " [\n",
+ " [0, 1, 0, 1, 1, 0, 1, 0, 1, 1, ],\n",
+ " [1, 1, 1, 2, 0, 1, 2, 0, 1, 1, ],\n",
+ " ],\n",
+ " [\n",
+ " [0, 1, 0, 1, 1, 1, 1, 0, 1, 1, ],\n",
+ " [0, 1, 3, 1, 1, 1, 1, 1, 1, 1, ],\n",
+ " ],\n",
+ "]) * 4 + 1 # refractive index\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "mee = meent.call_mee(backend=0, pol=pol, n_top=n_top, n_bot=n_bot, theta=theta, phi=phi,\n",
+ " fto=fto, wavelength=wavelength, period=period, ucell=ucell_2d_m, \n",
+ " thickness=thickness, type_complex=type_complex)\n",
+ "mee.pol = 0.5\n",
+ "\n",
+ "result = mee.conv_solve()\n",
+ "\n",
+ "res = result.res\n",
+ "res_te_inc = result.res_te_inc\n",
+ "res_tm_inc = result.res_tm_inc"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### RCWA result"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "time: 0.04631304740905762\n"
+ "R_s, R_p, T_s, T_p, de_ri, de_ri_s, de_ri_p, de_ti, de_ti_s, de_ti_p\n"
]
}
],
"source": [
- "t0 = time.time()\n",
- "field_cell = mee.calculate_field(res_z=100, res_y=1, res_x=100)\n",
- "print(f'time: ', time.time() - t0)"
+ "attrs = vars(res)\n",
+ "print(', '.join(\"%s\" % item for item in attrs))\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "#### ZX direction (Side View)"
+ "Attributes of subresult instance.\n",
+ "\n",
+ "\n",
+ "R_s: reflectivity coefficient (Rayleigh coefficient) from TE component
\n",
+ "R_p: reflectivity coefficient (Rayleigh coefficient) from TM component
\n",
+ "T_s: transmittivity coefficient (Rayleigh coefficient) from TE component
\n",
+ "T_p: transmittivity coefficient (Rayleigh coefficient) from TM component
\n",
+ "de_ri: diffraction efficiency of reflection in total
\n",
+ "de_ri_s: diffraction efficiency of reflection from TE component
\n",
+ "de_ri_p: diffraction efficiency of reflection from TM component
\n",
+ "de_ti: diffraction efficiency of transmission in total
\n",
+ "de_ti_s: diffraction efficiency of transmission from TE component
\n",
+ "de_ti_p: diffraction efficiency of transmission from TM component
\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Diffraction Efficiency"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "\n",
+ " Incidence Backward Diffraction \n",
+ " (Reflected)\n",
+ " || \n",
+ " || -1th 0th +1th\n",
+ " || order order order\n",
+ " || \\ | /\n",
+ " || ... \\ | / ...\n",
+ " || \\ | / \n",
+ " || \\ | / n_top:refractive index of superstrate\n",
+ " ____________________________________\n",
+ " | Layer 1 |\n",
+ " |____________________________________|\n",
+ " . z-axis \n",
+ " . |\n",
+ " . |\n",
+ " ____________________________________ |_____ x-axis \n",
+ " | Layer N |\n",
+ " |____________________________________|\n",
+ " n_bot:refractive index of substrate\n",
+ " / | \\ \n",
+ " / | \\\n",
+ " ... / | \\ ...\n",
+ " / | \\\n",
+ " -2nd -1th 0th +1th +2th\n",
+ " order order order order order\n",
+ " \n",
+ " Forward Diffraction \n",
+ " (Transmitted) "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 1.4 Field construction result"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 1D TE"
]
},
{
"cell_type": "code",
- "execution_count": 12,
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pol = 0 # 0: TE, 1: TM\n",
+ "\n",
+ "n_top = 1 # n_superstrate\n",
+ "n_bot = 1 # n_substrate\n",
+ "\n",
+ "theta = 20 * np.pi / 180\n",
+ "phi = 50 * np.pi / 180\n",
+ "\n",
+ "wavelength = 900\n",
+ "\n",
+ "thickness = [500]\n",
+ "period = [1000]\n",
+ "\n",
+ "fto = [30]\n",
+ "\n",
+ "type_complex = np.complex128\n",
+ "\n",
+ "mee = meent.call_mee(backend=0, pol=pol, n_top=n_top, n_bot=n_bot, theta=theta, fto=fto, \n",
+ " wavelength=wavelength, period=period, ucell=ucell_1d_s, thickness=thickness,\n",
+ " type_complex=type_complex)\n",
+ "\n",
+ "result, field_cell = mee.conv_solve_field(res_z=100, res_y=1, res_x=100)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "ZX direction (Side View)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
"metadata": {},
"outputs": [
{
"data": {
- "image/png": "iVBORw0KGgoAAAANSUhEUgAAAy4AAADcCAYAAACWAfUkAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOz9e3hkV3XnjX/UVaUqlaqkakmWZHW3LeM2vmODjY2B3xtCTJwbDwQmIYSAgYTkCZib38kEJ4DBJBgCAw53yHBJBpwhZAhJhoQkOCF5E8zN4Bl7fIG2LbvbsiRL7ZJUXapSVUm/P/Ze56yza5+6tdqWTX2f5zxVdeqcffZZZ++113ettfcZ2N7e3qaPPvroo48++uijjz766GMXY89jXYE++uijjz766KOPPvroo4926BOXPvroo48++uijjz766GPXo09c+uijjz766KOPPvroo49djz5x6aOPPvroo48++uijjz52PfrEpY8++uijjz766KOPPvrY9egTlz766KOPPvroo48++uhj16NPXProo48++uijjz766KOPXY8+cemjjz766KOPPvroo48+dj36xKWPPvroo48++uijjz762PXoE5c++uijjz766KOPPvroY9ejT1weByiVSlx77bX8zM/8DGNjYwwMDPC5z33Oe+xznvMcBgYGGBgYYM+ePYyMjHDmmWfy8pe/nH/6p3/q+JqvfOUrg3LcLZPJ7NCd9dFHHycaj5X+yOVysf8PDAxw1VVXdXsrffTRx2OIvi7pYzcg+VhXoI/2WF5e5rrrruOUU07hggsu4Bvf+EbL4/fv38/1118PwLFjxzh06BBf/vKX+fznP88v//Iv8/nPf55UKtX2uul0mv/23/5b0/5EItHTffTRRx+PPh4r/dFHH308sdDXJX3sBvSJy+MAJ598Mg899BDT09N873vf4+lPf3rL40dHR/m1X/u1yL73vOc9vOENb+BjH/sYs7OzvPe972173WQy2VROH3308fjCY6U/+uijjycW+rqkj92AfqrY4wDpdJrp6enjKiORSPChD32Ic845h4985COsrq4ed73uvfdeBgYG+OAHP9j03ze/+U0GBgb48z//8+O+Th999NE7dqv+0LjyyivJZDLceeedkf1XXHEFe/fuZX5+fkev10cffXSPx4MumZ2djU1zbxch6uPxgT5x+TFCIpHgpS99KeVymX//93/v6Jzl5eWmbW1tDYAnPelJPOtZz+ILX/hC03lf+MIXyOfzvOAFL9jRe+ijjz4eG+yU/lheXm467o//+I856aSTuPLKK2k0GgB88pOf5B//8R/58Ic/zMzMzI7eSx999PHY4UTqkhtuuIH//t//e2R72tOexp49exgfH9/pW+njMUA/VezHDOeddx4A99xzT9tjjx07xkknndS0/4orruBrX/saAK94xSv4rd/6Le666y7OOussAGq1Gn/xF3/Bi170IrLZ7A7Wvo8++ngssRP6w4dCocCnP/1prrjiCt7znvfwq7/6q/zn//yfeeELX9hPV+2jjycgTpQueeELXxj5/aUvfYnvf//7XHfddZx//vld17OP3Yc+cfkxg6zOsb6+3vbYTCbD3/7t3zbtn5iYCL7/8i//Mm984xv5whe+wLve9S4A/uEf/oHl5eW+wdFHH08w7IT+AHje857XtO+nf/qn+a3f+i2uu+46/vIv/5JMJsMnP/nJ46twH330sStxInWJ4I477uDVr341L3jBC3jrW9/aW0X72HXoE5cfM5RKJQDy+XzbYxOJBJdffnnLYwqFAs9//vO58cYbA+LyhS98gX379vHc5z73+CvcRx997BrstP5w8f73v5+//uu/5tZbb+XGG29kcnKyp3r20UcfuxsnWpesra3xohe9iH379vFnf/ZnDAwM9FTPPnYf+nNcfsxw++23A3Dw4MEdK/MVr3gF9957L9/85jdZX1/nb/7mb3jpS1/Knj395tVHH08knAj9ofGDH/yApaUlAG677bYTco0++ujjsceJ1iWvfOUrmZ+f5ytf+QojIyMn5Bp9PDboR1x+jNBoNLjxxhvJZrM8+9nP3rFyf+ZnfoaTTjqJL3zhC1x66aWUy2Ve/vKX71j5ffTRx2OPE6U/BMeOHeNVr3oV55xzDs985jP5oz/6I37xF3+x7ZKrffTRx+MLJ1qXvOc97+ErX/kKX/7yl4O5t308cdAnLj8maDQavOENb+DOO+/kLW95y456IJLJJC996Uu58cYbufPOOzn//PN5ylOesmPl99FHH48tTqT+EPzu7/4uDzzwAN/61rc488wzuemmm7jyyiv5wQ9+QDqd3vHr9dFHH48+TrQu+frXv85b3/pWfv/3f79pon4fTwz0icvjBB/5yEcoFovB+wz+9m//liNHjgDw+te/ntHR0eDY1dVVPv/5zwNQLpeDt9Xec889/Mqv/EowF6Ud6vV6UI6LX/zFX2R4eDj4/YpXvIIPfehD/Mu//Ev/hVJ99LHL8Fjoj27wz//8z3zsYx/j2muv5WlPexoAn/3sZ3nOc57D2972Nv7oj/5ox6/ZRx99dI/drkte+tKXctJJJ3HGGWc02S/Pe97zmJqa2vFr9vEoY7uPxwVOPfXUbcC73XfffcFxP/ETPxH5L5fLbZ9xxhnbv/Zrv7b9j//4jx1f78orr4y9nntNwbnnnru9Z8+e7SNHjuzAHffRRx87hcdCfwwPD8f+D2y/7nWv297e3t5eW1vbPvXUU7ef9rSnbddqtchxb37zm7f37NmzffPNN3d3w3300ccJwW7WJfI7bvuXf/mXXm65j12Gge3t7e0TxIn6+DHDU5/6VMbGxrjpppse66r00UcfffTRRx999PEEQ3/Zpz52BN/73ve49dZbecUrXvFYV6WPPvroo48++uijjycg+hGXPo4Lt99+O7fccgv/9b/+V5aXl7n33nvJZDKPdbX66KOPPvroo48++niCoR9x6eO48Jd/+Ze86lWvolar8ed//ud90tJHH3300UcfffTRxwlBP+LSRx999NFHH3300Ucffex6nLCIy0c/+lFmZ2fJZDJceumlfOc73zlRl+qjjz6egOjrkD766ON40dcjffTxxMIJIS5f/OIXufrqq7n22mv5/ve/zwUXXMAVV1zB0tLSibhcH3308QRDX4f00Ucfx4u+HumjjyceTkiq2KWXXsrTn/50PvKRjwCwtbXFgQMHeP3rX89b3vKWludubW0xPz9PPp9nYGBgp6vWRx+PGba3t1lfX2dmZoY9e9r7DCqVCpubm7H/Dw4OPmHnFB2PDpHj+3qkjycautUh0FqPPJF1CPRtkT768OHxboskd7rAzc1NbrnlFq655ppg3549e7j88su5+eabm46vVqtUq9Xg94MPPsg555yz09Xqo49dg8OHD7N///6Wx1QqFU4aGqLU4pjp6Wnuu+++J5zh0a0Ogb4e6ePHC53oEGivR56oOgT6tkgffbTD49UW2XHisry8TKPRYGpqKrJ/amqKu+66q+n466+/nne+851N+598+G/57trzuWX/2byf/5e/e+CF8MUUnAPMwMg5iwCs3TwFGRh40jGeP/m/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",
+ "image/png": "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qRuo38GrAx3icpPg4x//OMVi0V/P4E//9E+ZmZkB4P3vfz99fX3+sSsrK/zu7/4uAGtra/5qtRcvXuSf/tN/6o9F2QnVatUvx8YP/MAP0Nvb6///rne9i9/4jd/gb/7mbzoLSnXQwR7DK6E/WsFf//Vf85u/+Zt86EMf4q677gLg05/+NG95y1v4t//23/Jrv/Zru37NDjrooHXsdV3ywz/8w+zfv58jR47U2S9ve9vbGBoa2vVrdnCDsd3BTYHbbrttG3B+nn/+ef+4f/yP/3Hot0wms33kyJHtH/3RH93+i7/4i6av9+53vzvyevY1Ba997Wu39+3btz0zM7MLd9xBBx3sFl4J/dHb2xv5O7D93ve+d3t7e3v7ypUr27fddtv2XXfdtb21tRU67oMf/OD2vn37tr/61a+2dsMddNDBdcFe1iXyf9Tnb/7mb9q55Q72GLq2t7e3rxMn6uBbDK9//evp7+/n0UcffaWr0kEHHXTQQQcddNDBqwydaZ862BU8+eSTnD59mne9612vdFU66KCDDjrooIMOOngVohNx6eCacPbsWZ566in+43/8jywuLvLcc8+RSqVe6Wp10EEHHXTQQQcddPAqQyfi0sE14fd///d5z3vew9bWFr/3e7/XIS0ddNBBBx100EEHHVwXdCIuHXTQQQcddNBBBx100MGex3WLuDz88MOMj4+TSqW49957+drXvna9LtVBBx28CtHRIR100MG1oqNHOujg1YXrQlz+x//4Hzz44IN86EMf4utf/zqve93rePvb387CwsL1uFwHHXTwKkNHh3TQQQfXio4e6aCDVx+uS6rYvffeyxve8AY+/vGPA3D16lXGxsZ4//vfz8/+7M82PPfq1avMzs6SzWbp6ura7ap10MErhu3tbUqlEiMjI+zbt7PPoFKpsLm5Gfl7d3f3q3ZM0bXoEDm+o0c6eLWhVR0CjfXIq1mHQMcW6aADF252WyS+2wVubm7y1FNP8dBDD/n79u3bxwMPPMBXv/rVuuM3NjbY2Njw/3/ppZc4ceLEblergw72DKanpxkdHW14TKVSYX9PD+UGxwwPD/P888+/6gyPVnUIdPRIB99aaEaHwM565NWqQ6Bji3TQwU64WW2RXScui4uL1Go1hoaGQvuHhoY4f/583fEf/vCH+cVf/EVHSc8Al4AKcBm4AlSBdW8rny11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d+5c7Q1veEPtDW94w12tc61mVnI5duxY7UMf+lDoeLfKuYe7j27XIbXawdQjB1WH1Go9PdJD++h2PXIQdUitdnD1SE+HdA+6jrjUarXab/zGb9SOHTtW6+/vr33zN39z7emnn77bVfIBOF+f/OQna7Varfbyyy/X3vzmN9dGR0dryWSyduLEidpP/dRP1TY3N+9qvX/gB36gduTIkVp/f3/t6NGjtR/4gR+ozc3N+b9vb2/XfvRHf7R2zz331NLpdO1f/at/Vbt27dpdrLHBX/zFX9SA2uXLl0PHu1XOPXQHulmH1GoHU48cVB1Sq/X0SA+doZv1yEHUIbXawdUjPR3SPeir1Wq1Oxri6aGHHnrooYceeuihhx56aBNdNcelhx566KGHHnrooYceeujBhR5x6aGHHnrooYceeuihhx66Hj3i0kMPPfTQQw899NBDDz10PXrEpYceeuihhx566KGHHnroevSISw899NBDDz300EMPPfTQ9egRlx566KGHHnrooYceeuih69EjLj300EMPPfTQQw899NBD16NHXHrooYceeuihhx566KGHrkePuPTQQw899NBDDz300EMPXY8ecemhhx566KGHHnrooYceuh494tJDDz300EMPPfTQQw89dD16xKWHHnrooYceeuihhx566Hr8/wOsmHdItqPnAAAAAElFTkSuQmCC",
"text/plain": [
"