EME

Every section function below accepts a ports argument, which names, windows, or displaces the ports on either end of the section, in place of its default left and right ports. Sections are otherwise connected in the order they are declared. Use connect_ports to wire named ports explicitly instead, including into branching topologies. See Ports for the port model, the Port and Bounds classes, and make_angled_facet_port() for declaring an angled facet on a port. For a grating, grating_sections() gives the length of every section of a uniform, chirped or noisy grating.

EME_settings

Declare global EME defaults that are inherited by sections created afterward. Each parameter left unspecified leaves the corresponding stored default unchanged. A section’s own settings argument at creation still overrides these globals. Sections created before this call are not affected.

Parameters:
  • apply_scattering (bool or list[float]) – whether interfacial scattering loss is automatically applied to the S-matrix, either for every wavelength (True), no wavelengths (False), or only the listed wavelengths, default: False

  • beta_interpolation (str) – either ‘uniform’ or ‘linear’ to specify how the propagation constant is interpolated between z-slices of a taper/gds section, default: ‘uniform’

  • junction_normalization (bool or str or dict) – controls how junction S-matrices are power-normalized. It takes either a single bool/’gain’ value applied to both junction kinds, or a dict with ‘inter_section’ and ‘intra_section’ keys, each accepting True (full SVD unitarization), False (raw, unnormalized), or ‘gain’ (clip singular values at 1 while keeping the raw lossy mode-matching), default: ‘gain’ for inter-section junctions and True for intra-section junctions. It applies to junctions with a plane-wave side (pwd_section) or an angled facet. A junction between two mode-solved cross-sections conserves power by construction and is not normalized.

  • overlap_variation (float) – threshold of mode-list overlap between z-slices used by the automatic z-slicing bisection for taper/gds sections, default: 0.01

  • minimum_z_step (float) – [nm] the minimum allowed z-step for the automatic z-slicing bisection, default: 1. When the largest junction error has not fallen by 20 % over three bisections, the bisection follows instead the power that the modes guided at both ends of the section lose at each junction, and if that stalls too it stops with a warning

  • snap_window (float) – the fraction of a slice’s length around its midpoint within which the automatic z-slicing bisects at a cross-section already solved elsewhere (for the same settings, mesh and wavelength) instead of at the midpoint, which saves a mode solve wherever two tapers or GDS sections share widths. 0 always bisects at the midpoint, default: 0.25

  • initial_slices (str) – how a taper or GDS section is sliced before the automatic z-slicing bisection refines it, either ‘auto’ (boundaries placed where the cross-section changes, from its rasterized refractive index: none along a straight run, evenly along a linear taper, densely inside a short fast feature, and at every corner of a GDS outline) or ‘single’ (one slice, the behavior before EMode 1.0.6). Refinement still runs afterward and decides the final slicing, so the choice moves results only within its tolerance and changes how many rounds it takes. A warm_start in EME takes its slices from the previous solve instead, default: ‘auto’

  • simulation_name (str) – simulation name, default: ‘emode’

A section’s own settings dictionary accepts the keys above (except simulation_name), and five more that tune the slicing. They are rarely needed, and EME_settings does not take them.

  • schedule_samples: the number of positions along the section at which the refractive index is rasterized (no mode solve) to find where it changes, at least 2, default: 128

  • schedule_change_per_slice: the relative change in the rasterized index that one initial slice spans, so a smaller value starts with more slices, default: 0.05

  • schedule_max_slices: the largest number of initial slices, default: 64

  • schedule_coarsen: how many times coarser than the profile’s mesh the index is rasterized, at least 1, default: 4

  • slice_phase_limit: a slice over which the cross-section changes is kept shorter than slice_phase_limit * wavelength / (2 n_max), where n_max is the largest effective index of its modes. Equal slices longer than about wavelength / (2 n_max) act as a grating and reflect in phase, which refinement cannot see. The initial slices are cut evenly under this limit, up to schedule_max_slices. Refinement then estimates how much power each guided mode loses to the steps’ in-phase reflections and conversions, and cuts a slice under the limit only where that power is significant, so a long, slow taper keeps about schedule_max_slices slices. Between 0 and 1, or None to turn it off, default: 0.8

For example, taper_section(..., settings={'schedule_change_per_slice': 0.02}). Initial slice boundaries closer together than minimum_z_step are merged.

straight_section

Define a uniform-cross-section waveguide section for the EME solver. Sections are placed sequentially in the z-direction in the order they are declared (or updated, if name already exists).

Parameters:
  • name (str) – section name, default: an auto-generated number

  • profile (str) – name of the profile that defines the section’s cross-section

  • length (float) – [nm] the length of the section

  • offset (float or tuple[float, float]) – [nm] transverse displacement of this section relative to whatever its ports connect to. A single value is taken as the x-offset, default: (0, 0). Legacy: this is the same displacement as Port’s own offset applied to every port of the section at once, and setting both on one section raises an error. Prefer declaring offset on the specific port that sits off-axis.

  • ports (list[Port] or None) – replace the section’s default [Port('left', None, 'left'), Port('right', None, 'right')] on a per-side basis, where a supplied port replaces every existing port on the same side, and multiple ports may share a side. See Port, default: None (keep existing/default ports)

  • nonlinear (SHGProcess or SFGProcess or DFGProcess or PulsedProcess or False or None) – declare a \(\chi^{(2)}\) process on this section (EMode3D only), one of SHGProcess(pump_wavelength=...), SFGProcess(pump_wavelength_1=..., pump_wavelength_2=...) or DFGProcess(pump_wavelength=..., signal_wavelength=...) from emodeconnection. You name the process’s input wavelength(s), and the remaining one follows from energy conservation and is matched against the section’s own profile wavelengths. Quasi-phase matching is declared on the process with poling_period [nm], plus duty_cycle (default: 0.5) and qpm_order (default: 1). Setting inverted=True instead flips the local \(\chi^{(2)}\) sign for this section alone, the building block for poling assembled by hand from alternating sections. The two routes agree. Omitting nonlinear (or passing None) when updating an existing section leaves the declared process in place, passing a new one replaces it, and nonlinear=False removes it, so the section propagates linearly again. A pump must also be declared, see excitation in settings, and the power-dependent result is read back with get('response'). The process also carries z_samples (default: 100), the number of points at which the forward amplitudes are recorded along each slice of this section once the driven solve converges. Those reach the caller as get('response').power_vs_z(wavelength), which returns the pump or signal power against position from a single solve rather than one solve per length. Set it to 0 to skip the sampling. A pulse’s nonlinearity is declared instead with a PulsedProcess, see pulsed nonlinear propagation. Default: None

  • settings (dict or None) – per-section overrides of the EME_settings, merged onto the current global EME settings (or onto the section’s existing settings, when updating), e.g. settings={'overlap_variation': 0.005}, default: None

  • simulation_name (str) – simulation name, default: ‘emode’

