Release Notes

1.0.7

  • New

    • optimize finds the parameters of a simulation you have already set up that give the best value of a quantity you choose, within bounds and constraints, available with EMode3D. The parameters are named with the keys of sweep: a setting, one profile’s setting ('setting, <profile>, <name>'), a shape property ('shape, <name>, <property>'), a section property ('section, <name>, <property>') or an argument of a monitor_coupling beam ('beam, <name>'). The Parameter, Objective and Constraint classes of emodeconnection declare them as data. An objective is an S-matrix element’s power or phase, a monitor coupling, a mode’s effective index, group index or loss, a pulse’s energy or average power in a wavelength band, or a parameter itself, and a list of objectives is their weighted sum. The methods are ‘cobyqa’ (the default), ‘nelder-mead’, ‘powell’, ‘differential_evolution’, ‘l-bfgs-b’ and ‘slsqp’, the last two with finite-difference gradients. Every evaluation starts from the simulation as declared, so its result depends only on its parameters. A cross-section an earlier evaluation solved is not solved again, so optimizing a section length solves the modes once. record= writes each evaluation to a file as it completes, and the simulation is left solved at the best point. An EMode2D session that calls it gets a LicenseError. See Optimization.

    • Poling design with every domain free. Parameter('poling, <section>, domain_lengths', bounds=(min, max)) makes the length of each domain of a poled section a variable of optimize, within a fabrication minimum and maximum, and 'poling, <section>, total_length' can be constrained. With ‘l-bfgs-b’ or ‘slsqp’ and a 'band_energy' or 'band_average' objective, the gradient with respect to every domain comes from one adjoint propagation, so an evaluation with its gradient costs one and a half to two and a half propagations, whatever the number of domains. Poling has a new form, Poling(domain_lengths=[...]). The website has a new example, ‘Nonlinear-EME: PPLN Inverse Design’, which optimizes the poling of the ‘Nonlinear-EME: Supercontinuum’ device for the 750 nm band with every domain length free.

    • evaluate evaluates a simulation at one point. It restores the simulation’s snapshot, sets every parameter, solves what the requested results need and returns a record, so the same parameters give the same results whatever ran before. snapshot takes that snapshot, and with file_name also writes it as an .eph file that another session can evaluate from. Both are available in every license tier.

    • converge finds the resolution, window, PML thickness or number of modes a result needs (setting='resolution', 'window', 'pml', 'modes', or 'all' for each in turn), for one mode’s effective index, group index, loss or orthogonality (target). It steps the setting, follows the mode by its overlap rather than its place in the mode list, and stops once the estimated error is within tolerance on two consecutive steps, or reports why it could not ('oscillating', 'lost mode' or 'budget', for example). It prints one row per solve and returns a ConvergenceResult. The profile’s settings change only with apply=True. It works with the FDM and the FEM and is available in every license tier. See converge and the new example ‘Straight Waveguide Convergence’.

    • sweep takes several keys at once: with key and values as lists it sweeps every combination. A key can name one profile’s setting, 'setting, <profile>, <name>'.

    • Measured pulses. PulsedSource(shape='measured', measurement=PulseMeasurement(...)) launches a measured spectrum or temporal trace, with or without a retrieved phase (FROG or SPIDER). The samples are fitted first (‘smooth’, ‘sech’ or ‘gaussian’), and a fit whose quality is below min_fit_quality (default 0.99) is refused with an error that gives the quality reached. background subtracts a constant pedestal before the fit, and get('pulse_response').measured_fit reports the fit. See PulseMeasurement.

    • Pulsed propagation follows the tracked modes through an avoided crossing inside the band, adding the crossing partner on its own, carries modes that are guided over only part of the band, and follows the second-harmonic modes a \(\chi^{(2)}\) section phase-matches. A pulse response reports window_gap, the empty stretch of the periodic time window, and warns when the pulse wraps onto itself.

    • Pulsed propagation searches for the second-harmonic modes an unpoled \(\chi^{(2)}\) section phase-matches wherever the pump has light as it broadens, not only where the launched pulse has light. When a match lies outside the launched spectrum, a quick run of the pump alone finds where the pump reaches first, which adds its time to EME. PulsedProcess(modal_search=) chooses the search: 'broadened' (the default), 'launch' (the launched spectrum only) or a (min, max) band of fundamental wavelengths in nm. max_harmonic_modes (default 3) caps the second-harmonic modes tracked, and harmonic_coupling (default 0.1) is the weakest coupling tracked, relative to the strongest. get('pulse_response').rank_report gives, for each slice, the number of field patterns used, the number pattern_threshold asks for and the share of each tracked mode’s field the patterns leave out. The solver warns when max_rank leaves out more than 1 % of a tracked mode’s field.

    • Free-space beams (free_space_field). waist_position places the beam’s waist along its path, so the launched beam carries the wavefront curvature and Gouy phase of that distance. waist and waist_position take (x, y) pairs for elliptical and astigmatic beams. modes, with powers and phases, launches a combination of the port’s own modes instead of a beam.

    • SMatrix.mode_power holds the power each port mode carries, and unitarity_error(), power() and insertion_loss_dB() weigh by it, so they read power at a port whose modes carry unequal power, such as a plane-wave section. unitarity_error(from_ports=) counts only the modes of the ports named.

    • New website examples: ‘Straight Waveguide Convergence’ (the SiN guide of ‘Bend Mode Solvers’ without the bend, solved by the FDM and the FEM, with the effective and group index errors against run time), ‘EME: MMI’ (a 1x2 and a 2x2 MMI, the 2x2’s width and length set by optimize), ‘EME: S-Bend’, ‘EME: Y-Branch’, ‘Nonlinear-EME: SFG’, ‘Ring Resonator Design’ and ‘SMF-28 Fiber’. The examples page shows a card with a thumbnail for every example.

    • The layout view (plot(view='layout')) draws the Index and Shapes panels on the chip, from each shape’s placed outline. The ‘z-y’ panel offers Index and Shapes too. A branching circuit, such as an MZI or a Y-branch, draws each arm’s field on its own arm. EME ports are drawn as lines over their windows, and an angled facet is drawn at its tilt.

    • With EMODE_HEADLESS=1, material_explorer and plot_S_matrix return a PlotImage, as plot does, instead of opening a window.

    • EMode(quiet=True) hides the solver’s printed output. Warnings and errors still reach stderr.

    • get('n_eff') reads the effective index. permittivity and permeability take a MaterialSpec and every other material form refractive_index takes. A member of a multi-wavelength profile is named '<profile>.<wavelength>', as in get('effective_index', profile='tfln.1550'), and an ambiguous name raises an error that names the candidates.

    • dn_dT and eps_r_dc can be given per axis for an anisotropic material, so heat shifts each axis by its own dn_dT.

    • When every EMode3D seat is in use and a session falls back to EMode2D, it warns once: “Every EMode3D seat is in use, so this session runs as EMode2D.” emode login on the command line can clear one numbered session, as the login window can.

    • Every installer includes THIRD-PARTY-NOTICES.html, which lists the third-party components it contains and their licenses.

    • emodeconnection 1.1.24 carries the client side of this release: the optimizer’s and converge’s types, Confinement, measured pulses, poling by domain lengths, the second-harmonic options and RankReport, SMatrix.mode_power and quiet mode.

  • Changed

    • The FDM’s subpixel averaging follows each interface: the field component normal to it sees the harmonic mean of the materials’ permittivities, the tangential ones the arithmetic mean, and a gap or strip thinner than a cell is averaged as a layer. A material face between grid lines no longer gives a first-order error, so effective indices move toward their converged values. A 500 x 220 nm silicon strip’s TM mode at 1550 nm on a 25 nm grid moves from 1.8174 to 1.7760, against 1.7735 converged. Structures whose faces sit on grid lines give the same results as before.

    • FDM(), FEM() and solve on a multi-wavelength profile return a WavelengthDict with every wavelength’s result, keyed by wavelength, where they returned only the last wavelength’s. A single wavelength still returns a plain dict.

    • Enabling a PML no longer shifts the cell centers of an expansion region. Expansion regions are now built from their cell edges, so results on grids with an expansion region change slightly (by 5e-7 in the ‘Angled: LNOI’ example’s effective index, from this change alone).

    • confinement indexes modes by integer on the client, as the server does: confinement()['core'][0] is the fundamental mode’s confinement factor in core. It returns a Confinement, a read-only mapping. get_shape’s metadata['confinement'] is keyed by integer too. A client older than emodeconnection 1.1.24 still reads string keys.

    • A window that is not a whole even number of cells is grown to the next even count, with a warning, instead of being trimmed without one, in the FEM too. The cells are added at the top, so y = 0 stays a grid line, or evenly above and below when the solve folds y, so the fold’s wall is a grid line. get('window_width') and get('window_height') read the meshed size.

    • Symmetry folds are decided from the whole cross-section: a symmetric pair of masks folds, a shape of the background’s material no longer prevents a fold, and an EME chain folds only where every section agrees. A fold’s wall must lie on a grid line: where it cannot, a fold that ‘TE’ or ‘TM’ implies is dropped, and an explicit ‘S’ or ‘A’ warns. A heat or electrostatic change that breaks the mirror symmetry unfolds the solve, with a warning.

    • The cylindrical bend’s FDM operator is reciprocal: a bend’s modes are orthogonal under the solver’s reciprocity product to 6e-14 (3e-3 before), and a bend toward +x and the same bend toward -x give one effective index. Bend effective indices move by up to about 8e-5, the first-order error removed, which halved with the cell size.

    • EME of a crystal turned in the plane, such as an x-cut lithium niobate bend, conserves power and is reciprocal. Lossy modes keep their exact backward partners, and the junction is solved for the turned crystal’s mirrored orientation, which is turned the other way. An x-cut half ring keeps 0.999992 of its power at the default settings, where it lost about 1.6 %. The ‘EME: TFLN Ring Resonator’ example’s spectrum now shows a second mode family beside the fundamental’s resonances.

    • An angled facet’s field is rotated onto the facet plane with the right sign. A 30 degree facet now matches the Fresnel coefficients (Rs 0.0827, 0.0195 before), and every angled-facet result moves. The ‘EME: Angled Facet’ example is regenerated.

    • alpha keeps its sign, and a solve warns when a mode of a passive structure gains power under a PML. The apparent gain is the window’s truncation error and falls as the PML and window converge.

    • The heat solve takes the background material’s thermal conductivity, where it used a fixed 1e-9.

    • Only the first automatically stacked shape, the substrate, extends down past the bottom of the window. A shape given an explicit y position never does, even when its bottom is at y = 0.

    • The section keys etch_depth and sidewall_angle are no longer sweep keys. A sweep over them raises an error.

    • An unknown keyword argument raises an error that names it and the valid ones, before the function runs.

    • emodeconnection 1.1.24 sends only the settings of an argument that differ from their defaults, so it works with EMode 1.0.6. An option new in 1.0.7, used with an older EMode, is refused before anything is sent, with an error that names the option. When a client sends a setting or a type EMode does not know, EMode ignores the setting with a warning, or answers with an error and keeps the session, where it stopped before.

    • Every installer, the Intel Mac one included, meshes FEM cross-sections with triangle 20250106.

    • Examples whose numbers moved, each regenerated: ‘Multi-physics: Phase Shifter’ (VpiL 5.91 V·cm, 5.39 on the 1.0.6 page, from the electrostatic and electro-optic fixes below), ‘Multi-physics: Heater’ (the power for a pi phase shift is 14.2 mW, 13.2 on the 1.0.6 page, from the heat fixes below), ‘Nonlinear-EME: SHG’ (its pump moves to 1971.958 nm to stay phase-matched on its 10 nm grid under the new averaging, straight conversion 18.53 %, 17.72 % in 1.0.6), ‘Bend Mode Solvers’ (the ‘TE’ fold is back, and the two FDM bend methods give the same effective index on every grid) and ‘Nonlinear-EME: Supercontinuum’ (the pulse follows TE0’s TM partner and a \(\chi^{(2)}\) harmonic).

  • Fixed

    • With bend_method='TO', the FDM’s bend loss was wrong: the ‘Bend Mode Solvers’ bend read 0.0003 dB/m or less against the cylindrical method’s 0.0174, and about 27 % high with PMLs on the side walls only. The PML saw an almost unbent guide. The two methods now agree to about 1 %, and the loss no longer changes with the PML thickness.

    • With bend_method='TO', a symmetry fold changed the modes (the ‘Bend Mode Solvers’ bend read 1.677891 with ‘TE’ and 1.665974 unfolded at 20 nm), and on an off-center SiN bend the TO and cylindrical methods differed by 1e-3 at 40 nm. The shapes and the bend were sampled a quarter of a cell off. Folded and unfolded solves now agree to 1e-5, and the two methods to 1.5e-5 at 10 nm. The page’s earlier finding that TO converges faster was this error cancelling the discretization error at 25 nm.

    • A mode plot of a bend solved with bend_method='TO' ran one PML thickness past the grid. Its axes and PML hatch now match the cylindrical method’s, and both methods draw the PML only with window_display=True.

    • group_index compared mode k of the solve with mode k of the solve at the shifted wavelength, so a PML mode that moved in the list gave nonsense, such as about -150 on a bend with bend_method='TO'. It now pairs modes by overlap and seeds the shifted solve. A mode it cannot pair reads NaN, with a warning.

    • The FDM’s permittivity was rounded to single precision, which put up to 1e-3 of noise into group_index. The FEM’s group_index remeshed the shifted solve, which could put 6e-4 of error into it.

    • With a PML on a leaky substrate, the FDM could sort the guided mode behind PML modes, so a one-mode solve returned an absorber mode. Modes confined to the window now come first, and a solve that returns only PML modes asks for more.

    • With a PML, a guided mode whose effective index carried a small spurious gain could come back on the backward branch, with a negative effective index.

    • The FEM ignored pml_thickness, num_pml_layers and pml_kappa_max. It now honors all three, as the FDM does.

    • A south symmetry fold with a PML on only one of the north and south walls crashed the FDM. A PML on a folded wall is now dropped with a warning naming the wall, and the kept wall’s PML is mirrored.

    • In an anisotropic solve with a PML, the off-diagonal terms dropped the PML’s phase, so a 10-layer PML read a turned bend’s loss 4 % low.

    • A lossy crystal turned in the plane was solved as a gain medium, and one of its modes grew along the guide.

    • An earlier solve could change which lossy modes a later anisotropic solve returned, so the results depended on the order of the solves.

    • The TE fraction of a hybrid mode could exceed 1. It is now a power fraction.

    • A symmetric pair of masks never folded, an ellipse was not quite symmetric so a round core never folded, and a settings change on a meshed profile reset its boundary condition to ‘00’, which silently unfolded a ‘TE’ solve.

    • A symmetry fold on a window with an odd number of cells put the mirror wall in the middle of a cell, which mirrored that cell and widened the core.

    • A folded mode on an ‘S’ or ‘A’ wall was weighted as a full cell at the wall, so a junction between folded sections could create up to 2.4 % power.

    • Cell-centered fields at a window’s high wall were left at zero, so the two walls differed by up to 8e-3 of the peak field.

    • heat gave fill cells beside an etched core the dn/dT of a heat_only substrate, 21 times too large. The heat change and the electro-optic change now take each permittivity component at its own grid point, the electro-optic change takes each material’s own static field at a material wall, and a fold checks the heat or electrostatic change against its mirror image.

    • The electrostatic solve summed its bilinear form into one number over the whole mesh, so it ignored eps_r_dc, and it left each electrode’s outline free, so VpiL followed the electrode’s mesh size. The Phase Shifter example is now converged with its mesh.

    • settings with wavelength= did not reach labeled profiles, so EME re-solved sections at their old wavelength, and sweep set a setting on the default profile only. A wavelength sweep of an EME chain now moves the labeled profiles the chain is built on, a sweep of another setting of such a chain warns naming the profiles that keep their own value, and EME warns when a chain’s profiles are at another wavelength than the default profile.

    • A taper’s equal slices could act as a grating and reflect in phase, which refinement could not see (a Y-branch splitter’s taper reflected 2.5e-2, against 2.05e-4 converged). A slice over which the cross-section changes is now cut under a fraction of half the guided wavelength (the section setting slice_phase_limit) where that reflection matters, and a long, slow taper keeps about schedule_max_slices slices.

    • A taper’s sidewall scattering loss dropped to zero on slices whose cross-section had already been solved, and changed with the slice count.

    • Pulsed propagation now applies sidewall scattering loss on sections with EME_settings(apply_scattering=True), each tracked mode at its own rate, and a smooth cross-section with apply_scattering=True no longer makes a pulsed EME raise.

    • Poling(inverted_first=True) never reached the pulsed solver.

    • A PulsedSource on a port other than the first was launched into the first, and a second PulsedProcess with different modes was ignored. Both now raise an error naming the argument.

    • The ‘Nonlinear-EME: Supercontinuum’ example under-resolved an avoided crossing between 800 and 1100 nm. Its pulse now follows the crossing.

    • The continuous-wave \(\chi^{(2)}\) solve’s fixed absolute tolerance swamped a weak generated field, so a 1 µW pump’s conversion read far too high. The tolerance now follows the fields (solver_atol=None, the new default), and strong pumps solve as before.

    • The \(\chi^{(2)}\) coupling cache ignored a shape’s scattering values, so two sections that differed only in roughness gave the same conversion.

    • A junction of a crystal turned in the plane was not reciprocal or passive with lossy modes (a TFLN ring’s bend junction amplified, largest singular value 2.40), an x-cut bend’s refinement did not finish with more than 4 modes, and an FEM-sliced x-cut bend appeared to lose 1.2 % of its power in pi/8.

    • mode_power and the junctions used different products at a slot-like port (up to 2 % apart), and a tilted facet’s trace dropped its staggered-grid fields. Both now use the product the junctions conserve.

    • A facet’s power budget weighted the reflection with the forward trace’s power instead of the backward trace’s.

    • A confined port window’s crop dropped a row or column of the parent’s cells, so an off-lattice window read up to 1.2e-3 low.

    • The interpolated wavelength sweep zeroed modes it could not track even at the center wavelength, held a lost mode’s junction entries (which could gain power), divided by zero on a chain with windowed ports (on lossy modes with no real index), and over-warned on resonators. Its S-matrices now carry mode_power.

    • SMatrix.unitarity_error was meaningless for plane-wave sections. It now measures power conservation in each mode’s own power frame.

    • On Linux, Windows and the Intel Mac, a re-solved port window whose cut wall lay in uniform cladding failed with a misleading “uniform refractive index profile” error. The solver shift is now moved off the eigenvalue and the matrix refactored.

    • FDM(scattering=True) on a multi-wavelength profile raised “profile has not been solved yet”. mesh and label_profile raised on an unsolved multi-wavelength profile.

    • settings(res=...) raised instead of setting both resolutions.

    • reset(profile=) acted on the default profile only, and clear_field_data(profile=) ignored its profile and raised on a file holding a multi-wavelength profile.

    • save(save_all_fields=True) saved no fields. A file saved with it now reopens with its fields, without solving.

    • shape rejected per-edge lists of roughness_rms and correlation_length.

    • In the plot window, choosing a profile other than the default from the menu left the data menu unchanged, so a solved profile’s field components were not offered.

    • Saving a plot from the toolbar as an unsupported file type failed with an internal error instead of showing a message.

    • An Outline panel of the layout view drew an empty colorbar.

    • material_explorer and plot_S_matrix changed later plot figures through matplotlib’s global settings.

    • plot dropped a port label when its window ended outside the panel, logged a warning about “port None” for a chain with a bend, and in the simulation view drew a bend’s outline curved, with the sections after it off the panel.

    • A license manager outage while a session was running could crash its license check-in or open the login window.

    • When no license was available, the client reported “EMode failed to launch”.

    • A run with EMODE_HEADLESS=1 opened the login window when EMode was not logged in, or after an upgrade, when the stored login was from the earlier version, and a solver started from a script could stop without a message. It now reports that EMode is not logged in and how to log in: run emode login, or set EMODE_EMAIL and EMODE_PASSWORD and run emode login --write.

  • Faster

    • Meshing is 6 to 22 times faster.

    • Junction overlaps are one matrix product over all the modes instead of one integral per pair of modes. At 200 modes they take 0.17 s instead of 14.5 s.

    • Sampling an FEM field at points is about 1000 times faster, so FEM field plots and junctions between FEM sections no longer stall. An FEM EME taper builds about 20 % faster.

    • EME decides each section’s fold from its geometry before solving, so a chain whose sections differ in their folds is solved once.

    • The pulsed Kerr, Raman and two-photon absorption terms need 4.5 to 6.6 times less memory, and the full Raman response is 3.2 times faster. The ‘Nonlinear-EME: Supercontinuum’ example’s EME takes 115 s instead of 300 s.

    • An x-cut bend’s refinement with 8 modes takes under a minute, where it did not finish in 10 minutes.

