GenericStructure#

class simpleEMS.components.GenericStructure(params: GenericParams, sim: SimSetup)#

Bases: SimTools

Arbitrary structure assembled from CSXCAD primitives.

Each create_* method adds one primitive to the CSXCAD structure and returns it, so a caller can transform it further or register its edges with the FDTD grid. Shared quantities – the trace elevation, the copper thickness, the port impedance, the substrate footprint – default to the matching value on params and only need to be passed when a primitive differs from it.

Parameters:
  • params (GenericParams) – Substrate, frequency and mesh parameters.

  • sim (SimSetup) – Named tuple containing the CSXCAD geometry and openEMS FDTD engine.

Methods Summary

add_field_dump(sim, params[, output_path, ...])

Add a field dump box to the simulation setup.

compute_nf2ff_3d(nf2ff, freq[, output_path, ...])

Compute the 3D far-field radiation pattern over a full theta/phi sweep (theta in [0, 180] deg, phi in [0, 360] deg, 2 deg steps).

compute_sim_data(sim, port[, output_path])

Compute S-parameters, impedance, VSWR, and port power/voltage/current from simulation results, for plotting and post-processing.

create_cpw([name, z_elevation_mm, ...])

Create a straight coplanar waveguide (CPW) segment.

create_cpw_lumped_port([name, prop_dir, ...])

Create a coplanar waveguide port from one lumped port per slot.

create_cpw_port([name, prop_dir, excite, ...])

Create a coplanar waveguide port on a CPW segment.

create_curved_bend([name, z_elevation_mm, ...])

Create a curved (circular-arc) bend.

create_gcpw([name, z_elevation_mm, ...])

Create a straight grounded (conductor-backed) CPW segment.

create_ground([name, start, stop, priority])

Create a copper ground plane from two corner points.

create_lumped_port(port_nr, start, stop[, ...])

Create a lumped port for S-parameter extraction.

create_mesh([smooth_ratio])

Generate the FDTD mesh for everything added so far.

create_microstrip([name, z_elevation_mm, ...])

Create a straight rectangular microstrip trace.

create_miter([name, z_elevation_mm, ...])

Create a 90-degree mitered bend.

create_nf2ff([sim])

Create the near-field-to-far-field (NF2FF) recording box.

create_radial_stub([name, z_elevation_mm, ...])

Create a radial (fan-shaped) stub.

create_substrate([name, start, stop, eps_r, ...])

Create a dielectric substrate from two corner points.

create_taper([name, z_elevation_mm, ...])

Create a trapezoidal taper trace.

create_via([name, antipad_diameter_mm, ...])

Create a plated through-hole via as a solid conductor.

export_csxcad_xml_to_step(structure_xml_path)

Load a structure.xml file and export it to STEP AP242 format.

export_gerber(sim[, output_path, options, ...])

Export CSXCAD geometry to Gerber format, one file per copper layer.

export_step(sim[, output_path])

Export CSXCAD geometry to a colored, multi-layer STEP AP242 file using CadQuery.

export_stl(sim[, output_path])

Export the CSXCAD structure to STL files (FDTD backend only).

export_touchstone(freqs[, s11, s21, ...])

Export S-parameters to a Touchstone .sNp file.

plot_2d_directivity(nf2ff, freq[, ...])

Plot the 2D directivity pattern (xz-plane, phi=0) at a specified frequency, annotated with the half-power beamwidth (HPBW).

plot_2d_rad_pattern(nf2ff, freq[, ...])

Plot the 2D E-field radiation pattern at a specified frequency.

plot_3d_directivity(nf2ff_3d_result, freq[, ...])

Plot the 3D directivity pattern of an antenna at a specified frequency.

plot_3d_gain(nf2ff_3d_result, freq, input_power)

Plot the 3D gain pattern of an antenna at a specified frequency.

plot_3d_power(nf2ff_3d_result, freq[, ...])

Plot the 3D radiated power pattern of an antenna at a specified frequency.

plot_group_delay(freqs, s21[, x_label, ...])

Compute and plot the group delay as a function of frequency.

plot_impedance(freqs, z11[, x_label, ...])

Plot the input impedance (Z11) as a function of frequency.

plot_phase(freqs, s21[, x_label, y_label, title])

Plot the transmission phase (angle of S21) as a function of frequency.

plot_s_param(freqs, s11[, s21, x_label, ...])

Plot S-parameters (S11 and optionally S21) against frequency.

plot_smith_chart(freqs, s11[, label, charac_imp])

Plot S11 data on a Smith chart.

plot_vswr(freqs, vswr[, x_label, y_label, ...])

Plot the voltage standing wave ratio (VSWR) as a function of frequency.

print_and_save_params(params[, output_path])

Print the simulation parameters to the console and save them to params/params.txt.

run_simulation(sim[, output_path])

Run the simulation and write results to output_path.

save_plots([output_path, file_format])

Save all currently open matplotlib figures to output_path / "plots".

show_plots()

Display all generated plots.

write_and_show_structure(sim[, output_path, ...])

Display the simulation geometry.

Methods Documentation

static add_field_dump(sim: SimSetup, params: SimParams, output_path: Path | None = None, dump_freq: float | None = None, dump_type: DumpType = DumpType.efield_frequency) → None#

Add a field dump box to the simulation setup.

Configures and attaches a field/current dump of the requested dump_type to the CSXCAD structure. The box follows the simulation box in x and y – params.simulation_box when defined, or the same geometry-padded box the FDTD mesher builds when it is None (see simpleEMS.fdtd_mesh.auto_simulation_bounds()) – and spans z from 0 to the top of the copper layer (substrate_thickness_mm + copper_thickness_mm).

Parameters:
  • sim (SimSetup) – Simulation setup named tuple returned by setup_simulation.

  • params (SimParams) – Parameter object supplying simulation_box, lambda0, substrate_thickness_mm, copper_thickness_mm and main_freq, used to size and configure the dump.

  • output_path (Path, optional) – Directory the dump data will be written under (a field_dump subdirectory is created). Defaults to cwd / "Sim_Path".

  • dump_freq (float, optional) – Frequency of a frequency-domain dump. Defaults to params.main_freq.

  • dump_type (DumpType, optional) – Type of field/current dump to add (time-domain or frequency-domain E-field, H-field, current, current density, or SAR). Default is DumpType.efield_frequency.

Return type:

None

static compute_nf2ff_3d(nf2ff: nf2ff, freq: float, output_path: Path | None = None, read_cached: bool = False) → nf2ff_results#

Compute the 3D far-field radiation pattern over a full theta/phi sweep (theta in [0, 180] deg, phi in [0, 360] deg, 2 deg steps).

The resulting far-field data can be used for visualization, directivity/gain/power analysis, and post-processing of antenna performance via plot_3d_directivity, plot_3d_gain, and plot_3d_power.

Parameters:
  • nf2ff (object) – The near-field-to-far-field (NF2FF) object containing the simulation data.

  • freq (float) – Frequency (in Hz) at which the 3D far-field pattern is evaluated. Must be a scalar.

  • output_path (Path, optional) – Path to the directory where the simulation result is saved. Defaults to cwd / "Sim_Path".

  • read_cached (bool, optional) – If True, read cached NF2FF results instead of re-computing. Default is False.

Returns:

The NF2FF result object for the full theta/phi sweep, holding (among other fields) E_norm, Dmax, Prad, theta, and phi.

Return type:

nf2ff_results

Raises:

TypeError – If freq is not a scalar (e.g. an array of frequencies).

static compute_sim_data(sim: SimSetup, port: LumpedPort | list[LumpedPort], output_path: Path | None = None) → SimData#

