# REMSOL — ZED AI Assistant Rules

## Project Overview

REMSOL (Rust-based Electromagnetic Multi-layer Solver) computes **effective refractive
indices** and **field profiles** of guided photonic modes in 1D multilayer (planar slab)
structures. The core solver is written in Rust for performance and exposed to Python via
PyO3 bindings, built with Maturin.

---

## Tech Stack

| Layer        | Tool / Version              |
|--------------|-----------------------------|
| Core solver  | Rust, edition 2021          |
| Python bindings | PyO3 v0.24              |
| Build bridge | Maturin >=1.7               |
| Python env   | uv                          |
| Testing      | cargo test (Rust), pytest (Python), pytest-benchmark |
| Versioning   | python-semantic-release (bumps Cargo.toml version)   |
| CI           | GitHub Actions              |
| Docs         | Jupyter Book                |

---

## Repository Layout

```
src/
  lib.rs                — PyO3 module root; registers all Python-visible types
  enums.rs              — Polarization (TE/TM) and BackEnd (Transfer/Scattering) enums
  layer.rs              — Layer struct (n: f64, d: f64) + LayerCoefficientVector (a, b)
  multilayer.rs         — MultiLayer: top-level solver, field calculation, Python API
  transfer_matrix.rs    — Transfer Matrix Method (TMM) backend
  scattering_matrix.rs  — Scattering Matrix Method (SMM) backend
  bin/remsol.rs         — CLI binary entry point

test/
  test_slab.py          — pytest regression tests: single slab, coupled slab, asymmetric
  test_field.py         — pytest: field profile amplitude at a specific grid point

remsol.pyi              — Python type stubs (must stay in sync with lib.rs pymodule)
pyproject.toml          — Maturin + uv configuration
Cargo.toml              — Rust crate metadata and dependencies
CHANGELOG.md            — Auto-generated by python-semantic-release
docs/                   — Jupyter Book documentation + example notebooks
.github/workflows/      — CI: test.yml, publish_pypi.yml, publish_crate.yml, pages.yml
```

---

## Critical Build Rule

**After ANY change to Rust source files, the Python extension must be rebuilt before
running Python tests or using the Python API:**

```bash
maturin develop
```

Forgetting this step is the most common source of confusion — Python will silently use
the old compiled extension.

---

## All Commands

```bash
# Rebuild Python extension (required after any .rs change)
maturin develop

# Run Rust unit tests (no Python needed, tests live in multilayer.rs mod tests)
cargo test --lib

# Run all Python integration tests
uv run pytest test/

# Run only benchmarks
uv run pytest test/ --benchmark-only

# Run a specific test file
uv run pytest test/test_slab.py -v

# Check Rust code without building
cargo check

# Lint Rust code
cargo clippy

# Build release-mode extension (for performance measurement)
maturin develop --release
```

---

## Physics Domain

### Units and Conventions
- All **lengths** (layer thickness `d`) are in **micrometers (µm)**.
- The input parameter named `omega` in the API is actually **k₀ = ω/c = 2π/λ** in
  natural units where c = 1, with units of **rad/µm**. Example: `omega = 2π/1.55` for
  1550 nm light.
- **neff** (effective refractive index) is dimensionless and always real for guided modes.
- The parallel wavevector: `k_parallel = neff * omega`.
- The transverse wavevector in layer i: `k_z = sqrt((k0*n_i)² - k_parallel²)`.

### Layer Stack Convention
- Layers are ordered **left to right**: `layers[0]` is the left cladding,
  `layers[-1]` is the right cladding.
- The first and last layers act as semi-infinite claddings (their thickness `d` is
  ignored by the solver).
- Mode indices start at 0 (0 = fundamental mode).

### Polarizations
- **TE** (Transverse Electric): Ey is the dominant field component for propagation along z.
- **TM** (Transverse Magnetic): Hy is the dominant field component.

### Backends
- **Transfer Matrix Method (TMM)** — `BackEnd::Transfer` — supports both `neff()` and
  `field()`. This is the canonical, fully-featured backend.
- **Scattering Matrix Method (SMM)** — `BackEnd::Scattering` — supports `neff()` only;
  field calculation is not implemented for this backend.
- Default backend is Transfer.

### Mode Finding Strategy
- The solver scans neff between `n_min` (lowest cladding index) and `n_max` (highest
  layer index) looking for minima of the characteristic function (TMM: |t22|, SMM:
  |det(S)|).
- Peaks are detected using the `find_peaks` crate; accuracy is controlled by
  `required_accuracy`.

### Field Normalization
- Fields are normalised so that the integrated Poynting vector across the cross-section
  equals 1. See `FieldData::normalize` and `FieldData::get_poynting_vector`.

---

## Python API Surface

Exposed classes (registered in `lib.rs` pymodule, typed in `remsol.pyi`):

| Rust type     | Python name   | Purpose                                      |
|---------------|---------------|----------------------------------------------|
| `Polarization`| `Polarization`| Enum: `.TE`, `.TM`                           |
| `BackEnd`     | `BackEnd`     | Enum: `.Transfer`, `.Scattering`             |
| `Layer`       | `Layer`       | `Layer(n: float, d: float)`                  |
| `MultiLayer`  | `MultiLayer`  | `MultiLayer(layers: list[Layer])`            |
| `IndexData`   | `IndexData`   | Returned by `MultiLayer.index()`: x and n arrays |
| `FieldData`   | `FieldData`   | Returned by `MultiLayer.field()`: x, Ex, Ey, Ez, Hx, Hy, Hz |

