Thermal#
gdsfactory has an FEM femwell plugin that you can use for thermal simulations. You can simulate directly the component layout and include important effects such as metal dummy fill.
try:
import google.colab
is_running_on_colab = True
!pip install gdsfactory gplugins[femwell] > /dev/null
!apt install python3-gmsh gmsh > /dev/null
except ImportError:
is_running_on_colab = False
import gdsfactory as gf
import meshio
from gdsfactory.generic_tech import get_generic_pdk
from gdsfactory.technology import LayerStack
from skfem.io import from_meshio
from gplugins.gmsh import create_physical_mesh, get_mesh
gf.config.rich_output()
PDK = get_generic_pdk()
PDK.activate()
gf.CONF.display_type = 'klayout' # for plotting in Colab notebook
LAYER_STACK = PDK.layer_stack
LAYER_STACK.layers["heater"].thickness = 0.13
LAYER_STACK.layers["heater"].zmin = 2.2
heater = gf.components.straight_heater_metal(length=50)
heater.plot()
2024-03-09 00:45:18.661 | INFO | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/straight_heater_metal_undercut_8589be07.lyp'.
Example based on femwell
from collections import OrderedDict
import matplotlib.pyplot as plt
import numpy as np
from shapely.geometry import LineString, Polygon
from skfem import Basis, ElementTriP0
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 import solve_thermal
w_sim = 8 * 2
h_clad = 2.8
h_box = 2
w_core = 0.5
h_core = 0.22
h_heater = 0.14
w_heater = 2
offset_heater = 2 + (h_core + h_heater) / 2
h_silicon = 0.5
polygons = OrderedDict(
bottom=LineString(
[
(-w_sim / 2, -h_core / 2 - h_box - h_silicon),
(w_sim / 2, -h_core / 2 - h_box - h_silicon),
]
),
core=Polygon(
[
(-w_core / 2, -h_core / 2),
(-w_core / 2, h_core / 2),
(w_core / 2, h_core / 2),
(w_core / 2, -h_core / 2),
]
),
heater=Polygon(
[
(-w_heater / 2, -h_heater / 2 + offset_heater),
(-w_heater / 2, h_heater / 2 + offset_heater),
(w_heater / 2, h_heater / 2 + offset_heater),
(w_heater / 2, -h_heater / 2 + offset_heater),
]
),
clad=Polygon(
[
(-w_sim / 2, -h_core / 2),
(-w_sim / 2, -h_core / 2 + h_clad),
(w_sim / 2, -h_core / 2 + h_clad),
(w_sim / 2, -h_core / 2),
]
),
box=Polygon(
[
(-w_sim / 2, -h_core / 2),
(-w_sim / 2, -h_core / 2 - h_box),
(w_sim / 2, -h_core / 2 - h_box),
(w_sim / 2, -h_core / 2),
]
),
wafer=Polygon(
[
(-w_sim / 2, -h_core / 2 - h_box - h_silicon),
(-w_sim / 2, -h_core / 2 - h_box),
(w_sim / 2, -h_core / 2 - h_box),
(w_sim / 2, -h_core / 2 - h_box - h_silicon),
]
),
)
resolutions = dict(
core={"resolution": 0.04, "distance": 1},
clad={"resolution": 0.6, "distance": 1},
box={"resolution": 0.6, "distance": 1},
heater={"resolution": 0.1, "distance": 1},
)
mesh = from_meshio(
mesh_from_OrderedDict(polygons, resolutions, default_resolution_max=0.6)
)
mesh.draw().show()
And then we solve it!
currents = np.linspace(0.0, 7.4e-3, 10)
current_densities = currents / polygons["heater"].area
neffs = []
for current_density in tqdm(current_densities):
basis0 = Basis(mesh, ElementTriP0(), intorder=4)
thermal_conductivity_p0 = basis0.zeros()
for domain, value in {
"core": 90,
"box": 1.38,
"clad": 1.38,
"heater": 28,
"wafer": 148,
}.items():
thermal_conductivity_p0[basis0.get_dofs(elements=domain)] = value
thermal_conductivity_p0 *= 1e-12 # 1e-12 -> conversion from 1/m^2 -> 1/um^2
basis, temperature = solve_thermal(
basis0,
thermal_conductivity_p0,
specific_conductivity={"heater": 2.3e6},
current_densities={"heater": current_density},
fixed_boundaries={"bottom": 0},
)
if current_density == current_densities[-1]:
basis.plot(temperature, shading="gouraud", colorbar=True)
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=1.55, num_modes=1, solver="scipy")
if current_density == current_densities[-1]:
modes[0].show(modes[0].E.real)
neffs.append(np.real(modes[0].n_eff))
length = 320 # um
print(f"Phase shift: {2 * np.pi / 1.55 * (neffs[-1] - neffs[0]) * length}")
plt.xlabel("Current / mA")
plt.ylabel("Effective refractive index $n_{eff}$")
plt.plot(currents * 1e3, neffs)
plt.show()
Phase shift: 3.786321638790853