Post

PN Junction Modulator

PN Junction Modulator
Note: The device designs presented here are intended as proof-of-concept examples to illustrate workflows, modeling approaches, and design methodology. Performance metrics are not optimized, and the structures are not intended for tape-out.
Source Code


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Import the necassry packages
import gplugins.modes as gm
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import meep as mp
import gdsfactory as gf
# from ubcpdk import PDK, cells
# from functools import partial
# PDK.activate()
import sax
from jax import config
config.update("jax_enable_x64", True)
import jax.numpy as jnp
from simphony.libraries import siepic
from gplugins.devsim import get_simulation_xsection
from gplugins.devsim.get_simulation_xsection import k_to_alpha, clear_devsim_cache
import pyvista as pv
import os
1
2
3
4
5
6
7
8
Using MPI version 4.1, 1 processes
2025-11-26 06:39:24.584 | INFO     | gplugins.gmeep:<module>:39 - Meep '1.31.0' installed at ['/home/ramprakash/anaconda3/envs/si_photo/lib/python3.13/site-packages/meep']
Searching DEVSIM_MATH_LIBS="libopenblas.so:liblapack.so:libblas.so"
Loading "libopenblas.so": ALL BLAS/LAPACK LOADED
Skipping liblapack.so
Skipping libblas.so
loading UMFPACK 5.1 as direct solver
2025-11-26 06:39:27.467 | INFO     | gplugins.devsim:<module>:16 - DEVSIM '2.10.0' installed at ['/home/ramprakash/anaconda3/envs/si_photo/lib/python3.13/site-packages/devsim']

Design of PN junction

Loading a straing pn junciton component from gdsfactory

The aim is to do full TCAD simulation to get the Vpi

Step 1: Import PN junction straight waveguide form GDSFactory

Source Code


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import gdsfactory as gf
from gdsfactory.generic_tech import LAYER, LAYER_STACK
from gdsfactory.technology import LayerLevel, LayerStack

# We choose a representative subdomain of the component
waveguide = gf.Component()
waveguide.add_ref(
    gf.functions.trim(
        component=gf.components.straight_pn(length=10, taper=None).copy(),
        domain=[[3, -4], [3, 4], [5, 4], [5, -4]],
    )
)

waveguide.plot()

scene = waveguide.to_3d()
scene.show()

png

Run a TCAD simualtion to calculate the carrier desnity of electron (dN) and holes (dP)

Source Code


