Skip to main content

Modulator

Introduction

​We have developed an electro-optic modulator based on carrier-depletion mechanism in silicon to simulate carrier and index distribution in a global structure. The drift and diffusion of carriers result in depletion at the PN junction. Applying a reverse bias voltage at the cathode broadens the depletion region, altering the carrier concentration and modulating the material's refractive index. Gaussian doping was employed in this model. Modulation efficiency, capacitance, and resistance were calculated to analyze the electro-optic characteristics of the modulator.

​The figure below illustrates the device structure we constructed, wherein the aluminum electrode is applied to the silicon modulator, and the entire device is encapsulated with silicon dioxide material.

Simulation Structure

​The present structural file provides a comprehensive guide for constructing a simulation structure and establishing a Gaussian doping distribution. Initially, one must construct the geometric structure of the device, incorporate materials and physical models, specify the doping distribution, and simulation boundary conditions, and set the light source and simulation solver. Eventually, the simulation result data should be extracted and output.

​Our simulation is designed with a light source entering along the X-axis, and the primary optoelectronic characteristic analysis takes place within the three-dimensional structure on the YZ plane. The FDE solver is utilized to preview the distribution of effective refractive index, and the DDM solver is used to preview the doping distribution of the device. Finally, we generate ad output the distribution map of doping and index in modulator structure.

Application Library path: :\mo_sdk_solution_v3.2.3.0\examples\active_demo\doping_function\MOD\MOD00_structure.py

1. Basic Operations

It is essential to incorporate materials for structure. Although module of maxoptics_sdk.all provides default values for standard semiconductors, these values can be customized in MOD_material.py .

Application Library path: :\mo_sdk_solution_v3.2.3.0\examples\active_demo\doping_function\MOD\MOD_material.py

The module of fundamental specifies the base parameters of the material electrical structure . The recombination includes recombination of Auger, SRH, and Radiative. It is crucial to define the model type and parameter values, if another model is used instead of the default. The default mobility model of Silicon is Analytic , but we use the mobility model of Masetti in Mobility of this modulator.

elec_Si_properties = {
"permittivity": {
"permittivity": 11.7
},
"work_function":4.2,
"fundamental": {
"electron": "density_of_states",
"hole": "density_of_states",
"nc": {
# "constant": 3.21657e19,
"enable_model": True,
"nc300": 3.21657e19
},
"nv": {
# "constant": 1.82868e19,
"enable_model": True,
"nv300": 1.82868e19
},
"eg": {
# "constant": 1.12416,
"enable_model": True,
"alpha": 0.000473,
"beta": 636,
"eg0": 1.16
},
"narrowing": {
"model": "slotboom",
"slotboom": {
"e0": 0.0045,
"n0": 1.0e17
}
},
},
"recombination":{
"trap_assisted": {
"enabled": True,
"taun": {
"enable_model": False,
# "constant": 1e-5,
# "alpha": 0,
"dopant": {
"model": "scharfetter",
"scharfetter": {
"nref": 7.1e15,
"taumax": 3.3e-6,
"taumin":0
}
},
"field": {
"model": "none",
# "schenk": {
# "hbar_omega": 0.068,
# "mt": 0.258,
# "s": 3.5
# }
}
},
"taup": {
"enable_model": False,
# "constant": 3e-6,
# "alpha": 0,
"dopant": {
"model": "scharfetter",
"scharfetter": {
"nref": 7.1e15,
"taumax": 4e-6,
"taumin": 0
}
},
"field": {
"model": "none",
# "schenk": {
# "hbar_omega": 0.068,
# "mt": 0.24,
# "s": 3.5
# }
}
},
"ei_offset": 0.0
},
"radiative": {
"enabled": True,
"copt": 1.6e-14
},
"auger": {
"enabled": True,
"caun": {
"constant": 2.8e-31,
"enable_model": False,
# "a": 6.7e-32,
# "b": 2.45e-31,
# "c": -2.2e-32,
# "h": 3.46667,
# "n0": 1e18
},
"caup": {
"constant": 9.9e-32,
"enable_model": False,
# "a": 7.2e-32,
# "b": 4.5e-33,
# "c": 2.63e-32,
# "h": 8.25688,
# "n0": 1e18
}
},
"band_to_band_tunneling": {
"enabled": False,
# "model": "hurkx", # or "schenk"
# "hurkx": {
# "agen": 3.5e21,
# "arec": 3.5e21,
# "bgen": 2.25e7,
# "brec": 2.25e7,
# "pgen": 2.0,
# "prec": 2.0,
# "alpha": 0
# },
# "schenk": {
# "a": 8.977e20,
# "b": 2.1466e7,
# "hbar_omega": 0.0186
# }
}

},
"mobility":{
"mun": {
"lattice": {
# "constant": 1417,
"enable_model": True,
"eta": -2.5,
"mumax": 1471
},
"impurity": {
"model": "masetti",
"masetti": {
"alpha": 0.68,
"beta": 2,
"cr": 9.68e16,
"cs": 3.43e20,
"mu1": 43.4,
"mumin1": 52.2,
"mumin2": 52.2,
"pc": 0
}
},
"high_field": {
"model": "none",
# "canali": {
# "alpha": 0,
# "beta0": 1.109,
# "eta": 0.66
# },
# "driving_field": {
# "model": "e_dot_j", # or "grad_phi",
# "grad_phi": {
# "nref": 1e5
# }
# },
# "vsat": {
# "constant": 1.07e7,
# "enable_model": False,
# "gamma": 0.87,
# "vsat0": 1.07e7
# }
}
},
"mup": {
"lattice": {
# "constant": 470.5,
"enable_model": True,
"eta": -2.2,
"mumax": 470.5
},
"impurity": {
"model": "masetti", # or "none"
"masetti": {
"alpha": 0.719,
"beta": 2,
"cr": 2.23e17,
"cs": 6.1e20,
"mu1": 29,
"mumin1": 44.9,
"mumin2": 44.9,
"pc": 0
}
},
"high_field": {
"model": "none",
# "canali": {
# "alpha": 0,
# "beta0": 1.213,
# "eta": 0.17
# },
# "driving_field": {
# "model": "e_dot_j", # or "grad_phi",
# "grad_phi": {
# "nref": 1e5
# }
# },
# "vsat": {
# "constant": 8.37e6,
# "enable_model": True,
# "gamma": 0.52,
# "vsat0": 8.37e6
# },
},
},
},
}

The parameter values of opto_Si_properties_1550 and opto_Si_properties_1310 are used to describe the perturbation variation of carrier distribution on refractive index. Should choose the "index_perturbation" model and enable the "np_density" model to define these parameters.

opto_Si_properties_1550 = {
"index_perturbation":
{
"enabled": True,
"np_density":
{
"enable_model": True,
"np_density_model": "soref_and_bennett",
"coefficients": "user_input",
"isotropic_data": [
{
"dalpha_an": 8.5e-18,
"dalpha_ap": 6e-18,
"dalpha_en": 1,
"dalpha_ep": 1,
"dn_an": -8.8e-22,
"dn_ap": -8.5e-18,
"dn_en": 1,
"dn_ep": 0.8,
}
]
}
}
}

