Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 103 additions & 24 deletions Examples/ExampleSecondOrderNonlinearTmm.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,46 +7,125 @@

from NonlinearTMM import Material, SecondOrderNLTMM

if __name__ == "__main__":
# Define params
wlP1 = 1000e-9
wlP2 = 1000e-9
polP1 = "s"
polP2 = "s"
polGen = "s"
I0P1 = 1.0
I0P2 = I0P1
betas = np.linspace(0.0, 0.99, 10000)
crystalD = 1000e-6

def CalcSHG() -> None:
# Parameters
# ---------------------------------------------------------------------------
wl = 1000e-9 # Pump wavelength
pol = "s" # Polarization
I0 = 1.0 # Intensity of incident pump wave
crystalD = 1000e-6 # Crystal thickness
betas = np.linspace(0.0, 0.99, 10000) # Sweep range for beta

# Define materials
# ---------------------------------------------------------------------------
wlsCrystal = np.array([400e-9, 1100e-9])
nsCrystal = np.array([1.54, 1.53], dtype=complex)
prism = Material.Static(1.0)
crystal = Material(wlsCrystal, nsCrystal)
dielectric = Material.Static(1.0)
crystal.chi2.Update(d22=1e-12)
dielectric = Material.Static(1.0)

# Init SecondOrderNLTMM
# ---------------------------------------------------------------------------
tmm = SecondOrderNLTMM()
tmm.P1.SetParams(wl=1000e-9, pol="s", beta=0.2, I0=1.0)
tmm.P2.SetParams(wl=1000e-9, pol="s", beta=0.2, I0=1.0)
tmm.Gen.SetParams(pol="s")
tmm.P1.SetParams(wl=wl, pol=pol, beta=0.2, I0=I0)
tmm.P2.SetParams(wl=wl, pol=pol, beta=0.2, I0=I0)
tmm.Gen.SetParams(pol=pol)

# Add layers
tmm.AddLayer(math.inf, prism)
tmm.AddLayer(crystalD, crystal)
tmm.AddLayer(math.inf, dielectric)

# Sweep over beta = sin(th) * n_prism
sr = tmm.Sweep("beta", betas, betas)
# Beta sweep
# ---------------------------------------------------------------------------
sr = tmm.Sweep("beta", betas, betas, outP1=True, outGen=True)

# Crystal thickness sweep at normal incidence (beta = 0)
# ---------------------------------------------------------------------------
thicknesses = np.linspace(10e-6, 2000e-6, 200)
shg_t = np.empty(len(thicknesses))
for i, d in enumerate(thicknesses):
tmm2 = SecondOrderNLTMM()
tmm2.P1.SetParams(wl=wl, pol=pol, beta=0.0, I0=I0)
tmm2.P2.SetParams(wl=wl, pol=pol, beta=0.0, I0=I0)
tmm2.Gen.SetParams(pol=pol)
tmm2.AddLayer(math.inf, prism)
tmm2.AddLayer(d, crystal)
tmm2.AddLayer(math.inf, dielectric)
tmm2.Solve()
intensities = tmm2.GetIntensities()
shg_t[i] = intensities.Gen.T

# Plot results
# ---------------------------------------------------------------------------
fig, axes = plt.subplots(1, 3, figsize=(9.6, 3.2))

# Left: Schematic of the setup
ax = axes[0]
ax.set_xlim(-1, 5)
ax.set_ylim(-2, 2)
ax.set_aspect("equal")
ax.axis("off")
ax.set_title("Setup")

# Draw layers
from matplotlib.patches import Rectangle

# Plot generated reflection and transmission
plt.title(rf"SHG generation from crystal (d = {1e6 * crystalD:.0f} $\mu m$)")
plt.plot(betas, sr.Gen.Ir, label="R")
plt.plot(betas, sr.Gen.It, label="T")
plt.legend()
plt.xlabel(r"$\beta$")
plt.ylabel(r"($W / m^{2}$)")
ax.add_patch(Rectangle((-0.5, -1.5), 1.5, 3, fc="#ddeeff", ec="k", lw=0.8))
ax.add_patch(Rectangle((1, -1.5), 2, 3, fc="#ffe0cc", ec="k", lw=1.2))
ax.add_patch(Rectangle((3, -1.5), 1.5, 3, fc="#ddeeff", ec="k", lw=0.8))
ax.text(0.25, -1.8, "air", ha="center", fontsize=8)
ax.text(2.0, -1.8, r"$\chi^{(2)}$ crystal", ha="center", fontsize=8)
ax.text(3.75, -1.8, "air", ha="center", fontsize=8)

# Pump arrow
ax.annotate(
"",
xy=(0.9, 0.3),
xytext=(-0.6, 0.3),
arrowprops=dict(arrowstyle="-|>", color="C0", lw=2),
)
ax.text(-0.5, 0.6, r"$\omega$ pump", fontsize=7, color="C0")