Compute S-parameters, impedance, VSWR, and port power/voltage/current from simulation results, for plotting and post-processing.

For the FDTD backend this calls CalcPort on the given port(s) and derives S11 (and S21 for two-port setups), Z11, VSWR, and input power from the port voltage/current waves. For the FEM backend the port argument is ignored and results are instead read from the GetDP sweep output previously written to output_path by run_simulation.

Parameters:
  • sim (SimSetup) – Simulation setup named tuple returned by setup_simulation; supplies freqs, backend_engine, and charac_imp.

  • port (LumpedPort or list of LumpedPort) – The openEMS port object representing a single-port simulation, or a two-element list [port1, port2] for a two-port simulation. Ignored when sim.backend_engine == "FEM".

  • output_path (Path, optional) – Directory the simulation results were written to. Defaults to cwd (FDTD backend) or cwd / "Sim_Path" (FEM backend, to match the default used by run_simulation).

Returns:

Named tuple containing:

  • freqsNDArray

    Frequency points used in post-processing.

  • s11NDArray

    Complex S11 values across the frequency range.

  • s21NDArray or None

    Complex S21 values across the frequency range, or None for a single-port simulation.

  • z11NDArray

    Complex Z11 values across the frequency range.

  • vswrNDArray

    VSWR values across the frequency range, computed from S11 (clipped to |S11| <= 0.999 to avoid division by zero).

  • input_powerfloat

    Time-averaged input power at the driven port.

  • port_voltageNDArray

    Total complex voltage at the driven port across the frequency range.

  • port_currentNDArray

    Total complex current at the driven port across the frequency range.

  • ref_impedancefloat

    Reference impedance charac_imp (from sim) that S11 and Z11 were computed against.

Return type:

SimData

create_cpw(name: str = 'cpw', *, position: tuple[float, float], trace_width_mm: float, gap_mm: float, ground_width_mm: float, length_mm: float, z_elevation_mm: float | None = None, copper_thickness_mm: float | None = None, rotation: float = 0, priority: int = 6) → tuple[CSPrimitives, CSPrimitives, CSPrimitives]#

Create a straight coplanar waveguide (CPW) segment.

Three parallel copper pours sharing z_elevation_mm/copper_thickness_mm: a central signal trace of width trace_width_mm, flanked on both sides by a slot of width gap_mm and then a ground pour of width ground_width_mm. Nothing is drawn underneath – this is the plain, ungrounded line, whose return path is the two coplanar pours alone.

A ground plane does not belong under it on its own. Pours that are not tied to that plane are free to drift apart in potential, and the parallel-plate mode between them and it starts to carry power; a plane comes with the stitching vias that hold the pours to it, which is create_gcpw(), the conductor-backed line.

Feed a CPW segment with create_cpw_port(), not a lumped port: the mode lives in the two slots, and a lumped port can only drive one gap at a single point.

position is the midpoint of the signal trace’s input edge, so the segment butts flush against the end of the incoming line. In the unrotated frame it runs +y, with the trace centred on cx and the two pours flanking it in x; the output edge – where the next primitive starts – is (cx, cy + length_mm) before rotation is applied.

The segment also asks create_mesh() for four mesh lines across each slot – its two edges plus the interior – which the automatic mesher would otherwise leave unresolved: a slot is thinner than the resolution at which it stops subdividing an interval, so it would get one line at its midpoint, which is both too coarse for the region the mode lives in and makes every box create_cpw_port() hands openEMS a box with no extent. A rotation that leaves the slots off-axis cannot be expressed as an axis-aligned line, so nothing is requested for it and create_cpw_port() raises on that grid.

Parameters:
  • name (str, optional) – Base name for the segment’s properties: the trace goes on "<name>_trace" and the pours on "<name>_ground_1" and "<name>_ground_2". Default "cpw".

  • position (tuple[float, float]) – (x, y) midpoint of the trace’s input edge, in the unrotated frame.

  • trace_width_mm (float) – Width of the central signal trace.

  • gap_mm (float) – Width of the slot separating the signal trace from each ground pour.

  • ground_width_mm (float) – Width of each of the two ground pours.

  • length_mm (float) – Segment length along y, in the unrotated frame.

  • z_elevation_mm (float | None, optional) – Z position of the conductors’ bottom face. Defaults to the substrate’s top face.

  • copper_thickness_mm (float | None, optional) – Conductor thickness along z. Defaults to params.copper_thickness_mm.

  • rotation (float, optional) – Rotation in degrees, counter-clockwise about position. Default 0.

  • priority (int, optional) – CSXCAD priority of the trace and both pours; where primitives overlap, the higher priority wins. Default 6.

Returns:

The signal trace and the two ground pours, in the order (trace, ground_lo, ground_hi) (lo/hi by x in the unrotated frame).

Return type:

tuple[CSPrimitives, CSPrimitives, CSPrimitives]

create_cpw_lumped_port(name: str = 'cpw_lumped_port', *, port_nr: int, start: list[float], stop: list[float], gap_mm: float, prop_dir: str = 'y', excite: float = 0, impedance: float | None = None, priority: int = 7) → CPWLumpedPort#

Create a coplanar waveguide port from one lumped port per slot.

The alternative to create_cpw_port(), and the one to reach for when the line’s conductors have thickness. openEMS’s CPW port is drawn entirely on one z plane, so on a line with real copper on it the probes sit on a face of that copper rather than across the slot, and the current probe’s loop has to guess where in z the conductor ends. This port instead gives each slot a lumped port whose box fills the slot – the gap across, the port length along the run, and the whole conductor thickness in z.

Both slots are driven signal pour -> ground pour, which puts their excitation vectors in opposition along the slot axis and so excites the odd mode the line carries rather than the even parallel-plate one a single lumped port would. Each is terminated in 2 * impedance, which is impedance in parallel, and the two are returned as one CPWLumpedPort that behaves like any other openEMS port.

Corners follow create_cpw_port(): start and stop are two opposite corners of the signal trace, never of the slots or the pours, and gap_mm is how far past each trace edge the boxes reach. The one difference is z, which here must differ between the two – that span is the conductor thickness the port fills. start is the reference plane, so a port feeding a line starts where the line starts and a port terminating one starts at its far end, with stop behind start.

Call this before create_mesh(), the opposite of create_cpw_port(). openEMS’s CPW port reads the grid to place its probes on it, so it has to come after; a lumped port never looks at the grid, and the mesher should see its boxes and the lines it asks for – above all the one on the port’s far edge, which is what fixes the port’s length instead of letting it snap to whatever the grid happened to offer.

Parameters:
  • name (str, optional) – Base name for the two slot ports, which take the suffixes "_lo_" and "_hi_" (by position along the slot axis) as the prefix on their probe file names. Default "cpw_lumped_port".

  • port_nr (int) – Port number (1-indexed), shared by both slots – they are one port, and it is the returned object that carries the number.

  • start (list[float]) – [x, y, z] opposite corners of the port’s signal trace. Along prop_dir they give the reference plane and the end of the run; along the other in-plane axis, the two edges of the trace; in z, the bottom and top of the conductor.

  • stop (list[float]) – [x, y, z] opposite corners of the port’s signal trace. Along prop_dir they give the reference plane and the end of the run; along the other in-plane axis, the two edges of the trace; in z, the bottom and top of the conductor.

  • gap_mm (float) – Width of the slot on each side of the trace. Not covered by start/stop: it is how far past each trace edge the slot boxes reach.

  • prop_dir (str, optional) – In-plane axis the run follows, "x" or "y". Default "y". It cannot be inferred, since the corners differ on both in-plane axes – one span is the run, the other the trace width.

  • excite (float, optional) – Excitation amplitude. 0 (default) is a matched load, nonzero drives the port.

  • impedance (float | None, optional) – Reference impedance of the pair, in ohms. Each slot is terminated in twice it. Defaults to params.charac_imp.

  • priority (int, optional) – CSXCAD priority for the port’s primitives.