opto_Si_properties_1310 = {
"index_perturbation":
{
"enabled": True,
"np_density":
{
"enable_model": True,
"np_density_model": "soref_and_bennett",
"coefficients": "user_input",
"isotropic_data": [
{
"dalpha_an": 6e-18,
"dalpha_ap": 4e-18,
"dalpha_en": 1,
"dalpha_ep": 1,
"dn_an": -6.2e-22,
"dn_ap": -6e-18,
"dn_en": 1,
"dn_ep": 0.8,
}
]
}
}
}

2. Code description

2.1 Import Modules

To begin, we need to use the import command to call the relevant functional modules and parameter values of material from previously defined file.

import sys

# encoding: utf-8

from moapi.v3.aggregate import AggregatedUIService as Project
import maxoptics_sdk.all as mo
from maxoptics_sdk.helper import timed, with_path
import os
import time
from typing import NamedTuple
import sys
current_dir = os.path.dirname(__file__)
sys.path.extend([current_dir])
from MOD_material import elec_Si_properties, opto_Si_properties_1550, opto_Si_properties_1310

The typing module provides support for type hints and annotations, which are used to indicate the expected types of variables, function arguments, and return values in your code.
The os module provides a way to use operating system-dependent functionality such as reading or writing to the file system, working with environment variables, and executing system commands.
The time module provides various functions to work with time-related operations and to measure time intervals.
The maxoptics_sdk package provides all in one optical simulation with Python.

2.2 Define Parameters

2.2.1 General Parameters

We define these parameters for entire simulation process.

wavelength = 1.55    # um
temperature = 300 # K
normal_length = 1e4 # um
egrid_global = 0.01 # um
ogrid_global_y = 0.02 # um
ogrid_global_z = 0.02 # um
ogrid_local_y = 0.002 # um
ogrid_local_z = 0.002 # um
run_mode = "local"
simu_name = "MOD00"

ParameterUnitsDescription
wavelengthumSpecifies the optical wavelength of the source beam (in the vacuum) for mono-spectral simulations.
temperatureKSpecifies the temperature in Kelvin.
normal_lengthumSpecifies the extent of a mesh section in the X direction.
egrid_loacalumSpecifies the appropriate size of mesh in the Y and Z direction for overall region of DDM simulation.
ogrid_global_yumSpecifies the mesh spacing in the Y direction for region of optical simulation.
ogrid_global_zumSpecifies the mesh spacing in the Z direction for region of optical simulation.
ogrid_localumSpecifies the mesh spacing in the Y and Z direction for local region of optical simulation.
run_modeSpecifies the simulate model.
simu_nameSpecifies this file name to define name of output result folder.

Since we explore two communication wavelength, you should define the wavelength, and use next code to choose relavent parameters values, which we defines in MOD_material.py .

if wavelength > 1.4:
Si_index_vs_doping = opto_Si_properties_1550
else:
Si_index_vs_doping = opto_Si_properties_1310
2.2.2 Structure Geometry

You should define the location of material in X, Y and Z direction about the modulator structure.

st_x_min = -0.3
st_x_max = 0.3
st_x_mean = 0.5*(st_x_min+st_x_max)
st_x_span = st_x_max-st_x_min

st_y_min = -5
st_y_max = 5
st_y_mean = 0.5*(st_y_min+st_y_max)
st_y_span = st_y_max-st_y_min

st_z_min = -0.3
st_z_max = 0.8

slab_height = 0.09
rg_height = 0.22
rg_width = 0.5
elec_height = 0.5
elec_width = 1.2
ParameterUnitsDescription
x_min, x_max, y_min, y_max, z_min, z_maxumSpecifies the minimum or maximum value in X/Y boundary of modulator structure.
x_mean, x_span, y_mean, y_span, z_mean, z_spanumSpecifies the center or spacing value in X/Y boundary of modulator structure.
slab_heightumSpecifies the height of slab layer in Z direction.
rg_heightumSpecifies the height of RG layer in Z direction.
elec_height, elec_widthumSpecifies the Z-direction height and Y-direction width of electron.
2.2.3 Electrical Simulation Boundary

Specifies the boundary of electrical simulation for DDM solver in 3D direction.

oe_x_min = 0
oe_x_max = 0
oe_x_mean = 0.5*(oe_x_min+oe_x_max)
oe_x_span = oe_x_max-oe_x_min

oe_y_min = st_y_min
oe_y_max = st_y_max
oe_y_mean = 0.5*(oe_y_min+oe_y_max)
oe_y_span = oe_y_max-oe_y_min

oe_z_min = st_z_min
oe_z_max = st_z_max
oe_z_mean = 0.5*(oe_z_min+oe_z_max)
oe_z_span = oe_z_max-oe_z_min

Specifies the concentration of uniform doping by constant_con and its boundary throuth center and span in Y-Z plane.

constant_y_center = 0
constant_y_span = 10
constant_z_center = 0.1
constant_z_span = 0.3
constant_con = 1e15

Specifies the junction width and peak concentration of gaussian doping by_jw and _con , its boundary throuth center and span in Y-Z plane of slab.

p_slab_y_center = -2.575
p_slab_y_span = 4.85
p_slab_z_center = -0.105
p_slab_z_span = 0.39
p_slab_jw = 0.1
p_slab_con = 7e17

n_slab_y_center = 2.575
n_slab_y_span = 4.85
n_slab_z_center = -0.105
n_slab_z_span = 0.39
n_slab_jw = 0.1
n_slab_con = 5e17

Specifies the doping region of high concentration in slab layer, including boundary、junction width and peak concentration.

p_plus_y_center = -3.5
p_plus_y_span = 3
p_plus_z_center = -0.04
p_plus_z_span = 0.52
p_plus_jw = 0.1
p_plus_con = 1e19

n_plus_y_center = 3.5
n_plus_y_span = 3
n_plus_z_center = -0.04
n_plus_z_span = 0.52
n_plus_jw = 0.1
n_plus_con = 1e19

Specifies the doping region of high concentration in waveguide structure, including boundary、junction width and peak concentration.

p_wg_y_center = -0.12
p_wg_y_span = 0.36
p_wg_z_center = 0.1275
p_wg_z_span = 0.255
p_wg_jw = 0.12
p_wg_con = 5e17

n_wg_y_center = 0.105
n_wg_y_span = 0.29
n_wg_z_center = 0.12
n_wg_z_span = 0.235
n_wg_jw = 0.11
n_wg_con = 7e17
2.2.4 Optical simulation Boundary

Specifies the boundary of optical simulation for FDE solver in 3D direction. We only consider optical characteristic of Y-Z plane, since the light travels along X-axis.

x_min = 0
x_max = 0
x_mean = 0.5*(x_min+x_max)
x_span = x_max-x_min

y_min = -2.3
y_max = 2.3
y_mean = 0.5*(y_min+y_max)
y_span = y_max-y_min

z_min = -1.5
z_max = 1.5
z_mean = 0.5*(z_min+z_max)
z_span = z_max-z_min

2.3 Define Engineering Function

To facilitate the calling of other simulation scripts, it is recommended to define a function that can set materials, model, dope, and add boundary conditions.

2.3.1 Create Project

Create a new simulation project.

class RunOptions(NamedTuple):
index_preview: bool = False
doping_preview: bool = False
calculate_modes: bool = False
run: bool = False
extract: bool = False
def create_project(project_name, run_options: RunOptions) -> Project:
# region --- 1. Project ---
pj = mo.Project(name=project_name)
# endregion
return pj

2.3.2 Set Material