pwd_section

Define a free space section, in which the field is expanded in plane waves (a plane-wave decomposition, PWD) over a uniform material, rather than in the guided modes of a cross-section. This is intended for gaps, facets, and free space propagation between waveguide sections. The junctions between a free space section and its neighbors are computed analytically instead of from a mode solve.

The basis is the set of plane-wave harmonics of the section window, with transverse wavevectors kx = 2*pi*m/window_width and ky = 2*pi*n/window_height, each harmonic carrying both a TE and a TM polarization. At least one of max_order, max_kt, or num_modes is required to truncate the basis. When more than one is given, they are applied in the order max_order, max_kt, num_modes.

Parameters:
  • name (str) – section name, default: an auto-generated number

  • length (float) – [nm] the length of the section

  • material (str or ~MaterialProperties or ~MaterialSpec) – the material filling the section, either a material name (from the material database) or a ~MaterialProperties or ~MaterialSpec object. It must be isotropic, default: “Vacuum”

  • profile (str) – name of the profile that supplies the default window and the wavelength(s) of the section, default: ‘_default’

  • window (tuple[float, float] or None) – [nm] the (width, height) of the plane-wave window, which overrides the window of profile, default: None (use the window_width and window_height of profile)

  • offset (tuple[float, float]) – [nm] transverse displacement of the window’s origin, which is its horizontal center and bottom edge, default: (0, 0)

  • max_order (tuple[int, int] or None) – the largest harmonic order to keep in the x- and y-directions, so that |m| <= max_order[0] and |n| <= max_order[1], default: None

  • max_kt (float) – [1/nm] the largest transverse wavevector magnitude to keep, default: None

  • num_modes (int) – the largest number of modes to keep, counting both polarizations of each harmonic, and applied after max_order and max_kt. The basis always keeps a harmonic together with its opposite-direction pair, so the resulting count can land slightly below the requested value. Default: None

  • ports (list[Port] or None) – replace the section’s default [Port('left', None, 'left'), Port('right', None, 'right')] on a per-side basis, where a supplied port replaces every existing port on the same side, and multiple ports may share a side. See Port, default: None (keep existing/default ports)

  • settings (dict or None) – per-section overrides of the EME_settings, default: None

  • simulation_name (str) – simulation name, default: ‘emode’

Note

A junction with a free space section matches the two continuity conditions (for E and for H) against the modes of the side the light arrives from. When the other side has fewer modes, the junction cannot represent every incident field, and some incident modes leave it with almost none of their power: they are dropped by the truncated basis, not attenuated by any physics. EMode warns when an incident mode keeps less than 1 % of its power, naming the dropped modes and the reflected plus transmitted power over all of the incident modes. Raise the mode count on the side with fewer modes, num_modes of the waveguide section or max_order, max_kt or num_modes of the free space section.

Note

The reflection of a confined mode at a facet into free space is not the modal Fresnel value ((n_eff - n) / (n_eff + n))^2. That formula gives the free space the admittance of a single plane wave at normal incidence. A mode confined to a fraction of a wavelength is a spread of plane waves, and those at an angle see a different admittance: smaller for the field component normal to their plane of incidence, larger for the one in it. A quasi-TE mode therefore reflects more than its modal Fresnel value and a quasi-TM mode less, more so the tighter the confinement. A 2640 x 816 nm Si guide meeting SiO2 reflects 0.216 of its quasi-TE fundamental, 1.35 times its modal Fresnel value of 0.160, and an independent solve with an empty section in place of the free space agrees within 1.4 %. Give the waveguide section periodic walls (boundary_condition='PP') and the free space section the same window, so that both describe the same far side. Walls on a section that stands in for free space raise the reflection when they are close to the guide (by 35 % with walls 1 µm from that guide’s core).

taper_section

Define a section that linearly interpolates its cross-section between two profiles for the EME solver, automatically slicing the z-direction to track the mode evolution.

Parameters:
  • name (str) – section name, default: an auto-generated number

  • profile (str) – name of the profile that defines the section beginning

  • profile_end (str) – name of the profile that defines the section ending

  • length (float) – [nm] the length of the section

  • ports (list[Port] or None) – replace the section’s default [Port('left', None, 'left'), Port('right', None, 'right')] on a per-side basis, where a supplied port replaces every existing port on the same side, and multiple ports may share a side. See Port, default: None (keep existing/default ports)

  • nonlinear (SHGProcess or SFGProcess or DFGProcess or PulsedProcess or False or None) – declare a \(\chi^{(2)}\) process on this section (EMode3D only), one of SHGProcess(pump_wavelength=...), SFGProcess(pump_wavelength_1=..., pump_wavelength_2=...) or DFGProcess(pump_wavelength=..., signal_wavelength=...) from emodeconnection. You name the process’s input wavelength(s), and the remaining one follows from energy conservation and is matched against the section’s own profile wavelengths. Quasi-phase matching is declared on the process with poling_period [nm], plus duty_cycle (default: 0.5) and qpm_order (default: 1). Setting inverted=True instead flips the local \(\chi^{(2)}\) sign for this section alone, the building block for poling assembled by hand from alternating sections. The two routes agree. Omitting nonlinear (or passing None) when updating an existing section leaves the declared process in place, passing a new one replaces it, and nonlinear=False removes it, so the section propagates linearly again. A pump must also be declared, see excitation in settings, and the power-dependent result is read back with get('response'). The process also carries z_samples (default: 100), the number of points at which the forward amplitudes are recorded along each slice of this section once the driven solve converges. Those reach the caller as get('response').power_vs_z(wavelength), which returns the pump or signal power against position from a single solve rather than one solve per length. Set it to 0 to skip the sampling. A pulse’s nonlinearity is declared instead with a PulsedProcess, see pulsed nonlinear propagation. Default: None

  • settings (dict or None) – per-section overrides of the EME_settings (notably overlap_variation and minimum_z_step, which control the automatic z-slicing), default: None

  • simulation_name (str) – simulation name, default: ‘emode’

