TiN TOPS heater transient

TiN TOPS heater transient#

from collections import OrderedDict

import matplotlib.pyplot as plt
import numpy as np
import scipy.constants
from shapely.geometry import LineString, Polygon
from skfem import Basis, ElementTriP0, LinearForm
from skfem.io import from_meshio
from tqdm import tqdm

from femwell.maxwell.waveguide import compute_modes
from femwell.mesh import mesh_from_OrderedDict
from femwell.thermal_transient import solve_thermal_transient

# Simulating the TiN TOPS heater in https://doi.org/10.1364/OE.27.010456

w_sim = 8 * 2
h_clad = 2.8
h_box = 1
w_core = 0.5
h_core = 0.22
offset_heater = 2.2
h_heater = 0.14
w_heater = 2
h_silicon = 3

wavelength = 1.55

polygons = OrderedDict(
    bottom=LineString([(-w_sim / 2, -h_box), (w_sim / 2, -h_box)]),
    core=Polygon(
        [
            (-w_core / 2, 0),
            (-w_core / 2, h_core),
            (w_core / 2, h_core),
            (w_core / 2, 0),
        ]
    ),
    heater=Polygon(
        [
            (-w_heater / 2, offset_heater),
            (-w_heater / 2, offset_heater + h_heater),
            (w_heater / 2, offset_heater + h_heater),
            (w_heater / 2, offset_heater),
        ]
    ),
    clad=Polygon(
        [
            (-w_sim / 2, 0),
            (-w_sim / 2, h_clad),
            (w_sim / 2, h_clad),
            (w_sim / 2, 0),
        ]
    ),
    box=Polygon(
        [
            (-w_sim / 2, 0),
            (-w_sim / 2, -h_box),
            (w_sim / 2, -h_box),
            (w_sim / 2, 0),
        ]
    ),
    # silicon=Polygon([
    #    (-w_sim / 2, - h_box - h_silicon),
    #    (-w_sim / 2, - h_box),
    #    (w_sim / 2, - h_box),
    #    (w_sim / 2, - h_box - h_silicon),
    # ]),
)

resolutions = dict(
    core={"resolution": 0.05, "distance": 1},
    clad={"resolution": 1, "distance": 1},
    box={"resolution": 1, "distance": 1},
    silicon={"resolution": 1, "distance": 1},
    heater={"resolution": 0.05, "distance": 1},
)

mesh = from_meshio(mesh_from_OrderedDict(polygons, resolutions, default_resolution_max=0.3))

basis0 = Basis(mesh, ElementTriP0(), intorder=4)
thermal_conductivity_p0 = basis0.zeros()
for domain, value in {
    "core": 148,
    "box": 1.38,
    "clad": 1.38,
    "heater": 28,
}.items():  # , 'silicon': 28
    thermal_conductivity_p0[basis0.get_dofs(elements=domain)] = value
thermal_conductivity_p0 *= 1e-12  # 1e-12 -> conversion from 1/m^2 -> 1/um^2

thermal_diffusivity_p0 = basis0.zeros()
for domain, value in {
    "heater": 28 / 598 / 5240,
    "box": 1.38 / 709 / 2203,
    "clad": 1.38 / 709 / 2203,
    "core": 148 / 711 / 2330,
    # "silicon": 148 / 711 / 2330,
}.items():
    thermal_diffusivity_p0[basis0.get_dofs(elements=domain)] = value
thermal_diffusivity_p0 *= 1e12  # 1e-12 -> conversion from m^2 -> um^2

dt = 0.1e-5
steps = 100


def current(t):
    return 0.007 / polygons["heater"].area * ((t < dt * steps / 10) + (t > dt * steps / 2))


basis, temperatures = solve_thermal_transient(
    basis0,
    thermal_conductivity_p0,
    thermal_diffusivity_p0,
    specific_conductivity={"heater": 2.3e6},
    current_densities_0={"heater": current(0)},
    current_densities={"heater": current},
    fixed_boundaries={"bottom": 0},
    dt=dt,
    steps=steps,
)


@LinearForm
def unit_load(v, w):
    return v


M = unit_load.assemble(basis.with_elements("core"))

times = np.array([dt * i for i in range(steps + 1)])
plt.xlabel("Time / us")
plt.ylabel("Average temperature offset / K")
plt.plot(times * 1e6, M @ np.array(temperatures).T / np.sum(M))
plt.show()

# for i in range(0, steps, 10):
#     fig, ax = plt.subplots(subplot_kw=dict(aspect=1))
#     for subdomain in mesh.subdomains.keys() - {'gmsh:bounding_entities'}:
#         mesh.restrict(subdomain).draw(ax=ax, boundaries_only=True)
#     basis.plot(temperatures[i], ax=ax, vmin=0, vmax=np.max(temperatures), shading='gouraud')
#     plt.show()

# Calculate modes

epsilon_0 = basis0.zeros() + 1.444**2
epsilon_0[basis0.get_dofs(elements="core")] = 3.4777**2
modes_0 = compute_modes(basis0, epsilon_0, wavelength=wavelength, num_modes=1, solver="slepc")