Returns:

The created port (needed for post-simulation CalcPort).

Return type:

CPWLumpedPort

Raises:

ValueError – If prop_dir is not "x" or "y"; if start and stop agree along prop_dir, along the trace-width axis, or in z; or if the mesh has already been generated.

See also

create_cpw

the segment this port feeds.

create_cpw_port

openEMS’s own CPW port, for a zero-thickness line.

create_cpw_port(name: str = 'cpw_port', *, port_nr: int, start: list[float], stop: list[float], gap_mm: float, prop_dir: str = 'y', excite: float = 0, impedance: float | None = None, feed_shift_mm: float | None = None, meas_plane_shift_mm: float | None = None, priority: int = 7) → CPWPort#

Create a coplanar waveguide port on a CPW segment.

The CPW mode is odd: the signal trace swings against both ground pours at once, and the voltage that defines it is the field across the two slots together. A lumped port cannot represent that – it drives a single gap at a single point, so it excites the wrong field and reports the wrong impedance. openEMS’s CPW port instead places voltage probes across both slots at three planes along the run and current probes between them, which gives the propagation constant and the line’s own characteristic impedance as well as a clean single-mode excitation.

Like create_lumped_port(), and unlike the rotatable primitives, the port is given as two opposite corners of the signal trace it sits on – the same pair openEMS itself takes. The corners span the trace only, never the slots or the pours; gap_mm adds those. They share one z, because every part of the port is a sheet. prop_dir says which of the two in-plane axes the run follows, leaving the other one to span the trace width, and the direction along it is the sign of stop - start: a port that feeds a segment starts where the segment starts, and a port that terminates one starts at its far end and runs back into it, with stop behind start.

start is the reference plane – the plane the termination sits on and the S-parameters are referenced to – so it is the corner worth placing deliberately. Both it and the shared z are snapped onto the mesh, since a sheet the grid has no line on covers no cells at all: openEMS drops it, excitation included.

Two ordering rules come from openEMS, not from here. The port reads the grid to place its probes on it, so the mesh must already exist – build the geometry, call create_mesh(), then add CPW ports. And the port draws its own zero-thickness trace, on the same cpw_trace property create_cpw() uses, so it lands inside the segment’s own copper rather than beside it.

Parameters:
  • name (str, optional) – Name of the CSXCAD property the trace sheet openEMS draws for the port is added to, kept distinct from the segment it sits on so it reads as the port’s own copper. Default "cpw_port".

  • port_nr (int) – Port number (1-indexed).

  • start (list[float]) – [x, y, z] opposite corners of the port’s trace sheet. Both z values must be equal. Along prop_dir they give the reference plane and the end of the run; along the other in-plane axis, the two edges of the signal trace.

  • stop (list[float]) – [x, y, z] opposite corners of the port’s trace sheet. Both z values must be equal. Along prop_dir they give the reference plane and the end of the run; along the other in-plane axis, the two edges of the signal trace.

  • gap_mm (float) – Width of the slot on each side of the trace. Not covered by start/stop: it is how far past each trace edge the probes, the excitation and the termination reach.

  • prop_dir (str, optional) – In-plane axis the run follows, "x" or "y". Default "y". It cannot be inferred, since the corners differ on both in-plane axes – one span is the run, the other the trace width.

  • excite (float, optional) – Excitation amplitude. 0 (default) is a matched load, nonzero drives the port.

  • impedance (float | None, optional) – Termination resistance in ohms, placed at the reference plane. Defaults to params.charac_imp.

  • feed_shift_mm (float | None, optional) – Distance from the reference plane to the excitation plane. None (default) excites at the reference plane.

  • meas_plane_shift_mm (float | None, optional) – Distance from the reference plane to the measurement plane. None (default) measures at the middle of the run.

  • priority (int, optional) – CSXCAD priority for the port’s primitives.

Returns:

The created port object (needed for post-simulation CalcPort).

Return type:

CPWPort

Raises:

ValueError – If prop_dir is not "x" or "y", if start and stop disagree in z or agree along prop_dir, if the mesh has not been generated yet, or if either slot holds fewer than two mesh lines – which would collapse every probe, excitation and termination box the port spans across it onto a single line.

create_curved_bend(name: str = 'curved_bend', *, position: tuple[float, float], width_mm: float, bend_radius_mm: float, z_elevation_mm: float | None = None, copper_thickness_mm: float | None = None, rotation: float = 0, turn: str = 'left', bend_angle: float = 90, num_segments: int = 60, priority: int = 6) → CSPrimitives#

Create a curved (circular-arc) bend.

An alternative to create_miter() that joins two width_mm-wide arms with a constant-width band swept along a circular arc instead of a mitered corner block. The primitive is an annular sector: the outer edge is an arc of radius bend_radius_mm + width_mm/2 and the inner edge an arc of radius bend_radius_mm - width_mm/2, both centred on the bend’s centre of curvature; bend_radius_mm itself is the radius of the arm’s centreline. position is the midpoint of the input edge, so the band butts flush against the end of the incoming arm rather than straddling it. In the unrotated frame the arm arrives travelling +y, the centre of curvature sits at (cx - bend_radius_mm, cy), and the arc sweeps bend_angle degrees from there. rotation is degrees CCW about position, which keeps the join flush at any angle.

turn is the handedness of the bend as seen by a wave travelling along the incoming arm, with the same meaning as in create_miter(): "left" curves towards the arm’s left (-x at rotation=0), "right" towards its right (+x). The two are mirror images, so rotation alone cannot swap them.

The outgoing arm’s input edge – where the next primitive should start – is centred at (cx - bend_radius_mm + bend_radius_mm*cos(a), cy + bend_radius_mm*sin(a)) for turn="left", or the mirror image about cx for turn="right", with a = radians(bend_angle) before rotation is applied.

Parameters:
  • name (str, optional) – Name of the CSXCAD property the bend is added to. Default "curved_bend".

  • position (tuple[float, float]) – (x, y) midpoint of the input edge, in the unrotated frame.

  • width_mm (float) – Width of the arms joined by the bend.

  • bend_radius_mm (float) – Radius of the arm’s centreline through the bend. Must exceed width_mm / 2 so the inner edge does not cross the centre of curvature.

  • z_elevation_mm (float | None, optional) – Z position of the bend’s bottom face. Defaults to the substrate’s top face.

  • copper_thickness_mm (float | None, optional) – Bend thickness along z. Defaults to params.copper_thickness_mm.

  • rotation (float, optional) – Rotation in degrees, counter-clockwise about position. Default 0.

  • turn (str, optional) – Bend handedness, "left" or "right". Default "left".

  • bend_angle (float, optional) – Angle swept by the arc, in degrees, in (0, 360]. Default 90.

  • num_segments (int, optional) – Number of polygon segments approximating each arc. Default 60.

  • priority (int, optional) – CSXCAD priority of the primitive; where primitives overlap, the higher priority wins. Default 6.

Returns:

The bend primitive.

Return type:

CSPrimitives

Raises:

ValueError – If bend_radius_mm does not exceed width_mm / 2, turn is neither "left" nor "right", or bend_angle lies outside (0, 360].