# SHG arrows (reflected + transmitted)
ax.annotate(
"",
xy=(-0.6, -0.3),
xytext=(0.9, -0.3),
arrowprops=dict(arrowstyle="-|>", color="C3", lw=1.5, ls="--"),
)
ax.text(-0.5, -0.7, r"$2\omega$ R", fontsize=7, color="C3")

ax.annotate(
"",
xy=(4.6, -0.3),
xytext=(3.1, -0.3),
arrowprops=dict(arrowstyle="-|>", color="C3", lw=2),
)
ax.text(3.7, -0.7, r"$2\omega$ T", fontsize=7, color="C3")

# Middle: SHG R, T vs beta
ax = axes[1]
ax.plot(betas, sr.Gen.Ir, label="R")
ax.plot(betas, sr.Gen.It, label="T")
ax.set_xlabel(r"$\beta$")
ax.set_ylabel(r"Intensity ($W/m^{2}$)")
ax.set_title(r"SHG intensity vs $\beta$")
ax.legend()

# Right: SHG T vs crystal thickness
ax = axes[2]
ax.plot(thicknesses * 1e6, shg_t)
ax.set_xlabel(r"Crystal thickness ($\mu m$)")
ax.set_ylabel(r"SHG transmitted ($W/m^{2}$)")
ax.set_title(r"Thickness dependence ($\beta$ = 0)")

fig.tight_layout()
fig.savefig("docs/images/SecondOrderNLTMM-example.png", dpi=100)
plt.show()


if __name__ == "__main__":
CalcSHG()
1 change: 1 addition & 0 deletions Examples/ExampleTMM.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def CalcSpp() -> None:
plt.colorbar(label=r"$E_z$ (V/m)")

plt.tight_layout()
plt.savefig("docs/images/TMM-example.png", dpi=100)
plt.show()


Expand Down
1 change: 1 addition & 0 deletions Examples/ExampleTMMForWaves.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def CalcSppGaussianBeam() -> None:
plt.colorbar(cm, label=r"$‖E‖$ (kV/m)")

plt.tight_layout()
plt.savefig("docs/images/TMMForWaves-example.png", dpi=100)
plt.show()