1.0.6

  • Changed

    • On Apple silicon, the sparse direct solver is rslab 1.1.2 (from 1.1.0).

    • The Intel Mac installer continues, although the 1.0.5 notes announced 1.0.5 as its last release.

    • Improved EME junctions between mode-solved cross-sections: they conserve power and converge as modes are added, especially at large mode numbers, and across the mode solvers and their meshes. Results can move slightly from 1.0.5.

    • A taper or GDS section now starts refinement from slices placed where its cross-section changes (EME_settings(initial_slices='auto'), the new default), read 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. Refinement still runs afterward and decides the final slicing, so results change only within its tolerance, and it takes fewer rounds. EME_settings(initial_slices='single') restores the one-slice start of earlier versions.

    • EME() and plot() use less memory.

  • New

    • plot(normalization='section') scales each section of a ‘z-x’ or ‘z-y’ panel (a straight, a taper, a free-space gap) by its own peak, so a section that carries little of the launched field, such as the free-space gap after an angled facet, is still visible. The ‘Norm’ selector in the plot window offers it too. Brightness is then not comparable across sections.

    • plot(view='layout'), the new default, draws the ‘z-x’ panel as the chip layout: every section where its connections place it, a bend as an arc and the sections after it turned with it, the field following the guide, both axes in um. view='simulation' is the unrolled display of earlier versions, and the ‘Physical space’ checkbox switches between them.

    • check_layout() reports overlapping sections, a section whose outline crosses itself, how far each loop of connected sections misses closing, and sections no connection reaches. EME() runs it first and refuses an overlap or a loop that misses by more than minimum_z_step before solving anything, and warns about a self-intersecting outline.

    • sweep(key='wavelength', values=..., result='S_matrix', method='interpolated') sweeps a circuit’s S-matrix over hundreds of wavelengths for the cost of a few: the circuit is refined once, each cross-section is solved at num_nodes wavelengths (default 8), and the S-matrix at every requested wavelength is rebuilt from propagation constants and junctions interpolated between them. sweep_data reports the node count and the interpolation residual at a held-out node, with a warning above interpolation_tolerance.

    • bend_section(name, profile, radius, sweep_angle, method) declares a circular bend (sweep_angle in radians, positive toward +x), solved with the bend in cylindrical coordinates (method='cylindrical', the default) or by transformation optics (method='TO'). Sections declared after it continue from its end. An anisotropic crystal turns with the guide: a bend in x-cut lithium niobate is sliced along its heading, and a section placed after a turn sees the crystal turned. A bend of an isotropic material is one mode solve. copy_section raises when a copy’s placement would show the crystal differently from its source, rather than reusing the wrong S-matrix.

    • Monitors: monitor(name, w, y, angle, length, resolution) declares a plane inside the chain (output only, not a port), monitor_field(name) returns the solved field on it for 1 W launched with the power through it, and monitor_coupling(name, mode_field_diameter, angle) its coupling to a tilted Gaussian beam such as a fiber mode, the figure a grating coupler is designed for. By reciprocity the same number is the coupling from the fiber into the waveguide. sweep(result='monitor_coupling', monitor=, beam=) sweeps the beam’s angle, size or position without solving again, or any other key with a solve at each value, and plot() draws each monitor’s plane on the ‘z-y’ panel. The website has a new example, ‘EME: Grating Coupler’.

    • A new website example, ‘Bend Mode Solvers’: the SiN bend of ‘Bending: SiN’ solved by the FDM with bend_method='cylindrical' and 'TO' on four grids and by the FEM on six fixed meshes, with each solve’s effective index, its error against the finest FEM mesh, its bend loss and its runtime, and plots of the effective-index and bend-loss errors against runtime.

    • The ‘Bending: SiN’ example uses the window and PMLs of ‘Bend Mode Solvers’: PMLs on the bend’s outer, top and bottom walls (1 µm, pml_kappa_max=1), 2 µm of cladding and 1.5 µm of trench, and a max_effective_index just above the guided mode.

    • A new website example, ‘EME: TFLN Ring Resonator’: a racetrack ring in x-cut thin-film lithium niobate, closed with connect_ports, its coupler’s bus and ring arms windowed ports, the through-port spectrum over several free spectral ranges from sweep(method='interpolated'), with the free spectral range, extinction and loaded quality factor of each resonance, and the field built up around the ring on resonance, drawn on the chip.

    • Pulsed nonlinear propagation through an EME chain (EMode3D). settings(excitation=PulsedSource(...)) declares a femtosecond pulse, straight_section(nonlinear=PulsedProcess(...)) or taper_section(...) a section’s nonlinearity and poling (Poling: a period, a period varying along the section, or explicit domain walls), and EME() refines the chain, solves each slice at a set of sample wavelengths and propagates the pulse through every slice and junction. get('pulse_response') returns the spectrum of every tracked mode along the chain, with band_energy() and band_power() for the energy and average power in a wavelength band. pulse_propagate(poling=...) reruns the propagation with another poling without solving any mode again. gds_section(nonlinear=PulsedProcess(...)) declares a pulse’s nonlinearity on a GDS section too. The website has a new example, ‘Nonlinear-EME: Supercontinuum’, and the EME guide a section on pulsed propagation. Each section’s chi(2), Kerr (chi(3)), Raman and two-photon absorption come from its materials: PulsedProcess(chi3=True) takes chi(3) from every material’s n2 and raises naming a material that has none, the Raman response is that of the material holding most of the Kerr overlap, and two-photon absorption (on by default where a material has a coefficient, tpa=False to turn it off) acts only on photon pairs above the bandgap. The run warns when the pulse will drift far enough to wrap around the time window, when energy reached the window’s edges, and when the band leaves a material’s index model, and it refuses a band where an index model has no finite value (silicon’s fit has a pole at 1135 nm).

    • Nonlinear material data for the pulsed solver: n2 for SiO2, SiN, LN and LN_MgO, the Raman response of fused silica (Blow and Wood) and lithium niobate (its four A1(TO) lines, Bache and Schiek), two-photon absorption coefficients for Si, AlGaAs, LN and LN_MgO, and bandgaps for those and for SiO2, SiN and GaAs, each with its reference in property_refs. A user material takes the same through MaterialProperties(n2=, raman=, tpa=, bandgap=), with raman=(fraction, [(weight, position, half_width), ...]) in cm^-1.

    • settings(bend_method='TO') solves a bend by transformation optics: the straight cross-section is solved with its material transformed by the bend’s conformal map, instead of adding the bend factor 1 + x / R to the solver (bend_method='cylindrical', the default). It works with the FDM and the FEM, in 2D and 3D sessions alike (the transformed tensor of an isotropic material is diagonal), and agrees with the cylindrical method to about 6e-4 in the effective index and 5 % in bend loss at R = 10 um. With a PML, set max_effective_index just above the guided mode: a TO bend’s spectrum starts with PML modes. report() names the method next to the solver.

  • Fixed

    • With EMODE_NONINTERACTIVE=1, a plot() could hang. It no longer shows a window in that mode.

    • On Apple silicon, a circuit with large free-space ports could run out of memory. The sparse solver now solves the port modes in small blocks.

    • Under a PML, a lossy absorber mode could come out as mode 0, ahead of the guided mode. Guided modes now come first, and remove_pml_modes_bool is more reliable.

    • In a lossy medium, some lossy modes grew along the guide instead of decaying, so an EME chain could amplify. Every mode of a passive structure now decays.

    • sweep(key='wavelength', solve_type='EME') never returned its data.

    • A function whose first argument is not key, such as monitor_field('plane'), failed when that argument was given positionally.

    • monitor_field() and monitor_coupling() failed on a chain built with copy_section, such as a grating.

    • A wavelength scan with the FEM solved each wavelength on a different mesh, which added about 1e-5 of noise to the effective index. Wavelength scans now share one mesh (the new setting fem_mesh_wavelength chooses its wavelength).

    • A mirrored section (copy_section(mirror=True)) and a bend toward -x were laid out in the wrong place.

    • The ‘z-x’ outline left out shapes given by a width rather than a mask.

    • A symmetry wall in x, including the one boundary_condition='TE' or 'TM' adds, folded a bend about x, which a bend does not allow. A bend now keeps only the vertical fold, and an explicit x wall is replaced by '0' with a warning.

    • plot() of a circuit with a loop (a ring closed with connect_ports) failed. It now draws the ring’s layout, cross-sections and field. EME() of a ring with no open port no longer fails.

    • Windowed ports (Port(window=...)): several windows on one side of a section now conserve power and are reciprocal, connected or not, each window keeps only the modes its content guides, and each is placed on its own window in the layout. Results with windowed ports move slightly.

    • A section after a bend could meet it off-center when anything upstream was offset. Sections now meet centered along any path.

    • A refined taper or GDS section with interface roughness and EME_settings(apply_scattering=True) raised an error.

  • Faster

    • Refinement bisects a slice at an already-solved cross-section near its midpoint when there is one (EME_settings(snap_window=0.25), a fraction of the slice, 0 to disable), so a taper or GDS section that passes through widths another one solved reuses those solves.

    • Refinement reuses a solved cross-section wherever the geometry repeats it: the mirror-image half of a taper declared as two sections, or a GDS cell that tapers up and back down. Interpolated and cut geometry is rounded to about 1e-6 nm, so the two halves no longer differ in the last bits and miss the cache (one slice in six re-solved on a mirrored taper).