1
2
3
active_layer_stack = gf.pdk.get_active_pdk().layer_stack
active_layer_stack.layers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{'substrate': LayerLevel(name=None, layer=WAFER, derived_layer=None, thickness=10.0, thickness_tolerance=None, width_tolerance=None, zmin=-13.0, zmin_tolerance=None, sidewall_angle=0.0, sidewall_angle_tolerance=None, width_to_z=0.0, z_to_bias=None, bias=None, mesh_order=101, material='si', info={}),
 'box': LayerLevel(name=None, layer=WAFER, derived_layer=None, thickness=3.0, thickness_tolerance=None, width_tolerance=None, zmin=-3.0, zmin_tolerance=None, sidewall_angle=0.0, sidewall_angle_tolerance=None, width_to_z=0.0, z_to_bias=None, bias=None, mesh_order=9, material='sio2', info={}),
 'core': LayerLevel(name=None, layer=((WG - DEEP_ETCH) - SHALLOW_ETCH), derived_layer=WG, thickness=0.22, thickness_tolerance=None, width_tolerance=None, zmin=0.0, zmin_tolerance=None, sidewall_angle=10.0, sidewall_angle_tolerance=None, width_to_z=0.5, z_to_bias=None, bias=None, mesh_order=2, material='si', info={}),
 'shallow_etch': LayerLevel(name=None, layer=(SHALLOW_ETCH & WG), derived_layer=SLAB150, thickness=0.07, thickness_tolerance=None, width_tolerance=None, zmin=0.0, zmin_tolerance=None, sidewall_angle=0.0, sidewall_angle_tolerance=None, width_to_z=0.0, z_to_bias=None, bias=None, mesh_order=1, material='si', info={}),
 'deep_etch': LayerLevel(name=None, layer=DEEP_ETCH, derived_layer=SLAB90, thickness=0.13, thickness_tolerance=None, width_tolerance=None, zmin=0.0, zmin_tolerance=None, sidewall_angle=0.0, sidewall_angle_tolerance=None, width_to_z=0.0, z_to_bias=None, bias=None, mesh_order=1, material='si', info={}),
 'clad': LayerLevel(name=None, layer=WAFER, derived_layer=None, thickness=3.0, thickness_tolerance=None, width_tolerance=None, zmin=0.0, zmin_tolerance=None, sidewall_angle=0.0, sidewall_angle_tolerance=None, width_to_z=0.0, z_to_bias=None, bias=None, mesh_order=10, material='sio2', info={}),
 'slab150': LayerLevel(name=None, layer=SLAB150, derived_layer=None, thickness=0.15, thickness_tolerance=None, width_tolerance=None, zmin=0.0, zmin_tolerance=None, sidewall_angle=0.0, sidewall_angle_tolerance=None, width_to_z=0.0, z_to_bias=None, bias=None, mesh_order=3, material='si', info={}),
 'slab90': LayerLevel(name=None, layer=SLAB90, derived_layer=None, thickness=0.09, thickness_tolerance=None, width_tolerance=None, zmin=0.0, zmin_tolerance=None, sidewall_angle=0.0, sidewall_angle_tolerance=None, width_to_z=0.0, z_to_bias=None, bias=None, mesh_order=2, material='si', info={}),
 'nitride': LayerLevel(name=None, layer=WGN, derived_layer=None, thickness=0.35000000000000003, thickness_tolerance=None, width_tolerance=None, zmin=0.32, zmin_tolerance=None, sidewall_angle=0.0, sidewall_angle_tolerance=None, width_to_z=0.0, z_to_bias=None, bias=None, mesh_order=2, material='sin', info={}),
 'ge': LayerLevel(name=None, layer=GE, derived_layer=None, thickness=0.5, thickness_tolerance=None, width_tolerance=None, zmin=0.22, zmin_tolerance=None, sidewall_angle=0.0, sidewall_angle_tolerance=None, width_to_z=0.0, z_to_bias=None, bias=None, mesh_order=1, material='ge', info={}),
 'undercut': LayerLevel(name=None, layer=UNDERCUT, derived_layer=None, thickness=-5.0, thickness_tolerance=None, width_tolerance=None, zmin=-3.0, zmin_tolerance=None, sidewall_angle=0.0, sidewall_angle_tolerance=None, width_to_z=0.0, z_to_bias=([0.0, 0.3, 0.6, 0.8, 0.9, 1.0], [0.0, -0.5, -1.0, -1.5, -2.0, -2.5]), bias=None, mesh_order=1, material='air', info={}),
 'via_contact': LayerLevel(name=None, layer=VIAC, derived_layer=None, thickness=1.01, thickness_tolerance=None, width_tolerance=None, zmin=0.09, zmin_tolerance=None, sidewall_angle=-10.0, sidewall_angle_tolerance=None, width_to_z=0.0, z_to_bias=None, bias=None, mesh_order=1, material='Aluminum', info={}),
 'metal1': LayerLevel(name=None, layer=M1, derived_layer=None, thickness=0.7000000000000001, thickness_tolerance=None, width_tolerance=None, zmin=1.1, zmin_tolerance=None, sidewall_angle=0.0, sidewall_angle_tolerance=None, width_to_z=0.0, z_to_bias=None, bias=None, mesh_order=2, material='Aluminum', info={}),
 'heater': LayerLevel(name=None, layer=HEATER, derived_layer=None, thickness=0.75, thickness_tolerance=None, width_tolerance=None, zmin=1.1, zmin_tolerance=None, sidewall_angle=0.0, sidewall_angle_tolerance=None, width_to_z=0.0, z_to_bias=None, bias=None, mesh_order=2, material='TiN', info={}),
 'via1': LayerLevel(name=None, layer=VIA1, derived_layer=None, thickness=0.49999999999999956, thickness_tolerance=None, width_tolerance=None, zmin=1.8000000000000003, zmin_tolerance=None, sidewall_angle=0.0, sidewall_angle_tolerance=None, width_to_z=0.0, z_to_bias=None, bias=None, mesh_order=1, material='Aluminum', info={}),
 'metal2': LayerLevel(name=None, layer=M2, derived_layer=None, thickness=0.7000000000000001, thickness_tolerance=None, width_tolerance=None, zmin=2.3, zmin_tolerance=None, sidewall_angle=0.0, sidewall_angle_tolerance=None, width_to_z=0.0, z_to_bias=None, bias=None, mesh_order=2, material='Aluminum', info={}),
 'via2': LayerLevel(name=None, layer=VIA2, derived_layer=None, thickness=0.20000000000000018, thickness_tolerance=None, width_tolerance=None, zmin=3.0, zmin_tolerance=None, sidewall_angle=0.0, sidewall_angle_tolerance=None, width_to_z=0.0, z_to_bias=None, bias=None, mesh_order=1, material='Aluminum', info={}),
 'metal3': LayerLevel(name=None, layer=M3, derived_layer=None, thickness=2.0, thickness_tolerance=None, width_tolerance=None, zmin=3.2, zmin_tolerance=None, sidewall_angle=0.0, sidewall_angle_tolerance=None, width_to_z=0.0, z_to_bias=None, bias=None, mesh_order=2, material='Aluminum', info={})}