The electrical and optical parameters of SiO2、Al and Si materials in the material library can be accessed. If the parameters defined in the Si section of MOD_material.py are different from the default parameters in the material library, the former will be prioritized. In the overlapping area of the materials, the material with a higher order value will take precedence over the one with a lower value. If the values are the same, the material defined later will override the one defined earlier. It is worth noting that the material Aluminium will call the PEC material library.

# region --- 2. Material ---
mt = pj.Material()

mt.add_lib(name="mat_sio2", data=mo.OE_Material.SiO2, order=1)
mt.add_lib(name="mat_al", data=mo.OE_Material.Al, order=2, override={"work_function": 4.1})
mt.add_lib(name="mat_si", data=mo.OE_Material.Si, order=2, override=elec_Si_properties)
mt["mat_sio2"].set_optical_material(data=mo.Material.SiO2_Palik)
mt["mat_al"].set_optical_material(data=mo.Material.PEC)
mt["mat_si"].set_optical_material(data=mo.Material.Si_Palik, override=Si_index_vs_doping)
# endregion
2.3.3 Define structure

To begin with, it is advisable to define the geometric region of the device structure in geometry and add materials to this region by material. The background material, which is usually Air or SiO2, should be added first.

# region --- 3. Structure ---
st = pj.Structure()

st.add_geometry(name="Slab", type="Rectangle", property={
"material": {"material": mt["mat_si"]},
"geometry": {"x": st_x_mean, "x_span": st_x_span, "y": st_y_mean, "y_span": st_y_span, "z_min": 0, "z_max": slab_height}})

st.add_geometry(name="Rg", type="Rectangle", property={
"material": {"material": mt["mat_si"]},
"geometry": {"x": st_x_mean, "x_span": st_x_span, "y": 0, "y_span": rg_width, "z_min": slab_height, "z_max": rg_height}})

# endregion
ParameterUnitsDescription
nameRestricts the applicability of the statement to regions with the specified name.
typeSpecifies the geometry of specified region.
materialRestricts the applicability of the statement to regions of the specified material.
geometrySpecifies the boundary of specified region in 3D direction

We offer several alternative electrode region for establishing the electrical simulation area.

    st.add_geometry(name="Anode", type="Rectangle", property={
"material": {"material": mt["mat_al"]},
"geometry": {"x": st_x_mean, "x_span": st_x_span, "y_min": st_y_min, "y_max": st_y_min + elec_width,
"z_min": slab_height, "z_max": slab_height + elec_height}})

st.add_geometry(name="Cathode", type="Rectangle", property={
"material": {"material": mt["mat_al"]},
"geometry": {"x": st_x_mean, "x_span": st_x_span, "y_min": st_y_max - elec_width, "y_max": st_y_max,
"z_min": slab_height, "z_max": slab_height + elec_height}})
2.3.4 Add Doping

You should define the name and type for doping region,and basic parameters of doping module in property, such as dopant and geometry. applicable regions can choose the all region、material or solid to reduce the doping region.Then define source face、junction width、peak concentration and reference concentration in gaussian doping, or only concentration in uniform doping. type specifies the n-type or donor dopant in n , and p-type or acceptor dopant in p , which may be used with gaussian and uniform profile types. ref_concentration specifies the diffusion boundary of Gaussian doping.

def add_ddm_settings(pj: Project, run_options: RunOptions):
# region --- 4. DDM:Doping ---
mt = pj.Material()
st = pj.Structure()
dp = pj.Doping()
dp.add(name="background_doping", type="constant_doping", property={
"dopant": {"dopant_type": "p", "concentration": constant_con},
"geometry": {"x": st_x_mean, "x_span": st_x_span,
"y": constant_y_center, "y_span": constant_y_span,
"z": constant_z_center, "z_span": constant_z_span,
"applicable_regions": "all_regions"
}})
dp.add(name="p_slab", type="diffusion_doping", property={
"dopant": {"dopant_type": "p", "concentration": p_slab_con, "ref_concentration": 1e6,
"source_face": "upper_z", "diffusion_function": "gaussian", "junction_width": p_slab_jw},
"geometry": {"x": st_x_mean, "x_span": st_x_span,
"y": p_slab_y_center, "y_span": p_slab_y_span,
"z": p_slab_z_center, "z_span": p_slab_z_span,
"applicable_regions": "all_regions"
}})
dp.add(name="n_slab", type="diffusion_doping", property={
"dopant": {"dopant_type": "n", "concentration": n_slab_con, "ref_concentration": 1e6,
"source_face": "upper_z", "diffusion_function": "gaussian", "junction_width": n_slab_jw},
"geometry": {"x": st_x_mean, "x_span": st_x_span,
"y": n_slab_y_center, "y_span": n_slab_y_span,
"z": n_slab_z_center, "z_span": n_slab_z_span,
"applicable_regions": "all_regions"
}})
dp.add(name="p_plus", type="diffusion_doping", property={
"dopant": {"dopant_type": "p", "concentration": p_plus_con, "ref_concentration": 1e6,
"source_face": "upper_z", "diffusion_function": "gaussian", "junction_width": p_plus_jw},
"geometry": {"x": st_x_mean, "x_span": st_x_span,
"y": p_plus_y_center, "y_span": p_plus_y_span,
"z": p_plus_z_center, "z_span": p_plus_z_span,
"applicable_regions": "all_regions"
}})
dp.add(name="n_plus", type="diffusion_doping", property={
"dopant": {"dopant_type": "n", "concentration": n_plus_con, "ref_concentration": 1e6,
"source_face": "upper_z", "diffusion_function": "gaussian", "junction_width": n_plus_jw},
"geometry": {"x": st_x_mean, "x_span": st_x_span,
"y": n_plus_y_center, "y_span": n_plus_y_span,
"z": n_plus_z_center, "z_span": n_plus_z_span,
"applicable_regions": "all_regions"
}})
dp.add(name="p_wg", type="diffusion_doping", property={
"dopant": {"dopant_type": "p", "concentration": p_wg_con, "ref_concentration": 1e6,
"source_face": "lower_y", "diffusion_function": "gaussian", "junction_width": p_wg_jw},
"geometry": {"x": st_x_mean, "x_span": st_x_span,
"y": p_wg_y_center, "y_span": p_wg_y_span,
"z": p_wg_z_center, "z_span": p_wg_z_span,
"applicable_regions": "all_regions"
}})
dp.add(name="n_wg", type="diffusion_doping", property={
"dopant": {"dopant_type": "n", "concentration": n_wg_con, "ref_concentration": 1e6,
"source_face": "upper_y", "diffusion_function": "gaussian", "junction_width": n_wg_jw},
"geometry": {"x": st_x_mean, "x_span": st_x_span,
"y": n_wg_y_center, "y_span": n_wg_y_span,
"z": n_wg_z_center, "z_span": n_wg_z_span,
"applicable_regions": "all_regions"
}})
# endregion

ParameterUnitsDescription
x_min, x_max, y_min, y_max, z_min, z_maxumSpecifies the minimum or maximum value in X/Y boundary of modulator structure.
x_mean, x_span, y_mean, y_span, z_mean, z_spanumSpecifies the center or spacing value in X/Y boundary of modulator structure.
applicable_regionsSelections are ['all_regions','solid','material']
2.3.5 Set Monitor