bend_section

Define a bend for the EME solver: the cross-section of profile swept along a curve. The bend is placed in the layout like any other section, and sections declared after it continue from its end, turned by the bend’s angle.

The curve is a circular arc (radius and sweep_angle), or a curve whose radius varies along its length, which avoids the abrupt change of curvature where a circular bend meets a straight or where an S-bend reverses. A tuple of radii with sweep_angle gives a clothoid, whose curvature changes linearly with arc length, and inf stands for a straight end, so radius=(inf, 5000) eases a straight into a 5 um bend. euler=dict(angle=..., min_radius=...) gives a symmetric Euler bend, its curvature rising linearly from zero to 1 / min_radius at its middle and back to zero, of length 2 |angle| min_radius. curvature with length gives any other profile. A bend whose radius varies is sliced along its length like a taper, each slice solved at its own radius, and refined by EME. Its layout follows the curve.

An anisotropic crystal is fixed in the chip, so the guide sees it turn along the bend. When a material is anisotropic in the plane of the chip (x-cut lithium niobate, for example), the bend is sliced along its heading and refined like a taper, and every section placed after a turn sees the crystal turned with it. A circular bend of an isotropic material, or of a crystal whose optic axis is vertical (z-cut thin film), is a single slice and a single mode solve. A copy_section reuses its source’s S-matrix, so it raises an error when its own placement would show the crystal differently, and asks for the copy to be declared as its own section.

A bend has no mirror symmetry about x, so an x symmetry wall in the profile’s boundary_condition (such as ‘0S’ or ‘SA’) is replaced by ‘0’ with a warning, and ‘TE’ and ‘TM’ keep only the vertical fold. In a circuit that also has straight sections, the x fold then applies to none of them (see EME). Updating an existing bend changes only the arguments given.

Parameters:
  • name (str) – section name, default: an auto-generated number

  • profile (str) – name of the profile that defines the cross-section

  • radius (float or list[float]) – [nm] the bend radius, measured to the origin of the profile’s x axis, must be positive. A list or tuple of radii, equally spaced in arc length with the curvature linear between them, gives a bend whose radius varies, inf for straight. A new bend needs radius and sweep_angle, euler, or curvature and length

  • sweep_angle (float) – [rad] the angle the bend turns through, positive toward +x and negative toward -x, not zero. With a list of radii it sets the bend’s length

  • euler (dict or None) – a symmetric Euler bend, dict(angle=..., min_radius=...), with angle in radians (positive toward +x) and min_radius in nm, default: None

  • curvature (list[float] or None) – [1/nm] the signed curvature (positive toward +x) at equally spaced points along the bend, from start to end, linear between them, at least two values. A change of sign gives an S-bend, default: None

  • length (float) – [nm] the arc length of a bend given by curvature, default: None

  • method (str) – how each slice is solved, either ‘cylindrical’ (the bend in cylindrical coordinates, as bend_radius in settings) or ‘TO’ (transformation optics, see bend_method in settings), default: ‘cylindrical’

  • ports (list[Port] or None) – replace the section’s default [Port('left', None, 'left'), Port('right', None, 'right')] on a per-side basis, where a supplied port replaces every existing port on the same side, and multiple ports may share a side. See Port, default: None (keep existing/default ports)

  • settings (dict or None) – per-section overrides of the EME_settings (notably overlap_variation and minimum_z_step, which control the automatic z-slicing), default: None

  • simulation_name (str) – simulation name, default: ‘emode’

gds_section

Define a section whose cross-section varies along z according to a mask imported from a GDS file, for more complex geometries defined with third-party layout tools.

Parameters:
  • name (str) – section name, default: an auto-generated number

  • profile (str) – name of the profile that defines the section’s cross-section outside of the GDS mask

  • gds_path (str) – path to the GDS file used to mask the section

  • layer_map (dict[str, int]) – mapping of profile shape names to GDS layer numbers, used to determine which shapes in profile are masked by which GDS layer

  • length (float) – [nm] the length of the section, default: derived from the GDS bounding box

  • cell_index (int or str) – the cell index (or name) in the specified GDS file, default: 0

  • scale (float) – unit scale factor applied to the GDS coordinates (e.g. use 1000 if the GDS file is drawn in microns but the simulation is in nanometers), default: 1

  • port_poses (dict) – explicit port positions/orientations at the section boundaries, default: derived from the GDS bounding box (left port at w=0, right port at w=length)

  • heal (bool) – whether to heal small gaps/self-intersections in the imported GDS polygons before meshing, default: True

  • grow (float) – amount to grow imported GDS polygons by, to close small gaps, default: 1e-3

  • nonlinear (PulsedProcess or False or None) – declare a pulse’s nonlinearity on this section as a PulsedProcess (see pulsed nonlinear propagation), False to remove one. The CW \(\chi^{(2)}\) processes are not available on a GDS section. Default: None

  • ports (list[Port] or None) – replace the section’s default [Port('left', None, 'left'), Port('right', None, 'right')] on a per-side basis, where a supplied port replaces every existing port on the same side, and multiple ports may share a side. See Port, default: None (keep existing/default ports)

  • settings (dict or None) – per-section overrides of the EME_settings, default: None

  • simulation_name (str) – simulation name, default: ‘emode’

Note

Once sections are declared, call plot() with plane='z-x' or plane='z-y' to preview a geometry-only ‘Outline’ of the section layout before running EME.

copy_section

Place a second instance of an already-declared section in the chain, reusing its already-computed S-matrix instead of re-solving it.

Parameters:
  • name (str) – section name, default: an auto-generated number

  • section_name (str) – name of the existing section to copy

  • mirror (bool) – whether to reverse the placement along the propagation direction (swapping the left/right ports), default: False

  • port_swap (dict) – explicit port-name mapping to use instead of the automatically computed left/right swap when mirror is True, default: None

  • ports (list[Port] or None) – relabel/window this copy’s own left/right boundary, with the same semantics as any other section’s ports, independent of port_swap and of the source section’s own ports. See Port, default: None (keep existing/default ports)

  • settings (dict or None) – per-section overrides of the EME_settings, default: None

  • simulation_name (str) – simulation name, default: ‘emode’

