"""
Plotting commands for PADRE simulations.
Includes PLOT.1D, PLOT.2D, CONTOUR, and VECTOR commands.
"""
from typing import Optional, Union, List
from .base import PadreCommand
[docs]
class Plot2D(PadreCommand):
"""
Set up 2D plot area and optionally plot grid/boundaries.
Parameters
----------
Area definition:
x_min, x_max : float
X bounds (microns)
y_min, y_max : float
Y bounds (microns)
z_pos : float
Z position for xy-plane slice
What to plot:
grid : bool
Plot the mesh grid
boundary : bool
Plot region boundaries
junction : bool
Plot doping junctions
depl_edg : bool
Plot depletion edges
crosses : bool
Mark grid points with crosses
Control:
no_fill : bool
Draw to scale (don't fill screen)
no_clear : bool
Don't clear screen before plot
no_tic : bool
No tic marks
title : bool
Show title
labels : bool
Show color labels
flip_x : bool
Mirror plot about y-axis
tilt : bool
3D tilted view
a_elevation : float
Elevation angle for tilt
a_azimuth : float
Azimuth angle for tilt
pause : bool
Pause after plot
outfile : str
Output plot file
Example
-------
>>> # Plot grid to scale
>>> p = Plot2D(grid=True, no_fill=True)
>>>
>>> # Plot boundaries and junctions in region
>>> p = Plot2D(x_min=0, x_max=5, y_min=0, y_max=10,
... junction=True, boundary=True)
"""
command_name = "PLOT.2D"
[docs]
def __init__(
self,
# Area
x_min: Optional[float] = None,
x_max: Optional[float] = None,
y_min: Optional[float] = None,
y_max: Optional[float] = None,
z_pos: Optional[float] = None,
top: bool = False,
bottom: bool = False,
left: bool = False,
right: bool = False,
# What to plot
grid: bool = False,
mesh: bool = False,
obtuse: bool = False,
crosses: bool = False,
boundary: bool = False,
interface: int = 1,
depl_edg: bool = False,
junction: bool = False,
# Control
no_tic: bool = False,
no_top: bool = False,
no_fill: bool = False,
no_clear: bool = False,
no_end: bool = False,
no_diag: bool = False,
labels: bool = False,
title: bool = False,
flip_x: bool = False,
tilt: bool = False,
a_elevation: float = 30,
a_azimuth: float = -30,
pause: bool = False,
spline: bool = False,
nspline: int = 100,
# Line types
l_elect: Optional[int] = None,
l_deple: Optional[int] = None,
l_junct: Optional[int] = None,
l_bound: Optional[int] = None,
l_grid: Optional[int] = None,
color: bool = False,
grey: bool = False,
# Output
outfile: Optional[str] = None,
geomfile: Optional[str] = None,
criter: Optional[float] = None,
):
super().__init__()
self.x_min = x_min
self.x_max = x_max
self.y_min = y_min
self.y_max = y_max
self.z_pos = z_pos
self.top = top
self.bottom = bottom
self.left = left
self.right = right
self.grid = grid or mesh
self.obtuse = obtuse
self.crosses = crosses
self.boundary = boundary
self.interface = interface
self.depl_edg = depl_edg
self.junction = junction
self.no_tic = no_tic
self.no_top = no_top
self.no_fill = no_fill
self.no_clear = no_clear
self.no_end = no_end
self.no_diag = no_diag
self.labels = labels
self.title = title
self.flip_x = flip_x
self.tilt = tilt
self.a_elevation = a_elevation
self.a_azimuth = a_azimuth
self.pause = pause
self.spline = spline
self.nspline = nspline
self.l_elect = l_elect
self.l_deple = l_deple
self.l_junct = l_junct
self.l_bound = l_bound
self.l_grid = l_grid
self.color = color
self.grey = grey
self.outfile = outfile
self.geomfile = geomfile
self.criter = criter
[docs]
def to_padre(self) -> str:
params = {}
flags = []
# Area
if self.x_min is not None:
params["X.MIN"] = self.x_min
if self.x_max is not None:
params["X.MAX"] = self.x_max
if self.y_min is not None:
params["Y.MIN"] = self.y_min
if self.y_max is not None:
params["Y.MAX"] = self.y_max
if self.z_pos is not None:
params["Z.POS"] = self.z_pos
if self.top:
flags.append("TOP")
if self.bottom:
flags.append("BOTTOM")
if self.left:
flags.append("LEFT")
if self.right:
flags.append("RIGHT")
# What to plot
if self.grid:
flags.append("GRID")
if self.obtuse:
flags.append("OBTUSE")
if self.crosses:
flags.append("CROSSES")
if self.boundary:
flags.append("BOUND")
if self.interface != 1:
params["INTERFACE"] = self.interface
if self.depl_edg:
flags.append("DEPL.EDG")
if self.junction:
flags.append("JUNC")
# Control
if self.no_tic:
flags.append("NO.TIC")
if self.no_top:
flags.append("NO.TOP")
if self.no_fill:
flags.append("NO.FILL")
if self.no_clear:
flags.append("NO.CLEAR")
if self.no_end:
flags.append("NO.END")
if self.no_diag:
flags.append("NO.DIAG")
if self.labels:
flags.append("LABELS")
if self.title:
flags.append("TITLE")
if self.flip_x:
flags.append("FLIP.X")
if self.tilt:
flags.append("TILT")
params["A.ELEVATION"] = self.a_elevation
params["A.AZIMUTH"] = self.a_azimuth
if self.pause:
flags.append("PAUSE")
if self.spline:
flags.append("SPLINE")
params["NSPLINE"] = self.nspline
# Line types
if self.l_elect is not None:
params["L.ELECT"] = self.l_elect
if self.l_deple is not None:
params["L.DEPLE"] = self.l_deple
if self.l_junct is not None:
params["L.JUNCT"] = self.l_junct
if self.l_bound is not None:
params["L.BOUND"] = self.l_bound
if self.l_grid is not None:
params["L.GRID"] = self.l_grid
if self.color:
flags.append("COLOR")
if self.grey:
flags.append("GREY")
# Output
if self.outfile:
params["OUTF"] = self.outfile
if self.geomfile:
params["GEOMF"] = self.geomfile
if self.criter is not None:
params["CRITER"] = self.criter
return self._build_command(params, flags)
[docs]
class Contour(PadreCommand):
"""
Plot contours of a quantity (requires preceding PLOT.2D).
Parameters
----------
Quantity (one of):
potential, qfn, qfp : bool
Potentials
electrons, holes : bool
Carrier concentrations
doping : bool
Doping concentration
e_field : bool
Electric field magnitude
j_electr, j_hole, j_total : bool
Current densities
recomb : bool
Recombination rate
flowlines : bool
Current flowlines
Range:
min_value, max_value : float
Contour bounds
del_value : float
Contour spacing
ncontours : int
Number of contours
Control:
logarithm : bool
Logarithmic scale
absolute : bool
Absolute value
x_compon, y_compon : bool
Vector components
line_type : int
Line style
color : bool
Color fill
grey : bool
Grey scale fill
pause : bool
Pause after plot
Example
-------
>>> # Potential contours
>>> c = Contour(potential=True, min_value=-1, max_value=3, del_value=0.25)
>>>
>>> # Log doping contours
>>> c = Contour(doping=True, logarithm=True, absolute=True,
... min_value=10, max_value=20, del_value=1)
"""
command_name = "CONTOUR"
[docs]
def __init__(
self,
# Quantity
potential: bool = False,
qfn: bool = False,
qfp: bool = False,
n_temp: bool = False,
p_temp: bool = False,
band_val: bool = False,
band_cond: bool = False,
doping: bool = False,
ion_imp: bool = False,
electrons: bool = False,
holes: bool = False,
net_charge: bool = False,
net_carrier: bool = False,
j_conduc: bool = False,
j_electr: bool = False,
v_electr: bool = False,
j_hole: bool = False,
v_hole: bool = False,
j_displa: bool = False,
j_total: bool = False,
e_field: bool = False,
recomb: bool = False,
flowlines: bool = False,
# Range
min_value: Optional[float] = None,
max_value: Optional[float] = None,
del_value: Optional[float] = None,
ncontours: Optional[int] = None,
constrain: bool = True,
# Control
line_type: int = 1,
absolute: bool = False,
logarithm: bool = False,
x_compon: bool = False,
y_compon: bool = False,
mix_mater: bool = False,
pause: bool = False,
color: bool = False,
grey: bool = False,
):
super().__init__()
self.potential = potential
self.qfn = qfn
self.qfp = qfp
self.n_temp = n_temp
self.p_temp = p_temp
self.band_val = band_val
self.band_cond = band_cond
self.doping = doping
self.ion_imp = ion_imp
self.electrons = electrons
self.holes = holes
self.net_charge = net_charge
self.net_carrier = net_carrier
self.j_conduc = j_conduc
self.j_electr = j_electr
self.v_electr = v_electr
self.j_hole = j_hole
self.v_hole = v_hole
self.j_displa = j_displa
self.j_total = j_total
self.e_field = e_field
self.recomb = recomb
self.flowlines = flowlines
self.min_value = min_value
self.max_value = max_value
self.del_value = del_value
self.ncontours = ncontours
self.constrain = constrain
self.line_type = line_type
self.absolute = absolute
self.logarithm = logarithm
self.x_compon = x_compon
self.y_compon = y_compon
self.mix_mater = mix_mater
self.pause = pause
self.color = color
self.grey = grey
[docs]
def to_padre(self) -> str:
params = {}
flags = []
# Quantity flags
if self.potential:
flags.append("POTEN")
if self.qfn:
flags.append("QFN")
if self.qfp:
flags.append("QFP")
if self.n_temp:
flags.append("N.TEMP")
if self.p_temp:
flags.append("P.TEMP")
if self.band_val:
flags.append("BAND.VAL")
if self.band_cond:
flags.append("BAND.COND")
if self.doping:
flags.append("DOPING")
if self.ion_imp:
flags.append("ION.IMP")
if self.electrons:
flags.append("ELECT")
if self.holes:
flags.append("HOLES")
if self.net_charge:
flags.append("NET.CH")
if self.net_carrier:
flags.append("NET.CA")
if self.j_conduc:
flags.append("J.CONDUC")
if self.j_electr:
flags.append("J.ELECTR")
if self.v_electr:
flags.append("V.ELECTR")
if self.j_hole:
flags.append("J.HOLE")
if self.v_hole:
flags.append("V.HOLE")
if self.j_displa:
flags.append("J.DISPLA")
if self.j_total:
flags.append("J.TOTAL")
if self.e_field:
flags.append("E.FIELD")
if self.recomb:
flags.append("RECOMB")
if self.flowlines:
flags.append("FLOW")
# Range
if self.min_value is not None:
params["MIN"] = self.min_value
if self.max_value is not None:
params["MAX"] = self.max_value
if self.del_value is not None:
params["DEL"] = self.del_value
if self.ncontours is not None:
params["NCONT"] = self.ncontours
if not self.constrain:
params["CONSTRAIN"] = False
# Control
if self.line_type != 1:
params["LINE.TYPE"] = self.line_type
if self.absolute:
flags.append("ABS")
if self.logarithm:
flags.append("LOG")
if self.x_compon:
flags.append("X.COMPON")
if self.y_compon:
flags.append("Y.COMPON")
if self.mix_mater:
flags.append("MIX.MATER")
if self.pause:
flags.append("PAUSE")
if self.color:
flags.append("COLOR")
if self.grey:
flags.append("GREY")
return self._build_command(params, flags)
[docs]
class Plot1D(PadreCommand):
"""
1D line plot through device or I-V curve plot.
Parameters
----------
Mode A - Line through device:
x_start, y_start : float
Start point (A)
x_end, y_end : float
End point (B)
z_pos : float
Z position
Quantity (one of):
potential, qfn, qfp, doping, electrons, holes, etc.
Mode B - I-V plot:
x_axis : str
X axis quantity (e.g., "V1", "V2", "I1")
y_axis : str
Y axis quantity
infile : str
Log file to plot from
frequency : float
Frequency for AC plots
Axes control:
min_value, max_value : float
Y axis bounds
x_min, x_max : float
X axis bounds
x_scale, y_scale : float
Scale factors
x_label, y_label : str
Axis labels
Plot control:
logarithm : bool
Log Y scale
x_log : bool
Log X scale
absolute : bool
Absolute value
points : bool
Mark data points
no_line : bool
Don't connect points
line_type : int
Line style
no_clear : bool
Don't clear screen
unchanged : bool
Keep same axes
pause : bool
Pause after plot
outfile : str
Output file
Example
-------
>>> # Plot potential along a line
>>> p = Plot1D(potential=True, x_start=0, y_start=0,
... x_end=5, y_end=0)
>>>
>>> # I-V curve
>>> p = Plot1D(x_axis="V2", y_axis="I1")
"""
command_name = "PLOT.1D"
[docs]
def __init__(
self,
# Segment
x_start: Optional[float] = None,
y_start: Optional[float] = None,
x_end: Optional[float] = None,
y_end: Optional[float] = None,
z_pos: Optional[float] = None,
coordinate: Optional[str] = None,
# Quantity for Mode A
potential: bool = False,
qfn: bool = False,
qfp: bool = False,
n_temp: bool = False,
p_temp: bool = False,
doping: bool = False,
ion_imp: bool = False,
electrons: bool = False,
holes: bool = False,
net_charge: bool = False,
net_carrier: bool = False,
j_conduc: bool = False,
j_electr: bool = False,
v_electr: bool = False,
j_hole: bool = False,
v_hole: bool = False,
j_displa: bool = False,
j_total: bool = False,
e_field: bool = False,
recomb: bool = False,
band_val: bool = False,
band_con: bool = False,
# Mode B - I-V
x_axis: Optional[str] = None,
y_axis: Optional[str] = None,
frequency: Optional[float] = None,
infile: Optional[str] = None,
# Axes
right_axis: bool = False,
short_axis: bool = False,
min_value: Optional[float] = None,
max_value: Optional[float] = None,
x_min: Optional[float] = None,
x_max: Optional[float] = None,
x_scale: float = 1.0,
y_scale: float = 1.0,
unscale: bool = False,
x_label: Optional[str] = None,
y_label: Optional[str] = None,
x_mark: Optional[float] = None,
y_mark: Optional[float] = None,
title: bool = True,
# Plot control
no_clear: bool = False,
no_axis: bool = False,
unchanged: bool = False,
no_end: bool = False,
no_order: bool = False,
order_y: bool = False,
unique: float = 1e-6,
points: bool = False,
no_line: bool = False,
pause: bool = False,
line_type: int = 1,
# Data control
absolute: bool = False,
logarithm: bool = False,
x_log: bool = False,
decibels: bool = False,
integral: bool = False,
negative: bool = False,
inverse: bool = False,
d_order: float = 0,
x_component: bool = False,
y_component: bool = False,
spline: bool = False,
nspline: int = 100,
# Output
outfile: Optional[str] = None,
ascii: bool = False,
):
super().__init__()
self.x_start = x_start
self.y_start = y_start
self.x_end = x_end
self.y_end = y_end
self.z_pos = z_pos
self.coordinate = coordinate
# Quantities
self.potential = potential
self.qfn = qfn
self.qfp = qfp
self.n_temp = n_temp
self.p_temp = p_temp
self.doping = doping
self.ion_imp = ion_imp
self.electrons = electrons
self.holes = holes
self.net_charge = net_charge
self.net_carrier = net_carrier
self.j_conduc = j_conduc
self.j_electr = j_electr
self.v_electr = v_electr
self.j_hole = j_hole
self.v_hole = v_hole
self.j_displa = j_displa
self.j_total = j_total
self.e_field = e_field
self.recomb = recomb
self.band_val = band_val
self.band_con = band_con
# Mode B
self.x_axis = x_axis
self.y_axis = y_axis
self.frequency = frequency
self.infile = infile
# Axes
self.right_axis = right_axis
self.short_axis = short_axis
self.min_value = min_value
self.max_value = max_value
self.x_min = x_min
self.x_max = x_max
self.x_scale = x_scale
self.y_scale = y_scale
self.unscale = unscale
self.x_label = x_label
self.y_label = y_label
self.x_mark = x_mark
self.y_mark = y_mark
self.title = title
# Plot control
self.no_clear = no_clear
self.no_axis = no_axis
self.unchanged = unchanged
self.no_end = no_end
self.no_order = no_order
self.order_y = order_y
self.unique = unique
self.points = points
self.no_line = no_line
self.pause = pause
self.line_type = line_type
# Data control
self.absolute = absolute
self.logarithm = logarithm
self.x_log = x_log
self.decibels = decibels
self.integral = integral
self.negative = negative
self.inverse = inverse
self.d_order = d_order
self.x_component = x_component
self.y_component = y_component
self.spline = spline
self.nspline = nspline
# Output
self.outfile = outfile
self.ascii = ascii
[docs]
def to_padre(self) -> str:
params = {}
flags = []
# Segment
if self.x_start is not None:
params["A.X"] = self.x_start
if self.y_start is not None:
params["A.Y"] = self.y_start
if self.x_end is not None:
params["B.X"] = self.x_end
if self.y_end is not None:
params["B.Y"] = self.y_end
if self.z_pos is not None:
params["Z.POS"] = self.z_pos
if self.coordinate:
params["COORD"] = self.coordinate
# Quantities
if self.potential:
flags.append("POT")
if self.qfn:
flags.append("QFN")
if self.qfp:
flags.append("QFP")
if self.n_temp:
flags.append("N.TEMP")
if self.p_temp:
flags.append("P.TEMP")
if self.doping:
flags.append("DOP")
if self.ion_imp:
flags.append("ION.IMP")
if self.electrons:
flags.append("ELE")
if self.holes:
flags.append("HOLE")
if self.net_charge:
flags.append("NET.CHARGE")
if self.net_carrier:
flags.append("NET.CA")
if self.j_conduc:
flags.append("J.CONDUC")
if self.j_electr:
flags.append("J.ELECTR")
if self.v_electr:
flags.append("V.ELECTR")
if self.j_hole:
flags.append("J.HOLE")
if self.v_hole:
flags.append("V.HOLE")
if self.j_displa:
flags.append("J.DISPLA")
if self.j_total:
flags.append("J.TOTAL")
if self.e_field:
flags.append("E.FIELD")
if self.recomb:
flags.append("RECOMB")
if self.band_val:
flags.append("BAND.VAL")
if self.band_con:
flags.append("BAND.CON")
# Mode B
if self.x_axis:
params["X.AXIS"] = self.x_axis
if self.y_axis:
params["Y.AXIS"] = self.y_axis
if self.frequency is not None:
params["FREQ"] = self.frequency
if self.infile:
params["INF"] = self.infile
# Axes
if self.right_axis:
flags.append("RIGHT.AXIS")
if self.short_axis:
flags.append("SHORT.AXIS")
if self.min_value is not None:
params["MIN"] = self.min_value
if self.max_value is not None:
params["MAX"] = self.max_value
if self.x_min is not None:
params["X.MIN"] = self.x_min
if self.x_max is not None:
params["X.MAX"] = self.x_max
if self.x_scale != 1.0:
params["X.SCALE"] = self.x_scale
if self.y_scale != 1.0:
params["Y.SCALE"] = self.y_scale
if self.unscale:
flags.append("UNSCALE")
if self.x_label:
params["X.LABEL"] = self.x_label
if self.y_label:
params["Y.LABEL"] = self.y_label
if self.x_mark is not None:
params["X.MARK"] = self.x_mark
if self.y_mark is not None:
params["Y.MARK"] = self.y_mark
if not self.title:
params["TITLE"] = False
# Plot control
if self.no_clear:
flags.append("NO.CLEAR")
if self.no_axis:
flags.append("NO.AXIS")
if self.unchanged:
flags.append("UNCH")
if self.no_end:
flags.append("NO.END")
if self.no_order:
flags.append("NO.ORDER")
if self.order_y:
flags.append("ORDER.Y")
if self.unique != 1e-6:
params["UNIQUE"] = self.unique
if self.points:
flags.append("POINTS")
if self.no_line:
flags.append("NO.LINE")
if self.pause:
flags.append("PAUSE")
if self.line_type != 1:
params["LINE"] = self.line_type
# Data control
if self.absolute:
flags.append("ABS")
if self.logarithm:
flags.append("LOG")
if self.x_log:
flags.append("X.LOG")
if self.decibels:
flags.append("DECIBELS")
if self.integral:
flags.append("INTEGRAL")
if self.negative:
flags.append("NEGATIVE")
if self.inverse:
flags.append("INVERSE")
if self.d_order != 0:
params["D.ORDER"] = self.d_order
if self.x_component:
flags.append("X.COMP")
if self.y_component:
flags.append("Y.COMP")
if self.spline:
flags.append("SPLINE")
params["NSPL"] = self.nspline
# Output
if self.outfile:
params["OUTF"] = self.outfile
if self.ascii:
flags.append("ASCII")
return self._build_command(params, flags)
[docs]
class Vector(PadreCommand):
"""
Plot vector quantities (requires preceding PLOT.2D).
Parameters
----------
Quantity (one of):
j_conduc, j_electr, j_hole, j_total, j_displa : bool
Current densities
v_electr, v_hole : bool
Velocities
e_field : bool
Electric field
Control:
logarithm : bool
Logarithmic scaling
minimum, maximum : float
Magnitude bounds
scale : float
Scale factor
clipfact : float
Threshold for not plotting
line_type : int
Line style
Example
-------
>>> # Plot electron and hole currents
>>> v1 = Vector(j_electr=True, line_type=2)
>>> v2 = Vector(j_hole=True, line_type=3)
"""
command_name = "VECTOR"
[docs]
def __init__(
self,
j_conduc: bool = False,
j_electr: bool = False,
v_electr: bool = False,
j_hole: bool = False,
v_hole: bool = False,
j_displa: bool = False,
j_total: bool = False,
e_field: bool = False,
logarithm: bool = False,
minimum: Optional[float] = None,
maximum: Optional[float] = None,
scale: float = 1.0,
clipfact: float = 0.1,
line_type: int = 1,
):
super().__init__()
self.j_conduc = j_conduc
self.j_electr = j_electr
self.v_electr = v_electr
self.j_hole = j_hole
self.v_hole = v_hole
self.j_displa = j_displa
self.j_total = j_total
self.e_field = e_field
self.logarithm = logarithm
self.minimum = minimum
self.maximum = maximum
self.scale = scale
self.clipfact = clipfact
self.line_type = line_type
[docs]
def to_padre(self) -> str:
params = {}
flags = []
if self.j_conduc:
flags.append("J.CONDUC")
if self.j_electr:
flags.append("J.ELECTR")
if self.v_electr:
flags.append("V.ELECTR")
if self.j_hole:
flags.append("J.HOLE")
if self.v_hole:
flags.append("V.HOLE")
if self.j_displa:
flags.append("J.DISPLA")
if self.j_total:
flags.append("J.TOTAL")
if self.e_field:
flags.append("E.FIELD")
if self.logarithm:
flags.append("LOG")
if self.minimum is not None:
params["MIN"] = self.minimum
if self.maximum is not None:
params["MAX"] = self.maximum
if self.scale != 1.0:
params["SCALE"] = self.scale
if self.clipfact != 0.1:
params["CLIPFACT"] = self.clipfact
if self.line_type != 1:
params["LINE"] = self.line_type
return self._build_command(params, flags)