Charge Monitor and Electrical Monitor support both 2D and 1D geometry, whereas Band Monitor can only be used for 1D geometry and Doping Monitor only supports 2D geometry.

# region --- 5. DDM:Monitor ---

mn = pj.Monitor()
mn.add(name="charge_monitor", type="charge_monitor", property={
"general": {"interpolate_accuracy": 1},
"geometry": {"monitor_type": "2d_x_normal", "x": st_x_mean, "x_span": 0,
"y": st_y_mean, "y_span": st_y_span, "z": st_z_mean, "z_span": st_z_span}
})

mn.add(name="elec_monitor", type="electrical_monitor", property={
"general": {"interpolate_accuracy": 1},
"geometry": {"monitor_type": "2d_x_normal", "x": st_x_mean, "x_span": 0,
"y": st_y_mean, "y_span": st_y_span, "z": st_z_mean, "z_span": st_z_span}
})
mn.add(name="band_monitor", type="band_monitor", property={
"general": {"interpolate_accuracy": 1},
"geometry": {"monitor_type": "y_linear", "x": st_x_mean, "x_span": 0,
"y": st_y_mean, "y_span": st_y_span, "z": 0.08, "z_span": 0}
})
mn.add(name="doping_monitor", type="doping_monitor", property={
"general": {"interpolate_accuracy": 4},
"geometry": {"monitor_type": "2d_x_normal", "x": st_x_mean, "x_span": 0,
"y": 0, "y_span": 0.8, "z_min": -0.1, "z_max": 0.3}
})

# endregion
ParametersDescription
nameThe name of band monitor defined in the project.
typeTo decide the type of monitor.
propertyThe property of band monitor.
ParametersDefaultTypeNotes
general.interpolate_accuracy1integerSet the accuracy of the rectangular grid for extracting the monitor result. Restrained by condition: >=1 && <= 10. Here 1 means the grid size is 10nm, and 10 means the grid size is 1nm, and the grid size varies uniformly with the variation in 'interpolate_accuracy'.
monitor_typestringSelections are ['linear_x', 'linear_y', 'linear_z', '2d_x_normal', '2d_y_normal', '2d_z_normal'].
2.3.6 Add Mesh

When specifying meshes, a balance should be struck between accuracy and numerical efficiency. The accuracy, convergence, and program memory of the subsequent computation are all affected by the quality and size of the elements in the mesh, making mesh partitioning crucial in this module. Achieving accuracy requires a fine mesh that can resolve all significant features of the solution, while numerical efficiency requires a coarse mesh that minimizes the total number of grid points.

Due to the relatively simple structure of this modulator, a rough initial mesh can be established for electrical and optical simulation of the entire device. In order to obtain more accurate calculation results and better convergence, the LocalMesh module is employed to refine the mesh of the regions with significant optical characteristics.

def add_fde_settings(pj: Project, run_options: RunOptions):
# region --- 6.FDE:Mesh ---
lm = pj.LocalMesh()

lm.add(name="Mesh", type="Mesh", property={
"general": {"dy": ogrid_local_y, "dz": ogrid_local_z},
"geometry": {"x": x_mean, "x_span": x_span,
"y": 0, "y_span": 2*rg_width,
"z_min": -0.5*rg_height, "z_max": 1.5*rg_height
}})
# endregion
2.3.6 Set preview Doping

​To analyze the doping distribution of the device, the DDM module can be invoked. First, define the file name for the resulting image and save it to the "plot" folder. Then, configure the DDM solver and define the region from which to extract the refractive index distribution, which is the coordinate in the two-dimensional Y-Z plane where the X-axis equals 0. Additionally, save the simulation process to the "log" folder in the "project" directory.

Through invoking the time module and adding it to the file name, the output file of each simulation can be unique, which facilitates accurate file retrieval after multiple simulations. The plot_path will be used as the path for saving extracted results and is set here to the plots folder in the same directory as the script.

@timed
@with_path
def preview_doping(**kwargs):
# region --- 6. Preview Doping ---
run_options = RunOptions(index_preview=False, doping_preview=True, calculate_modes=False, run=False, extract=False)

vsource = "Cathode" # electrode solid
gnd = "Anode" # electrode solid
path = kwargs["path"]
time_str = time.strftime("%Y%m%d_%H%M%S", time.localtime())
simu_name = "MOD00_structure_doping"
project_name = f"{simu_name}_{time_str}"
plot_path = f"{path}/plots/{project_name}/"
current_file_path = os.path.abspath(__file__)

Adding electrical silmulation solver, is the prerequisite for output structure file. You can specify solver name and type in name and type module, and then define its other property.

The type of ddm module can be invoked to enable the charge carrier transport solver for analyzing the optoelectronic properties of a device. For this study, we set the solver to solve the steady state of the device at room temperature. , and should set the electrode in BoundaryCondition. Additionally, we define the length of the three-dimensional X direction and the solution mode and temperature during the composite process.

Since the simulation analysis is conducted in the two-dimensional Y-Z plane that is perpendicular to the X-axis, 2d_x_normal is adopted to define the simulation calculation geometry. And set the mesh size for entire region of DDM.

We utilize the newton iteration method for calculation, and the mumps direct solver is employed as the linear solver. The max_iterations parameter defines the maximum number of nonlinear iterations. When the number of iterations exceeds this value, the solver reduces the voltage step and starts a new iterative computations.

    pj: Project = create_project(project_name, run_options)

create_structures(pj, run_options)

mt = pj.Material()
st = pj.Structure()

simu = pj.Simulation()
simu.add(name=simu_name, type="DDM", property={
"background_material": mt["mat_sio2"],
"general": {"solver_mode": "steady_state",
"norm_length": normal_length,
"temperature_dependence": "isothermal",
"temperature": temperature,
},
"geometry": {"dimension": "2d_x_normal", "x": oe_x_mean, "x_span": 0, "y_min": oe_y_min, "y_max": oe_y_max, "z_min": oe_z_min, "z_max": oe_z_max},
"mesh_settings": {"mesh_size": egrid_global},
"advanced": {"non_linear_solver": "newton",
"linear_solver": "mumps",
"fermi_statistics": "disabled", # or "enabled"
"damping": "none", # or "potential"
"potential_update": 1.0,
"max_iterations": 15,
"relative_tolerance": 1e-5,
"tolerance_relax": 1e5,
"divergence_factor": 1e25
}
})

add_ddm_settings(pj, run_options)

bd = pj.BoundaryCondition()

bd.add(name=vsource,type="electrode", property={
"geometry": {"surface_type": "solid", "solid": st[vsource]},
"general": {"electrode_mode": "steady_state",
"contact_type": "ohmic_contact",
"sweep_type": "single", "voltage": 0,
"apply_ac_small_signal": "none",
# "envelop": "uniform",
}
})
bd.add(name=gnd,type="electrode", property={
"geometry": {"surface_type": "solid", "solid": st[gnd]},
"general": {"electrode_mode": "steady_state",
"contact_type": "ohmic_contact",
"sweep_type": "single", "voltage": 0,
"apply_ac_small_signal": "none",
# "envelop": "uniform",
}
})

simu[simu_name].preview_doping(monitor_name="doping_monitor", savepath=f"{plot_path}doping", export_csv=True, real=True, imag=False)
# endregion
2.3.6 Set Preview Index