connect_ports

Override the default left/right adjacency wiring between named ports declared with the ports argument of any section function.

Connecting two ports also pins their coordinate systems together: the two ports’ origins (the horizontal center and bottom edge of each cross-section) are made to coincide, so the connection is what positions one section relative to the other. Use Port’s offset to displace a section off that shared origin, and its window to choose which part of a wider cross-section the port exposes. The first section you declare defines the global frame that plots are drawn in.

Parameters:
  • pairs (list[tuple[str, str]] or list[tuple[str, str, JunctionOverride]]) – a list of ('<section_name>.<port_name>', '<section_name>.<port_name>') pairs to connect, e.g. [('a.right', 'b.left'), ('c.right', 'd.left')]. A port can only have one connection, so declaring a new connection for a port that already had one supersedes it. Names are resolved against the actual declared sections/ports at EME build time. An entry may carry an optional third element, a JunctionOverride, configuring the junction that connection forms, e.g. [('a.right', 'b.left', JunctionOverride(junction_normalization=False))].

  • simulation_name (str) – simulation name, default: ‘emode’

class JunctionOverride

Settings for a single junction, passed as the optional third element of a connect_ports entry. Every parameter left unspecified inherits the value the junction’s sections resolved, so setting one does not disturb the others.

These settings belong to the connection rather than to either port because each one describes the interface between the two sections, not one side of it: which algebra joins their mode sets and how the resulting scattering matrix is normalized.

Only an explicitly connected junction can carry these. A junction formed by the default left/right adjacency has nowhere to declare them, so name that pair in connect_ports to configure it, which supersedes the adjacency. The internal junctions of a taper or GDS section’s automatic z-slicing are controlled by that section’s settings instead.

Parameters:

junction_normalization (bool or str or None) – how this junction’s scattering matrix is power-normalized, one of True (full SVD unitarization), False (raw, unnormalized), or ‘gain’ (clip singular values at 1 while keeping the raw lossy mode-matching), for a junction with a plane-wave side or an angled facet, default: None (inherit)

EME

Calculate the complete scattering matrix of all defined sections using the eigenmode expansion method (EME). Before solving anything it checks the layout (see check_layout) and raises an error when two sections overlap or a loop of connected sections misses closing (its two ports more than minimum_z_step apart, or more than 1e-6 rad from facing each other), and warns when a section’s outline crosses itself or a section is reached by no connection.

A boundary_condition of ‘TE’, ‘TM’, ‘TE-h’, ‘TE-v’, ‘TM-h’ or ‘TM-v’ folds a cross-section only about a middle it is mirror symmetric about (see settings). In a circuit, a fold applies only when every section is symmetric about it, because a folded section has no modes of the other parity, and where it meets an unfolded section the power those modes would carry is lost. When some sections fold about a middle and others cannot, EME() warns and solves the circuit again with that fold taken out of every section. Set a boundary condition without it (for example ‘TE-v’ instead of ‘TE’ for a circuit with offset or bent guides) to solve once.

Parameters:
  • warm_start (bool) – whether to seed each taper/gds section’s automatic z-slice refinement from the previously solved chain (when available), instead of re-refining from scratch, default: False

  • simulation_name (str) – simulation name, default: ‘emode’

check_layout

Check the layout of the declared sections without solving. Every section is placed where its connections put it and drawn top down from its shapes that do not span the window. Returns a LayoutReport with overlaps (pairs of sections whose outlines overlap, as (section, section, area) with the area in nm2, where sections that only share a port edge do not overlap), self_intersecting (sections whose own outline crosses itself, such as a bend swept past a full turn), closures (for each connection that closes a loop, how far apart its two ports are in nm and how far from facing each other in radians), and unplaced (sections no connection reaches, which are placed after the section declared before them). max_closure() returns the largest closure distance and angle, or zeros without a loop. plot(view='layout') draws the same layout. LayoutReport is defined in emodeconnection.

Parameters:

simulation_name (str) – simulation name, default: ‘emode’

chi2_spectrum

Conversion spectrum of a \(\chi^{(2)}\) section, solved sparsely in wavelength (EMode3D only). Returns the signal power out over the pump power in at every wavelength in values.

A nonlinear spectrum oscillates with the phase mismatch, so it cannot be interpolated. At any useful resolution it has to be evaluated point by point. What can be interpolated is what the coupled-mode equations consume: the coupling and the propagation constants, which vary smoothly with wavelength. So the modes are solved at a handful of wavelengths chosen by how fast the mode list is actually moving, and the response is then evaluated at every requested wavelength from interpolated coupling. Against a directly solved 13-point spectrum across a phase-matching peak, five solves reproduce every point to four decimals, sidelobes included.

Quasi-phase matching is included: the grating enters as a wavevector shift and a Fourier coefficient built from poling_period, duty_cycle and qpm_order, none of which depend on wavelength, so a poled section keeps its exact grating while its phase mismatch moves with the interpolated propagation constants.

Requires a chain with a declared \(\chi^{(2)}\) section (see nonlinear on straight_section/taper_section) and a declared pump (excitation in settings).

Parameters:
  • values (list or array) – pump wavelengths [nm] to report at (the signal wavelength follows from the declared process)

  • section (str) – which nonlinear section to scan, default: the only one declared

  • tolerance (float) – the largest mode-list change tolerated between adjacent solves, default: 1e-3

  • minimum_step (float) – [nm] the smallest wavelength gap the sampler will bisect into, default: the output spacing, since resolving finer than you asked to see cannot change the reported curve

  • initial_points (int) – solves to place before any bisection, default: 5

  • power (float) – [W] pump power, default: the declared excitation’s

  • simulation_name (str) – simulation name, default: ‘emode’

Returns a dictionary with values, conversion (signal out over pump in), solved_wavelengths (where the solver actually ran), and unconverged (the intervals, if any, that the sampler could not resolve to tolerance before reaching minimum_step).

free_space_field