Simple step doping. See below for complex doping profile

Source Code


1
2
3
4
5
6
7
8
9
10
11
12
%%capture  
um = 1e-6
c = get_simulation_xsection.PINWaveguide(
    core_width=0.500 * um,
    core_thickness=0.220 * um,
    slab_thickness=0.090 * um,
)
# Initialize mesh and solver
clear_devsim_cache()
c.ddsolver()
c.save_device('straight_PN.dat')

Source Code


1
c.plot(scalars="Holes", log_scale=True, jupyter_backend="static")
1
2025-11-25 20:17:21.123 (5189.345s) [    7BAE26EE4740]vtkXOpenGLRenderWindow.:1458  WARN| bad X server connection. DISPLAY=

png

Source Code


1
c.list_fields()
HeaderData Arrays
UnstructuredGridInformation
N Cells8424
N Points4401
X Bounds-2.478e-05, 2.511e-05
Y Bounds9.000e-06, 2.200e-05
Z Bounds0.000e+00, 0.000e+00
N Arrays99
NameFieldTypeN CompMinMax
AcceptorsPointsfloat3210.000e+001.000e+17
AtContactNodePointsfloat3210.000e+000.000e+00
ContactNSurfaceNormal_xPointsfloat3210.000e+000.000e+00
ContactNSurfaceNormal_yPointsfloat3210.000e+000.000e+00
ContactSurfaceAreaPointsfloat3210.000e+000.000e+00
DonorsPointsfloat3210.000e+001.000e+17
ElectronGenerationPointsfloat3213.146e-261.285e-19
ElectronGeneration:ElectronsPointsfloat321-1.600e-14-1.600e-28
ElectronGeneration:HolesPointsfloat321-1.600e-14-1.600e-28
ElectronsPointsfloat3211.000e+031.000e+17
Electrons_prevPointsfloat3211.000e+101.000e+10
HoleGenerationPointsfloat321-1.285e-19-3.146e-26
HoleGeneration:ElectronsPointsfloat3211.600e-281.600e-14
HoleGeneration:HolesPointsfloat3211.600e-281.600e-14
HolesPointsfloat3211.000e+031.000e+17
Holes_prevPointsfloat3211.000e+101.000e+10
IntrinsicChargePointsfloat321-1.000e+171.000e+17
IntrinsicCharge:PotentialPointsfloat321-3.863e+18-7.736e+11
IntrinsicElectronsPointsfloat3211.000e+031.000e+17
IntrinsicElectrons:PotentialPointsfloat3213.863e+043.863e+18
IntrinsicHolesPointsfloat3211.000e+031.000e+17
IntrinsicHoles:PotentialPointsfloat321-3.863e+18-3.863e+04
NChargePointsfloat321-1.600e-02-1.600e-16
NCharge:ElectronsPointsfloat321-1.600e-19-1.600e-19
NSurfaceNormal_xPointsfloat3210.000e+000.000e+00
NSurfaceNormal_yPointsfloat321-1.000e+000.000e+00
NetDopingPointsfloat321-1.000e+171.000e+17
NodeVolumePointsfloat3217.631e-156.522e-13
PChargePointsfloat3211.600e-161.600e-02
PCharge:HolesPointsfloat3211.600e-191.600e-19
PotentialPointsfloat321-4.173e-014.173e-01
PotentialIntrinsicChargePointsfloat321-1.600e-021.600e-02
PotentialIntrinsicCharge:PotentialPointsfloat3211.238e-076.181e-01
PotentialNodeChargePointsfloat321-1.600e-021.600e-02
PotentialNodeCharge:ElectronsPointsfloat3211.600e-191.600e-19
PotentialNodeCharge:HolesPointsfloat321-1.600e-19-1.600e-19
Potential_prevPointsfloat3210.000e+000.000e+00
SurfaceAreaPointsfloat3210.000e+006.856e-07
USRHPointsfloat321-8.034e-01-1.966e-07
USRH:ElectronsPointsfloat3211.000e-091.000e+05
USRH:HolesPointsfloat3211.000e-091.000e+05
coordinate_indexPointsfloat3215.148e+031.246e+04
leftnodeelectronsPointsfloat3210.000e+000.000e+00
leftnodeelectrons:ElectronsPointsfloat3210.000e+000.000e+00
leftnodeholesPointsfloat3210.000e+000.000e+00
leftnodeholes:HolesPointsfloat3210.000e+000.000e+00
leftnodemodelPointsfloat3210.000e+000.000e+00
leftnodemodel:PotentialPointsfloat3210.000e+000.000e+00
node_indexPointsfloat3210.000e+004.400e+03
rightnodeelectronsPointsfloat3210.000e+000.000e+00
rightnodeelectrons:ElectronsPointsfloat3210.000e+000.000e+00
rightnodeholesPointsfloat3210.000e+000.000e+00
rightnodeholes:HolesPointsfloat3210.000e+000.000e+00
rightnodemodelPointsfloat3210.000e+000.000e+00
rightnodemodel:PotentialPointsfloat3210.000e+000.000e+00
Bern01Pointsfloat3211.000e+001.187e+00
Bern01:Potential@n0Pointsfloat321-2.368e+01-1.660e+01
Bern01:Potential@n1Pointsfloat3211.660e+012.368e+01
EdgeCouplePointsfloat3216.908e-085.446e-07
EdgeInverseLengthPointsfloat3211.101e+069.710e+06
EdgeLengthPointsfloat3211.399e-079.389e-07
EdgeNodeVolumePointsfloat3211.908e-151.087e-13
ElectricFieldPointsfloat321-6.838e+04-2.951e-04
ElectricField:Potential@n0Pointsfloat3211.101e+069.710e+06
ElectricField:Potential@n1Pointsfloat321-9.710e+06-1.101e+06
ElectronCurrentPointsfloat321-3.719e-103.361e-10
ElectronCurrent:Electrons@n0Pointsfloat321-1.822e-11-1.824e-12
ElectronCurrent:Electrons@n1Pointsfloat3211.824e-121.417e-11
ElectronCurrent:Holes@n0Pointsfloat3210.000e+000.000e+00
ElectronCurrent:Holes@n1Pointsfloat3210.000e+000.000e+00
ElectronCurrent:Potential@n0Pointsfloat3217.077e-082.070e+07
ElectronCurrent:Potential@n1Pointsfloat321-2.070e+07-7.077e-08
Electrons@n0Pointsfloat3211.000e+031.000e+17
Electrons@n1Pointsfloat3211.000e+031.000e+17
HoleCurrentPointsfloat321-1.647e-104.010e-10
HoleCurrent:Electrons@n0Pointsfloat3210.000e+000.000e+00
HoleCurrent:Electrons@n1Pointsfloat3210.000e+000.000e+00
HoleCurrent:Holes@n0Pointsfloat3219.121e-137.085e-12
HoleCurrent:Holes@n1Pointsfloat321-9.110e-12-9.121e-13
HoleCurrent:Potential@n0Pointsfloat3213.523e-081.036e+07
HoleCurrent:Potential@n1Pointsfloat321-1.036e+07-3.523e-08
Holes@n0Pointsfloat3211.000e+031.000e+17
Holes@n1Pointsfloat3211.000e+031.000e+17
Potential@n0Pointsfloat321-4.173e-014.173e-01
Potential@n1Pointsfloat321-4.173e-014.173e-01
PotentialEdgeFluxPointsfloat321-6.717e-08-2.899e-16
PotentialEdgeFlux:Potential@n0Pointsfloat3211.082e-069.539e-06
PotentialEdgeFlux:Potential@n1Pointsfloat321-9.539e-06-1.082e-06
contactcharge_edgePointsfloat3210.000e+000.000e+00
contactcharge_edge:Potential@n0Pointsfloat3210.000e+000.000e+00
contactcharge_edge:Potential@n1Pointsfloat3210.000e+000.000e+00
edge_indexPointsfloat3215.000e-011.281e+04
unitxPointsfloat3211.104e-027.402e-01
unityPointsfloat3213.912e-047.012e-01
vdiffPointsfloat321-3.476e-01-1.097e-12
vdiff:Potential@n0Pointsfloat3213.863e+013.863e+01
vdiff:Potential@n1Pointsfloat321-3.863e+01-3.863e+01
ElementEdgeCoupleCellsfloat3214.210e-082.739e-07
ElementNodeVolumeCellsfloat3218.770e-165.506e-14
Source Code


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
mesh = pv.read('straight_PN.dat')
fields = [
    "NetDoping","Donors","Acceptors",
    "Electrons","Holes"]