You can invoke the FDE module to analyze the refractive index distribution of the device. First, define the file name of the result image and save it to the "plot" folder, add structure and material about index region. To define the region of the refractive index distribution to be extracted in geometry , which is the coordinate in the two-dimensional Y-Z plane at X-axis equal to 0. Then run analysis module of FDE and store the extracted result in file path.

@timed
@with_path
def preview_index(**kwargs):
# region --- 7. Preview Index ---
run_options = RunOptions(index_preview=True, doping_preview=False, calculate_modes=False, run=False, extract=False)

path = kwargs["path"]
simu_name = "MOD00_structure_index"
time_str = time.strftime("%Y%m%d_%H%M%S", time.localtime())
project_name = f"{simu_name}_{time_str}"
plot_path = f"{path}/plots/{project_name}/"
current_file_path = os.path.abspath(__file__)

pj: Project = create_project(project_name, run_options)

create_structures(pj, run_options)

mt = pj.Material()
st = pj.Structure()

simu = pj.Simulation()
simu.add(name=simu_name, type="FDE",
property={"background_material": mt["mat_sio2"],
"geometry": {"x": x_mean, "x_span": x_span, "y": y_mean, "y_span": y_span, "z": z_mean, "z_span": z_span, },
"boundary_conditions": {"y_min_bc": "PEC", "y_max_bc": "PEC", "z_min_bc": "PEC", "z_max_bc": "PEC",},
# 'mode_removal': {'threshold': 0.02},
# default is '2d_x_normal' ['2d_x_normal','2d_y_normal','2d_z_normal']
'general': {'solver_type': '2d_x_normal'},
"mesh_settings": {"mesh_refinement": {"mesh_refinement": "curve_mesh"}, "mesh_factor": 1.2,
"global_mesh_uniform_grid": {"dy": ogrid_global_y, "dz": ogrid_global_z, },
# 'minimum_mesh_step_settings': {'min_mesh_step': 1.0e-4}
}})

add_fde_settings(pj, run_options)

simu_res = simu[simu_name].run()

analysis = pj.Analysis()
analysis.add(name="fde_analysis", type="FDEAnalysis",
property={"workflow_id": simu_res.workflow_id, "simulation_name": "FDE",
"modal_analysis": {"calculate_modes": run_options.run, "mesh_structure": run_options.index_preview,
"wavelength": wavelength, "wavelength_offset": 0.0001, "number_of_trial_modes": 5,
"search": "max_index", # ['near_n','max_index']
# "n": 1,
"calculate_group_index": False,
"bent_waveguide": {"bent_waveguide": False, "radius": 1, "orientation": 0, "location": "simulation_center"}
}})
result_fde = analysis["fde_analysis"].run()

result_fde.extract(data="mesh_structure", savepath=f"{plot_path}01_index", export_csv=True)
if __name__ == "__main__":
preview_doping()
preview_index()

3. Output Result

​Here, we present the device geometry as defined in the structure file, along with the P-type and N-type dopant distributions, and the overall doping distribution.

​Application Library path: :mo_sdk_solution_v3.2.3.0\examples\active_demo\doping_function\MOD\plots\MOD00_structure_doping_time

Na DopingN DopingNd Doping

​ We can see the distribution of the refractive index in these three directions of device.

​ Application Library path: :mo_sdk_solution_v3.2.3.0\examples\active_demo\doping_function\MOD\plots\MOD00_structure_index_time

X-axisY-axisZ-axis

Analytic Character

1.Modulation efficiency

​The half-wave voltage refers to the applied voltage required by the modulator's optical signal to generate a phase difference of pi radians, directly reflecting the modulation efficiency of the modulator. Usually, the modulation efficiency of a device is represented by multiplying the half-wave voltage with a parameter that represents the length of the modulator required for phase variation. A smaller value of this parameter indicates higher modulation efficiency, resulting in a smaller required device size.

According to the modulator's operating principle, the extra carriers created by the doped device under external bias form an internal electric field and depletion layer in the device via drift diffusion, resulting in changes in refractive index and loss. You may determine np density using DDM's steady-state solution by MOD02_np.py and then input the data into FDE to calculate changes in optical loss and refractive index by MOD0A_vpi.py.

​Application Library path: :mo_sdk_solution_v3.2.3.0\examples\active_demo\doping_function\MOD\MOD02_np.py :mo_sdk_solution_v3.2.3.0\examples\active_demo\doping_function\MOD\MOD0A_vpi.py

1.1 Basic Operations

​Before analyzing the characteristic parameters of the device, you need to set up the simulation environment in advance and invoke the modules into the file by import . Please refer to the PD documentation for detailed instructions on specific Python commands.

1.2 Code Description

1.2.1 Import Modules

​To begin, we need to use the import command to call the relevant functional modules.

import sys

# encoding: utf-8

from maxoptics_sdk.helper import timed, with_path
import os
import time
import sys
import numpy as np
current_dir = os.path.dirname(__file__)
sys.path.extend([current_dir])
from MOD00_structure import *

​For specific instructions, see the description document of PD device.

​By adding the runtime function module to the file name, you can ensure the orderly storage of simulation results without data overlay or overwriting during multiple simulation runs.

@timed
@with_path
def simulation(*, run_options: "RunOptions", **kwargs, ):
1.2.2 Define Parameters

​The operational mechanism of the device we simulated in this study primarily revolves around the application of a reverse voltage to modify the width of the depletion region, thereby effectively modifying the carrier concentration and subsequently modulating the refractive index. Hence, it is necessary to specify the voltage of initial、termination and step, with both parameters expressed in volts.

vsource = "Cathode" # electrode solid
gnd = "Anode" # electrode solid

sweep_vstart = -0.5
sweep_vstop = 4
sweep_vstep = 0.5
1.2.3 Set Path

​You could define the project name of path, and then define the parameters for path.


path = kwargs["path"]
simu_name = "MOD02_np"
time_str = time.strftime("%Y%m%d_%H%M%S", time.localtime())
project_name = f"{simu_name}_local_{time_str}"
plot_path = f"{path}/plots/{project_name}/"
current_file_path = os.path.abspath(__file__)

# endregion

# region --- 1. Project ---

pj: Project = create_project(project_name, run_options)

# endregion
1.2.4 Create Structure

Could directly reference the device structure and materials set in the previous file MOD00_structure.py and MOD_material.py.

# region --- 1. Project ---

pj: Project = create_project(project_name, run_options)

# endregion

# region --- 2. Structure ---

create_structures(pj, run_options)

mt = pj.Material()
st = pj.Structure()

# endregion
1.2.5 Set Simulation

​You can add a electrical model solver here, include DDM solver and boundary conditons. ​You can specify the physical attributes of an electrode. You can set the BC model and scanning method here. The bias voltage range is set for steady-state solutions. Detailed information about the Electrode attribute can be found in the appendix of the PD documentation.

    # region --- 3. Simulation ---
simu = pj.Simulation()
simu.add(name=simu_name, type="DDM", property={
"background_material": mt["mat_sio2"],
"general": {"solver_mode": "steady_state",
"norm_length": normal_length,
"temperature_dependence": "isothermal",
"temperature": temperature,
},
"geometry": {"dimension": "2d_x_normal", "x": oe_x_mean, "x_span": 0, "y_min": oe_y_min, "y_max": oe_y_max, "z_min": oe_z_min, "z_max": oe_z_max},
"mesh_settings": {"mesh_size": egrid_global},
"advanced": {"non_linear_solver": "newton",
"linear_solver": "mumps",
"fermi_statistics": "disabled", # or "enabled"
"damping": "potential", # or "none"
"potential_update": 1.0,
"max_iterations": 15,
"relative_tolerance": 1e-5,
"tolerance_relax": 1e5,
"divergence_factor": 1e25
}
})