Declare, or update, a free-space field: a Gaussian, Hermite-Gauss or Laguerre-Gauss beam, or a combination of the port’s own modes, launched into a port of the chain. This is an excitation, not a section. It does not appear in the scattering matrix or in the layout.

After EME, plot(excitation=name) (or the plot window’s Mode menu, '<name> (free space)') renders the field the beam launches, projected onto the target port’s modes with the same pairing the junctions use. Re-declaring an existing name updates only the arguments you give.

Parameters:
  • name (str) – free-space field name, default: the next number

  • port (str) – the external port the beam enters, either a bare side 'left' (default) or 'right', or a qualified '<section>.<port>' name. The beam travels inbound from that port.

  • waist (float or tuple[float, float]) – [nm] the 1/e field radius \(w_0\) in \(\exp(-r^2/w_0^2)\) at the beam’s waist, required when declaring a new Gaussian beam. A pair (w_x, w_y) declares an elliptical beam, \(\exp(-x^2/w_x^2 - y^2/w_y^2)\) at its waist

  • waist_definition (str) – how waist is measured: '1/e2_radius' is \(w_0\) above, the radius where the intensity falls to \(1/e^2\), 'fwhm' the full width at half maximum of the intensity (\(w_0 = \mathrm{FWHM}/\sqrt{2 \ln 2}\)), and 'mfd' the mode field diameter \(2 w_0\), the fiber convention. The beam stores \(w_0\), and a pair converts each axis. Default: '1/e2_radius'

  • center (tuple[float, float]) – [nm] beam center (x, y) in the port’s cross-section coordinates, default: (0, 0)

  • tilt (tuple[float, float]) – [degrees] (theta_x, theta_y), where a positive theta_x steers the beam toward +x. This is the angle inside the medium the beam travels through, default: (0, 0)

  • component (str) – the one transverse E component the beam carries, 'Ex' (default) or 'Ey'. Give component or polarization, not both

  • polarization (tuple[float, float] or tuple[float, float, float] or None) – the beam on both transverse E components, (a_x, a_y) or (a_x, a_y, delta): \(E_x\) carries \(a_x\) and \(E_y\) carries \(a_y e^{j\delta}\), with delta in degrees (default 0). The pair is scaled so that \(|a_x|^2 + |a_y|^2 = 1\), so amplitude keeps its meaning, and at least one of \(a_x\) and \(a_y\) must be nonzero. (1, 1, 90) is circular polarization, and other values give linear or elliptical polarization. It replaces component. Default: None (the beam on component alone)

  • envelope (str) – the beam’s transverse shape, 'gaussian', 'hermite_gauss' or 'laguerre_gauss'. Each order is normalized to the power of the Gaussian of the same waist and amplitude, and order (0, 0) is the Gaussian. A Hermite-Gauss beam is separable, so it takes a pair waist or waist_position. A Laguerre-Gauss beam is circular, so it takes one waist and one waist_position, and a pair raises an error. Default: 'gaussian'

  • order (tuple[int, int]) – the beam’s order, (m, n) of a Hermite-Gauss beam (\(H_m\) along x and \(H_n\) along y, both at least 0) or (p, l) of a Laguerre-Gauss beam (the radial index \(p \ge 0\) and the azimuthal index \(l\), which may be negative, with the phase \(e^{j l \phi}\)). Away from the waist the beam carries \(m + n + 1\) times the Gaussian’s Gouy phase for Hermite-Gauss, and \(2p + |l| + 1\) times it for Laguerre-Gauss. Default: (0, 0)

  • amplitude (float) – peak field value at the waist, default: 1

  • wavelength (float) – [nm], default: None (each port’s own wavelength)

  • waist_position (float or tuple[float, float]) – [nm] the distance from the port to the beam’s waist, along the direction the beam travels. A positive value focuses the beam inside the chain, a negative one puts the waist behind the port (a diverging beam). The launched beam carries the wavefront curvature and Gouy phase of that distance, with the Rayleigh range \(z_R = \pi n w_0^2/\lambda\) of the port’s background index \(n\). A pair (z_x, z_y) puts the waist along x and along y at different distances (an astigmatic beam). With a pair in waist or waist_position, each axis has its own Rayleigh range, wavefront curvature and half of the Gouy phase, and the beam’s power does not depend on either distance. Default: 0 (the waist on the port)

  • phase (float) – [radians] global phase of the launched field, default: 0

  • modes (list[int]) – launch a combination of the target port’s own modes instead of a beam: their indices. Giving a beam’s name a list of modes (or a mode combination’s name a waist) replaces it with the other kind

  • powers (list[float]) – one power per entry of modes, default: 1 each. Each mode is launched with the amplitude \(\sqrt{P}\,e^{j\phi}\), as a Source is

  • phases (list[float]) – [radians] one phase per entry of modes, default: 0 each

  • simulation_name (str) – simulation name, default: ‘emode’

free_space_field_diagnostics

How much of a declared free-space field’s power the target port’s modal basis actually captures, after EME has solved the chain. The solver already warns when this falls below 90%. This returns it as a value a script can check or plot instead of parsing a log line.

A low captured fraction means the port’s modes cannot represent the beam. Raise that section’s num_modes, or widen its window, before trusting a coupling number computed from it.

Parameters:
  • name (str) – the free-space field to diagnose

  • wavelength (float) – [nm] which of a multi-wavelength chain’s ports to diagnose, default: None (the lowest wavelength present)

  • simulation_name (str) – simulation name, default: ‘emode’

Returns a FreeSpaceProjection with captured_fraction, its complement dropped_fraction, the resolved port and wavelength, and the launch basis’s num_modes.

plot_S_matrix

Colormap plot of the transmission or reflection component of the EME scattering matrix.

Parameters:
  • input (int or str) – input field (first number of the scattering matrix identifier), default: 0

  • output (int or str) – output field (second number of the scattering matrix identifier), default: 1

  • plot_function (str or None) – specify the function to apply to the plot data (‘abs^2’, ‘angle’, ‘real’, ‘imag’), default: ‘abs^2’

  • file_name (str) – if specified, forces the function to save a file with the given parameters and will not display the interactive plot, file is saved under a directory called EM_figures, default: None

  • file_type (str) – file type for saving a plot, ‘pdf’ or ‘png’, default: ‘’ (no extension is added to file_name unless a file type is given)

  • simulation_name (str) – simulation name, default: ‘emode’