create_gcpw(name: str = 'gcpw', *, position: tuple[float, float], trace_width_mm: float, gap_mm: float, ground_width_mm: float, length_mm: float, via_diameter_mm: float, via_pitch_mm: float, via_z_bottom_mm: float, via_z_top_mm: float, z_elevation_mm: float | None = None, copper_thickness_mm: float | None = None, rotation: float = 0, priority: int = 6) → tuple[CSPrimitives, CSPrimitives, CSPrimitives]#

Create a straight grounded (conductor-backed) CPW segment.

Everything create_cpw() draws – a central signal trace flanked by a slot and a ground pour on each side – plus a row of stitching vias (via create_via()) centred along each pour, evenly spaced by via_pitch_mm with one via inset by its radius from each end of the segment, so its disc lands fully inside the ground pour rather than straddling the edge.

The vias are what make the line conductor-backed: they tie the coplanar pours to the plane underneath, which this method does not draw – add it with create_ground(), whose footprint is the whole board rather than this one segment. Keep via_pitch_mm well inside lambda/10, or the two pours drift apart in potential between stitches and the parallel-plate mode between them and the backside ground starts to carry power.

Feed the segment with create_cpw_port(), not a lumped port, for the reason given in create_cpw(); the slot mesh lines that port needs are requested here too, by the same call that draws the pours.

position is the midpoint of the signal trace’s input edge, so the segment butts flush against the end of the incoming line. In the unrotated frame it runs +y, with the trace centred on cx and the two pours flanking it in x; the output edge – where the next primitive starts – is (cx, cy + length_mm) before rotation is applied.

Parameters:
  • name (str, optional) – Base name for the segment’s properties: the trace goes on "<name>_trace", the pours on "<name>_ground_1" and "<name>_ground_2", and each stitching via on "<name>_via_<row>_<side>". Default "gcpw".

  • position (tuple[float, float]) – (x, y) midpoint of the trace’s input edge, in the unrotated frame.

  • trace_width_mm (float) – Width of the central signal trace.

  • gap_mm (float) – Width of the slot separating the signal trace from each ground pour.

  • ground_width_mm (float) – Width of each of the two ground pours.

  • length_mm (float) – Segment length along y, in the unrotated frame.

  • via_diameter_mm (float) – Diameter of the stitching vias placed along each ground pour. Must be less than both length_mm and ground_width_mm.

  • via_pitch_mm (float) – Centre-to-centre spacing along the segment between stitching vias.

  • via_z_bottom_mm (float) – Z extent of the stitching vias – typically the backside ground plane’s bottom face and the top of the copper.

  • via_z_top_mm (float) – Z extent of the stitching vias – typically the backside ground plane’s bottom face and the top of the copper.

  • z_elevation_mm (float | None, optional) – Z position of the conductors’ bottom face. Defaults to the substrate’s top face.

  • copper_thickness_mm (float | None, optional) – Conductor thickness along z. Defaults to params.copper_thickness_mm.

  • rotation (float, optional) – Rotation in degrees, counter-clockwise about position. Default 0.

  • priority (int, optional) – CSXCAD priority of the trace, the pours and the stitching vias; where primitives overlap, the higher priority wins. Default 6.

Returns:

The signal trace and the two ground pours, in the order (trace, ground_lo, ground_hi) (lo/hi by x in the unrotated frame). The stitching vias are reachable by name.

Return type:

tuple[CSPrimitives, CSPrimitives, CSPrimitives]

Raises:

ValueError – If via_diameter_mm is not less than both length_mm and ground_width_mm.

create_ground(name: str = 'ground', *, start: list[float] | None = None, stop: list[float] | None = None, priority: int = 2) → CSPrimitives#

Create a copper ground plane from two corner points.

Parameters:
  • name (str, optional) – Name of the CSXCAD property the ground plane is added to. Default "ground".

  • start (list[float] | None, optional) – [x, y, z] opposite corners of the ground box. Both default to the footprint in params: the substrate width and length centred on the origin, one copper thickness below z = 0.

  • stop (list[float] | None, optional) – [x, y, z] opposite corners of the ground box. Both default to the footprint in params: the substrate width and length centred on the origin, one copper thickness below z = 0.

  • priority (int, optional) – CSXCAD priority of the primitive; where primitives overlap, the higher priority wins. Default 2.

Returns:

The ground plane primitive.

Return type:

CSPrimitives

create_lumped_port(port_nr: int, start: list[float], stop: list[float], direction: str = 'z', impedance: float | None = None, excite: float = 0, priority: int = 6, edges2grid: str = 'y') → LumpedPort#

Create a lumped port for S-parameter extraction.

Parameters:
  • port_nr (int) – Port number (1-indexed).

  • start (list[float]) – [x, y, z] coordinates defining the port box. For a microstrip z-port, this typically spans from just below the ground plane to just above the trace.

  • stop (list[float]) – [x, y, z] coordinates defining the port box. For a microstrip z-port, this typically spans from just below the ground plane to just above the trace.

  • direction (str) – Excitation direction ("x", "y", or "z").

  • impedance (float | None, optional) – Port reference impedance in ohms. Defaults to params.charac_imp.

  • excite (float) – Excitation amplitude. 0 = matched load, nonzero = driven.

  • priority (int) – CSXCAD mesh priority for the port primitives.

  • edges2grid (str) – Which transverse edges to snap to the mesh grid. Typically the direction the line runs along, so a mesh line lands on the port plane ("y" for a line running along y).

Returns:

The created port object (needed for post-simulation CalcPort).

Return type:

LumpedPort

create_mesh(smooth_ratio: float = 1.5) → None#

Generate the FDTD mesh for everything added so far.

Scans the primitives already in the CSXCAD structure and builds the grid around them (see simpleEMS.fdtd_mesh.Mesh), so call this after the geometry and the ports are in place.

Any mesh line a primitive asked for while it was being built – a CPW’s slot edges are the only case today – is passed through as a fixed line, so it lands exactly where the primitive needs it rather than wherever the automatic passes would have put it.

Parameters:

smooth_ratio (float, optional) – Maximum ratio between adjacent mesh cells. Default 1.5.

Return type:

None

create_microstrip(name: str = 'microstrip', *, position: tuple[float, float], width_mm: float, length_mm: float, z_elevation_mm: float | None = None, copper_thickness_mm: float | None = None, rotation: float = 0, priority: int = 6) → CSPrimitives#

Create a straight rectangular microstrip trace.

position is the midpoint of the input edge, so the trace starts exactly there and butts flush against whatever precedes it. In the unrotated frame it runs +y, spanning x = cx +/- width_mm/2 and y = cy .. cy + length_mm; the output edge – where the next primitive starts – is the midpoint of y = cy + length_mm before rotation is applied.

Parameters:
  • name (str, optional) – Name of the CSXCAD property the trace is added to, e.g. "feed" – one per trace, so each piece is its own part in the viewer and in the STEP and Gerber exports. Default "microstrip".

  • position (tuple[float, float]) – (x, y) midpoint of the input edge, in the unrotated frame.

  • width_mm (float) – Trace width, across the direction of travel.

  • length_mm (float) – Trace length along y, in the unrotated frame.

  • z_elevation_mm (float | None, optional) – Z position of the trace’s bottom face. Defaults to the substrate’s top face.

  • copper_thickness_mm (float | None, optional) – Trace thickness along z. Defaults to params.copper_thickness_mm.

  • rotation (float, optional) – Rotation in degrees, counter-clockwise about position. Default 0.

  • priority (int, optional) – CSXCAD priority of the primitive; where primitives overlap, the higher priority wins. Default 6.

Returns:

The trace primitive.

Return type:

CSPrimitives

create_miter(name: str = 'miter', *, position: tuple[float, float], width_mm: float, miter_distance_mm: float, z_elevation_mm: float | None = None, copper_thickness_mm: float | None = None, rotation: float = 0, turn: str = 'left', priority: int = 6) → CSPrimitives#

