<a id="python-interface"></a>

# Python Interface

To control EMode directly from a Python script, we supply a Python package called `emodeconnection`. This has been tested with Python 3.10 or higher, including the Anaconda distribution, iPython terminals, and numerous IDEs.

The source code can be viewed here: [emodeconnection](https://github.com/emode-photonix/emodeconnection)

For more information about Python, see these links:

* [Download Python](https://www.python.org/downloads/)

* [Python Documentation](https://docs.python.org/3/tutorial/)

* [Python Tutorial](https://www.pythontutorial.net/)

<a id="setuppython"></a>

## Setup

Install the `emodeconnection` Python package from the command line.

```default
pip install emodeconnection
```

Update to the latest version with:

```default
pip install -U emodeconnection
```

Alternatively, the `uv` package manager may be used in place of `pip`.

<a id="emode-install"></a>

## Install EMode

The `emode-install` command-line tool is included with the `emodeconnection`
Python package. It handles downloading, installing, and updating EMode.
After installing `emodeconnection` via pip, you can run the installer.
You will be prompted to accept the
[EMode EULA](https://emodephotonix.com/legal/eula.pdf) and log in
with your EMode account credentials. For additional complete options,
run `emode-install --help`.

```bash
emode-install
```

Additional options:

```bash
emode-install                            # Install latest version
emode-install --help                     # Show all options
emode-install --list-versions            # Show available versions
emode-install --update                   # Update to the latest version
emode-install --update --version 1.0.5   # Switch to a specific version
emode-install --version 1.0.5            # Install a specific version
emode-install --install-dir /opt/bin     # Custom install directory
emode-install --sha256 <hex>             # Refuse to install unless the download matches
emode-install --uninstall                # Remove EMode
emode-install --uninstall --install-dir /opt/bin  # Remove from custom location
```

When you update EMode, update `emodeconnection` too, with `pip install -U emodeconnection`, since each EMode release expects the `emodeconnection` released with it. EMode warns at the start of a session when the installed `emodeconnection` is older.

`--sha256` takes the SHA-256 digest the downloaded file must have, as 64 hexadecimal characters. `emode-install` checks the download against it before installing, and on a mismatch it prints the expected and actual digests, installs nothing and exits with status 1. A value that is not a SHA-256 digest exits with status 2. Without `--sha256`, the download is checked against the digest the server publishes for the file, when it publishes one.

For automated or headless installs, set these environment variables
to skip interactive prompts:

| Variable              | Description                                     |
|-----------------------|-------------------------------------------------|
| EMODE_EULA_ACCEPTED=1 | Accept the EULA non-interactively (CI/headless) |
| EMODE_EMAIL           | Pre-fill login email                            |
| EMODE_PASSWORD        | Pre-fill password (use with caution)            |
| EMODE_INTERACTIVE=1   | Use interactive installer GUI                   |
| UNINSTALL_EMODE=y     | Accept the uninstallation non-interactively     |

<a id="pythonemodelogin"></a>

## EMode Login

Open the EMode Login GUI from a Python session to login to your EMode Photonix account and view your license subscriptions and active sessions. *Alternatively*, you can run `emode` from the command line to launch the login GUI. Once logged in, click the exit button to close the GUI and save the login credentials.

```default
import emodeconnection as emc
emc.EModeLogin()
```

To clear active sessions, select an option from the dropdown in the *Active Sessions* header row and click *Clear*. This terminates the selected sessions without consuming a license seat and refreshes the session table. The options are ‘none’ (default), ‘all’ (all non-roaming sessions), ‘mine’ (only sessions launched by the current user), ‘others’ (sessions owned by the current user but launched by other shared users), ‘latest’ (the latest non-roaming session), and ‘oldest’ (the oldest non-roaming session).

The window refreshes the *Active Sessions* table every 30 seconds while it is the active window, and at once when it regains focus. The periodic refresh pauses after 15 minutes without a key press or click, and resumes on the next one. Click *Refresh* in the *Active Sessions* header row to fetch the table at any time.

### Command-line login

Run `emode login` from the command line for an interactive text version of the login GUI. It validates any stored credentials against the license server and prompts for your email and password if they are missing or invalid. Credentials are only saved after successful validation. It then displays your subscriptions and active sessions, prompts for a session-clearing option, and asks whether to log out on this computer. The clearing options are the GUI dropdown’s, plus a number: each session you can clear by itself is numbered in the active sessions table (roaming sessions are not), and typing its number clears only that session, on whichever computer it runs, as the GUI’s per-row clear button does.

```bash
emode login
```

To enter credentials without contacting the license server, run `emode login --write`. This prompts for your email and password and writes them directly, without validation or session display. Use this as a basic fallback interface when the license server is unreachable.

### Error reporting

When a session closes, EMode sends EMode Photonix a short report of each internal error the session met: the error type, the EMode function, the EMode version and the platform. A report never includes your script, geometry, data or file paths. Reporting is on by default, and the first session of each new version prints one line saying so. Three ways turn it off (or back on), and all three change the same setting, saved for every session on this computer:

- `em.settings(error_reporting=False)` in a script
- the *Settings* link of the login window, which shows the current state
- `emode settings --error-reporting off` from the command line (`emode settings` alone prints the current state)

```bash
emode settings --error-reporting off
```

<a id="mcp-server"></a>

## MCP Server

`emodeconnection` includes an MCP (Model Context Protocol) server, `emode-mcp`, so an AI agent in any MCP client can drive EMode. It runs on your computer over stdio and starts EMode through `emodeconnection`, so it uses your installed EMode, your login and your license, as a script does.

Install it with the optional `mcp` extra, which adds the MCP Python SDK:

```bash
pip install "emodeconnection[mcp]"
```

EMode must be installed (see [EMode Install](#emode-install)) and logged in (see [EMode Login](#pythonemodelogin)). Running `emode-mcp` without the extra prints the `pip` command above and exits.

An MCP client starts the server itself, so add the `emode-mcp` command to the client rather than running it by hand. In Claude Code:

```bash
claude mcp add emode -- emode-mcp
```

A client configured with a JSON file of MCP servers takes the same command:

```json
{
  "mcpServers": {
    "emode": {
      "command": "emode-mcp"
    }
  }
}
```

If `emodeconnection` is installed in a virtual environment, give the full path of that environment’s `emode-mcp`. `emode-mcp --schemas` prints the tool definitions as JSON and exits, for a client that does not speak MCP.

The server holds one EMode session at a time. Its tools are:

- `emode_open` and `emode_close`: start a session and draw its cross-section, and close it, which releases the license. A session is saved when it closes only if `emode_open` was given a `save_path`. A client that disconnects without `emode_close` still has its session closed.
- `emode_settings`, `emode_shape`, `emode_profile` and `emode_material`: change the settings of the current cross-section or of labeled ones, draw or change a shape, label the cross-section drawn so far, and add a material or look up a refractive index.
- `emode_solve`: solve the cross-section’s modes with the FDM or the FEM (EMode<sub>3D</sub> for the FEM), returning each mode’s effective index and loss.
- `emode_section` and `emode_eme`: declare straight, taper, bend, GDS and free space ([`pwd_section`](emodeguide/eme.md#pwd_section)) sections, connected in the order declared, and run [`EME`](emodeguide/eme.md#EME) for the S-matrix.
- `emode_sweep`, `emode_converge`, `emode_evaluate` and `emode_optimize`: [`sweep`](emodeguide/solver-analysis.md#sweep), [`converge`](emodeguide/solver-analysis.md#converge), [`evaluate`](emodeguide/optimization.md#evaluate) and [`optimize`](emodeguide/optimization.md#optimize).
- `emode_get` and `emode_plot`: read a result or setting by key (without a key, the names that can be read), and plot the fields or circuit as a PNG image.

A sweep over more than 20 points (more than 20 `num_nodes` for an interpolated sweep) and every optimization ask for confirmation first. They run nothing and return a `'confirmation_required'` status with a message saying why, and the agent repeats the call with `confirm=true` to run them. Arrays of more than 1000 elements are saved to `.npy` files, and the tool returns the file path. The result of every tool but `emode_plot` carries the warnings the client logged during the call.

The server also offers two resources: `emode://skill`, guidance for an agent on using EMode well (converge before trusting a result, sweep the wavelength by interpolation, check passivity), and `emode://docs/<page>`, a page of this documentation as Markdown, such as `emode://docs/examples/Ring_Design`.

## The EMode class

### *class* EMode(simulation_name='emode', license_type='default', clear='none', save_path='.', verbose=False, roaming=False, open_existing=False, new_name=False, priority='pN', emode_cmd=None, force_open=False, save=True)

Opens the EMode software and initializes a local simulation file. The user’s account must have an active license available to successfully run EMode. If the user has not previously logged in, this will prompt for a username and password. To reset the user profile, run `emode login --write` from the command line.

An existing file can be opened for further analysis by specifying the input argument `open_existing = True`. This opened file can be copied to a new file name by specifying the input argument `new_name = 'new_filename'`.

#### NOTE
All [EMode functions](emodeguide.html) are accessible as methods of this class instance using keyword arguments for each function input parameter.

* **Parameters:**
  * **simulation_name** (*str*) – Name of the simulation file. The extension ‘.eph’ will be added to this name. Default: ‘emode’.
  * **license_type** (*str*) – The requested license type, either ‘2d’, ‘3d’, or ‘default’. When running with ‘default’ license type, a ‘3d’ license will be initialized if it is available, otherwise a ‘2d’ license will be initialized if it is available. The ‘2d’ or ‘3d’ options will only request that specific license type if it is available. Default: ‘default’.
  * **clear** (*str*) – Indicate whether to clear existing EMode sessions from this computer. Accepts ‘all’ (all non-roaming sessions), ‘mine’ (only sessions launched by the current user), ‘others’ (sessions owned by the current user but launched by other shared users), ‘latest’/’oldest’ (the latest/oldest non-roaming session), and ‘none’. Clearing can be used when a session crashes to free up a license seat, but it will terminate the associated session. Default: ‘none’.
  * **save_path** (*str* *or* *Path*) – Name of the path where the simulation file will be created and saved. Default: ‘.’.
  * **verbose** (*bool*) – Whether to run EMode in verbose mode for debugging. Default: False.
  * **roaming** (*bool*) – Set the license manager to roaming mode. Normal mode (roaming = False) re-checks the license connection every 2 minutes, allowing licenses to reset after 10 minutes if the internet connection fails. Roaming mode (roaming = True) can be used to extend the license re-check to 24 hours to support long simulations without continuously re-checking the license. When ending a session normally on roaming, the license is checked-in. However, when a session crashes, the license will remain checked-out until 24 hours after the start. Use the `clear` parameter or the [EMode Login](#pythonemodelogin) session clearing to terminate ghost license check-outs. Default: False.
  * **open_existing** (*bool*) – Whether to open an existing file or to start a new simulation. Default: False.
  * **new_name** (*bool* *or* *str*) – New name of opened existing file so the original file does not get modified. If False the original name is used. The extension ‘.eph’ will be added to this name. Default: False.
  * **priority** (*str*) – Set the CPU process priority. ‘pH’: High, ‘pAN’: Above normal, ‘pN’: Normal, ‘pBN’: Below normal, ‘pI’: Idle. Default: ‘pN’ (Normal).
  * **emode_cmd** (*list* *[**str* *]*) – Set the EMode executable command. For standard installations, this can be ignored. Only use this if the EMode executable has been moved to a custom location, or if multiple versions are installed in different locations.
  * **force_open** (*bool*) – If a simulation file with this name is already open elsewhere, open it anyway instead of auto-renaming. Default: False.
  * **save** (*bool*) – Whether to save the simulation file when the session ends. Set to False to run without writing to disk. Default: True.

## Functions

The `emodeconnection` package provides access to EMode Login GUI, to the EMode simulation files and to helpers such as [`grating_sections()`](#grating_sections) without using an active EMode license. This is helpful for analyzing simulation data without checking out an EMode license or for sharing the EMode simulation results with a collaborator who does not have an EMode license.

### EModeLogin()

Opens the EMode Login GUI to input your EMode Photonix account credentials, view active license subscriptions, and view or clear active sessions (see [EMode Login](#pythonemodelogin)). Once logged in, click the exit button to close the GUI and save the login credentials.

### open_file(sim='emode', simulation_name=None)

Opens an EMode simulation file with the extension ‘.eph’ or ‘.mat’ and returns a dictionary with the file contents.

* **Parameters:**
  * **sim** (*str*) – Name of the simulation file. The ‘.eph’ or ‘.mat’ extension is optional to include with the name. Default: ‘emode’.
  * **simulation_name** (*str*) – Alternative to `sim`. If given, it overrides `sim`. Default: None.
* **Returns:**
  Dictionary with all keys and data from the specified EMode simulation file.
* **Return type:**
  dict

### get(variable, sim='emode', simulation_name=None)

Returns the contents of a specified variable from an EMode simulation file with the extension ‘.eph’ or ‘.mat’.

* **Parameters:**
  * **variable** (*str*) – Name of the variable to return from EMode.
  * **sim** (*str*) – Name of the simulation file. The ‘.eph’ or ‘.mat’ extension is optional to include with the name. Default: ‘emode’.
  * **simulation_name** (*str*) – Alternative to `sim`. If given, it overrides `sim`. Default: None.
* **Returns:**
  Requested item from EMode.
* **Return type:**
  int, float, str, or array-like

### inspect(sim='emode', simulation_name=None)

Get the names of all top-level variables from EMode simulation file with the extension ‘.eph’ or ‘.mat’.

* **Parameters:**
  * **sim** (*str*) – Name of the simulation file. The ‘.eph’ or ‘.mat’ extension is optional to include with the name. Default: ‘emode’.
  * **simulation_name** (*str*) – Alternative to `sim`. If given, it overrides `sim`. Default: None.
* **Returns:**
  List of the top-level variable names in the file. Per-profile results are nested under `profiles` and are read with `get(key, profile=...)` or the typed accessors. See [Variable Descriptions](emodeguide/variables.md).
* **Return type:**
  list

### grating_sections(period, count, duty_cycle=0.5, chirp=0.0, jitter_rms=0.0, seed=None)

Returns the lengths [nm] of the `2 count` sections of a grating, in order: the first section of period 0, its second section, the first section of period 1, and so on. A grating alternates between two cross-sections once per period, so it is declared as one [`straight_section`](emodeguide/eme.md#straight_section) per length, alternating between two labeled profiles. It is computed in `emodeconnection` and sends nothing to the solver. The ‘EME: Bragg Grating’ example uses it.

```python
from emodeconnection import grating_sections

for k, length in enumerate(grating_sections(period=320, count=200, chirp=4)):
    em.straight_section(name=f'grating_{k}', profile=('wide', 'narrow')[k % 2],
                        length=length)
```

* **Parameters:**
  * **period** (*float*) – [nm] the grating period, the mean period of a chirped grating. Must be positive.
  * **count** (*int*) – the number of periods, at least 1.
  * **duty_cycle** (*float*) – the fraction of each period in its first section, in (0, 1). Default: 0.5.
  * **chirp** (*float*) – [nm] the change of the period from the first period to the last. The period changes linearly with the period’s index, from `period - chirp / 2` to `period + chirp / 2`, so the grating stays `count * period` long. Default: 0.
  * **jitter_rms** (*float*) – [nm] the rms of a Gaussian displacement added to every wall between two sections, drawn with `seed`, for fabrication tolerance studies. The two ends of the grating stay where they are. Default: 0.
  * **seed** (*int* *or* *None*) – the seed of the jitter, so the same grating can be drawn again. Default: None.
* **Returns:**
  The section lengths [nm]. An `ArgumentError` is raised when a chirp makes a period zero or negative, or a jitter makes a section of no length.
* **Return type:**
  list[float]

## Constants

The `emodeconnection.constants` module contains the following constants.

### c

The speed of light in vacuum in m/s.

### eps_0

The vacuum permittivity in F/m.

### mu_0

The vacuum permeability in H/m.

### eta

The impedance of free space in Ohms.

### e

The elementary charge in C.

### hbar

The reduced Planck constant in J·s.

## Types

Four types are available as classes of the emodeconnection package to specify material properties, field data, and grid data.

### *class* MaterialProperties

A container for linear and non-linear electromagnetic material parameters. At least one of n or eps **must** be supplied in any valid instance.

* **Parameters:**
  * **n** (*Optional* *[**float* *|* *list* *[**float* *]*  *|* *list* *[**list* *[**float* *]* *]* *]*) – Scalar or tensor refractive index. Accepts *None* (unspecified), a float, a list of 3 floats (for x,y,z), or a 3 x 3 list of list of floats for an anisotropic material.
  * **eps** (*Optional* *[**float* *|* *list* *[**float* *]*  *|* *list* *[**list* *[**float* *]* *]* *]*) – Relative permittivity tensor εᵣ. Accepts *None* (unspecified), a float, a list of 3 floats (for x,y,z), or a 3 x 3 list of list of floats for a fully anisotropic material.
  * **mu** (*Optional* *[**float* *|* *list* *[**float* *]*  *|* *list* *[**list* *[**float* *]* *]* *]*) – Relative permeability tensor μᵣ. Same shape conventions as eps. Default: *None* (μᵣ = 1).
  * **d** (*Optional* *[* *~DTensorType* *]*) – Second-order nonlinear susceptibility *d*. Expected shape is 3x6. Use *None* when the material is linear. Default: *None*.

---

### *class* MaterialSpec

A container that defines the adjustable properties of a material being used. All properties default to the materials
defaults (typically 0).

* **Parameters:**
  * **material** (*Union* *[**str* *,*  *~MaterialProperties* *]*) – Either a *str* pointing to a material in the Material Database, or a MaterialProperties object. Required.
  * **theta** (*Optional* *[**float* *]*) – The angle of rotation around *y* for the material in radians. Default: 0.
  * **phi** (*Optional* *[**float* *]*) – The angle of rotation around *z* for the material in radians, applied *after* the theta rotation. Default: 0.
  * **psi** (*Optional* *[**float* *]*) – The angle of rotation around the crystal’s own *z* axis in radians, applied *before* the theta rotation. Only a biaxial crystal (such as KTP or LBO) or a nonlinear tensor without symmetry about *z* needs it. Default: 0.

The three angles are *z-y-z* Euler angles that turn the crystal, not the simulation axes (an active rotation). A material tensor $T$ given on the crystal axes, such as the permittivity, permeability, $\chi^{(2)}$ or electro-optic tensor, appears in the simulation frame as $R T R^\mathsf{T}$ (with one $R$ per index for a higher-rank tensor), where

$$
R = R_z(\phi) \, R_y(\theta) \, R_z(\psi)

$$

and $R_z$, $R_y$ are the usual right-handed rotation matrices about *z* and *y*. Read right to left, the crystal is turned by `psi` about its *z* axis first, then by `theta` about *y*, then by `phi` about *z*. Column *a* of $R$ is the direction crystal axis *a* points in the simulation frame, so the crystal *z* axis ends up along $(\sin\theta \cos\phi, \sin\theta \sin\phi, \cos\theta)$. The simulation’s propagation direction *z* then lies along the crystal direction $R^\mathsf{T} \hat{z}$. For example, `theta=-pi/2, psi=-phi_pm` points the propagation direction along $(\cos\phi_\mathrm{pm}, \sin\phi_\mathrm{pm}, 0)$ in the crystal’s *xy* plane.

* **Parameters:**
  * **x** (*Optional* *[**float* *]*) – The mole fraction for ternary materials such as AlGaAs and InGaAs. This is ignored in materials without this parameter.  Must be between 0 and 1.
  * **loss** (*Optional* *[**float* *]*) – The absorption loss of the material in dB/m.

---

### *class* Field

A container for field data.

* **Parameters:**
  * **field** (*array-like*) – An array of all of the fields, including all modes with shape [N_fields, N_modes, Nx, Ny].
  * **field_names** (*list* *[**str* *]*) – A list of the field names, corresponding to the fields in the `field` array.
  * **num_modes** (*int*) – The number of modes.
  * **grid** ([*Grid*](#Grid)) – A grid object associated with the fields.

---

### *class* Grid

A container that defines the simulation grid.

* **Parameters:**
  * **x** (*array-like*) – Array of the x grid center points of the mesh.
  * **y** (*array-like*) – Array of the y grid center points of the mesh.
  * **is_pml** (*bool*) – Whether the grid includes a PML region.
  * **is_expanded** (*bool*) – Whether the grid includes an expansion region.
  * **is_bc** (*bool*) – True if the boundary condition is ‘00’, False for other boundary conditions.

---

### *class* SMatrix

A dense, port-structured scattering matrix returned by EME results (e.g. from `EME()` or `get(key='S_matrix')`). Ports are indexed 0 to `num_ports - 1`. Port *i* spans a contiguous range of modes whose count is `port_sizes[i]`. When `port_names`/`wavelengths` are available, ports can also be addressed by name instead of by index.

* **Parameters:**
  * **port_sizes** (*tuple* *[**int* *,*  *...* *]*) – number of modes in each port.
  * **data** (*ndarray*) – the complex scattering matrix, with shape `(total_modes, total_modes)`.
  * **port_names** (*tuple* *[**str* *,*  *...* *] or* *None*) – name of each port, parallel to `port_sizes`. Default: None.
  * **wavelengths** (*tuple* *[**float* *,*  *...* *] or* *None*) – wavelength of each port, parallel to `port_sizes`. Default: None.
  * **mode_power** (*ndarray* *or* *None*) – real power each mode carries, one entry per row of `data`. A lossy mode carries little or no real power. Default: None (an S-matrix from an older solver).

`SMatrix` also provides convenience methods: `transmission(from_port, to_port)`, `reflection(port)`, `power(from_port, to_port)`, `insertion_loss_dB(from_port, to_port, mode)`, `unitarity_error(from_ports)`, and `plot(kind)`.

`unitarity_error` measures how far the S-matrix is from conserving power. Each mode that carries real power is scaled to unit power, and the result is $\|S'^\dagger S' - I\|_F / \sqrt{n}$ over the $n$ incident modes counted, so a lossless S-matrix reads 0 and a 10% gain on every mode reads 0.1, whatever the number of modes. Lossy modes carry no power and are not counted. Without `mode_power` every mode counts as carrying unit power, which holds for guided modes but not for a plane-wave (PWD) section. `from_ports` counts only the modes incident from those ports. Use it when one side’s basis cannot receive what the other side sends: at a facet into free space, the guided modes of the waveguide cannot hold the plane waves arriving from the free-space side, so the whole matrix loses power while the balance counted from the waveguide port holds.

## Geometry

The `emodeconnection.geometry` module provides classes for defining waveguide centerline paths, used primarily for EME GDS sections.

### *class* Pose(w=0, z=0, x=0, y=0, azimuth=0)

Position and orientation of a curve’s local frame within the section frame.

* **Parameters:**
  * **w** (*float*) – Lateral offset.
  * **z** (*float*) – Axial position.
  * **x** (*float*) – x position.
  * **y** (*float*) – y position.
  * **azimuth** (*float*) – Orientation angle in radians.

---

### *class* Line(length)

Straight segment along the local +z axis.

* **Parameters:**
  **length** (*float*) – Length of the segment (must be positive).

---

### *class* Arc(radius, sweep_angle)

Circular arc segment.

* **Parameters:**
  * **radius** (*float*) – Bend radius (signed, where the sign determines bend direction).
  * **sweep_angle** (*float*) – Subtended angle in radians (signed).

---

### *class* BSpline(knots, coefficients, degree=3)

B-spline curve segment.

* **Parameters:**
  * **knots** (*list*) – Knot vector. Must satisfy `len(knots) == len(coefficients) + degree + 1`.
  * **coefficients** (*list*) – Control points as a list of `[x, z]` pairs.
  * **degree** (*int*) – Spline degree. Default: 3.

---

### *class* Path(segments)

A composed waveguide centerline built from one or more curve segments.

* **Parameters:**
  **segments** (*tuple*) – Tuple of [`Line`](#Line), [`Arc`](#Arc), or [`BSpline`](#BSpline) objects. The total path length is the sum of all segment lengths.

---

### Curve

Type alias for `Union[Line, Arc, BSpline]`.

<a id="ports"></a>

## Ports

The `emodeconnection.port` module provides classes for defining named ports on EME sections. Every EME section function accepts a `ports` argument (see [EME](emodeguide/eme.md)) that replaces the section’s default `[Port('left', None, 'left'), Port('right', None, 'right')]` on a per-`side` basis: a supplied port replaces every existing port on that side, and multiple ports may share a side. Ports are addressed globally as `"<section_name>.<port_name>"` for explicit wiring with [`connect_ports`](emodeguide/eme.md#connect_ports).

**Declaring ports renames the S-matrix ports of the whole chain.** A chain that declares no `ports` and no [`connect_ports`](emodeguide/eme.md#connect_ports) pair keeps the bare names `left` and `right`. As soon as any section declares `ports`, or any pair is connected, every external port in the chain is named `<section>.<port>` (`in.left` for the left port of a section named `in`), including the ports of sections that declared nothing themselves. Address a port in the returned S-matrix by that qualified name.

**Connecting two ports pins their coordinate systems together.** The two ports’ origins, which are the horizontal center and bottom edge of each port’s cross-section, are made to coincide, so a section is positioned by the connection rather than by absolute coordinates. Because the origin is the *bottom* edge in y, two profiles of different heights stay aligned at their substrate rather than at their vertical centers. Use `offset` below to displace a section off that shared origin.

### *class* Port(name, side, window=None, offset=(0.0, 0.0), basis_transform=None)

A named port on a section.

A `Port` carries two offsets that mean different things: `window.offset` says *which part of this section’s cross-section the port exposes*, while `offset` says *where this section sits* relative to what the port connects to. In short, `window` answers “which part of me?” and `offset` answers “where am I?”. A window never moves a section, and an offset never crops one.

* **Parameters:**
  * **name** (*str*) – Port name, unique within its section.
  * **window** ([*Bounds*](#Bounds) *or* *None*) – A rectangular sub-region of the port’s full cross-section to isolate, positioned within *this section’s own* cross-section. Must overlap the section’s profile. When set on a port that is not connected to anything (an unclaimed, external boundary port), or on a connected port that shares its side with other windows, the port’s modes are solved on just this window instead of the section’s full cross-section. This is the physically correct choice for exciting or measuring a single waveguide within a wider multi-waveguide cross-section (e.g. one arm of a directional coupler), rather than the coupled full-cross-section supermode. Default: None (the port sees the section’s entire cross-section).

A windowed port behaves as follows.

* A window keeps only the modes its content guides, those whose effective index is above what the layer stacks at its edges guide. The rest are standing waves between the window’s walls, which the section does not have, so they are dropped, and a windowed port can have fewer modes than `num_modes`. At least one mode, the one of largest index, is always kept. A section’s own ports, without a window, keep all their modes.
* Several windows on one side of a section (the bus and ring arms of a ring coupler, say) are matched to the section together, in one junction, and then read out as separate ports, so together they conserve power. A window connected to another section with [`connect_ports`](emodeguide/eme.md#connect_ports) takes part in the same junction and passes its light on to that section from there. A connection windowed on both of its ends keeps a junction of its own.
* With the FDM, each edge of a window is moved to the nearest cell edge of the section’s own mesh (by at most half a cell), so the window and the section share their grid and the junction stays reciprocal.
* Each windowed port is placed in the layout on its own window, so a section connected to one window is laid out on that window.

* **Parameters:**
  * **side** (*str*) – Which end of the section this port is declared on, either ‘left’ or ‘right’.
  * **offset** (*tuple* *[**float* *,* *float* *]*) – [nm] transverse displacement of this section’s cross-section relative to the port it connects to: `offset=(1300, 0)` places this section’s content 1300 nm in +x from the connection’s shared origin. Default: (0, 0). Typically you annotate whichever side is off-axis, e.g. giving a coupler’s input port `offset` so a centered adapter waveguide lines up with it, rather than offsetting the adapter. The legacy per-section `offset=` argument (see [EME](emodeguide/eme.md)) is this same displacement applied to every port at once. Setting both on one section raises an [`ArgumentError`](#ArgumentError).
  * **basis_transform** (*AngledFacetMap* *or* *None*) – Declares an angled facet on this port. Build it with [`make_angled_facet_port()`](#make_angled_facet_port) rather than constructing one directly. Default: None (the port’s cross-section is normal to the propagation axis).

---

### *class* Bounds(window_width, window_height, offset=(0.0, 0.0))

A rectangular sub-window of a port’s full cross-section, used as [`Port`](#Port)’s `window`.

* **Parameters:**
  * **window_width** (*float*) – Width of the window, in the cross-section’s x-direction.
  * **window_height** (*float*) – Height of the window, in the cross-section’s y-direction.
  * **offset** (*tuple* *[**float* *,* *float* *]*) – Position of the window within the port’s full cross-section: the x-coordinate of the window’s horizontal center, and the y-coordinate of the window’s bottom edge. Note the asymmetry: the second entry is the window’s **bottom**, not its center, matching the cross-section’s own y-axis which starts at 0 at the bottom of the profile. A full-height window therefore wants `y=0`, not `-window_height/2`. The window must overlap the section’s profile.

---

### make_angled_facet_port(name, side, pivot_angle, polar_angle=0.0, pivot_point=None, degrees=True, window=None)

Build a [`Port`](#Port) whose cross-section is tilted about a hinge line, modeling an angled facet: the plane where this section meets its neighbor is no longer normal to the propagation axis. Reflection back into the guided mode then falls off with tilt, as it does physically, once the reflected beam (deflected by twice the tilt) leaves the mode’s acceptance cone. For a 3 µm wide guide the reflection halves near 2.7° and is down 30 dB by 7°. Pass the result in the `ports` argument of the waveguide section that ends at the facet, and wire it with [`connect_ports`](emodeguide/eme.md#connect_ports) if it is not the section’s default left/right adjacency.

* **Parameters:**
  * **name** (*str*) – Port name, unique within its section.
  * **side** (*str*) – Which end of the section the facet is on, either ‘left’ or ‘right’.
  * **pivot_angle** (*float*) – Tilt of the facet plane away from the plane normal to the propagation axis, in degrees unless `degrees` is False. 0 is an untilted facet. A positive tilt about a vertical hinge moves the facet further along the propagation axis on the +x side of the hinge, so its plane is `z = z_facet + tan(pivot_angle) (x - x_hinge)`. Light arriving along +z meets it at an angle of incidence equal to the tilt. The reflection leaves at twice the tilt from the backward axis, toward +x, and the transmitted beam leaves at `asin(n_eff sin(pivot_angle) / n_gap)` from the facet normal (Snell’s law with the mode’s effective index), on the +x side of it. A negative tilt mirrors all of this to -x. For a horizontal hinge (`polar_angle` +90) the same holds with -y in place of +x, and plot() draws the facet as a line on the z-x or z-y panel.
  * **polar_angle** (*float*) – Orientation of the hinge line in the transverse plane: 0 for a vertical hinge (the facet is tilted in the x–z plane), ±90 for a horizontal one. Only these axis-aligned values are accepted. Default: 0.
  * **pivot_point** (*tuple* *[**float* *,* *float* *] or* *None*) – [nm] a point `(x, y)` the hinge line passes through, in the section’s own cross-section coordinates. Default: None (the horizontal centre of the cross-section, at its vertical middle).
  * **degrees** (*bool*) – Whether the angles are in degrees (True) or radians (False). Default: True.
  * **window** ([*Bounds*](#Bounds) *or* *None*) – As [`Port`](#Port)’s `window`. Default: None.

#### NOTE
Four things to know before using one.

* A facet is taken by the junction kernel directly. The junction is solved with the facet’s forward and backward traces kept explicit. A facet at zero tilt gives the same answer as no facet.
* The facet belongs on the waveguide side of the junction. It cannot sit on a [`pwd_section`](emodeguide/eme.md#pwd_section), whose basis is analytic, which is rejected when the EME chain is built. On a profile with a PML the facet moves the absorbing cells onto the tilted plane and keeps the absorption each one was designed with.
* Size `num_modes` on the waveguide section so its basis can hold the reflection deflected by twice the tilt. When it cannot, the fundamental’s reflected plus transmitted power under-runs and the solver warns. Ten modes on a 3 µm wide, 300 nm tall guide hold the power to about 8° of tilt and lose half of it by 15°.
* The facet is a model for small to moderate tilts. The cross-section is solved flat and its trace is carried onto the tilted plane, which assumes the mode is unchanged by the tilt, an assumption that weakens as the tilt grows. Past the critical angle, where `n_eff sin(pivot_angle)` exceeds the gap’s index, no plane wave carries the light away and it is totally internally reflected, into the higher-order modes the waveguide section has to hold. Keep tilts well below the critical angle (for a mode of effective index 3.4 that is about 25° into oxide and 17° into air), and check that the reflected plus transmitted power stays near 1 as `num_modes` grows.

## Exceptions

The `emodeconnection.types` module also contains the following exception classes. All errors in EMode are raised and passed back to the user as one of these errors.

### *class* EModeError(msg='', \*args, \*\*kwargs)

Base class for all EMode exceptions.

* **Parameters:**
  **msg** (*str*) – Human-readable explanation. Default: ‘’.

---

### *class* ArgumentError(msg, function, argument)

Invalid value passed to *function*.

* **Parameters:**
  * **msg** (*str*) – Human-readable explanation.
  * **function** (*str* *or* *None*) – Function name, or None.
  * **argument** (*str* *or* *None*) – Name of the offending argument, or None.

---

### *class* EPHKeyError(msg, filename, key)

Missing dataset or attribute *key* in the given EPH file.

* **Parameters:**
  * **msg** (*str*) – Details of the failure.
  * **filename** (*str*) – Path to the EPH file.
  * **key** (*str*) – Absent key.

---

### *class* FileError(msg, filename)

Generic file-system or I/O problem.

* **Parameters:**
  * **msg** (*str*) – Description of the error.
  * **filename** (*str*) – File path involved.

---

### *class* LicenseError(msg, license_type)

Operation not permitted under *license_type*.

* **Parameters:**
  * **msg** (*str*) – Explanation of the denial.
  * **license_type** (*LicenseType*) – Active or required license class.

---

### *class* ShapeError(msg, shape_name)

Unexpected tensor or geometry shape.

* **Parameters:**
  * **msg** (*str*) – Explanation of the mismatch.
  * **shape_name** (*str*) – Symbolic name of the offending shape.

---

### *class* NameError(msg, type, name)

Forbidden *name* within the given *type* category.

* **Parameters:**
  * **msg** (*str*) – Reason for the failure.
  * **type** (*str*) – Category being searched (e.g. `"section"` or `"label"`).
  * **name** (*str*) – Forbidden identifier.

---

### *class* NotImplementedError(msg)

Feature is recognized but not yet implemented.

* **Parameters:**
  **msg** (*str*) – Description of the missing capability.