1.0.5: October 2, 2026

  • New

    • On Apple silicon, EMode’s sparse direct solver is now rslab, a supernodal LU and complex-symmetric LDLT solver compiled into EMode. Mode solves return the same effective indices as with Intel MKL PARDISO. A mode solve with a PML is about 2.5 to 4.5 times faster (measured on an Apple silicon Mac against the previous release under Rosetta), and a solve without one takes the same time.

    • EMode runs natively on Apple silicon Macs, without Rosetta. There are now two Mac installers, EMode-<version>-Mac.pkg for Apple silicon and EMode-<version>-Mac-x86.pkg for Intel Macs. The Intel installer keeps MKL PARDISO. This is the last release with an Intel installer, and earlier versions stay available for download.

    • New settings options, per profile: solver_threads caps the number of threads the sparse solver uses (0, the default, lets it choose, up to every core), and sparse_solver picks the solver ('auto', 'rslab' or 'pardiso'). The Linux, Windows and Intel Mac installers keep MKL PARDISO as their default, since it is still faster than rslab on x86 processors, and include rslab as sparse_solver='rslab'. The EMODE_SOLVER_THREADS environment variable limits the thread count for every profile that does not set its own, and the 16-thread cap on the MKL thread count is gone.

    • The heat and electrostatic solves use the same solver.

    • Every installer except the Intel Mac one now meshes FEM cross-sections with the same version of the triangle mesher, so heat and electrostatic results no longer differ slightly between platforms. On Linux and Windows the mesh changes a little: the Phase Shifter example’s VpiL moves from 5.39 to 5.10 V·cm.

    • Full-vector finite-element mode solver, available with EMode3D. FEM solves a cross-section on a triangle mesh that follows every material interface, with second-order elements, and refines the mesh where the modes need it. Boundaries, PMLs, expansion regions, bends, lossy and anisotropic materials (including crystals tilted out of the cross-section with theta), and heater and electro-optic perturbations work as they do in the FDM, and FEM and FDM sections can meet at a junction. plot(mesh_display=True) draws the triangle mesh. See the new FEM: SOI example.

    • New solve runs either mode solver. solver_method='FDM' or 'FEM' is the same as calling FDM or FEM, and 'default' follows the new solver setting.

    • New settings option solver picks the mode solver for solve, EME and sweep. Its default, 'default', is the FDM in every session, so existing scripts solve as before. The FEM runs only when asked for, with solver='FEM', FEM or solve(solver_method='FEM'). Calling FDM or FEM sets solver to that method. The fem_* settings control the FEM mesh and its refinement.

    • report names the mode solver on its wavelength line, for example Wavelength: 1550.0 nm    Solver: FEM, and a saved CSV records it too.

    • Biaxial crystals. KTP and LBO are in the material database, each with three principal indices, its \(\chi^{(2)}\) tensor and, for KTP, its electro-optic tensor. A third orientation angle, psi (in MaterialSpec and add_material), turns a crystal about its own z axis before theta and phi, so a biaxial crystal can take any orientation. Omitted, psi is 0 and existing simulations are unchanged. See MaterialSpec for the convention.

    • New settings option include_scattering: with include_scattering=True, every solve of the profile (by FDM, FEM, solve, EME or sweep) also computes the interfacial scattering loss of each rough shape. FDM(scattering=True) still does this for a single solve.

    • nonlinear=False on straight_section or taper_section removes a declared \(\chi^{(2)}\) process, so the section propagates linearly again.

  • Fixed

    • Material loss now attenuates. A lossy material (loss= in dB/m, or a database material with an extinction coefficient) amplified the mode in EME: a 200 µm lossy strip transmitted 110% of its power. Lossy modes now have an effective index with a negative imaginary part, and EME attenuates them by exactly the reported loss. refractive_index, permittivity and plots still show n + jk. Profiles solved by an earlier release keep their old sign until they are solved again.

    • Symmetric boundaries unfold correctly. A mode solved with an ‘S’ or ‘A’ boundary was unfolded onto the full window with the same sign for every field component, but some components are odd under the mirror. Junctions between a folded and an unfolded section, the \(\chi^{(2)}\) coupling of folded solves, plane-wave junctions and plots of unfolded fields were all affected. Each component now takes its physical mirror parity.

    • A windowed port on a symmetry-folded profile couples correctly. A port with window= on a profile solved with an ‘S’ or ‘A’ boundary left the mirrored half of its window at zero, so its junction was wrong wherever the window crossed the symmetry plane. Two ports folded the same way at different transverse offsets had the same problem.

    • The \(\chi^{(2)}\) coupling of symmetry-folded solves (SHG, SFG, DFG, SPDC) used the pump’s symmetry for a signal folded differently from the pump. It now matches a full-window solve.

    • chi2_spectrum left the profiles it re-solved with no scattering data, so get('scattering_loss') and report read zero after a scan. The re-solved profiles now keep their per-mode scattering loss.

    • An FDM solve asked for more modes than a structure guides returns its lossy modes. Past the guided and radiation modes, the solve returned modes with an effective index near 0, and at higher mode counts it failed with “array must not contain infs or NaNs”. The next modes are now the lossy ones, with an imaginary effective index, ordered from least to most decay. Guided modes are unchanged.

    • The FDM solve returns the same modes on every run. Degenerate modes used to come back as a different mixture each time.

    • FDM ‘0’ walls are metal on all four sides. The bottom wall was a magnetic wall half a cell outside the window, and field_to_solve='Ht' returned modes the 'Et' solve did not. Both now return a metal waveguide’s modes. Modes kept away from the walls by a cladding or a PML are unchanged. Modes that reach a wall move: the EME: Taper example’s printed indices change by up to 3e-4.

    • A taper whose etch mask is offset from the waveguide center re-centered the mask on every interpolated slice, so the geometry jumped to the center mid-taper. A misspelled shape keyword now raises an error instead of being ignored.

    • A shape stacked on a layer that came out empty, such as a fully etched layer, was placed half that layer’s height too high. It now sits directly on top of it.

    • FDM(section=..., propagation_distance=...) failed on a straight or bend section, or any section whose cross-section does not change along its length.

    • group_index crashed on an FDM profile.

    • A junction between two differently gridded sections (a confined port, a PML against a plain window, a different resolution or offset) resampled both modes onto a common grid smaller than either and shifted by half a cell. A confined port’s S-matrix was off by up to 6e-2. The common grid now covers both sections exactly.

    • get reads back every setting settings accepts, including older aliases such as radius. Before, only wavelength, background_material and BC worked. inspect lists every name get can read and takes profile=.

    • overlap stores its result on the profile named by profile_b instead of always on the default profile, and no longer fails when the default profile has several wavelengths. The new get('overlaps') keeps one record per pair of modes, so overlaps no longer overwrite each other.

    • label_profile works on a multi-wavelength profile.

    • FDM(verbose=False) still printed its progress. It is now quiet.

    • The mode_filter and fLanczos arguments of FDM had no effect. They now reach the solver, and a bad value raises an error.

    • The eme_scattering option of settings never had an effect. It is deprecated: it is still accepted, with a warning that it does nothing. Use EME_settings(apply_scattering=True) to apply scattering loss in EME.

    • The permeability and fill_permeability arguments of shape raised an unclear error. The error now explains that a shape’s permeability comes from its material, as in material=MaterialProperties(n=1.5, mu=2.0).

    • A simulation name that is not a valid file name on every platform, such as 'run:1', is rejected by init and open with an error that names the problem, instead of producing a simulation file that cannot be written on Windows.

    • FDM failed on a multi-wavelength profile, from a wavelength list in settings or from create_profile_set. Each wavelength is now solved at its own wavelength and get returns the results keyed by wavelength. EME on a wavelength list in settings failed too.

    • On a multi-wavelength profile, the results of get, effective_area, group_index, orthogonality, scattering, confinement and report could not be indexed by wavelength: result[1550.0] raised a KeyError. They are now a read-only dict keyed by wavelength, in which result[1550] and result[1550.0] are the same entry. confinement returns its dictionary of confinement factors per shape directly, as documented, instead of inside a one-element list, so a script that used confinement()[0] should drop the [0].

    • A material given by its permittivity, MaterialSpec(material=MaterialProperties(eps=...)), ignored its theta and phi, while its d tensor was rotated by them. Its permittivity now turns with it.

    • shape with a single value for roughness_rms or correlation_length raised an error, although the documentation says a single value applies to all interfaces. It now does, and a list of any length other than two raises an error that names the argument.

    • A lossy anisotropic material rotated off its principal axes (loss with theta, phi or psi) had its loss applied to every element of its index tensor, which made the loss depend on polarization. The loss is now applied to the diagonal only, as for the unrotated material.

    • heat followed by a mode solve failed with an IndexError when a rotated anisotropic material had a nonzero dn_dT. The heated index of such a material is now its index tensor shifted by dn_dT times the temperature change.

    • refractive_index with an array of wavelengths failed for a rotated anisotropic material, and for exactly three wavelengths returned a wrong result. It now returns one 3x3 tensor per wavelength.