Create a 90-degree mitered bend.

The primitive is the square corner block that joins two width_mm-wide arms; the arms themselves are separate (see create_microstrip). position is the midpoint of the input edge, so the block sits wholly beyond it and butts flush against the end of the incoming arm rather than straddling it. In the unrotated frame the arm arrives travelling +y, the block spans y = cy .. cy + width_mm, the outgoing arm leaves through the -x edge with its centre line at position + (-width_mm/2, +width_mm/2), and the outer corner is (+hw, cy + width_mm). rotation is degrees CCW about position, which keeps the join flush at any angle.

turn is the handedness of the bend as seen by a wave travelling along the incoming arm: "left" exits to the arm’s left (-x at rotation=0), "right" to its right (+x). The two are mirror images, so rotation alone cannot swap them; being defined relative to the direction of travel, turn keeps its meaning at any rotation.

miter_distance_mm is the cutback from the outer corner along each edge, in the same units as width_mm, and must lie in [0, width_mm]. It maps to the usual miter percentage as M = miter_distance_mm / (2*width_mm) — M = 0 is an unmitered square, M = 0.5 cuts the full diagonal (a triangle).

Parameters:
  • name (str, optional) – Name of the CSXCAD property the miter is added to. Default "miter".

  • position (tuple[float, float]) – (x, y) midpoint of the input edge, in the unrotated frame.

  • width_mm (float) – Width of the arms joined by the miter.

  • miter_distance_mm (float) – Cutback from the outer corner, in [0, width_mm].

  • z_elevation_mm (float | None, optional) – Z position of the miter’s bottom face. Defaults to the substrate’s top face.

  • copper_thickness_mm (float | None, optional) – Miter thickness along z. Defaults to params.copper_thickness_mm.

  • rotation (float, optional) – Rotation in degrees, counter-clockwise about position. Default 0.

  • turn (str, optional) – Bend handedness, "left" or "right". Default "left".

  • priority (int, optional) – CSXCAD priority of the primitive; where primitives overlap, the higher priority wins. Default 6.

Returns:

The miter primitive.

Return type:

CSPrimitives

Raises:

ValueError – If miter_distance_mm lies outside [0, width_mm], or turn is neither "left" nor "right".

static create_nf2ff(sim: SimSetup | None = None) → nf2ff#

Create the near-field-to-far-field (NF2FF) recording box.

Parameters:

sim (SimSetup, optional) – Simulation setup named tuple returned by setup_simulation. Omit for the standalone STEP-FEM workflow (simulate_step_FEM), which has no SimSetup – None always returns a FEMNF2FF adapter.

Returns:

nf2ff – NF2FF object. For the FEM backend (or when sim is omitted) this is a FEMNF2FF adapter that exposes the same CalcNF2FF interface, so the radiation-plotting methods work identically for both backends.

Return type:

object

create_radial_stub(name: str = 'radial_stub', *, position: tuple[float, float], inner_radius_mm: float, outer_radius_mm: float, angle_start: float, angle_end: float, z_elevation_mm: float | None = None, copper_thickness_mm: float | None = None, rotation: float = 0, num_segments: int = 60, priority: int = 6) → CSPrimitives#

Create a radial (fan-shaped) stub.

position is the midpoint of the stub’s neck (its flat inner edge), so the copper starts exactly there. angle_start/angle_end are in degrees, measured from +x, and set the fan opening; the fan points along their bisector, and rotation turns the whole fan a further amount CCW about position. With half_span half the opening, the neck is 2*inner_radius_mm*sin(half_span) wide and the stub reaches outer_radius_mm - inner_radius_mm*cos(half_span) radially beyond position.

Parameters:
  • name (str, optional) – Name of the CSXCAD property the stub is added to. Default "radial_stub".

  • position (tuple[float, float]) – (x, y) midpoint of the stub’s neck, in the unrotated frame.

  • inner_radius_mm (float) – Radii of the fan’s inner (neck) and outer edges.

  • outer_radius_mm (float) – Radii of the fan’s inner (neck) and outer edges.

  • angle_start (float) – Fan opening bounds, in degrees, measured from +x.

  • angle_end (float) – Fan opening bounds, in degrees, measured from +x.

  • z_elevation_mm (float | None, optional) – Z position of the stub’s bottom face. Defaults to the substrate’s top face.

  • copper_thickness_mm (float | None, optional) – Stub thickness along z. Defaults to params.copper_thickness_mm.

  • rotation (float, optional) – Rotation in degrees, counter-clockwise about position. Default 0.

  • num_segments (int, optional) – Number of polygon segments approximating the outer arc. Default 60.

  • priority (int, optional) – CSXCAD priority of the primitive; where primitives overlap, the higher priority wins. Default 6.

Returns:

The radial stub primitive.

Return type:

CSPrimitives

create_substrate(name: str = 'substrate', *, start: list[float] | None = None, stop: list[float] | None = None, eps_r: float | None = None, tand: float | None = None, main_freq: float | None = None, priority: int = 0) → CSPrimitives#

Create a dielectric substrate from two corner points.

Also asks create_mesh() for params.substrate_cells mesh lines through the thickness, since the field between a trace and the ground plane under it varies across exactly that region and the automatic mesher leaves a thin substrate with a single line in it.

Parameters:
  • name (str, optional) – Name of the CSXCAD property the substrate is added to. Default "substrate".

  • start (list[float] | None, optional) – [x, y, z] opposite corners of the substrate box. Both default to the footprint in params: the substrate width and length centred on the origin, from z = 0 to the substrate thickness.

  • stop (list[float] | None, optional) – [x, y, z] opposite corners of the substrate box. Both default to the footprint in params: the substrate width and length centred on the origin, from z = 0 to the substrate thickness.

  • eps_r (float | None, optional) – Relative permittivity. Defaults to params.substrate_eps_r.

  • tand (float | None, optional) – Loss tangent. Defaults to params.substrate_tand.

  • main_freq (float | None, optional) – Frequency in Hz used to convert tand into an equivalent conductivity (kappa). Defaults to params.main_freq.

  • priority (int, optional) – CSXCAD priority of the primitive; where primitives overlap, the higher priority wins. Default 0.

Returns:

The substrate box primitive.

Return type:

CSPrimitives

create_taper(name: str = 'taper', *, position: tuple[float, float], width1_mm: float, width2_mm: float, length_mm: float, z_elevation_mm: float | None = None, copper_thickness_mm: float | None = None, rotation: float = 0, priority: int = 6) → CSPrimitives#

Create a trapezoidal taper trace.

position is the midpoint of the input edge, the one width1_mm wide, so the taper sits wholly beyond it and butts flush against the end of the incoming arm. In the unrotated frame the taper runs +y: it is width1_mm wide at y = cy and width2_mm wide at y = cy + length_mm, which is where the next primitive starts.

Parameters:
  • name (str, optional) – Name of the CSXCAD property the taper is added to. Default "taper".

  • position (tuple[float, float]) – (x, y) midpoint of the input edge, in the unrotated frame.

  • width1_mm (float) – Trace width at the input (position) and output ends.

  • width2_mm (float) – Trace width at the input (position) and output ends.

  • length_mm (float) – Taper length along y, in the unrotated frame.

  • z_elevation_mm (float | None, optional) – Z position of the trace’s bottom face. Defaults to the substrate’s top face.

  • copper_thickness_mm (float | None, optional) – Trace thickness along z. Defaults to params.copper_thickness_mm.

  • rotation (float, optional) – Rotation in degrees, counter-clockwise about position. Default 0.

  • priority (int, optional) – CSXCAD priority of the primitive; where primitives overlap, the higher priority wins. Default 6.