# endregion

# region --- 4. Simulation settings ---

add_ddm_settings(pj, run_options)

bd = pj.BoundaryCondition()

bd.add(name=vsource,type="electrode", property={
"geometry": {"surface_type": "solid", "solid": st[vsource]},
"general": {"electrode_mode": "steady_state",
"contact_type": "ohmic_contact",
"sweep_type": "range", "range_start": sweep_vstart, "range_stop": sweep_vstop, "range_step": sweep_vstep,
"apply_ac_small_signal": "none",
# "envelop": "uniform",
}
})
bd.add(name=gnd,type="electrode", property={
"geometry": {"surface_type": "solid", "solid": st[gnd]},
"general": {"electrode_mode": "steady_state",
"contact_type": "ohmic_contact",
"sweep_type": "single", "voltage": 0,
"apply_ac_small_signal": "none",
# "envelop": "uniform",
}
})
# endregion
1.2.6 Run

​It is recommended to include a module that runs the solver and initiates the simulation. The result_device variable stores simulation results for subsequent extraction.

    # region --- 5. Run ---
if run_options.run:
result_ddm = simu[simu_name].run(resources={"threads": 4})
# endregion
1.2.7 Extract Parameters

​You can extract and export simulation results to the plot folder.

# region --- 6. Extract ---
if run_options.run and run_options.extract:
export_options = {"export_csv": True,
"export_mat": True, "export_zbf": False}

# --- Voltage list ---
result_ddm.extract(data="ddm:electrode", electrode_name=vsource, savepath=f"{plot_path}I_{vsource}",
target="line", attribute="I", plot_x=f"V_{vsource}", real=True, imag=False, show=False, export_csv=True)

result_ddm.save_as(data="ddm:charge_monitor", monitor_name="charge_monitor", savepath=f"{plot_path}/charge")

voltage_list = np.genfromtxt(f"{plot_path}I_{vsource}.csv", delimiter=",", skip_header=1)[:,0]

# --- Monitor Result ---
for voltage in voltage_list:
slice_options = {f"v_{vsource.lower()}": voltage, f"v_{gnd.lower()}": 0.0}

# --- Charge Monitor ---
attribute = "n" # "n", "p"
result_ddm.extract(data="ddm:charge_monitor", monitor_name="charge_monitor", savepath=f"{plot_path}{attribute}/{voltage}V",
target="intensity", attribute=attribute, real=True, imag=False, log=False, show=False, **slice_options, **export_options)

# --- Electrical Monitor ---
attribute = "E" # "E", "Ex", "Ey", "Ez"
result_ddm.extract(data="ddm:electrical_monitor", monitor_name="elec_monitor", savepath=f"{plot_path}{attribute}/{voltage}V",
target="intensity", attribute=attribute, real=True, imag=False, log=False, show=False, **slice_options, **export_options)

# --- Band Monitor ---
attribute = "Ec" # "Ec", "Ev", "Efn", "Efp"
result_ddm.extract(data="ddm:band_monitor", monitor_name="band_monitor", savepath=f"{plot_path}{attribute}/{voltage}V",
target="line", attribute=attribute, plot_x="y", real=True, imag=False, log=False, show=False, **slice_options, export_csv=True)

# endregion

return result_ddm if run_options.run else None

if __name__ == "__main__":
simulation(run_options=RunOptions(index_preview=False, doping_preview=False, calculate_modes=False, run=True, extract=True))

1.2.8 Calculate

Import the np density file, which contains the carrier distribution derived from the DDM method, into the FDE model. This integration facilitates the analysis of refractive index and loss variations as a function of bias voltage at specific wavelengths.

​According to the phase change formula, we can determine that the refractive index n is a function of bias voltage, which means that we can change the refractive index of the material by adjusting the bias voltage.

Δφ=2πλΔn(V)L=2πλdndVΔVL\Delta\varphi=\frac{2\pi}{\lambda}\Delta n(V)L\\=\frac{2\pi}{\lambda}\frac{dn}{dV}\Delta VL

​With the following formula, when the phase difference is pi:

ΔVπL=ΔVΔnλ2\Delta V_\pi L=\frac{\Delta V}{\Delta n}\frac{\lambda}{2}

​Application Library path: :mo_sdk_solution_v3.2.3.0\examples\active_demo\doping_function\MOD\MOD0A_vpi.py

The fundamental simulation process entails loading Python modules, retrieving the device's structural and material properties from MOD00_structure, and obtaining the DDM simulation setting from MOD02_np, while also specifying the file paths.

import sys

# encoding: utf-8
from maxoptics_sdk.helper import timed, with_path
import os
import time
import sys
import numpy as np
current_dir = os.path.dirname(__file__)
sys.path.extend([current_dir])

from MOD00_structure import *
from MOD02_np import simulation as ddm_simulation

run_with_ddm = True # True: run ddm first, then import the np result to fde solver automatically
# False: run fde only, the np file path need to be specified manually

@timed
@with_path
def simulation(*, run_options: "RunOptions", **kwargs, ):
# region --- 0. General Parameter ---

vsource = "Cathode" # electrode solid
gnd = "Anode" # electrode solid

path = kwargs["path"]
np_file = kwargs["np_file"]
simu_name = "MOD0A_FDE"
time_str = time.strftime("%Y%m%d_%H%M%S", time.localtime())
project_name = f"{simu_name}_{time_str}"
plot_path = f"{path}/plots/{project_name}/"
current_file_path = os.path.abspath(__file__)

# endregion
# region --- 1. Project ---
pj: Project = create_project(project_name, run_options)
# endregion

create_structures(pj, run_options)

mt = pj.Material()
st = pj.Structure()

The optical solver FDE can be configured in this location, allowing for the inclusion of np density file within data space.

    # region --- 2. Simulation ---
simu = pj.Simulation()
simu.add(name=simu_name, type="FDE",
property={"background_material": mt["mat_sio2"],
"geometry": {"x": x_mean, "x_span": x_span, "y": y_mean, "y_span": y_span, "z": z_mean, "z_span": z_span, },
"boundary_conditions": {"y_min_bc": "PEC", "y_max_bc": "PEC", "z_min_bc": "PEC", "z_max_bc": "PEC",},
# 'mode_removal': {'threshold': 0.02},
# default is '2d_x_normal' ['2d_x_normal','2d_y_normal','2d_z_normal']
'general': {'solver_type': '2d_x_normal'},
"mesh_settings": {"mesh_refinement": {"mesh_refinement": "curve_mesh"}, "mesh_factor": 1.2,
"global_mesh_uniform_grid": {"dy": ogrid_global_y, "dz": ogrid_global_z, },
# 'minimum_mesh_step_settings': {'min_mesh_step': 1.0e-4}
}})
# endregion

# region --- 3. Simulation settings ---
add_fde_settings(pj, run_options)

ds = pj.DataSpace()
ds.import_data(name="np", type="np_density", property={"path": np_file})

attr = pj.Attribute()
attr.add(name="np", type="np_density", property={
"data": ds["np"], "index": {f"V_{vsource}": kwargs[f"V_{vsource}_index"], f"V_{gnd}": 0}
})