1.0.4: September 24, 2026

  • New

    • Second-order nonlinear EME, available with EMode3D. A straight_section or taper_section takes nonlinear= with an SHGProcess, SFGProcess, or DFGProcess naming the process wavelengths. Quasi-phase matching is declared with poling_period (plus duty_cycle and qpm_order) on the process, or built by hand from alternating inverted=True sections, the two routes agree.

    • A CW pump is declared with settings(excitation=Source(port, wavelength, mode, power, phase)). EME still returns the small-signal S-matrix, and now also solves the power-dependent response, available as a CircuitResponse from get('response'), outgoing and incoming amplitudes at every port, radiated power per wavelength, and convergence. get('sources') returns the declared excitation.

    • chi2_spectrum returns a nonlinear conversion spectrum (signal out over pump in, at every wavelength asked for) from a handful of mode solves. A \(\chi^{(2)}\) spectrum oscillates too fast with the phase mismatch to interpolate, but what the coupled-mode equations consume (the coupling and the propagation constants) varies smoothly, so the modes are solved only where the mode list is still moving and the response is then evaluated point by point 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-matched sections are included: the grating enters as a wavevector shift and a Fourier coefficient, neither of which depends on wavelength, so a poled section keeps its exact grating while its phase mismatch moves with the interpolated propagation constants.

    • sweep(key='wavelength', values=..., adaptive=True) does the same for an ordinary sweep. values keeps its meaning (the wavelengths you want results at) and adaptive changes only where the solver runs: modes are matched between neighboring solves through a phase-corrected overlap, and a bisection in wavelength adds solves until the mode list stops moving by more than tolerance. Any stretch it could not resolve is reported in unconverged instead of raising.

    • Free-space excitation: free_space_field declares a Gaussian beam launched into a port. The beam is projected onto that port’s modes and drives the field plot through plot(excitation=name) or the plot window’s Mode menu. free_space_field_diagnostics returns the fraction of the beam’s power the port’s modes capture, and the solver warns when it is below 90%. tilt is the angle inside the medium the beam travels through.

    • Angled facet ports: make_angled_facet_port() tilts a port’s cross-section about a hinge line, so a facet’s reflection back into the guided mode falls off with tilt as it does physically (see Ports). A facet cannot sit on a pwd_section or on a profile with a PML. sweep(key='section, NAME, facet_angle') sweeps that tilt.

    • The solver warns at a facet junction when the waveguide’s num_modes is too small to hold the deflected reflection and R + T under-runs.

    • Junctions whose two sides carry different numbers of modes (every junction between a waveguide section and a pwd_section) now solve the mode-matching equations for the reflected and transmitted amplitudes jointly instead of eliminating the reflected ones first. The two closures coincide when the counts match, so taper and GDS staircases are unchanged. At a waveguide-to-free-space junction the reflection into the fundamental moves by 10-15% (down), the answer no longer depends on the order the two sections were declared, and declaring an angled facet at zero tilt no longer changes it.

    • Per-junction settings: a connect_ports entry accepts a third element, JunctionOverride(junction_normalization=...), applying to that junction alone.

    • Field plots: the color scale is normalized to the excitation plane’s field family, with a ‘Norm’ selector (vector or component), the log scale spans -60 dB to 0, an ‘abs sum’ cross-axis reduction.

    • plot(profile=...) renders a stored profile on a simulation that has EME sections declared. Previously the plotter always followed the sections.

    • get_fields with no key returns all six E and H components.

    • material_explorer with file_name= saves the figure without opening a window, like plot.

    • Setting EMODE_NONINTERACTIVE=1 makes every plot build its figure and return instead of blocking on the window. EMODE_EMAIL and EMODE_PASSWORD log in without the login window, for unattended runs.

    • EMode prints a notice when it checks out a license and a newer release is available. It turns itself on once a release’s installers are downloadable.

    • The default MKL thread count is capped at 16 (an explicit MKL_NUM_THREADS overrides this setting).

    • Second-order nonlinear coefficients (d) added for GaP, AlN, and InGaP, and for the new materials GaN, LiTaO3, and ZnSe, each with a citation, the BBO, GaAs, AlGaAs, LN, and LN_MgO entries are corrected and cited. d_matrix with reference=True returns the citation. get('<material>') now includes property_refs, the source of every non-optical and nonlinear value.

    • Electro-optic (Pockels) coefficients r for LN, LN_MgO, GaAs, InP, AlN, and GaN. electrostatic derives the DC field from the solved potential, and FDM then solves the biased waveguide through the r tensor (include_static), so a phase shifter’s VpiL comes straight out of report.

    • Thermal and electrical properties (thermal_conductivity, dn_dT, eps_r_dc, and resistivity for metals) filled in for every database material, plus the heater and electrode metals Au, Al, Cu, Ti, W, Cr, TiN, and NiCr with their optical constants at 1550 nm. The Material Database page tabulates every material’s non-optical properties, \(\chi^{(2)}\) and electro-optic coefficients, and their sources.

    • report and plot mark modes that live in the PML: the report gains a PML column and the plot’s mode menu reads 3, TE, PML. The designation is available as PML_indices and window_confinement.

    • New setting pml_seed_bool: a PML simulation is first solved without its PML, and the eigensolver is then seeded with that fundamental mode, so the guided mode lands at index 0 without a hand-tuned max_effective_index.

    • New website examples: Nonlinear-EME: SHG (GaAs-on-insulator second-harmonic generation, straight and tapered, with both devices’ conversion-efficiency spectra), Nonlinear-EME: PPLN (a periodically poled thin-film lithium niobate waveguide doubling a telecom TE pump, with propagation loss from sidewall and surface roughness), EME: Angled Facet (a lensed-fiber Gaussian into an angled-facet taper, swept over facet tilt), Heater (a Pt heater above an SOI waveguide, with the power for a \(\pi\) phase shift), and Phase Shifter (Au electrodes beside an x-cut LNOI waveguide, with VpiL).

    • The Variable Descriptions page now explains how the simulation file is organized (top-level keys vs. the profiles dictionary, default and named profiles, multi-wavelength profile sets) and groups variables by where they live. inspect is documented.

    • New website examples: Adaptive Scan (sweep(adaptive=True), a fine wavelength spectrum from a sparse set of solves) and Nonlinear-EME: DFG, difference-frequency generation in periodically poled lithium niobate, with two simultaneous Source excitations and a three-wavelength profile set.

    • A sweep or a chi2_spectrum no longer prints the mesh and eigensolve of every wavelength it solves. One line for the scan replaces one pair per solve.

    • A \(\chi^{(2)}\) section warns when its mode basis still contains PML modes: their large imaginary propagation constants make the coupled-mode solve stiff, and the warning names remove_pml_modes_bool, which drops them.

    • get('response').power_vs_z(wavelength) returns the forward power against position along a \(\chi^{(2)}\) section, from one solve. The coupled-amplitude ODE already integrates along z and used to discard the result, so a pump or second-harmonic power curve cost one full EME per length. The forward amplitudes are now recorded after the driven solve converges and returned with the response, stitched across the slices of a refined taper so the curve reads as one. amplitude_vs_z gives the per-mode complex amplitudes behind it. Sampling is controlled by z_samples on the process (default 100 points per slice, 0 to switch it off).

    • plot with plane='z-x' on a \(\chi^{(2)}\) section draws the driven field. It uses the declared excitation rather than a unit mode, since conversion depends on pump power, and renders each slice from the amplitudes recorded along it, so the generated wavelength builds along the guide instead of stepping at slice boundaries. wavelength= picks which wavelength to show. Without a declared excitation the field shown is the small-signal response and the solver says so.

    • The coupled-amplitude ODE’s default tolerances are solver_rtol=1e-6 and solver_atol=1e-9, with RK45 as the default method. Pass solver_rtol, solver_atol or solver_method on the process to override any of them. The explicit Runge-Kutta methods (RK45, RK23, DOP853) are the usable choices. The coupled amplitudes are complex, and SciPy’s implicit solvers (LSODA, Radau, BDF) reject a complex initial condition.

  • Fixed

    • scattering corrupted the profile it had just reported on, so every later get on that simulation failed. The same path also wrote into a material object returned by get('<material>').

    • get('scattering_loss') (and sweep(result='scattering_loss')) returned the per-shape metadata dictionary instead of the loss, and raised an error when more than one shape carried roughness.

    • In an EMode2D session every internal error was reported as “only available with EMode3D”. An unexpected internal error now names only the function that was called.

    • A saved simulation could not always be reopened: port references came back as plain tuples, section declarations reloaded as dictionaries, and some stored types were unknown to the reader unless that session happened to have imported them first.

    • sweep(result=['mode_order']) raised an error, and an unsupported section key failed an assertion instead of naming the key.

    • A saved simulation reopened only if the reading session happened to have already imported the module defining each stored type. Otherwise it failed with a bare KeyError naming an internal class. Every tagged type is now imported before the file is read, and an unknown tag raises a proper error.

    • A pwd_section with an offset window is drawn where its field is.

    • The z panel of a field plot is normalized to the excitation plane, not the global maximum.

    • A forbidden section name raised an opaque “unexpected exception” instead of the intended error, and a bad value in EME_settings or a section’s settings= did the same. create_profile_set raised an error on first use.

    • reset accepted any kind and silently did nothing for a misspelled one. It now raises an error. reset('shapes') also clears the stale mesh and mode data, so the next FDM solves the new geometry instead of the old one.

    • import_database failed on a citation containing =, rejected integer wavelength_range entries, and defaulted the wavelength range to 200-2000 um instead of 0.2-2.0 um.

    • get(key, profile=...) returned the default profile’s value for every per-profile variable regardless of profile. A multi-wavelength profile now returns a dict keyed by wavelength.

    • refractive_index on an unknown material raised a raw error. permeability returned a nested list for database materials and crashed on numeric input. create_profile_set split a bare string into characters and rejected its own documented default set name. get_shape crashed before any scattering/confinement call.

    • report returned a dictionary that paired column headers with mode rows and ignored its profile argument. It now returns one list per column for the requested profile.

    • The emode command line no longer prints a redundant “Opening the EMode Login window…” message.

    • An exception inside sweep left its output-suppression flag set, silencing every later operation in that session.

    • scattering (and any FDM(scattering=True)) failed with “‘FDM’ failed with an unexpected internal error” on a profile solved with remove_pml_modes_bool. Dropping the absorber’s modes leaves the basis shorter than num_modes asked for, but the per-mode loop still ran over the requested count and indexed past the end of the field arrays. A mode explicitly requested beyond the solved basis now raises an error naming mode_list.