Returns:

The taper primitive.

Return type:

CSPrimitives

create_via(name: str = 'via', *, position: tuple[float, float], via_diameter_mm: float, z_bottom_mm: float, z_top_mm: float, antipad_diameter_mm: float | None = None, antipad_layers: list[float] | None = None, antipad_thickness_mm: float | None = None, antipad_eps_r: float | None = None, priority: int = 6, antipad_priority: int = 4) → CSPrimitives#

Create a plated through-hole via as a solid conductor.

The barrel is a solid metal cylinder rather than a plated shell around a drilled hole. Copper’s skin depth is a fraction of a micron at these frequencies, so a plated wall and a solid barrel are the same conductor seen from outside, while the wall thickness would otherwise be a feature orders of magnitude finer than the requested mesh resolution – and the FDTD timestep follows the smallest cell in the grid.

No pad or annular ring is drawn; the barrel lands directly on whatever copper is already at its ends. Clearances are placed only at the elevations named in antipad_layers, and each is a ring of dielectric – the substrate by default – with the same thickness as the copper layer it cuts through; a zero-thickness sheet has no volume for the grid to resolve and would not reliably clear the plane.

The ring starts at the barrel wall and runs outwards to antipad_diameter_mm, so it never overlaps the via. A solid disc would work in FDTD, where the barrel’s higher priority wins inside the hole, but the STEP export carries no priorities: the FEM path sees only the geometry, and a dielectric disc intersecting the metal barrel is an ambiguous solid for Gmsh to mesh. An annulus is unambiguous for both.

Parameters:
  • name (str, optional) – Name of the CSXCAD property the barrel is added to, e.g. "short_via". Clearance discs go on "<name>_antipad". Default "via".

  • position (tuple[float, float]) – (x, y) centre of the via.

  • via_diameter_mm (float) – Finished outer diameter of the conductor – the drill diameter plus twice the plating thickness, as a fab would state it.

  • z_bottom_mm (float) – Extent of the barrel along z.

  • z_top_mm (float) – Extent of the barrel along z.

  • antipad_diameter_mm (float | None) – Outer diameter of the clearance rings; their inner diameter is via_diameter_mm. Required when antipad_layers is given, and must exceed via_diameter_mm. Default None.

  • antipad_layers (list[float] | None) – Elevations at which to place a clearance ring, each the bottom face of the copper layer being cleared. Default None, no antipads.

  • antipad_thickness_mm (float | None, optional) – Thickness of each clearance ring along z. Defaults to params.copper_thickness_mm, i.e. the thickness of the plane it cuts.

  • antipad_eps_r (float | None, optional) – Relative permittivity filling the clearance rings. Defaults to params.substrate_eps_r (with its loss), which suits a plane buried in the stack-up. Pass 1.0 for a plane on an outer face, where the clearance opens to air.

  • priority (int, optional) – CSXCAD priority of the barrel; where primitives overlap, the higher priority wins. Default 6.

  • antipad_priority (int, optional) – CSXCAD priority of the clearance rings. Keep it above the plane being cleared so the ring wins over the copper it clears. Default 4.

Returns:

The via barrel primitive.

Return type:

CSPrimitives

Raises:

ValueError – If antipad_layers is given without an antipad_diameter_mm, or the antipad is no larger than the via.

static export_csxcad_xml_to_step(structure_xml_path: str | Path, output_path: Path | None = None) → None#

Load a structure.xml file and export it to STEP AP242 format.

Useful for re-exporting a structure to STEP without re-running the simulation, given a structure.xml previously written by write_and_show_structure.

Parameters:
  • structure_xml_path (str or Path) – Path to the structure.xml file exported by CSXCAD / openEMS.

  • output_path (Path, optional) – Directory to save the step subdirectory under. Defaults to cwd / "Sim_Path".

Return type:

None

static export_gerber(sim: SimSetup, output_path: Path | None = None, options: dict[str, list] | None = None, prefix: str = 'layout') → list[Path]#

Export CSXCAD geometry to Gerber format, one file per copper layer.

Parameters:
  • sim (SimSetup) – Simulation setup named tuple returned by setup_simulation.

  • output_path (Path, optional) – Directory to save the gerber subdirectory under. Defaults to cwd / "Sim_Path".

  • options (dict[str, list], optional) – Dictionary of export options. Supported key "ignore": a list of property names to skip. Defaults to {}.

  • prefix (str, optional) – File name prefix. Default "layout".

Returns:

The Gerber and drill files written.

Return type:

list of Path

Notes

CSXCAD carries no layer information, so layers are inferred from the Z position of the metal: the highest metal is the top layer (F_Cu), the lowest the bottom layer (B_Cu), and anything in between an inner layer. Z-axis cylinders (vias) are written to an Excellon drill file and the substrate footprint to the board outline. See simpleEMS.export_gerber.export_gerber().

static export_step(sim: SimSetup, output_path: Path | None = None) → None#

Export CSXCAD geometry to a colored, multi-layer STEP AP242 file using CadQuery.

Parameters:
  • sim (SimSetup) – Simulation setup named tuple returned by setup_simulation.

  • output_path (Path, optional) – Directory to save the step subdirectory under. Defaults to cwd / "Sim_Path".

Return type:

None

static export_stl(sim: SimSetup, output_path: Path | None = None) → None#

Export the CSXCAD structure to STL files (FDTD backend only).

Reads the structure.xml previously written by write_and_show_structure and exports it to STL via AppCSXCAD.

Parameters:

output_path (Path, optional) – Directory containing structure.xml. STL files are written to a stl subdirectory. Defaults to cwd / "Sim_Path".

Raises:

ValueError – If structure.xml does not exist in output_path (i.e. write_and_show_structure was not called first).

static export_touchstone(freqs: ndarray[tuple[Any, ...], dtype[_ScalarT]], s11: ndarray[tuple[Any, ...], dtype[_ScalarT]] | None = None, *, s21: ndarray[tuple[Any, ...], dtype[_ScalarT]] | None = None, s_matrix: ndarray[tuple[Any, ...], dtype[_ScalarT]] | None = None, charac_imp: float = 50.0, output_path: Path | None = None, filename: str = 's_param') → Path#

Export S-parameters to a Touchstone .sNp file.

Writes the provided S-parameter data to output_path / "touchstone" in Touchstone version 1 format. Pass either s11 (plus s21 for a two-port) or the full s_matrix; the port count sets the file extension.

Parameters:
  • freqs (NDArray) – Frequency points (in Hz) to export.

  • s11 (NDArray, optional) – Complex S11 values across the frequency range. Required unless s_matrix is given.

  • s21 (NDArray, optional) – Complex S21 values across the frequency range. If provided, a two-port (.s2p) network is written; otherwise a single-port (.s1p) network is written. Default is None.

  • s_matrix (NDArray, optional) – Complex S-parameters of shape (F, P, P), with s_matrix[:, i, j] holding S(i+1)(j+1). Writes every S-parameter of a P-port network. Cannot be combined with s11 or s21. Default is None.

  • charac_imp (float, optional) – Reference impedance (in ohms) recorded in the Touchstone file. Default is 50.0.

  • output_path (Path, optional) – Directory to save the touchstone subdirectory under. Defaults to cwd / "Sim_Path".

  • filename (str, optional) – Base name of the exported file (extension is added automatically). Default is "s_param".

Returns:

Path of the written Touchstone file.

Return type:

Path

Raises:

ValueError – If neither s11 nor s_matrix is given, or if s_matrix is combined with s11 / s21.