# endregion

Run FDE solver and add the analysis to calculate the change of effective refractive index and optical loss under specific wavelength.

# region --- 4. Run ---
simu_res = simu[simu_name].run()

# --- Analysis ---
analysis = pj.Analysis()
analysis.add(name="fde_analysis", type="FDEAnalysis",
property={"workflow_id": simu_res.workflow_id, "simulation_name": "FDE",
"modal_analysis": {"calculate_modes": run_options.run, "mesh_structure": run_options.index_preview,
"wavelength": wavelength, "wavelength_offset": 0.0001, "number_of_trial_modes": 5,
"search": "max_index", # ['near_n','max_index']
# "n": 1,
"calculate_group_index": False,
"bent_waveguide": {"bent_waveguide": False, "radius": 1, "orientation": 0, "location": "simulation_center"}
}})
result_fde = analysis["fde_analysis"].run()
# endregion

​This section of the program is dedicated to calculating the parameters VpiL and VpiLoss.

​You should first extract the parameters for transmission loss and the real part of effective refractive index.

​The filename 01_neff.csv is automatically generated for the n-V data file. The initial 0 indicates the electrode number, which changes when extracting current from different electrodes. Therefore, iterate from 0 to 9 to locate the saved n-V data files.

    # region --- 5. Extract ---

export_options = {"export_csv": True,
"export_mat": True, "export_zbf": True}

if run_options.extract:
if run_options.index_preview:
result_fde.extract(
data="mesh_structure", savepath=f"{plot_path}01_index", export_csv=True)

if run_options.run:
res = result_fde.extract(
data="calculate_modes", savepath=f"{plot_path}02_neff_table", export_csv=True)
# print(res.to_string(index=True))
# for m in range(len(res)):
# result_fde.extract(data="calculate_modes", savepath=f"{plot_path}03_mode{m}",
# attribute="E", mode=m, real=True, imag=True, **export_options, show=False)
return res
# endregion

return None

if __name__ == "__main__":
simu_name="MOD0A_vpi"
time_str = time.strftime("%Y%m%d_%H%M%S", time.localtime())
plot_folder = os.path.join(current_dir, "plots", f"{simu_name}_{time_str}")

if not os.path.exists(plot_folder):
os.makedirs(plot_folder)

if run_with_ddm:
result_ddm = ddm_simulation(run_options=RunOptions(run=True))
result_ddm.save_as(data="ddm:charge_monitor", monitor_name="charge_monitor", savepath=f"{plot_folder}/charge")
np_density_file = f"{plot_folder}/charge.cdat"
else:
np_density_file = os.path.join(os.path.dirname(__file__), "MOD02_np.cdat")

voltage_list = np.linspace(-0.5, 4, 10)
neff = []
loss = []
for index in range(len(voltage_list)):
res = simulation(run_options=RunOptions(run=True, extract=True), V_Cathode_index=index, np_file=np_density_file)
neff.append(res["neff_real"][0])
loss.append(res["loss_dBpcm"][0])

​Generate the relationship curve and data file for the product of half-wave voltage and effective modulation length (VpiL), the product of half-wave voltage and loss (VpiLoss), as well as VBias, and save them in the respective newly created files.

    vpil = []
volt_out = []
vpiloss = []

for i in range(1, len(voltage_list)-1):
volt_out.append(voltage_list[i])
vpil.append((voltage_list[i+1]-voltage_list[i-1])/(neff[i+1]-neff[i-1])*wavelength/2*1e-4)
vpiloss.append(vpil[-1]*loss[i])

neff_file = os.path.join(plot_folder, "01_neff.csv")
loss_file = os.path.join(plot_folder, "02_loss.csv")
vpil_file = os.path.join(plot_folder, "03_vpil.csv")
vpiloss_file = os.path.join(plot_folder, "04_vpiloss.csv")

​You can use the following program to set up the title and style of the picture.

    np.savetxt(neff_file, np.array((voltage_list, neff)).T, fmt='%f,%.15f', header='voltage,neff')
np.savetxt(loss_file, np.array((voltage_list, loss)).T, fmt='%f,%.15f', header='voltage,loss')
np.savetxt(vpil_file, np.array((volt_out, vpil)).T, fmt='%f,%.15f', header='voltage,VpiL')
np.savetxt(vpiloss_file, np.array((volt_out, vpiloss)).T, fmt='%f,%.15f', header='voltage,VpiLoss')

1.3 Output Result

​Application Library path: :mo_sdk_solution_v3.2.3.0\examples\active_demo\doping_function\MOD\plots\MOD0A_vpi_local_time

1.3.1 Loss

​This section displays the variation of losses with bias voltage.

1.3.2 Effective Index

​These graphs depict the real and imaginary components of the refractive index as a function of bias voltage.

Reffective index realReffective index imaginary
1.3.3 Modulation efficiency

​These graphs illustrate the relationship between two parameters that represent modulation efficiency as a function of bias voltage.

VpiLVpiLoss

2. Capacitance And Resistance

​Capacitance and resistance play crucial roles in determining the performance of devices. Optimal capacitance values enable the modulator to selectively allow or block signals within specific frequency ranges, facilitating signal coupling. Suitable resistance values enable adjustment of signal amplitude and modulation current determination.

​Application Library path: :mo_sdk_solution_v3.2.3.0\examples\active_demo\doping_function\MOD\MOD0B_RC.py

2.1 Basic Operations

​The simulation program for calculating capacitance and voltage exhibits numerous similarities to the program used for investigating modulation efficiency. Prior to conducting the simulation, it is necessary to import the module in order to invoke the structures and physical models defined in the MOD00_structure.py .

2.2 Code Description

2.2.1 Import Modules

​The necessary Python modules for the program are invoked within the process.

import sys

# encoding: utf-8

from maxoptics_sdk.helper import timed, with_path
import os
import time
import sys
import numpy as np
current_dir = os.path.dirname(__file__)
sys.path.extend([current_dir])
from MOD00_structure import *
2.2.2 Define Parameters

​The voltages to be applied to the electrodes of the device are specified.

@timed
@with_path
def simulation(*, run_options: "RunOptions", **kwargs, ):

# region --- 0. General Parameter ---

vsource = "Cathode" # electrode solid
gnd = "Anode" # electrode solid

sweep_vstart = -0.5
sweep_vstop = 4
sweep_vstep = 0.5

ssac_amplitude = 0.001
2.2.3 Set Path

​You can use the time module for file naming conventions and then store the files in the plot_path folder.

  path = kwargs["path"]
simu_name = "MOD01_RC"
time_str = time.strftime("%Y%m%d_%H%M%S", time.localtime())
project_name = f"{simu_name}_local_{time_str}"
plot_path = f"{path}/plots/{project_name}/"
current_file_path = os.path.abspath(__file__)
2.2.4 Set Simulation

​You can add a solver for the simulation and define its properties. Furthermore, the properties of the AC small-signal module are defined in this section. The frequency interval is logarithmically defined, specifying the initial frequency, final frequency, and the number of frequencies. The variable ssac_amplitude" represents the amplitude of the small signal. In this instance, three frequency values (1, 100, and 10000 MHz) are selected from the range of 1e6 to 1e10.

# region --- 1. Project ---

pj: Project = create_project(project_name, run_options)