neffs = []
neffs_approximated = []
for temperature in tqdm(temperatures):
    # basis.plot(temperature, vmin=0, vmax=np.max(temperatures))
    # plt.show()

    temperature0 = basis0.project(basis.interpolate(temperature))
    epsilon = basis0.zeros() + (1.444 + 1.00e-5 * temperature0) ** 2
    epsilon[basis0.get_dofs(elements="core")] = (
        3.4777 + 1.86e-4 * temperature0[basis0.get_dofs(elements="core")]
    ) ** 2
    # basis0.plot(epsilon, colorbar=True).show()

    modes = compute_modes(basis0, epsilon, wavelength=wavelength, num_modes=1)

    # from femwell.mode_solver import plot_mode
    # plot_mode(basis_modes, xs[0])
    # plt.show()

    neffs.append(np.real(modes[0].n_eff))
    neffs_approximated.append(np.real(modes_0[0].calculate_pertubated_neff(epsilon - epsilon_0)))

fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xlabel("Time / us")
ax.set_ylabel("Current / mA")
ax.plot(times * 1e6, current(times) * 1000, "b-o")
ax2 = ax.twinx()
ax2.set_ylabel("Phase shift")
ax2.plot(
    times * 1e6, 2 * np.pi / wavelength * (neffs - modes_0[0].n_eff) * 320, "r-o", label="Exact"
)
ax2.plot(
    times * 1e6,
    2 * np.pi / wavelength * (neffs_approximated - modes_0[0].n_eff) * 320,
    "g-o",
    label="Approximation",
)
plt.legend()
plt.show()
../../_images/7a5e4b02d5dbf222cf89a14c5300179132ed7518f70aef2164887d39ee7bac22.png
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[1], line 148
    146 epsilon_0 = basis0.zeros() + 1.444**2
    147 epsilon_0[basis0.get_dofs(elements="core")] = 3.4777**2
--> 148 modes_0 = compute_modes(basis0, epsilon_0, wavelength=wavelength, num_modes=1, solver="slepc")
    150 neffs = []
    151 neffs_approximated = []

File ~/miniconda3/lib/python3.12/site-packages/femwell/maxwell/waveguide.py:574, in compute_modes(basis_epsilon_r, epsilon_r, wavelength, mu_r, num_modes, order, metallic_boundaries, radius, n_guess, solver)
    564     lams, xs = solve(
    565         *condense(
    566             -A,
   (...)    571         solver=solver(k=num_modes, sigma=sigma),
    572     )
    573 else:
--> 574     lams, xs = solve(
    575         -A,
    576         -B,
    577         solver=solver(k=num_modes, sigma=sigma),
    578     )
    580 xs[basis.split_indices()[1], :] /= 1j * np.sqrt(
    581     lams[np.newaxis, :] / k0**4
    582 )  # undo the scaling E_3,new = beta * E_3
    584 hs = []

File ~/miniconda3/lib/python3.12/site-packages/skfem/utils.py:268, in solve(A, b, x, I, solver, **kwargs)
    266 logger.info("Solving linear system, shape={}.".format(A.shape))
    267 if isinstance(b, spmatrix):
--> 268     out = solve_eigen(A, b, x, I, solver, **kwargs)  # type: ignore
    269 elif isinstance(b, ndarray):
    270     out = solve_linear(A, b, x, I, solver, **kwargs)  # type: ignore

File ~/miniconda3/lib/python3.12/site-packages/skfem/utils.py:218, in solve_eigen(A, M, x, I, solver, **kwargs)
    216         y[I] = X
    217     return L, y
--> 218 return solver(A, M, **kwargs)

File ~/miniconda3/lib/python3.12/site-packages/femwell/solver.py:93, in solver_eigen_slepc.<locals>.solver(K, M, **solve_time_kwargs)
     92 def solver(K, M, **solve_time_kwargs):
---> 93     from petsc4py import PETSc
     94     from slepc4py import SLEPc
     96     which = {
     97         "LM": SLEPc.EPS.Which.LARGEST_MAGNITUDE,
     98         "SM": SLEPc.EPS.Which.SMALLEST_MAGNITUDE,
   (...)    106         "ALL": SLEPc.EPS.Which.ALL,
    107     }

File ~/miniconda3/lib/python3.12/site-packages/petsc4py/PETSc.py:4
      1 ARCH = None
      2 from petsc4py.lib import ImportPETSc  # noqa: E402
----> 4 PETSc = ImportPETSc(ARCH)
      5 PETSc._initialize()
      6 del PETSc

File ~/miniconda3/lib/python3.12/site-packages/petsc4py/lib/__init__.py:30, in ImportPETSc(arch)
     26 """
     27 Import the PETSc extension module for a given configuration name.
     28 """
     29 path, arch = getPathArchPETSc(arch)
---> 30 return Import('petsc4py', 'PETSc', path, arch)

File ~/miniconda3/lib/python3.12/site-packages/petsc4py/lib/__init__.py:97, in Import(pkg, name, path, arch)
     95 warnings.filterwarnings('ignore', message='numpy.ndarray size changed')
     96 # import extension module from 'path/arch' directory
---> 97 module = import_module(pkg, name, path, arch)
     98 module.__arch__ = arch  # save arch value
     99 setattr(sys.modules[pkg], name, module)

File ~/miniconda3/lib/python3.12/site-packages/petsc4py/lib/__init__.py:76, in Import.<locals>.import_module(pkg, name, path, arch)
     74     module = importlib.util.module_from_spec(spec)
     75     sys.modules[fullname] = module
---> 76     spec.loader.exec_module(module)
     77     return module
     78 f, fn, info = imp.find_module(name, pathlist)

File petsc4py/PETSc.pyx:1, in init petsc4py.PETSc()

ValueError: numpy.dtype size changed, may indicate binary incompatibility. Expected 96 from C header, got 88 from PyObject