1.0.3: August 18, 2026

  • New

    • Added the setting pml_thickness, which specifies the PML by physical thickness in nm instead of by a layer count. It is [north, south, east, west] like every other four-sided setting, and 0 on a side falls back to num_pml_layers there, so the two can be mixed per side. A layer count measures grid cells, so the absorber shrinks as the mesh is refined. pml_thickness holds it fixed, which is what a resolution convergence sweep needs. See settings.

  • Fixed

    • A simulation with a PML could not be reopened: open failed before any plot or solve ran, and the .eph file was already wrong when it was written.

    • A boundary condition that pins the field at the wall left phantom modes in the mode list: one grid column wide, with exactly zero loss, sorted in among the physical modes. They are removed from the eigenvalue problem now, and genuine modes appear in the slots they were occupying.

    • The PML now delivers the thickness its profile specifies. The grid gave the absorber smoothed cell widths, so a default 10-layer PML measured 492.8 nm against a design value of 500.0 nm, and the outermost (most absorbing) layer absorbed 14% less than intended. Reported radiation loss shifts by a few percent in simulations with a PML.

    • The cached-solution key discarded the PML coordinate stretch, so two profiles differing only in their PML could share a junction S-matrix.

    • Lowering num_modes with settings after a solve produced a mode list that EME could not assemble.

    • expansion_size validation checked every direction against the north expansion instead of its own, so a valid single-direction expansion could be rejected and a bad resolution could escape the check.

    • FDM called without a profile argument no longer also writes a junk profile named ‘None’ alongside the default one.

    • A junction between a section with a symmetric or antisymmetric boundary condition and either a pwd_section or a differently reduced neighbor now integrates every overlap over the same domain.

    • A windowed port lost its normalization when its basis passed through a transform, giving a 13% error in the self-overlap on a symmetry-folded port.

    • A port with a composed basis_map raised an error instead of producing a cache key.

    • Overlap products are recomputed after the field data changes instead of being served from a stale cache.