Key `MultiLayer` methods:
- `neff(omega, polarization, mode) -> float`
- `field(omega, polarization, mode) -> FieldData`
- `index(omega) -> IndexData`

---

## Current Limitations (Known Scope Boundaries)

- Refractive index `n` is **real-valued only** (`f64`); complex indices (lossy/gain
  media) are not supported yet.
- Only **1D planar** structures; no 2D or 3D geometries.
- Only **guided modes** (real neff); leaky modes are not computed.
- The SMM backend does **not** support field calculation.

---

## Code Style and Conventions

### Rust
- All public structs, enums, and functions must have `///` doc comments.
- PyO3 Python-visible methods go in a dedicated `#[pymethods] impl` block, separate from
  the pure-Rust `impl` block.
- Internal Rust-only helpers are `fn` (private); only promote to `pub fn` when needed
  across modules.
- Avoid `clippy` warnings — CI runs `cargo test --lib` which will catch regressions.
- Use `num_complex::Complex<f64>` for all complex arithmetic.

### Python / Type Stubs
- **Always keep `remsol.pyi` in sync** whenever the Python-visible API changes in
  `lib.rs`. This file is the public contract for Python users and IDEs.
- Tests use `pytest.approx` for floating-point assertions.
- Test fixtures (MultiLayer instances) are defined at module level in each test file and
  reused across test functions.

### Versioning
- Version lives in `Cargo.toml` (`package.version`) only; `pyproject.toml` pulls it
  dynamically via Maturin.
- `python-semantic-release` bumps the version and generates `CHANGELOG.md` from
  conventional commits.
- Use conventional commit messages: `feat:`, `fix:`, `refactor:`, `test:`, `build:`,
  `docs:`.
- Version bump rules (Semantic Versioning):
  - `feat:` → **minor** bump (e.g. 0.1.3 → 0.2.0)
  - `fix:` or `perf:` → **patch** bump (e.g. 0.1.3 → 0.1.4)
  - `BREAKING CHANGE:` footer or `feat!:`/`fix!:` → **major** bump
  - `build:`, `chore:`, `ci:`, `docs:`, `style:`, `refactor:`, `test:` → no bump
- Releases are made from the `main` branch only. Develop on `dev`, merge to `main`
  when ready to release.

---

## Secrets and API Access

Sensitive credentials (e.g. API tokens) are stored in a **`.secrets`** file at the
repository root. This file is listed in `.gitignore` and must never be committed.

Format:
```
GITHUB_TOKEN=github_pat_...
```

To access the GitHub API (e.g. to fetch Actions logs), read the token from `.secrets`:

```bash
# Read token and use it
source .secrets
curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
     -H "Accept: application/vnd.github+json" \
     "https://api.github.com/repos/mpasson/REMSOL/actions/runs?per_page=5"
```

Or in a single call without sourcing:
```bash
GITHUB_TOKEN=$(grep GITHUB_TOKEN .secrets | cut -d= -f2)
curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
     -H "Accept: application/vnd.github+json" \
     "https://api.github.com/repos/mpasson/REMSOL/..."
```

The token has **read-only** access to the `mpasson/REMSOL` repository:
- **Actions** — read workflow runs and logs
- **Contents** — read repository files
- **Metadata** — read repository metadata

---

## Release Workflow

Releases are managed locally with `python-semantic-release` and then pushed to trigger
CI (which builds wheels and publishes to PyPI/crates.io).

```bash
# 1. Ensure you are on main and up to date
git checkout main
git pull

# 2. Ensure the working tree is clean — release must never be run with uncommitted changes
git status

# 3. Dry-run to preview the next version and changelog
uv run semantic-release --noop version

# 4. Create the release commit, tag, and update CHANGELOG.md — NO push
uv run semantic-release version --no-push --no-vcs-release

# 5. Review the commit and tag, then push when ready
git log --oneline -3
git push && git push --tags
```

> **Important:** Always ensure `git status` shows a **clean working tree** before step 4.
> Any uncommitted changes will be included in the release commit by PSR, mixing
> infrastructure/housekeeping changes with the version bump. Commit or stash everything first.
>
> **Note:** Step 4 will:
> - Bump the version in `Cargo.toml`
> - Update `CHANGELOG.md`
> - Create a release commit and an annotated git tag (e.g. `v0.2.0`)
>
> Pushing the tag triggers the `publish_pypi.yml` and `publish_crate.yml` CI workflows.

---

## Development Workflow for New Features

1. Implement the feature in Rust (`src/`).
2. If the feature should be Python-visible:
   a. Add `#[pymethods]` in the relevant `impl` block.
   b. Register new classes in `lib.rs` pymodule if needed.
   c. Update `remsol.pyi` with the new type stubs and docstrings.
3. Run `maturin develop` to rebuild the extension.
4. Add or update Rust unit tests in `mod tests` inside the relevant `.rs` file.
5. Add or update Python integration tests in `test/`.
6. Run `cargo test --lib` and `uv run pytest test/` — both must pass.
7. Commit with a conventional commit message.