Expand Down
204 changes: 187 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,203 @@
[![PyPI version](https://badge.fury.io/py/NonlinearTMM.svg)](https://badge.fury.io/py/NonlinearTMM)
[![Python](https://img.shields.io/pypi/pyversions/NonlinearTMM)](https://pypi.org/project/NonlinearTMM/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Pytest](https://github.com/ardiloot/NonlinearTMM/actions/workflows/pytest.yml/badge.svg)](https://github.com/ardiloot/NonlinearTMM/actions/workflows/pytest.yml)
[![PyPI](https://github.com/ardiloot/NonlinearTMM/actions/workflows/publish.yml/badge.svg)](https://github.com/ardiloot/NonlinearTMM/actions/workflows/publish.yml)
[![Pre-commit](https://github.com/ardiloot/NonlinearTMM/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/ardiloot/NonlinearTMM/actions/workflows/pre-commit.yml)
[![Build and upload to PyPI](https://github.com/ardiloot/NonlinearTMM/actions/workflows/publish.yml/badge.svg)](https://github.com/ardiloot/NonlinearTMM/actions/workflows/publish.yml)

# NonlinearTMM: Nonlinear Transfer-Matrix Method

# NonlinearTMM : Nonlinear transfer-matrix method
A Python library for optical simulations of **multilayer structures** using the transfer-matrix method, extended to support **nonlinear processes** (SHG, SFG, DFG) and **Gaussian beam propagation**.

## Overview
<p align="center">
<img src="docs/images/TMMForWaves-example.png" alt="Gaussian beam exciting surface plasmon polaritons" width="700">
</p>

Transfer-matrix method (TMM) is powerful analytical method to solve Maxwell equations in layered structures. However, standard TMM is limited by infinite plane waves (e.g no Gaussian beam excitation) and it is only limited to linear processes (i.e calculation of second-harmonic, sum-frequency, difference-frequency generation is not possible). The aim of this package is extand standard TMM to include those features. The physics of those extensions are described in the follwoing publications, first extends the standard TMM to nonlinear processes and the second extends to the beams with arbritary profiles.
> **See also:** [GeneralTmm](https://github.com/ardiloot/GeneralTmm) — a 4×4 TMM for **anisotropic** (birefringent) multilayer structures.

1. [A. Loot and V. Hizhnyakov, “Extension of standard transfer-matrix method for three-wave mixing for plasmonic structures,” Appl. Phys. A, vol. 123, no. 3, p. 152, 2017.](https://link.springer.com/article/10.1007%2Fs00339-016-0733-0)
2. [A. Loot and V. Hizhnyakov, “Modeling of enhanced spontaneous parametric down-conversion in plasmonic and dielectric structures with realistic waves,” Journal of Optics, vol. 20, no. 055502, 2018.](http://iopscience.iop.org/article/10.1088/2040-8986/aab6c0/meta)
## Table of Contents

For additional details see our documentation https://ardiloot.github.io/NonlinearTMM/. For getting started guide see [Getting started](https://ardiloot.github.io/NonlinearTMM/GettingStarted.html).
- [Features](#features)
- [Installation](#installation)
- [API Overview](#api-overview)
- [Examples](#examples)
- [Surface Plasmon Polaritons](#surface-plasmon-polaritons--exampletmmpy)
- [Gaussian Beam Excitation](#gaussian-beam-excitation--exampletmmforwavespy)
- [Second-Harmonic Generation](#second-harmonic-generation--examplesecondordernonlineartmmpy)
- [References](#references)
- [Documentation](#documentation)
- [Development](#development)
- [Setup](#setup)
- [Running tests](#running-tests)
- [Code formatting and linting](#code-formatting-and-linting)
- [CI overview](#ci-overview)
- [Releasing](#releasing)
- [License](#license)

## Main features
## Features

In addition to the standard TMM features this package also supports:
- **Standard TMM** — reflection, transmission, absorption for p- and s-polarized plane waves at arbitrary angles
- **Parameter sweeps** — over wavelength, angle of incidence, layer thickness, or any other parameter
- **1D and 2D electromagnetic field profiles** — E and H field distributions through the structure
- **Field enhancement** — calculation of field enhancement factors (e.g. for SPP excitation)
- **Gaussian beam propagation** — any beam profile through layered structures, not just plane waves
- **Second-order nonlinear processes** — SHG, SFG, DFG in multilayer structures
- **Wavelength-dependent materials** — interpolated from measured optical data (YAML format)
- **High performance** — C++ core (Eigen) with Cython bindings, OpenMP parallelization
- **Cross-platform wheels** — Linux (x86_64), Windows (x64, ARM64), macOS (ARM64); Python 3.10–3.14

* Calculation of Gaussian beam (or any other beam) propagartion inside layered structures
* Calculation of nonlinear processes SHG/SFG/DFG
## Installation

## Technical features
```bash
pip install NonlinearTMM
```

* Written in C++
* Python wrapper written in Cython
* Parallerization through OpenMP
* Use of SSE instructions for speedup
Pre-built wheels are available for most platforms. A C++ compiler is only needed when installing from source.

## API Overview

The library exposes three main classes: `Material`, `TMM`, and `SecondOrderNLTMM`.

| Class / method | Purpose |
|---|---|
| `Material(wls, ns)` | Wavelength-dependent material from arrays of λ and complex n |
| `Material.Static(n)` | Constant refractive index (shortcut) |
| `TMM(wl=…, pol=…, I0=…)` | Create a solver; `wl` = wavelength (m), `pol` = `"p"` or `"s"` |
| `tmm.AddLayer(d, mat)` | Append layer (`d` in m, `inf` for semi-infinite) |
| `tmm.Sweep(param, values)` | Solve for an array of values of any parameter |
| `tmm.GetFields(zs)` | E, H field profiles along the layer normal |
| `tmm.GetFields2D(zs, xs)` | E, H on a 2-D grid |
| `tmm.GetEnhancement(layerNr)` | Field enhancement in a given layer |
| `tmm.wave` | Access `_Wave` parameters for Gaussian beam calculations |
| `tmm.WaveSweep(param, values)` | Parameter sweep for beam calculations |
| `tmm.WaveGetFields2D(zs, xs)` | 2-D field map for beam excitation |
| `SecondOrderNLTMM(…)` | Second-order nonlinear TMM (SHG, SFG, DFG) |

For the full API, see the [reference documentation](https://ardiloot.github.io/NonlinearTMM/Reference.html).

## Examples

### Surface Plasmon Polaritons — [ExampleTMM.py](Examples/ExampleTMM.py)

Kretschmann configuration (prism | 50 nm Ag | air) at 532 nm. Demonstrates
reflection sweeps, field enhancement, and 1D/2D field visualization of surface
plasmon polaritons.

```python
import math
import numpy as np
from NonlinearTMM import TMM, Material

# Materials
prism = Material.Static(1.5)
ag = Material.Static(0.054007 + 3.4290j) # Silver @ 532nm
air = Material.Static(1.0)

# Set up TMM (Kretschmann configuration)
tmm = TMM(wl=532e-9, pol="p", I0=1.0)
tmm.AddLayer(math.inf, prism)
tmm.AddLayer(50e-9, ag)
tmm.AddLayer(math.inf, air)

# Sweep angle of incidence
betas = np.sin(np.radians(np.linspace(0, 80, 500))) * 1.5
result = tmm.Sweep("beta", betas, outEnh=True, layerNr=2)
```

<p align="center">
<img src="docs/images/TMM-example.png" alt="SPP reflection, enhancement, and field profiles" width="700">
</p>

### Gaussian Beam Excitation — [ExampleTMMForWaves.py](Examples/ExampleTMMForWaves.py)

The same Kretschmann structure excited by a 10 mW Gaussian beam (waist 10 μm).
Shows how finite beam width affects resonance depth and field enhancement.

<p align="center">
<img src="docs/images/TMMForWaves-example.png" alt="Gaussian beam SPP excitation" width="700">
</p>

### Second-Harmonic Generation — [ExampleSecondOrderNonlinearTmm.py](Examples/ExampleSecondOrderNonlinearTmm.py)

Second-harmonic generation (SHG) in a 1 mm nonlinear crystal with
χ⁽²⁾ nonlinearity. Two s-polarized pump beams at 1000 nm generate a
second-harmonic signal at 500 nm. The `SecondOrderNLTMM` class also supports
sum-frequency generation (SFG) and difference-frequency generation (DFG).

<p align="center">
<img src="docs/images/SecondOrderNLTMM-example.png" alt="SHG reflected and transmitted intensity vs beta" width="700">
</p>

## References

> Loot, A., & Hizhnyakov, V. (2017). Extension of standard transfer-matrix method for three-wave mixing for plasmonic structures. *Applied Physics A*, 123(3), 152. [doi:10.1007/s00339-016-0733-0](https://link.springer.com/article/10.1007%2Fs00339-016-0733-0)
>
> Loot, A., & Hizhnyakov, V. (2018). Modeling of enhanced spontaneous parametric down-conversion in plasmonic and dielectric structures with realistic waves. *Journal of Optics*, 20, 055502. [doi:10.1088/2040-8986/aab6c0](https://doi.org/10.1088/2040-8986/aab6c0)

## Documentation

https://ardiloot.github.io/NonlinearTMM/
Full documentation is available at https://ardiloot.github.io/NonlinearTMM/.

- [Getting started](https://ardiloot.github.io/NonlinearTMM/GettingStarted.html) — installation, package structure, examples
- [API reference](https://ardiloot.github.io/NonlinearTMM/Reference.html) — complete class and method reference

## Development

### Setup

```bash
git clone --recurse-submodules https://github.com/ardiloot/NonlinearTMM.git
cd NonlinearTMM

# Install uv if not already installed:
# https://docs.astral.sh/uv/getting-started/installation/

# Create venv, build the C++ extension, and install all dependencies
uv sync
```

### Running tests

```bash
uv run pytest -v
```

### Code formatting and linting

[Pre-commit](https://pre-commit.com/) hooks are configured to enforce formatting (ruff, clang-format) and catch common issues. To install the git hook locally:

```bash
uv run pre-commit install
```

To run all checks manually:

```bash
uv run pre-commit run --all-files
```

### CI overview

| Workflow | Trigger | What it does |
|----------|---------|--------------|
| [Pytest](.github/workflows/pytest.yml) | Push to `master` / PRs | Tests on {ubuntu, windows, macos} × Python {3.10–3.14} |
| [Pre-commit](.github/workflows/pre-commit.yml) | Push to `master` / PRs | Runs ruff, clang-format, ty, and other checks |
| [Publish to PyPI](.github/workflows/publish.yml) | Release published | Builds wheels + sdist via cibuildwheel, uploads to PyPI |
| [Publish docs](.github/workflows/publish_docs.yml) | Release published | Builds Sphinx docs and deploys to GitHub Pages |

## Releasing

Versioning is handled automatically by [setuptools-scm](https://github.com/pypa/setuptools-scm) from git tags.

1. **Ensure CI is green** on the `master` branch.
2. **Create a new release** on GitHub:
- Go to [Releases](https://github.com/ardiloot/NonlinearTMM/releases) → **Draft a new release**
- Create a new tag following [PEP 440](https://peps.python.org/pep-0440/) (e.g. `v1.2.0`)
- Target the `master` branch (or a specific commit on master)
- Click **Generate release notes** for an auto-generated changelog
- For pre-releases (e.g. `v1.2.0rc1`), check **Set as a pre-release** — these upload to TestPyPI instead of PyPI
3. **Publish the release** — the workflow builds wheels for Linux (x86_64), Windows (x64, ARM64), and macOS (ARM64), and uploads to [PyPI](https://pypi.org/project/NonlinearTMM/).

## License

[MIT](LICENSE)
Loading