1.0.2: August 5, 2026

  • New

    • Free space sections using plane-wave decomposition, declared with pwd_section. Junctions with neighboring waveguide sections are computed analytically instead of by a mode solve.

    • Generalized port definitions and connections, including branching: a ports argument on every section function, connect_ports for wiring named ports explicitly, and the Port and Bounds classes in emodeconnection (see Ports). A connection also positions its two sections relative to each other, replacing the now legacy per-section offset.

    • A port can expose a sub-window of a wider cross-section, and when it is not connected to anything its mode is solved on that window alone, isolating a single waveguide of a multi-waveguide cross-section instead of the coupled supermode.

    • The PML profile parameters are now user-tunable in settings: pml_order, pml_reflection, pml_kappa_max, and pml_alpha_max, with defaults unchanged.

    • The port selector in the field plot lists the simulation’s port names.

  • Fixed

    • remove_pml_modes_bool had no effect at all. It now removes the artificial modes of the PML regions from the mode list.

    • The innermost PML layer was transparent, so a PML of N layers had only N-1 absorbing layers, and the PML parameters were missing from the cached-solution key, so two profiles differing only in their PML could share a solution.

    • Shape geometry is now resolved in absolute coordinates, independent of the simulation window.

    • scattering() raised an error when shapes or mode_list was given as a list.

    • In emodeconnection, a single-element list argument was collapsed to the bare element before being sent to EMode, affecting every list-valued argument.

    • In EME plots, section outlines disagreed with the field for every section after the first in a chain with transverse offsets.

    • plot() crashed when previewing a profile or taper section after FDM(), when given a slice_position, and for a slice containing only background material.

    • The emode command line no longer requires Tk/Tcl for --help, --version, and login.