Monitors

A monitor is a plane inside the chain on which the solved field is sampled after EME. It is output only: it is not a port, does not enter the scattering matrix and does not change the solve, so monitors can be added, moved or removed without solving again. The plane runs across the full window in x and is tilted about x, so it can sample the light a grating coupler radiates upward. monitor_field returns the field on it, and monitor_coupling its coupling to a tilted Gaussian beam, such as the mode of a fiber above a grating. plot() draws each monitor’s plane on the ‘z-y’ panel, and sweep(result='monitor_coupling') sweeps the coupling (see sweep). The ‘EME: Grating Coupler’ example uses all three.

monitor

Declare a monitor. Declaring the same name again replaces it.

Parameters:
  • name (str) – the monitor’s name

  • w (float) – [nm] the position along the chain, from its left port, that the plane passes through

  • y (float) – [nm] the height that the plane passes through at w. An untilted plane must lie inside the window, between 0 and window_height

  • angle (float) – [degrees] the tilt of the plane about x from horizontal, strictly between -90 and 90, where a positive angle tips the far end of the plane up, default: 0

  • length (float or None) – [nm] the extent along the plane, centered on (w, y), default: None (as far as the chain and the window allow)

  • resolution (float or None) – [nm] the sampling step along the plane, default: None (the window’s x_resolution)

  • simulation_name (str) – simulation name, default: ‘emode’

monitor_field

The field on a monitor’s plane after EME, for 1 W launched in one mode of one port.

Parameters:
  • name (str) – the monitor’s name

  • port (str) – the external port the chain is excited from, default: ‘left’

  • mode (int) – the launched mode, default: 0

  • wavelength (float or None) – [nm] which of a multi-wavelength chain’s wavelengths to use, the nearest one present, default: None (the chain’s first)

  • simulation_name (str) – simulation name, default: ‘emode’

Returns a PlaneField with fields, a dictionary of the six components (‘Ex’, ‘Ey’, ‘Ez’, ‘Hx’, ‘Hy’, ‘Hz’) on the plane, each a complex array of shape (len(x), len(s)), where x [nm] runs across the plane and s [nm] along it, at chain positions w and heights y [nm]. Sn is the time-averaged Poynting flux through the plane (positive along its upward normal), power_through_plane [W] its integral, and power_in the 1 W launched, so power_through_plane is also the fraction of the launched power that crosses the plane.

monitor_coupling

The coupling between a launched waveguide mode and a Gaussian beam through a monitor’s plane, after EME. It is the fraction of the launched power crossing the plane times the normalized overlap of the field’s polarization component with the beam. By reciprocity the same number is the coupling from the beam into the launched mode, which is the figure a grating coupler is designed for. Only the overlap is computed, so trying other beams costs no solve.

Parameters:
  • name (str) – the monitor’s name

  • mode_field_diameter (float) – [nm] the beam’s mode field diameter, for example 10.4e3 for SMF-28 at 1550 nm

  • angle (float) – [degrees] the beam’s angle from the plane’s normal, positive toward +s (along the chain), default: 0

  • center (tuple[float, float] or None) – [nm] the beam’s center (x, s) on the plane, default: None (centered on the plane)

  • index (float or None) – the refractive index of the medium the beam travels in, default: None (the profile’s background material)

  • polarization (str) – the field component the beam couples to, ‘x’, ‘y’, or ‘s’ (along the plane), default: ‘x’

  • port (str) – the launching port, default: ‘left’

  • mode (int) – the launched mode, default: 0

  • wavelength (float or None) – [nm] which of a multi-wavelength chain’s wavelengths to use, default: None (the chain’s first)

  • simulation_name (str) – simulation name, default: ‘emode’

Returns a BeamCoupling with coupling, its two factors power_fraction and overlap, and the beam it was computed for (mode_field_diameter, center, angle, index, polarization) at wavelength.

Pulsed nonlinear propagation

A femtosecond pulse is declared as the excitation, settings(excitation=PulsedSource(...)), and a section’s nonlinearity with nonlinear=PulsedProcess(...) on straight_section, taper_section or gds_section (EMode3D only). EME refines the chain at the pulse’s center wavelength, solves each distinct cross-section at dispersion_samples wavelengths across the pulse’s band, and propagates the pulse forward through every slice and junction with \(\chi^{(2)}\), Kerr (\(\chi^{(3)}\)), Raman and two-photon absorption, three- and four-photon absorption when asked for, and the free carriers that absorption makes. Reflections and power scattered into modes that are not tracked are discarded and reported, with a warning where that is not safe. The classes below are imported from emodeconnection.

The nonlinear coefficients come from the materials: \(\chi^{(2)}\) from the material’s \(d\) tensor, \(\chi^{(3)}\) from its nonlinear index \(n_2\) as \(\chi^{(3)} = \tfrac{4}{3}\varepsilon_0 c n^2 n_2\), the Raman response from the material that holds most of the Kerr overlap, and N-photon absorption from its coefficients, \(\beta_\mathrm{TPA}\) for two photons and \(\beta_3\) and \(\beta_4\) for three and four, each active only where N photons carry more than the material’s bandgap. In the material database, SiO2, SiN, Si, Diamond, LN and LN_MgO carry \(n_2\) (Air and Vacuum carry an \(n_2\) of zero), SiO2 (Blow and Wood) and LN/LN_MgO (four A1(TO) lines, Bache and Schiek) a Raman response, Si, GaAs, AlGaAs, Diamond, LN and LN_MgO a two-photon coefficient, Si, GaAs, LN and LN_MgO a three-photon coefficient, and Si a free-carrier model (Soref and Bennett), each with its reference in property_refs. A material of your own takes the same through MaterialProperties(n=..., n2=..., raman=(fraction, [(weight, position, half_width), ...]), tpa=..., beta_3=..., beta_4=..., bandgap=...), with \(n_2\) in m2/W, the Raman line positions and half widths in cm-1, \(\beta_\mathrm{TPA}\) in m/W, \(\beta_3\) in m3/W2, \(\beta_4\) in m5/W3 and the bandgap in eV. PulsedProcess(chi3=True) raises an error naming a material that has no \(n_2\).

class PulsedSource

A pulse launched into one port of the circuit, for settings(excitation=...).