p = pv.Plotter(shape=(3,2), notebook='static', window_size=(320*4, 240*4))
# c.plot(scalars="NetDoping", jupyter_backend="static")

idx = 0
for r in range(3):
    for ccol in range(2):
        p.subplot(r, ccol)

        if idx >= len(fields):
            p.add_text("")  # empty cell
            continue

        fname = fields[idx]
        idx += 1

        # if fname not in mesh.array_names:
        #     p.add_text(f"Missing {fname}")
        #     continue

        p.add_mesh(mesh, scalars=fname, cmap="RdBu")
        p.add_text(fname, position="upper_edge", font_size=12)
        _ = p.camera_position = "xy"
        p.add_scalar_bar(title=fname)

p.link_views()
p.show()
1
2
3
4
5
6
/home/ramprakash/anaconda3/envs/si_photo/lib/python3.13/site-packages/pyvista/jupyter/notebook.py:56: UserWarning: Failed to use notebook backend: 

No module named 'trame'

Falling back to a static output.
  warnings.warn(

png

Applying voltage and finding the solution

Source Code


1
2
3
4
%%capture
# Find a solution with 1V across the junction, ramping by 0.1V steps
c.ramp_voltage(Vfinal=1, Vstep=0.1)
c.save_device('straight_PN_1V.dat')
Source Code


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
mesh = pv.read('straight_PN_1V.dat')
fields = [
    "NetDoping","Donors","Acceptors",
    "Electrons","Holes"]

p = pv.Plotter(shape=(3,2), notebook='static', window_size=(320*4, 240*4))
# c.plot(scalars="NetDoping", jupyter_backend="static")

idx = 0
for r in range(3):
    for ccol in range(2):
        p.subplot(r, ccol)

        if idx >= len(fields):
            p.add_text("")  # empty cell
            continue

        fname = fields[idx]
        idx += 1

        # if fname not in mesh.array_names:
        #     p.add_text(f"Missing {fname}")
        #     continue

        p.add_mesh(mesh, scalars=fname, cmap="RdBu")
        p.add_text(fname, position="upper_edge", font_size=12)
        _ = p.camera_position = "xy"
        p.add_scalar_bar(title=fname)

p.link_views()
p.show()
1
2
3
4
5
6
/home/ramprakash/anaconda3/envs/si_photo/lib/python3.13/site-packages/pyvista/jupyter/notebook.py:56: UserWarning: Failed to use notebook backend: 

No module named 'trame'

Falling back to a static output.
  warnings.warn(

png

Negative bias -1V

Source Code


1
2
3
4
%%capture
# Find a solution with 1V across the junction, ramping by 0.1V steps
c.ramp_voltage(Vfinal=-1, Vstep=-0.1)
c.save_device('straight_PN_-1V.dat')
Source Code


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
mesh = pv.read('straight_PN_-1V.dat')
fields = [
    "NetDoping","Donors","Acceptors",
    "Electrons","Holes"]

p = pv.Plotter(shape=(3,2), notebook='static', window_size=(320*4, 240*4))
# c.plot(scalars="NetDoping", jupyter_backend="static")

idx = 0
for r in range(3):
    for ccol in range(2):
        p.subplot(r, ccol)

        if idx >= len(fields):
            p.add_text("")  # empty cell
            continue

        fname = fields[idx]
        idx += 1

        # if fname not in mesh.array_names:
        #     p.add_text(f"Missing {fname}")
        #     continue

        p.add_mesh(mesh, scalars=fname, cmap="RdBu")
        p.add_text(fname, position="upper_edge", font_size=12)
        _ = p.camera_position = "xy"
        p.add_scalar_bar(title=fname)

p.link_views()
p.show()
1
2
3
4
5
6
/home/ramprakash/anaconda3/envs/si_photo/lib/python3.13/site-packages/pyvista/jupyter/notebook.py:56: UserWarning: Failed to use notebook backend: 

No module named 'trame'

Falling back to a static output.
  warnings.warn(

png

Source Code


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def plot_carriers(label):
    x  = c.get_field("core", "x")
    Ne = c.get_field("core", "Electrons")
    Ph = c.get_field("core", "Holes")

    idx = np.argsort(x)
    plt.semilogy(x, Ne, label=f"Ne {label}")
    plt.semilogy(x, Ph, label=f"Ph {label}")
    plt.legend()

clear_devsim_cache()
c.ddsolver()
plot_carriers("0V")

clear_devsim_cache()
c.ddsolver()
c.ramp_voltage(Vfinal=-1, Vstep=-0.1)
plot_carriers("-1 V")

clear_devsim_cache()
c.ddsolver()
c.ramp_voltage(Vfinal=1, Vstep=0.1)
plot_carriers("+1 V")

Making waveguide from the devsim device with perturebd index and calculaitng the effective index. Using tidy3d for the mode calcaultions

Source Code


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
%%capture

voltages = [0, -0.2, -0.4, -0.6, -0.8, -1,-1.5, -2,-2.5, -3]#-0.5, -1, -1.5, -2, -2.5, -3, -3.5, -4]
ramp_step = -0.1 

n_dist = {}
neffs   = {}

clear_devsim_cache()
c.ddsolver()     # fresh, clean equilibrium state
cached = "/home/ramprakash/.gdsfactory/modes/Waveguide_*.npz"
if os.path.exists(cached):
    os.remove(cached)
current_bias = 0.0   # solved at 0 V


for ind, voltage in enumerate(voltages):
    clear_devsim_cache()
    c.ddsolver()
    c.ramp_voltage(Vfinal=voltage, Vstep=ramp_step)
    waveguide = c.make_waveguide(wavelength=1.55, perturb=True, grid_resolution=20)
    n_dist[voltage] = waveguide.index.values
    neffs[voltage] = waveguide.n_eff[0]

# for V in voltages:
#     # Skip ramp if already at this voltage
#     if V != current_bias:
#         # choose step direction automatically
#         step = ramp_step if V < current_bias else abs(ramp_step)
#         # ramp from current bias → new voltage
#         c.ramp_voltage(Vfinal=V, Vstep=step, Vinit=current_bias)

#     current_bias = V
    
#     # build perturbed optical waveguide - Smaller res for faster run
#     wg = c.make_waveguide(wavelength=1.55, perturb=True)

#     n_dist[V] = wg.index.values
#     # neffs[V] = wg.n_eff[0]
#     print(f"Voltage value: {V}")

Source Code


1
2
3
4
%%capture
clear_devsim_cache()
c.ddsolver()     # fresh, clean equilibrium state
c.ramp_voltage(Vfinal=-0.5, Vstep=-0.1)
Source Code


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
%%capture
voltages = [0, -0.5, -1, -1.5, -2, -2.5, -3, -3.5, -4, -5, -6, -7, -8, -9, -10]
ramp_rate = -0.1

n_dist = {}
neffs = {}
clear_devsim_cache()
um = 1e-6

dev = get_simulation_xsection.PINWaveguide(
    core_width=0.500 * um,
    core_thickness=0.220 * um,
    slab_thickness=0.090 * um,
)
dev.ddsolver()     # fresh, clean equilibrium state
for ind, voltage in enumerate(voltages):
    Vinit = 0 if ind == 0 else voltages[ind - 1]
    dev.ramp_voltage(Vfinal=voltage, Vstep=ramp_rate, Vinit=Vinit)
    waveguide = dev.make_waveguide(wavelength=1.55,grid_resolution=20)
    n_dist[voltage] = waveguide.index.values
    neffs[voltage] = waveguide.n_eff[0]
Source Code


1
2
3
4
5
6
7
8
9
10
11
12
import gplugins.tidy3d as td
wg_td = td.modes.Waveguide(wavelength=1.55,
                core_width=0.5,
                core_thickness=0.22,
                slab_thickness=0.090,
                grid_resolution=50,
                core_material='si',
                clad_material='sio2',
                cache_path=None,
                overwrite=True)
wg_td.plot_field('Ex')
wg_td.n_eff[0]
1
np.complex128(2.5932203425309455+3.8757826065200905e-05j)

png

Source Code


1
2
ax = waveguide.plot_index()
ax.axes.set_ylim([0,0.5])
1
(0.0, 0.5)

png

Source Code


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
neffs = {0: np.complex128(2.579233149832461+4.2558454254802517e-05j),
 -0.5: np.complex128(2.579248605051533+4.20043472854569e-05j),
 -1: np.complex128(2.5792605310824923+4.1578101252069765e-05j),
 -1.5: np.complex128(2.5792684479189205+4.1298845722944675e-05j),
 -2: np.complex128(2.5792768911186554+4.100265496131908e-05j),
 -2.5: np.complex128(2.579284107736812+4.0752849952389265e-05j),
 -3: np.complex128(2.5719153080505834+4.2195904369172816e-05j),
 -3.5: np.complex128(2.5792960309736594+4.033633789155556e-05j),
 -4: np.complex128(2.57930195707953+4.012628342291738e-05j),
 -5: np.complex128(2.5793103370214245+3.9827958190374006e-05j),
 -6: np.complex128(2.5793178726112416+3.956270654250005e-05j),
 -7: np.complex128(2.579323275257858+3.935982457145146e-05j),
 -8: np.complex128(2.5719524331528962+4.084224984011639e-05j),
 -9: np.complex128(2.5793333855608185+3.901735265199731e-05j),
 -10: np.complex128(2.5793378293861204+3.888440364347489e-05j)}

neffs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{0: np.complex128(2.579233149832461+4.2558454254802517e-05j),
 -0.5: np.complex128(2.579248605051533+4.20043472854569e-05j),
 -1: np.complex128(2.5792605310824923+4.1578101252069765e-05j),
 -1.5: np.complex128(2.5792684479189205+4.1298845722944675e-05j),
 -2: np.complex128(2.5792768911186554+4.100265496131908e-05j),
 -2.5: np.complex128(2.579284107736812+4.0752849952389265e-05j),
 -3: np.complex128(2.5719153080505834+4.2195904369172816e-05j),
 -3.5: np.complex128(2.5792960309736594+4.033633789155556e-05j),
 -4: np.complex128(2.57930195707953+4.012628342291738e-05j),
 -5: np.complex128(2.5793103370214245+3.9827958190374006e-05j),
 -6: np.complex128(2.5793178726112416+3.956270654250005e-05j),
 -7: np.complex128(2.579323275257858+3.935982457145146e-05j),
 -8: np.complex128(2.5719524331528962+4.084224984011639e-05j),
 -9: np.complex128(2.5793333855608185+3.901735265199731e-05j),
 -10: np.complex128(2.5793378293861204+3.888440364347489e-05j)}
Source Code


1
2
3
4
5
6
7
8
9
voltage_list = sorted(neffs.items())
x, y = zip(*voltage_list)
y_array = np.array(y)
x_array = np.array(x)
dneff = y-neffs[0]
plt.plot(x, dneff)

plt.xlabel("Voltage (V)")
plt.ylabel(r"$\Delta n_{eff}$")
1
2
3
4
5
6
7
8
9
10
/home/ramprakash/anaconda3/envs/si_photo/lib/python3.13/site-packages/matplotlib/cbook.py:1719: ComplexWarning: Casting complex values to real discards the imaginary part
  return math.isfinite(val)
/home/ramprakash/anaconda3/envs/si_photo/lib/python3.13/site-packages/matplotlib/cbook.py:1355: ComplexWarning: Casting complex values to real discards the imaginary part
  return np.asarray(x, float)





Text(0, 0.5, '$\\Delta n_{eff}$')

png

Source Code


1
2
3
4
5
6
7
voltage_list = sorted(neffs.items())
x, y = zip(*voltage_list)

plt.plot(x, 10 * np.log10(k_to_alpha(np.imag(y), wavelength=1.55)))

plt.xlabel("Voltage (V)")
plt.ylabel(r"$\alpha (dB/cm)$")
1
Text(0, 0.5, '$\\alpha (dB/cm)$')

png

Source Code


1
2
3
4
5
6
7
c_undoped = c.make_waveguide(wavelength=1.55, perturb=False, precision="double", grid_resolution=30)
# c_undoped.compute_modes()
n_undoped = c_undoped.index.values
ax2 =  c_undoped.index.imag.plot()
ax2.axes.set_aspect("equal")
ax2.axes.set_ylim([0, 0.5])
plt.title("Imaginary part of refractive index (k)")
1
Text(0.5, 1.0, 'Imaginary part of refractive index (k)')

png

Source Code


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
plt.figure(figsize=(10,10))
for ind, voltage in enumerate(voltages):
    plt.subplot(5,2,ind+1)
    plt.imshow(
        np.log10(np.abs(np.imag(n_dist[voltage]- n_undoped))),
        origin="lower",
        extent=[
            -c.xmargin - c.ppp_offset - c.core_width / 2,
            c.xmargin + c.npp_offset + c.core_width / 2,
            0,
            c.clad_thickness + c.box_thickness + c.core_thickness,
        ],
    )
    plt.colorbar(label="$(|n_{doped} - n_{undoped}|)$")
    plt.xlabel("x (m)")
    plt.ylabel("y (m)")
    plt.ylim(1.72e-6, 2.5e-6)
    plt.title(f"Voltage = {voltage}V")
1
2
/tmp/ipykernel_2589823/3365713181.py:5: RuntimeWarning: divide by zero encountered in log10
  np.log10(np.abs(np.imag(n_dist[voltage]- n_undoped))),

png

Calacute the Vpi then have to build the compact model for the same. Finally use this in the ring resonator model

Phase change can be claculated form dn. So using this Vpi is calcaulted

Not too good - But okay for a proof of concept design

Source Code


1
2
3
4
5
6
7
lamda = 1.55e-6
dphi = (2*np.pi/lamda)*(dneff*1e-2)/np.pi
plt.plot(-x_array,dphi)
plt.axhline(y=1, color='r', linestyle='--')
plt.xlabel('Voltage (Reverse bias)')
plt.ylabel(r"$\Delta\phi (\pi/cm)$")

1
Text(0, 0.5, '$\\Delta\\phi (\\pi/cm)$')

png

This post is licensed under CC BY 4.0 by the author.