1.0.1: July 18, 2026

  • New

    • Added a section outline view in EME plots, available before running the EME solve.

    • EME slice view uses ‘sum’ rather than ‘mean’ by default, and ‘mean’ is still an option.

    • Added functionality to clear sessions from the EModeLogin GUI.

    • Added a CLI replica of the EModeLogin GUI, accessed by running emode login from the command line.

    • Added an alternative basic interface for entering credentials with emode login --write.

    • The clear parameter of EMode() accepts two new options: ‘mine’ and ‘others’, for clearing sessions by user.

  • Fixed

    • Offset section display x-y view.

    • Settings issues for plots, including window and mesh display for EME.

    • In emodeconnection, the save parameter no longer shadows the save() function in EMode.

    • effective_area(), group_index(), orthogonality(), scattering(), and confinement() now return the value directly for single-wavelength profiles. A dict keyed by wavelength is returned only for multi-wavelength profiles.

    • get_fields() and get_grid() return FieldSet/GridSet objects for multi-wavelength profiles, preserving numeric wavelength keys.

1.0.0: July 8, 2026

  • Major internal architectural changes to enable the pending release of nonlinear EME functions and complex circuit solutions.

  • EME API changed such that each section type is defined with a unique function.

  • Improved PMLs for stability, especially with radiation modes.

  • Fixed a bug with the command line installer for Windows. Now emode is added to path automatically.

  • Plot now shows bidirectional field data by default, controlled by the setting “Forward only”.

  • Plot now blends fields with interpolation by default, controlled by the setting “Blend slices”.

0.2.6: April 27, 2026

  • Fixed sweep() function to deliver mode_order and scattering_loss in results.

  • Added mode_order() as a user accesible function.

  • Updated FDM(), orthogonality(), and overlap() to use profile instead of label nomenclature.

0.2.5: March 25, 2026

  • Updated license manager connection to use a standard REST API (https).

  • Users now have the ability to clear existing EMode sessions in case a simulation crash locks up a license. By default, stale licenses will be cleared every 9 minutes (24 hours with roaming enabled). Users can clear non-roaming stale sessions. See the Python Interface page for details.

0.2.4: February 10, 2026

  • Boundary condition implementations have been updated. This may impact (anti-)symmetric boundary mode lists.

  • Improved EME junction algorithm for robustness.

  • Implemented speed improvements, leading to a 60% speedup in our taper example.

  • Added an input to the settings() function called frequency as an alternative way to set the simulation wavelength. Internally, the wavelength is calculated from the frequency parameter as wavelength = c/frequency.

  • Improved error handling when keywords are used incorrectly.

  • Fixed a bug with scattering loss for films smaller than the mesh size.

  • Added functionality to automatically clear all mode data at each call to mesh() and FDM().

  • Added support for layers with zero thickness.

  • Fixed a bug with conformal layers that extend outside the simulation window.

  • Improved PML parameters for better accuracy and stability.

0.2.3: October 24, 2025

  • The effective_area() function now returns the effective area values. The effective area is in units of um^2.

  • The shape vertices are now available to the user in the form of Well-Known Binary (WKB). The Shapely package in Python is required to decode this representation of the vertices into a Shapely geometry.

  • Shape names are now automatically generated if not specified.

  • In the sweep() function, scattering losses are now available in the ‘result’ parameter. Use ‘scattering_loss’ to get a sum of all the shapes, or specify any of the scattering loss metadata keys to get them for each shape.

  • Mode tracking has been added to the sweep() function. Specify ‘mode_order’ in the ‘results’ parameter to get a list of the mode orders relative to the first solution.

  • The Field dimensions are reduced when only one field is requested, consistent with EMode prior to v0.2.2.

0.2.2: October 14, 2025

  • Hotfix on the previous version to remedy a few bugs.

  • Using the get() function to extract ‘x’ and ‘y’ now returns the full-window variables.

  • Using the get() function to extract a single field variable returns all modes for that field.

  • Added get_fields() and get_grid() functions to extract Field and Grid class information (see documentation updates) from a simulation (using get() for this is now deprecated).

  • Fixed a bug when shape() is called after meshing.