Parameters:
  • center_wavelength (float) – [nm] the carrier wavelength, inside wavelength_range

  • duration (float) – [fs] the intensity FWHM, required except for shape='cw' and shape='measured'

  • energy (float) – [pJ] the pulse energy, give exactly one of energy and peak_power (a measured pulse is scaled to either, as an analytic one is)

  • peak_power (float) – [W] the peak power

  • port (str) – the launch port, default: ‘left’

  • mode (int or dict) – the tracked mode that carries the pulse, or a dictionary {mode: amplitude} of complex amplitudes that splits it between tracked modes (normalized, so only their ratios matter), default: 0

  • shape (str) – ‘sech’, ‘gaussian’, ‘cw’ (a continuous wave of peak_power at center_wavelength, with no duration or energy), or ‘measured’ (the pulse of measurement, with no duration or chirp), default: ‘sech’

  • chirp (float) – the chirp \(C\) of \(\exp(j C t^2 / 2 T_0^2)\), where a positive value raises the instantaneous frequency across the pulse, default: 0

  • measurement (PulseMeasurement or None) – a measured pulse for shape='measured', see PulseMeasurement. A measured spectrum is launched at its own wavelengths, and center_wavelength stays the wavelength at which the circuit is refined. A measured temporal trace is centered on center_wavelength. Default: None

  • repetition_rate (float) – [Hz] for average powers, default: None

  • wavelength_range (tuple[float, float]) – [nm] the span of the frequency grid, default: (450, 3500)

  • num_points (int) – frequency points, even and at least 16, which with wavelength_range set the time window (the number of points over the frequency span, 28 ps at the defaults), default: 16384

  • alias (int) – oversampling of the real-field products, 1, 2 or 3 (2 suffices for \(\chi^{(2)}\), 3 for \(\chi^{(3)}\)), default: 2

class PulseMeasurement

A measured pulse, for PulsedSource(shape='measured', measurement=...): a sampled spectrum (as from a spectrometer, often with a phase retrieved by FROG or SPIDER) or a sampled temporal trace. Give exactly one of wavelength and time.

The samples are never launched as they are. settings fits them first, so noise and sparse sampling do not reach the propagation, and refuses the source with an error when the fit is not good enough. The fit’s quality is 1 minus its normalized residual: the \(R^2\) of the intensity, less the intensity-weighted mean of \(1 - \cos\) of the phase error when a phase is given. Below min_fit_quality the error states the quality reached, the model and where the residual is largest, so you can choose another model, clean the data, or lower min_fit_quality knowingly. The accepted pulse is resampled onto the frequency grid, centered in the time window and scaled to the source’s energy or peak_power. The fit is reported on the result as get('pulse_response').measured_fit, with its model, quality, fwhm [fs], center_wavelength [nm], gdd [fs2], tod [fs3], chirp (for ‘sech’ and ‘gaussian’), transform_limited, energy_outside (the fraction of the energy cut by the window), residual_peak and background (the constant background subtracted, as a fraction of the samples’ peak).

Data that stops while the pulse is still bright (above 5 % of its peak), or whose pulse lies mostly outside the source’s wavelength_range, is refused. A spectrometer’s dark counts or stray light put a constant background under the whole trace, which reads as a pulse that never reaches zero: give background to subtract it before the fit and this check. Data that reaches past wavelength_range (or past the time window) is cut with a smooth window, with a warning giving the fraction of the energy outside.

Parameters:
  • wavelength (list[float] or numpy array or None) – [nm] the wavelengths of a measured spectrum

  • time (list[float] or numpy array or None) – [fs] the times of a measured temporal trace

  • intensity (list[float] or numpy array) – the intensity at each sample, on any scale. A spectrum’s intensity is per unit wavelength, as a spectrometer records it.

  • phase (list[float] or numpy array or None) – [rad] the retrieved spectral or temporal phase at each sample, in the convention of FROG and SPIDER retrievals: the group delay is \(d\varphi/d\omega\), so a positive quadratic spectral phase is a positive group delay dispersion, and the instantaneous angular frequency is \(\omega_0 - d\varphi/dt\). Without a phase the pulse is taken as transform limited, with a warning. Default: None

  • fit (str) – the model fitted to the samples: ‘smooth’ (a smoothing spline of the intensity, with a polynomial phase of order 2 to 4 chosen by the fit, or a smoothing spline of the phase weighted by the intensity when no polynomial reaches min_fit_quality, as for a pulse shaper’s output or a pulse broadened by self-phase modulation), ‘sech’ or ‘gaussian’ (the analytic shapes of PulsedSource, fitted for width, center and chirp), default: ‘smooth’

  • min_fit_quality (float) – the lowest fit quality accepted, in [0, 1], default: 0.99

  • background (float or 'auto' or None) – a constant background subtracted from intensity before the fit, on the same scale as intensity (per unit wavelength for a spectrum), at least 0 and below the samples’ peak. ‘auto’ takes it as the mean of the outer 5 % of the samples at each end, and refuses the measurement when those ends slope or sit at different levels (by more than 1 % of the peak, or four times their noise), since they are then the pulse’s wings rather than a background. None subtracts nothing. Default: None

class PulsedProcess

The nonlinear optics of one section for a pulsed simulation, for a section’s nonlinear=.