# endregion

# region --- 2. Structure ---

create_structures(pj, run_options)

mt = pj.Material()
st = pj.Structure()

# endregion

# region --- 3. Simulation ---
simu = pj.Simulation()
simu.add(name=simu_name, type="DDM", property={
"background_material": mt["mat_sio2"],
"general": {"solver_mode": "ssac",
"norm_length": normal_length,
"temperature_dependence": "isothermal",
"temperature": temperature,
"perturbation_amplitude": ssac_amplitude, "frequency_spacing": "log",
"log_start_frequency": 1e6, "log_stop_frequency": 1e10, "number_of_points": 3
},
"geometry": {"dimension": "2d_x_normal", "x": oe_x_mean, "x_span": 0, "y_min": oe_y_min, "y_max": oe_y_max, "z_min": oe_z_min, "z_max": oe_z_max},
"mesh_settings": {"mesh_size": egrid_global},
"advanced": {"non_linear_solver": "newton",
"linear_solver": "mumps",
"fermi_statistics": "disabled", # or "enabled"
"damping": "potential", # or "none"
"potential_update": 1.0,
"max_iterations": 15,
"relative_tolerance": 1e-5,
"tolerance_relax": 1e5,
"divergence_factor": 1e25
}
})

# endregion
2.2.5 Create Component

​You can directly invoke the engineering function and device structure created in the MOD00_structure.py file, and then add the electrodes and their attributes. In this case, a bias voltage ranging from -0.5 V to 4 V with a scan step of 0.5 V is applied to the cathode electrode during small-signal simulation.


# region --- 4. Simulation settings ---

add_ddm_settings(pj, run_options)

bd = pj.BoundaryCondition()

bd.add(name=vsource,type="electrode", property={
"geometry": {"surface_type": "solid", "solid": st[vsource]},
"general": {"electrode_mode": "steady_state",
"contact_type": "ohmic_contact",
"sweep_type": "range", "range_start": sweep_vstart, "range_stop": sweep_vstop, "range_step": sweep_vstep,
"apply_ac_small_signal": "all",
# "envelop": "uniform",
}
})
bd.add(name=gnd,type="electrode", property={
"geometry": {"surface_type": "solid", "solid": st[gnd]},
"general": {"electrode_mode": "steady_state",
"contact_type": "ohmic_contact",
"sweep_type": "single", "voltage": 0,
"apply_ac_small_signal": "none",
# "envelop": "uniform",
}
})
# endregion
2.2.6 Run

​It is recommended to include a module to execute the solver and commence the simulation.

    # region --- 5. Run ---
if run_options.run:
result_ddm = simu[simu_name].run(resources={"threads": 4})
# endregion
2.2.7 Extract Parameters

​Firstly, the names of the folders used to store the extracted data for the real and imaginary components of the refractive index should be defined. ​Secondly, the following program can be used to define the output files for the real and imaginary parts of the refractive index, as well as the capacitance and resistance, while also specifying the data content within the files and ensuring proper attention to the frequency units.

    # region --- 6. Extract ---
if run_options.run and run_options.extract:
export_options = {"export_csv": True,
"export_mat": True, "export_zbf": False}

# --- Frequency list ---
result_ddm.extract(data="ddm:electrode_ac", electrode_name=vsource, savepath=f"{plot_path}Iac_f",
target="line", attribute="Iac", plot_x=f"frequency", real=True, imag=False, show=False, export_csv=True)
ssac_frequency_span = np.genfromtxt(f"{plot_path}Iac_f.csv", skip_header=1, delimiter=',')[:,0]

# --- Iac ---
for i,val in enumerate(ssac_frequency_span):
plot_path_f = os.path.join(plot_path, f"{val/1e6:.2f}MHz")
result_ddm.extract(data="ddm:electrode_ac", electrode_name=vsource, savepath=f"{plot_path_f}/Iac_{vsource}_real",
target="line", attribute="Iac", plot_x=f"V_{vsource}", real=True, imag=False, frequency=val, show=False, export_csv=True)

result_ddm.extract(data="ddm:electrode_ac", electrode_name=vsource, savepath=f"{plot_path_f}/Iac_{vsource}_imag",
target="line", attribute="Iac", plot_x=f"V_{vsource}", real=False, imag=True, frequency=val, show=False, export_csv=True)
# endregion

2.2.8 Calculate

​Finally, the column names in the previously defined data files can be set, with the first column representing voltage and the second column representing capacitance or resistance. Additionally, the axis titles and styles should be set for the output images.

    # region --- 7. Post Processing ---
fontsize = 20
linewidth = 1
plt.rcParams.update({"font.size": fontsize})
for i,val in enumerate(ssac_frequency_span):
plot_path_f = os.path.join(plot_path, f"{val/1e6:.2f}MHz")
Iac_real_data = np.genfromtxt(f"{plot_path_f}/Iac_Cathode_real.csv", skip_header=1, delimiter=",")
Iac_imag_data = np.genfromtxt(f"{plot_path_f}/Iac_Cathode_imag.csv", skip_header=1, delimiter=",")
Iac = Iac_real_data[:,1] + 1j * Iac_imag_data[:,1]
Vdc = Iac_real_data[:,0]
Z = ssac_amplitude/Iac
R = np.abs(np.real(Z))
C = np.abs(np.imag(1/Z)/(2*np.pi*ssac_frequency_span[i]))/1e4*1e15

np.savetxt(f"{plot_path_f}/resistance.csv", np.array((Vdc, R)).T, fmt='%f,%.15e', header='voltage,resistance')
np.savetxt(f"{plot_path_f}/capacitance.csv", np.array((Vdc, C)).T, fmt='%f,%.15e', header='voltage,capacitance')

fig, ax1 = plt.subplots()
fig.set_size_inches(12, 8)
ax1.plot(Vdc, R, c='b', linewidth=linewidth, label=f"f:{val/1e6:.2f}MHz",marker='o')
ax1.set_xlabel('VBias[V]')
ax1.set_ylabel('Resistance[Ohm]')
ax1.legend()
ax1.grid()
plt.savefig(f"{plot_path_f}/resistance.jpg")
plt.close()

fig, ax2 = plt.subplots()
fig.set_size_inches(12, 8)
ax2.plot(Vdc, C, c='b', linewidth=linewidth, label=f"f:{val/1e6:.2f}MHz",marker='o')
ax2.set_xlabel('VBias[V]')
ax2.set_ylabel('Capacitance[fF/um]')
ax2.legend()
ax2.grid()
plt.savefig(f"{plot_path_f}/capacitance.jpg")
plt.close()
# endregion
if __name__ == "__main__":
simulation(run_options=RunOptions(index_preview=False, doping_preview=False, calculate_modes=False, run=True, extract=True))

2.3 Output Result

​Application Library path: :\examples\active_demo\doping_function\MOD\plots\MOD0B_RC_local_time

2.3.1 Small Signal AC Current

​This section displays the variations of the real and imaginary components of the AC current with respect to bias voltage at different frequencies.

1 MHZ100 MHZ10000 MHZ
Real
Image
2.3.2 Capacitance

​This section displays the variations of capacitance with respect to bias voltage at different frequencies.

1 MHZ100 MHZ10000 MHZ
2.3.3 Resistance

​This section displays the variations of resistance with respect to bias voltage at different frequencies.

1 MHZ100 MHZ10000 MHZ