Notes

With s11 and s21, only S11 and S21 are populated; S12 and S22 are written as zero (the reverse-direction parameters are not simulated). Pass s_matrix to export all of them.

static plot_2d_directivity(nf2ff: nf2ff, freq: float, output_path: Path | None = None, read_cached: bool = False) → None#

Plot the 2D directivity pattern (xz-plane, phi=0) at a specified frequency, annotated with the half-power beamwidth (HPBW).

Computes directivity (in dBi) from NF2FF E-field data, plots it as a polar pattern, and marks the -3 dB beamwidth, main-lobe direction, and peak magnitude around the pattern’s maximum.

Parameters:
  • nf2ff (object) – The near-field-to-far-field (NF2FF) object containing the simulation data.

  • freq (float) – Frequency (in Hz) at which the directivity is evaluated. Must be a scalar.

  • output_path (Path, optional) – Path to the directory where the simulation result is saved. Defaults to cwd / "Sim_Path".

  • read_cached (bool, optional) – If True, read cached NF2FF results instead of re-computing. Default is False.

Returns:

Plots the 2D directivity pattern; does not return a value.

Return type:

None

Raises:
  • TypeError – If freq is not a scalar (e.g. an array of frequencies).

  • ValueError – If the -3 dB HPBW crossings cannot be found on either side of the peak.

static plot_2d_rad_pattern(nf2ff: nf2ff, freq: float, output_path: Path | None = None, read_cached: bool = False) → None#

Plot the 2D E-field radiation pattern at a specified frequency.

Computes far-field E-field cuts in the xz-plane (phi=0) and xy-plane (theta=90) from NF2FF data and plots both as normalized polar patterns (in dB) side by side.

Parameters:
  • nf2ff (object) – The near-field-to-far-field (NF2FF) object containing the simulation data.

  • freq (float) – Frequency (in Hz) at which the radiation pattern is evaluated. Must be a scalar.

  • output_path (Path, optional) – Path to the directory where the simulation result is saved. Defaults to cwd / "Sim_Path".

  • read_cached (bool, optional) – If True, read cached NF2FF results instead of re-computing. Default is False.

Returns:

Generates the 2D radiation pattern plot; does not return a value.

Return type:

None

Raises:

TypeError – If freq is not a scalar (e.g. an array of frequencies).

static plot_3d_directivity(nf2ff_3d_result: nf2ff_results, freq: float, output_path: Path | None = None) → None#

Plot the 3D directivity pattern of an antenna at a specified frequency.

Visualizes the directivity pattern (in dBi) of the antenna in 3D space, based on the far-field results from compute_nf2ff_3d, and saves the mesh as a VTK file under output_path / "3D_plots".

Parameters:
  • nf2ff_3d_result (nf2ff_results) – The 3D far-field result object returned by compute_nf2ff_3d.

  • freq (float) – The frequency (in Hz) at which the 3D directivity pattern is evaluated. Must be a scalar; used only for plot/file labeling.

  • output_path (Path, optional) – Path to the directory where the simulation result is saved. Defaults to cwd / "Sim_Path".

Returns:

Opens an interactive 3D plot window and saves the mesh to 3D_plots/3D_directivity.vtk; does not return a value.

Return type:

None

Raises:

TypeError – If freq is not a scalar (e.g. an array of frequencies).

static plot_3d_gain(nf2ff_3d_result: nf2ff_results, freq: float, input_power: float, output_path: Path | None = None) → None#

Plot the 3D gain pattern of an antenna at a specified frequency.

Visualizes the realized gain pattern (in dBi) of the antenna in 3D space: gain is directivity (from compute_nf2ff_3d) scaled by the radiation efficiency Prad / input_power. Saves the mesh under output_path / "3D_plots".

Parameters:
  • nf2ff_3d_result (nf2ff_results) – The 3D far-field result object returned by compute_nf2ff_3d.

  • freq (float) – The frequency (in Hz) the pattern was computed at. Used only for plot/file labeling.

  • input_power (float) – Input power at the port (same units as Prad), used to normalize directivity into realized gain.

  • output_path (Path, optional) – Path to the directory where the simulation result is saved. Defaults to cwd / "Sim_Path".

Returns:

Opens an interactive 3D plot window and saves the mesh to 3D_plots/3D_Gain.vtk; does not return a value.

Return type:

None

static plot_3d_power(nf2ff_3d_result: nf2ff_results, freq: float, output_path: Path | None = None) → None#

Plot the 3D radiated power pattern of an antenna at a specified frequency.

Visualizes the normalized radiated power pattern (in dB) of the antenna in 3D space, based on the P_rad field of the far-field results from compute_nf2ff_3d. Saves the mesh under output_path / "3D_plots".

Parameters:
  • nf2ff_3d_result (nf2ff_results) – The 3D far-field result object returned by compute_nf2ff_3d.

  • freq (float) – The frequency (in Hz) the pattern was computed at. Used only for plot/file labeling.

  • output_path (Path, optional) – Path to the directory where the simulation result is saved. Defaults to cwd / "Sim_Path".

Returns:

Opens an interactive 3D plot window and saves the mesh to 3D_plots/3D_Power.vtk; does not return a value.

Return type:

None

static plot_group_delay(freqs: ndarray[tuple[Any, ...], dtype[_ScalarT]], s21: ndarray[tuple[Any, ...], dtype[_ScalarT]] | None, x_label: str = 'Frequency', y_label: str = 'Group Delay', title: str = 'Group Delay vs Frequency') → None#

Compute and plot the group delay as a function of frequency.

The group delay is computed from the frequency derivative of the unwrapped phase of S21 and displayed in seconds (via an engineering time-unit axis formatter).

Parameters:
  • freqs (NDArray) – A 1D array of frequencies (in Hz) to plot on the x-axis.

  • s21 (NDArray) – A 1D array of complex S21 values corresponding to each frequency.

  • x_label (str, optional) – Label for the x-axis. Default is "Frequency".

  • y_label (str, optional) – Label for the y-axis. Default is "Group Delay".

  • title (str, optional) – Title of the plot. Default is "Group Delay vs Frequency".

Returns:

Displays the group delay plot in a new figure; does not return a value.

Return type:

None

static plot_impedance(freqs: ndarray[tuple[Any, ...], dtype[_ScalarT]], z11: ndarray[tuple[Any, ...], dtype[_ScalarT]], x_label: str = 'Frequency', y_label: str = 'Z11', title: str = 'Z11 vs Frequency') → None#

Plot the input impedance (Z11) as a function of frequency.

Opens a new figure and plots the real and imaginary parts of Z11, in ohms, as separate traces over the given frequency range.

Parameters:
  • freqs (NDArray) – A 1D array of frequencies (in Hz) to plot on the x-axis.

  • z11 (NDArray) – A 1D array of complex Z11 values (in ohms) corresponding to each frequency.

  • x_label (str, optional) – Label for the x-axis. Default is "Frequency".

  • y_label (str, optional) – Label for the y-axis. Default is "Z11".

  • title (str, optional) – Title of the plot. Default is "Z11 vs Frequency".

Returns:

Displays the impedance plot in a new figure; does not return a value.

Return type:

None

static plot_phase(freqs: ndarray[tuple[Any, ...], dtype[_ScalarT]], s21: ndarray[tuple[Any, ...], dtype[_ScalarT]] | None, x_label: str = 'Frequency', y_label: str = 'Phase (deg)', title: str = 'Phase vs Frequency') → None#

Plot the transmission phase (angle of S21) as a function of frequency.

Opens a new figure and plots the phase of S21, in degrees, over the given frequency range.