Parameters:
  • chi2 (bool) – include \(\chi^{(2)}\), default: True

  • chi3 (bool) – include the Kerr effect, default: True

  • raman (bool) – include the delayed Raman part of the Kerr response, default: True

  • raman_mode (str) – ‘dominant’ (the response of the strongest field pattern, fast) or ‘full’, default: ‘dominant’

  • tpa (bool or None) – two-photon absorption on (True), off (False), or on wherever a material has a coefficient (None), default: None

  • multi_photon (bool or None) – three- and four-photon absorption, on (True) wherever a material has a three- or four-photon coefficient, or off (False or None). True raises an error for a cross-section in which no material has one. Default: None (off)

  • free_carriers (bool or None) – the electron-hole pairs that two-, three- and four-photon absorption make, with their absorption and index change: on (True), off (False), or on wherever an absorbing material has a free-carrier model (None). True raises an error for a cross-section in which no absorbing material has one. Default: None

  • carrier_lifetime (float or None) – [ns] the free-carrier lifetime, which depends on the waveguide, in place of the material’s own (1 ns for Si). It must be positive. Default: None (the material’s lifetime)

  • modes (list[int] or None) – the tracked mode indices at the center wavelength, default: None (the source’s mode or modes)

  • poling (Poling or None) – the section’s domain pattern, default: None (unpoled)

  • dispersion_samples (int or None) – wavelengths at which each cross-section is solved, Chebyshev nodes over the source’s wavelength_range, at least 4, default: None (as many as the band’s width needs)

  • pattern_threshold (float) – singular value, relative to the first, below which field patterns are dropped, in [0, 1), default: 1e-3

  • max_rank (int) – the largest number of field patterns. PulseResponse.rank_report says where it binds and how much of each mode’s field that drops. Default: 12

  • max_harmonic_modes (int) – on a \(\chi^{(2)}\) section, the most second-harmonic modes added to the tracked modes, ranked by their \(\chi^{(2)}\) overlap with the first tracked mode times the length they build over. 0 adds none. Default: 3

  • harmonic_coupling (float) – the weakest second-harmonic mode added, relative to the strongest, in [0, 1], default: 0.1

  • modal_search (str or tuple[float, float]) – where an unpoled \(\chi^{(2)}\) section’s modal phase matches are searched for: ‘broadened’ (where the launched pulse’s spectrum, or the pump’s spectrum anywhere in the section as it broadens, is within 30 dB of its peak), ‘launch’ (only where the launched pulse’s spectrum is within 30 dB of its peak), or a band (min, max) of fundamental wavelengths [nm]. When a match lies outside the launched spectrum, ‘broadened’ first runs the pump alone (one mode, one field pattern, no second-harmonic modes) to find it. Default: ‘broadened’

Several \(\chi^{(2)}\) sections over one cross-section share its modes, which take the largest max_harmonic_modes and the smallest harmonic_coupling among them.

class Poling

The domain pattern of a poled section. Give exactly one of period, z_knots with periods, domain_walls, and domain_lengths.

Parameters:
  • period (float) – [nm] a constant poling period

  • z_knots (list[float]) – [nm] positions along the section at which periods are given, interpolated between them (a chirp), at least 2

  • periods (list[float]) – [nm] the local period at each knot

  • domain_walls (list[float]) – [nm] explicit domain wall positions along the section, strictly increasing

  • domain_lengths (list[float]) – [nm] the length of every domain in order, each > 0, the signs alternating from the first. The walls are the running sum: a wall at or past the end of the section is dropped, and when the domains end before the section does, the sign flips once more and the last domain runs to the end. This is the form optimize varies, every domain on its own

  • duty_cycle (float) – the fraction of each period in the first domain, default: 0.5

  • interpolation (str) – ‘linear’ or ‘spline’ between the knots, default: ‘linear’

  • inverted_first (bool) – start with an inverted domain, default: False

  • jitter_rms (float) – [nm] rms of a Gaussian random displacement of every wall, for fabrication tolerance studies, default: 0

  • seed (int) – the seed of the jitter, default: None

walls(section_length) returns the domain walls [nm] of any form over a section of that length, and to_domain_lengths(section_length) the length of every domain, which sum to the section length, so a design can start from a fixed or chirped poling.

class PulseResponse

Returned by get('pulse_response') after EME or pulse_propagate. spectrum is (records, modes, points), the root-energy spectrum of every tracked mode in \(\sqrt{\mathrm{J/Hz}}\) on frequency [Hz], with wavelength [nm] the same axis, z [nm] the record positions and energy [pJ] per record and mode. junction_loss is, per junction, the fraction of the arriving energy that left the tracked modes there, and rank_per_slice and samples_per_slice report the field patterns and mode solves of each slice. window_gap is, per record, the longest stretch of the time window (as a fraction of it) that holds almost none of the energy of all the tracked modes together. The window is periodic, so below about 0.05 the pulse’s leading and trailing parts have met around it and wrapped onto each other, and num_points should grow (the solver warns). It does not depend on the comoving frame. edge_energy is, per record, the fraction of the energy in the outer 5 % of the time window around the comoving frame (the first tracked mode’s group delay), which says where the pulse sits relative to that frame, not whether it wraps. domain_walls [nm] the positions along the chain of the domain walls the run used (jitter included), and steps and rejected_steps count the propagation steps. For a measured source, measured_fit is the fit that was launched (see PulseMeasurement), and None otherwise. rank_report gives, per slice, the field patterns used (rank), the number pattern_threshold asks for (natural_rank), the max_rank that caps it and the share of each tracked mode’s field weight the patterns leave out (dropped_weight). absorbed_energy is the energy [pJ] each nonlinear absorption process took out of the tracked modes over the whole chain, a dictionary keyed 'two_photon', 'three_photon', 'four_photon' and 'free_carrier' for the processes that ran.

band_energy(wavelength_min, wavelength_max, z=None, mode=None) returns the energy [pJ] in a band at the record nearest z (default: the output), summed over the tracked modes unless mode is given. band_power(...) returns the average power [W] of a pulse train, with repetition_rate taken from the source unless given. psd(z, mode) returns the spectral density in dB relative to 1 pJ/nm, envelope(z, mode) the time [fs] and complex envelope [\(\sqrt{\mathrm{W}}\)], smallest_window_gap() the smallest window_gap at any record (None for a response saved before it was reported), energy_outside_window() the largest edge_energy, and total_absorbed_energy() the sum of absorbed_energy [pJ], the energy every nonlinear absorption process took together.

pulse_propagate

Propagate the pulse again through the chain the last EME prepared, without any mode solve (EMode3D only). Use it to try another poling, for example a chirp or fabrication jitter on the domain walls, at the cost of the propagation alone.

Parameters:
  • poling (Poling or dict or None) – a new domain pattern for every section with a PulsedProcess (a single Poling), or for the named sections (a dictionary of section name to Poling), default: None (keep the declared poling)

  • records (int) – number of evenly spaced positions along the chain at which the spectrum is recorded, default: 100

  • local_error (float) – relative error allowed per propagation step, default: 1e-6

  • simulation_name (str) – simulation name, default: ‘emode’

The prepared chain lives for the session. After open() of a saved file, run EME again first. The result is read back with get('pulse_response').