0.2.1: October 8, 2025

  • Updated the general anisotropic solver to support bend waveguides and all types of settings like symmetric boundaries, expansion, and PMLs, available with EMode3D.

  • Added an input parameter to FDM() called mode_filter. By default, this is set to ‘forward’, but it can also be set to ‘backward’ or ‘none’. This will filter the mode list to select only forward or backward propagating modes (or don’t filter) for the anisotropic mode solver.

  • Improved EME stability and accuracy.

  • Added a new parameter to settings called eme_scattering, which specifies whether or not to automatically calculate all scattering losses within the EME calculation, defaults to False.

  • Fixed a bug in the MATLAB connection.

  • To use MATLAB on macOS, users must install .NET (https://dotnet.microsoft.com/en-us/download/dotnet/9.0). Installation instructions have been updated to include this detail.

  • Added support for directly saving EME plots with plot() and plot_S_matrix().

  • Fixed bug with conformal layers defined over multi-masked layers.

  • Fixed bug with the reset() function for resetting shapes.

  • Removed input parameter label from sweep() because it has not been implement yet.

  • Updated the confinement() function, which now includes the background shape and returns a dictionary of the confinement factors for each mode.

  • Updated orthogonality() so that it returns the orthogonality value.

  • For taper and gds section types, the default minimum_z_step is now ‘auto’, which uses 1/8th of the wavelength.

  • Default overlap_variation is now 0.02.

  • Added option lossless to taper and gds section types. Defaults to False.

  • Added the following parameters to the add_material() function: thermal_conductivity, resistivity, dn_dT, eps_r_dc, phi, and theta. See the documentation for details.

  • Added the following parametrs to the shape() function: fem_resolution, current, voltage, and heat_only. See the documentation for details.

  • Added a parameter taper_algorithm for the taper and gds section types. This is intended to be used for slowly varying structures that are known to be adiabatic.

0.2.0: June 27, 2025

  • Added an explicit restriction on the tolerance parameter so that it cannot be set to be smaller than the machine precision. The default for tolerance is 1e-12.

  • Removed a bug in EME for symmetric section simulations.

  • Optimized and improved the underlying EME algorithm, improving simulation accuracy.

  • Added beta support for macOS and Linux.

  • Improved the subpixel averaging algorithm.

  • Added general anisotropic solver for straight waveguides, available with EMode3D.

  • The shape() function input refractive_index has been changed to material. Similarly, the fill_refractive_index has been changed to fill_material.

  • All scattering loss results are now stored under the meta_data dictionary of the shape.

  • Added a progress bar for meshing and solving.

  • Added the ability to return material properties from database materials using get("material_name").

  • Added two special types available to the user for defining materials and material properties: MaterialSpec and MaterialProperties.

  • The crystal orientation of a material can now be rotated relative to the simulation window axis by defining a MaterialSpec and specifying rotation parameters theta and phi.

  • Changes to biaxial materials: extraordinary index is always aligned with the z-axis (ordinary is aligned with the x and y axes). Rotations can be performed to orient a crystal to the simulation axes as desired. Affected materials: BBO, LN_z –> LN, LN_MgO_z –> LN_MgO, TiO2_rutile.

  • When EMode encounters errors, emodeconnection.py (1.0.11) will now raise an exception instead of just printing an error message.

0.1.2: March 16, 2025

  • Fixed a bug where num_modes was reset after modes are automatically removed from the pml regions.

  • Fixed a bug with the label() function when using the fill_refractive_index parameter.

  • Fixed a bug in the plot() function when fill_refractive_index was set on conformal layers.

  • Fixed a bug with EModeLogin when displaying error messages.

  • Updated the EModeLogin GUI layout.

  • For both the Python and the MATLAB interfaces, a static function called EModeLogin() was added, which replaces the previous EModeLogin executable for running the EMode Login GUI. New instructions are added to the website for running the EMode Login GUI.

0.1.1: May 7, 2024

  • Fixed a bug with the MATLAB connection.

  • Fixed a bug with the shape() argument position.

  • Fixed a bug with the scattering() calculate that was overestimating the scattering loss.

0.1.0: April 24, 2024

  • The EME module has been added, including section(), EME(), and plot_S_matrix(). These are only available with an active EMode3D license.

  • EME solver is limited to only use field_to_solve of ‘Et’.

  • EME length sweeps are solved in the reverse order.

  • Changed the parameter length_nm to length for all EME sections and functions.

  • EME sections automatically re-solved after a shape change and label reset.

  • Materials added to the material database: InGaAs, InGaP, InP, InSb, PMMA, SU8, TiO2_rutile, HfO2, H2O, GGG, GaSb, GaP, BBO, and InSb.

  • Material explorer: fixed bug saving certain filetypes, fixed bug with logo placement in saved files, and added gridlines.

  • Updated material_explorer() plot display and saving resolution.

  • The add_material() function now accepts citation and wavelength_range as an inputs.

  • Updated add_material() so that is accepts a list of three equations for defining anisotropic materials with dispersion.

  • New function import_database() added, which supports the import of a custom database text file.

  • The west boundary condition has been re-defined to reflect the Ex (a)symmetry, so it matches the south boundary condition.

  • The default y-position has been changed from -1 to ‘auto’.

  • In the plot() function, scaling is added to the cursor data-value for field plotting.

  • Fixed a plot error following a call to group_index().

  • Fixed a bug when iteratively updating a shape setting.

  • MATLAB connection now outputs text to the MATLAB command window.

  • Updated the scattering loss tool to make the calculation more consistent.

  • Fixed bugs with saving and opening EMode simulation files with MATLAB.

  • Added the new parameter propagation_resolution to the settings() function.

0.0.7-b5: December 12, 2023

  • Fixed bugs with conformal layers.

  • Added arbitrary number of masks for each shape.

  • Added input parameters to shape: mask_offset and tone. Either mask, mask_offset, or both can be a list. The length of each parameter will be matched by padding with the last value. Settings from tone and etch are applied to all mask/mask_offset values.

  • Added etch functionality to conformal layers.

  • Fixed the bug the made incorrect mode list ordering.

  • Added the function orthogonality to test the othogonality of the mode list. This is helpful to determine if the mode list is sufficiently accurate.

  • Added the input parameter label to get(), so users can extract data from labeled datasets.

  • The label() function now saves all settings and field data under in a Python dictionary or a MATLAB structure array called dataset_LABEL.

  • Changed the default operation in confinement() to only include areas of a shape that define the refractive index, not including areas that are overlapped by other shapes with higher priority.

  • Added a boolean option ignore_priority to confinement() that toggles ignoring overlapping shapes with higher priority.

  • Added user-accessible variables shape_materials, priority_indices, and shape_vertices.

  • Added support for iPython.

  • Added functionality to use an existing material name to define a custom material. This will override the EMode database material during the session.

  • FDM() now takes the input parameter label to automatically save the simulation result as a labaled dataset.

  • Updated group_index() so it will run without running FDM() first.

  • Added ‘min’ as an option for slice_op parameter in scattering().

  • Shapes and associated properties are now saved as dictionaries. This also includes the scattering loss results.

  • Interfacial roughness and correlation length values are now specified in the shape parameters: correlation_length (alias Lc_nm) and roughness_rms (alias st_dev_nm).

  • Added the new function reset() to clear all shapes, reset settings, or both.

  • Shape argument position now accepts a single float to specify only the x-position and automatically place the y-position of a planar layer on top of the previous layer.

  • Added boundary conditions options ‘TE-h’, ‘TE-v’, ‘TM-h’, and ‘TM-v’ to specify only horizontal or vertical symmetry for TE or TM modes.

  • Fixed bugs with MATLAB connection: default file_type is now ‘.mat’ for close(), and ‘.eph’ file is preserved after save().

0.0.6-b4: March 13, 2023

  • Added an option to remove the legend from plot.

  • Now planar layers do not add fill material by default, only if fill_refractive_index is defined.

  • Updated meshing algorithm to fix bugs at shape interfaces.

  • plot now shows positive and negative field values on the colorbar.

  • Fixed error when setting wavelength units to ‘um’ in add_material.

  • Added options to improve sidewall scattering accuracy. See details in the documentation for the scattering function.

0.0.5-b3: February 3, 2023

  • Scattering loss now works with angled sidewalls.

  • For scattering, the default for mode is ‘all’.

  • Added imaginary index capability to add_material.

  • Bend mode solver updated so field patterns and loss are now more accurate.

  • Switched from uniaxial pml to stretched-coordinate pml.

  • User-defined materials now override EMode database materials.

  • plot now shows shapes with the same refractive index in a different color.

  • Added capability to solve for the magnetic field instead of the electric field. See the field_to_solve parameter in settings.

  • Removed the field_calc option from FDM. Now the fields will always be available, but they will not be stored in memory. This reduces the memory usage by about 7x.

  • Saved .eph files do not contain the raw field data by default. This can be controlled with the save_all_fields parameter in save and close.

  • material_explorer now shows user added materials.