User Guide
This guide covers all components of nanohub-padre in detail.
Running Simulations
The recommended workflow is to call sim.run() after building the simulation.
On failure it returns a non-zero returncode — always check and raise so
errors surface as Jupyter cell exceptions rather than silent output.
from nanohubpadre import create_pn_diode
sim = create_pn_diode(p_doping=1e17, n_doping=1e17, log_iv=True,
forward_sweep=(0.0, 1.0, 0.05))
result = sim.run()
if result.returncode != 0:
raise RuntimeError(f"Simulation failed:\n{result.stderr}")
# Access outputs directly
sim.plot_iv(title="PN Diode I-V")
sim.plot_band_diagram(title="PN Diode Band Diagram")
Visualization Methods
After a successful sim.run() all output data is accessible through
sim.outputs or via convenience methods on the Simulation object.
Band Diagrams
# Plot equilibrium band diagram (Ec, Ev, Efn, Efp)
sim.plot_band_diagram(title="Equilibrium")
# Plot a specific suffix (e.g. after a bias sweep)
sim.plot_band_diagram(suffix="eq")
sim.plot_band_diagram(suffix="bias")
# Choose backend
sim.plot_band_diagram(backend="matplotlib")
sim.plot_band_diagram(backend="plotly")
Carrier Concentrations
sim.plot_carriers(suffix="eq", log_scale=True,
title="Carrier Concentrations at Equilibrium")
C-V Characteristics
# Normalized C/Cox (default)
sim.plot_cv(title="MOS Capacitor C-V")
# Raw capacitance in Farads
sim.plot_cv(normalize=False)
Electrostatics
# Side-by-side: potential and electric field
sim.plot_electrostatics(suffix="eq", title="Electrostatics at Equilibrium")
I-V Characteristics
# Default: current_electrode=1
sim.plot_iv(title="Forward I-V")
# Specify electrode explicitly
sim.plot_iv(current_electrode=2, log_scale=True)
Transfer / Output Characteristics (FET/BJT)
sim.plot_transfer(gate_electrode=3, drain_electrode=2)
sim.plot_output(drain_electrode=2)
sim.plot_gummel(base_electrode=2, collector_electrode=3)
2D Contour Maps
sim.plot_contour("pot_bias", title="Potential", colorscale="RdBu_r")
sim.plot_contour("el_bias", title="Electrons", log_scale=True)
Accessing Raw Output Data
# Get a named output (returns PlotData with .x and .y arrays)
pot = sim.outputs.get("pot_eq")
print(pot.x, pot.y)
# Get all AC (C-V) data
ac_map = sim.outputs.get_ac_data() # dict name -> ACData
# Get by variable type
band_data = sim.outputs.get_by_variable("band_con")
Mesh Definition
The mesh defines the computational grid for your device simulation.
Rectangular Mesh
from nanohubpadre import Mesh
mesh = Mesh(nx=100, ny=50)
# X grid lines — first node has no ratio
mesh.add_x_mesh(1, 0.0)
mesh.add_x_mesh(50, 0.5, ratio=0.8)
mesh.add_x_mesh(100, 1.0, ratio=1.2)
# Y grid lines
mesh.add_y_mesh(1, 0.0)
mesh.add_y_mesh(25, 0.1, ratio=0.7)
mesh.add_y_mesh(50, 1.0, ratio=1.3)
The ratio parameter controls mesh grading:
ratio < 1: Mesh becomes finer toward this noderatio = 1: Uniform spacingratio > 1: Mesh becomes coarser toward this nodeOmit
ratio(or set toNone) on the first node — PADRE ignores it there
Loading Existing Mesh
mesh = Mesh(infile="previous_mesh.pg", previous=True)
Regions
from nanohubpadre import Region
silicon = Region(1, ix_low=1, ix_high=100, iy_low=5, iy_high=50,
material="silicon", semiconductor=True)
oxide = Region(2, ix_low=20, ix_high=80, iy_low=1, iy_high=5,
material="sio2", insulator=True)
The material type keyword (semiconductor=True / insulator=True) is
appended after the name= parameter in the generated PADRE deck, which
is required by PADRE’s parser.
Electrodes
from nanohubpadre import Electrode
source = Electrode(1, ix_low=1, ix_high=20, iy_low=5, iy_high=5)
gate = Electrode(2, x_min=0.3, x_max=0.7, y_min=-0.01, y_max=-0.01)
drain = Electrode(3, ix_low=80, ix_high=100, iy_low=5, iy_high=5)
substrate = Electrode(4, ix_low=1, ix_high=100, iy_low=50, iy_high=50)
Doping Profiles
Uniform Doping
from nanohubpadre import Doping
sim.add_doping(Doping(p_type=True, concentration=1e17, uniform=True, region=1))
sim.add_doping(Doping(n_type=True, concentration=1e20, uniform=True,
x_left=0.0, x_right=0.3))
Gaussian Profile
sim.add_doping(Doping(gaussian=True, n_type=True, concentration=5e19,
junction=0.2, peak=0.0, characteristic=0.05))
Contacts
from nanohubpadre import Contact
sim.add_contact(Contact(all_contacts=True, neutral=True))
sim.add_contact(Contact(number=1, n_polysilicon=True))
sim.add_contact(Contact(number=2, workfunction=4.87))
Card ordering in the generated deck: doping → contact → material → interface → models. This matches the Rappture reference decks.
Materials
from nanohubpadre import Material
sim.add_material(Material(name="silicon", taun0=1e-6, taup0=1e-6))
# Gate oxide — use permi= for permittivity (PADRE keyword)
sim.add_material(Material(name="sio2", permittivity=3.9, qf=0))
The permittivity parameter is output as permi= in the deck, which is
the keyword PADRE accepts.
Interfaces
Interface cards are always emitted for oxide/semiconductor boundaries, even
when qf=0.
from nanohubpadre import Interface
sim.add_interface(Interface(number=1, qf=0))
Physical Models
from nanohubpadre import Models
sim.models = Models(
temperature=300,
srh=True,
conmob=True,
fldmob=True,
print_models=True, # Emit 'print' flag in PADRE deck
)
Solve Commands
from nanohubpadre import Solve
sim.add_solve(Solve(initial=True, outfile="eq"))
# Voltage sweep
sim.add_solve(Solve(v1=0, vstep=0.1, nsteps=20, electrode=1))
# AC analysis (C-V)
sim.add_solve(Solve(v1=-2.0, vstep=0.1, nsteps=20, electrode=1,
ac_analysis=True, frequency=1e6))
# Continuation — needs one prior solution
sim.add_solve(Solve(previous=True))
# Projection — needs two prior solutions
sim.add_solve(Solve(project=True, vstep=0.05, nsteps=10, electrode=1))
Logging and Output
from nanohubpadre import Log
sim.add_log(Log(ivfile="iv_data")) # I-V log
sim.add_log(Log(acfile="cv_data")) # AC/C-V log
sim.add_log(Log(off=True)) # Stop logging
1D Profile Logging
Use the helper methods on Simulation to add Plot1D commands cleanly:
x_mid = device_width / 2
sim.log_band_diagram("eq", x_start=x_mid, x_end=x_mid,
y_start=0.0, y_end=total_thickness, include_qf=True)
sim.log_carriers("carriers_eq", x_start=x_mid, x_end=x_mid,
y_start=0.0, y_end=total_thickness)
sim.log_potential("pot_eq", x_start=x_mid, x_end=x_mid,
y_start=0.0, y_end=total_thickness)
sim.log_efield("ef_eq", x_start=x_mid, x_end=x_mid,
y_start=0.0, y_end=total_thickness)
Complete Workflow Example
from nanohubpadre import (
Simulation, Mesh, Region, Electrode, Doping,
Contact, Material, Interface, Models, System,
Solve, Log, Plot3D
)
sim = Simulation(title="MOSFET Example")
sim.mesh = Mesh(nx=60, ny=40)
sim.mesh.add_x_mesh(1, 0.0)
sim.mesh.add_x_mesh(30, 0.5, ratio=0.9)
sim.mesh.add_x_mesh(60, 1.0)
sim.mesh.add_y_mesh(1, -0.01)
sim.mesh.add_y_mesh(5, 0.0)
sim.mesh.add_y_mesh(40, 1.0, ratio=1.2)
sim.add_region(Region(1, ix_low=1, ix_high=60, iy_low=1, iy_high=5,
material="sio2", insulator=True))
sim.add_region(Region(2, ix_low=1, ix_high=60, iy_low=5, iy_high=40,
material="silicon", semiconductor=True))
sim.add_electrode(Electrode(1, x_min=0.3, x_max=0.7, iy_low=1, iy_high=1))
sim.add_electrode(Electrode(2, ix_low=1, ix_high=10, iy_low=5, iy_high=5))
sim.add_electrode(Electrode(3, ix_low=50, ix_high=60, iy_low=5, iy_high=5))
sim.add_electrode(Electrode(4, ix_low=1, ix_high=60, iy_low=40, iy_high=40))
sim.add_doping(Doping(p_type=True, concentration=1e17, uniform=True, region=2))
sim.add_doping(Doping(gaussian=True, n_type=True, concentration=1e20,
junction=0.1, x_right=0.2, region=2))
sim.add_doping(Doping(gaussian=True, n_type=True, concentration=1e20,
junction=0.1, x_left=0.8, region=2))
# Card order: doping → contact → material → interface → models
sim.add_contact(Contact(number=1, n_polysilicon=True))
sim.add_contact(Contact(number=2, neutral=True))
sim.add_contact(Contact(number=3, neutral=True))
sim.add_contact(Contact(number=4, neutral=True))
sim.add_material(Material(name="silicon", taun0=1e-6, taup0=1e-6))
sim.add_material(Material(name="sio2", permittivity=3.9, qf=0))
sim.add_interface(Interface(number=1, qf=0))
sim.models = Models(temperature=300, srh=True, conmob=True, fldmob=True,
print_models=True)
sim.system = System(electrons=True, holes=True, newton=True)
sim.add_solve(Solve(initial=True, outfile="eq"))
sim.add_solve(Solve(v3=0.05))
sim.add_log(Log(ivfile="idvg"))
sim.add_solve(Solve(v1=0, vstep=0.1, nsteps=15, electrode=1))
sim.add_log(Log(off=True))
result = sim.run()
if result.returncode != 0:
raise RuntimeError(f"Simulation failed:\n{result.stderr}")
sim.plot_transfer(gate_electrode=1, drain_electrode=3,
title="NMOS Transfer Characteristic")