Parameters:
  • freqs (NDArray) – A 1D array of frequencies (in Hz) to plot on the x-axis.

  • s21 (NDArray) – A 1D array of complex S21 values corresponding to each frequency.

  • x_label (str, optional) – Label for the x-axis. Default is "Frequency".

  • y_label (str, optional) – Label for the y-axis. Default is "Phase (deg)".

  • title (str, optional) – Title of the plot. Default is "Phase vs Frequency".

Returns:

Displays the phase plot in a new figure; does not return a value.

Return type:

None

static plot_s_param(freqs: ndarray[tuple[Any, ...], dtype[_ScalarT]], s11: ndarray[tuple[Any, ...], dtype[_ScalarT]], s21: ndarray[tuple[Any, ...], dtype[_ScalarT]] | None = None, x_label: str = 'Frequency', y_label: str = 'S-parameter (dB)', title: str = 'S-parameters vs Frequency', label_s11: str | None = None, label_s21: str | None = None) → None#

Plot S-parameters (S11 and optionally S21) against frequency.

Draws onto the current matplotlib axes (call plt.figure() beforehand to start a new figure). Both s11 and s21 are expected as complex/linear values; they are converted to dB (20*log10(|.|)) internally before plotting.

Parameters:
  • freqs (NDArray) – A 1D array of frequencies (in Hz) to plot on the x-axis.

  • s11 (NDArray) – A 1D array of complex (linear) S11 values.

  • s21 (NDArray, optional) – A 1D array of complex (linear) S21 values for two-port networks. If None (default), only S11 is plotted.

  • x_label (str, optional) – Label for the x-axis. Default is "Frequency".

  • y_label (str, optional) – Label for the y-axis. Default is "S-parameter (dB)".

  • title (str, optional) – Title of the plot. Default is "S-parameters vs Frequency".

  • label_s11 (str, optional) – Legend label for the S11 trace. Defaults to "S11".

  • label_s21 (str, optional) – Legend label for the S21 trace. Defaults to "S21".

Returns:

Draws the plot on the current axes; does not return a value.

Return type:

None

static plot_smith_chart(freqs: ndarray[tuple[Any, ...], dtype[_ScalarT]], s11: ndarray[tuple[Any, ...], dtype[_ScalarT]], label: str = '', charac_imp: float = 50.0) → None#

Plot S11 data on a Smith chart.

Opens a new figure with a Smith-chart projection and plots the complex reflection coefficient (S11) over the given frequency range, marking the value at the midpoint frequency.

Parameters:
  • freqs (NDArray) – A 1D array of frequencies (in Hz) corresponding to the S11 data.

  • s11 (NDArray) – A 1D array of complex S11 (reflection coefficient) values.

  • label (str, optional) – Label used for the plot legend. Default is an empty string.

  • charac_imp (float, optional) – Characteristic (normalizing) impedance of the chart, in ohms. Default is 50.0.

Returns:

Displays the Smith chart in a new figure; does not return a value.

Return type:

None

Notes

pysmithchart is used to render the Smith chart.

static plot_vswr(freqs: ndarray[tuple[Any, ...], dtype[_ScalarT]], vswr: ndarray[tuple[Any, ...], dtype[_ScalarT]], x_label: str = 'Frequency', y_label: str = 'VSWR', label: str = 'VSWR', title: str = 'VSWR vs Frequency') → None#

Plot the voltage standing wave ratio (VSWR) as a function of frequency.

Opens a new figure and plots VSWR over the given frequency range, to evaluate impedance matching performance.

Parameters:
  • freqs (NDArray) – A 1D array of frequencies (in Hz) to plot on the x-axis.

  • vswr (NDArray) – A 1D array of VSWR values corresponding to each frequency.

  • x_label (str, optional) – Label for the x-axis. Default is "Frequency".

  • y_label (str, optional) – Label for the y-axis. Default is "VSWR".

  • label (str, optional) – Label used for the plot legend. Default is "VSWR".

  • title (str, optional) – Title of the plot. Default is "VSWR vs Frequency".

Returns:

Displays the VSWR plot in a new figure; does not return a value.

Return type:

None

static print_and_save_params(params: SimParams, output_path: Path | None = None) → None#

Print the simulation parameters to the console and save them to params/params.txt.

Parameters:
  • params (SimParams) – Parameter object whose dataclass fields are printed and saved.

  • output_path (Path, optional) – Directory to save the params subdirectory under. Defaults to cwd / "Sim_Path".

Returns:

Does not return a value.

Return type:

None

static run_simulation(sim: SimSetup, output_path: Path | None = None) → None#

Run the simulation and write results to output_path.

Dispatches to the FDTD engine (FDTD.Run) or, for the FEM backend, to the adaptive GetDP frequency sweep (fem_backend.run_sweep), which performs up to sim.FEM_options.num_solve_points full solves. For the FEM backend this always (re-)meshes first, reusing the existing mesh if sim.CSX/sim.FEM_options haven’t changed since the last mesh in this output_path (e.g. a prior write_and_show_structure call) and rebuilding if they have – so a sweep/optimize loop that calls this directly, varying the geometry across iterations into the same output_path, still gets a fresh mesh each time the geometry actually changes.

Parameters:
  • sim (SimSetup) – Simulation setup named tuple returned by setup_simulation.

  • output_path (Path, optional) – Directory to write simulation results to. Defaults to cwd / "Sim_Path".

static save_plots(output_path: Path | None = None, file_format: str = 'png') → None#

Save all currently open matplotlib figures to output_path / "plots".

Each open figure is resized to 12x6 inches and saved as plot_<n>.<file_format>, in figure order.

Parameters:
  • output_path (Path, optional) – Directory to save the plots subdirectory under. Defaults to cwd / "Sim_Path".

  • file_format (str, optional) – File format to save the plots as (e.g. "png", "jpg", "pdf"). Default is "png".

Return type:

None

Notes

Call this before show_plots, since show_plots blocks until the plot windows are closed.

static show_plots() → None#

Display all generated plots. Blocks until the plot window(s) are closed.

If no matplotlib figures are open and a Qt application instance is already running (e.g. a PyVista/FEM mesh viewer opened by write_and_show_structure), blocks on that Qt event loop instead and exits the process once it closes. Otherwise calls matplotlib.pyplot.show() to display any open matplotlib figures.

Returns:

Does not return a value.

Return type:

None

static write_and_show_structure(sim: SimSetup, output_path: Path | None = None, mesh_style: str = 'wireframe', theme: str | None = None) → None#

Display the simulation geometry.

For the FDTD backend this opens the structure in AppCSXCAD. For the FEM backend it (re-)meshes the current CSX/FEM_options with Gmsh – reusing the existing mesh instead of rebuilding if neither has changed since the last call on this output_path – and renders it with PyVista, coloring cells by their Gmsh physical-group id.

Parameters:
  • sim (SimSetup) – Simulation setup named tuple returned by setup_simulation.

  • output_path (Path, optional) – Directory to write/read the structure XML (FDTD) or mesh files (FEM). Defaults to cwd / "Sim_Path", created if missing.

  • mesh_style (str) – FEM mesh only. PyVista add_mesh representation: "surface", "wireframe" (default), or "points". Mesh cells are colored by their Gmsh physical-group id (CellEntityIds: dielectric, PEC, port, absorbing boundary, …).

  • theme (str, optional) – FEM mesh only. None (default) keeps the active simpleEMS theme (see simpleEMS.plot_theme.use_dark_theme()). A PyVista theme name – "dark", "default", "document" or "paraview" – is applied globally via pyvista.set_plot_theme().

Raises:

ValueError – If theme is not one of PyVista’s native theme names.