Skip to main content

FDFD solver for simulating phase-driven fields through dielectric microelements

Project description

PyNJ

PyNJ is a small Python API for building finite-difference frequency-domain (FDFD) simulations of photonic nanojet-style microelement domains. It focuses on three pieces:

  • a domain: wavelength, refractive indices, grid resolution, bounds, PML, and microelements
  • a source line: the complex input field placed on a horizontal line in the domain
  • a result: solved electromagnetic fields, intensity, coordinates, source, and permittivity map

The public package name on PyPI is PyNJ; the Python import is:

import pynj

For compatibility, import PyNJ maps to the same public API.

Installation

pip install PyNJ

PyNJ depends on numpy, autograd, ceviche, and matplotlib.

Quick Start

from pathlib import Path
import numpy as np
import pynj

out = Path("demo_output")
out.mkdir(exist_ok=True)

domain = pynj.domain(
    lambda0=532e-9,
    n_bg=1.0,
    Lx=(-10e-6, 10e-6),
    Ly=(-8e-6, 12e-6),
    ppum=30,
    input_phase_line=9e-6,
)

domain.add_microelement(n=1.49, radius=3e-6, x0=-3e-6, y0=4e-6, shape="square")
domain.add_microelement(n=1.49, radius=2.5e-6, x0=4e-6, y0=4e-6, shape="circle")

phase = np.linspace(0.0, 2.0 * np.pi, 120)
source_item = pynj.build_source_line(phase, x_bounds=(-2e-6, 2e-6), ppum=30)

src_line = domain.build_empty_input_line()
src_line.insert_source((-2e-6, 2e-6), source_item)

result = domain.solve(src_line, title="quick_start", save_path=out)
result.preview(save_path=out / "field.png")

Units

All physical distances are in meters.

For readability, examples often write micrometer-scale numbers as 10e-6 or multiply by 1e6 when printing.

Domains

A domain is created with pynj.domain(...).

domain = pynj.domain(
    lambda0=532e-9,
    n_bg=1.0,
    Lx=(-10e-6, 10e-6),
    Ly=(-8e-6, 12e-6),
    ppum=30,
    pml_x=2e-6,
    pml_y=2e-6,
    input_phase_line=9e-6,
)

Domain Parameters

Parameter Type Meaning
lambda0 float Free-space wavelength in meters. Used to compute angular frequency.
n_bg float Background refractive index. The base permittivity is n_bg ** 2.
Lx float or (xmin, xmax) Physical x span. A float creates a centered domain from -Lx/2 to Lx/2; a tuple uses explicit bounds.
Ly float or (ymin, ymax) Physical y span. A float creates a centered domain; a tuple uses explicit bounds.
ppum int Pixels per micrometer. The grid spacing is dx = 1e-6 / ppum.
pml_x float PML thickness on each x side, in meters. Default: 2e-6.
pml_y float PML thickness on each y side, in meters. Default: 2e-6.
input_phase_line float or None y-coordinate where the source line is injected. If omitted, PyNJ places it just above the highest microelement.

After construction, the domain exposes:

Attribute Meaning
domain.x_min, domain.x_max Physical x bounds without PML.
domain.y_min, domain.y_max Physical y bounds without PML.
domain.Lx, domain.Ly Physical width and height.
domain.dx Grid spacing in meters.
domain.source_y Actual y-coordinate used for the input source line.
domain.source_x0 Center of the x support.
domain.source_width Width of the source support.

Source Line Position

domain.source_y is determined like this:

  1. If input_phase_line was provided, that value is used.
  2. Otherwise, if the domain has microelements, the line is placed at max(element.y0 + element.radius) + domain.dx.
  3. Otherwise, it is placed just inside the top boundary at domain.y_max - domain.dx.

During solving, the solver chooses the nearest y-grid index:

j_src = argmin(abs(y - domain.source_y))

The source is then inserted into the 2D simulation source array as:

source[:, j_src] = src_line

So the input field is a horizontal line at domain.source_y.

Microelements

Microelements are added with domain.add_microelement(...).

domain.add_microelement(
    n=1.49,
    radius=3e-6,
    x0=0.0,
    y0=4e-6,
    shape="square",
)

Microelement Parameters

Parameter Type Meaning
n float Refractive index inside the microelement. The solver writes n ** 2 into the permittivity map.
radius float Characteristic radius in meters. For squares this is the half-width.
x0, y0 float Center position in meters.
shape str Shape name. Supported: "circle", "square", "superformula".
**shape_kwargs varies Extra parameters for shape-specific masks.

For shape="superformula", use:

Parameter Meaning
m Symmetry/frequency parameter.
n1, n2, n3 Superformula exponents.

Example:

domain.add_microelement(
    n=1.49,
    radius=2.5e-6,
    x0=4e-6,
    y0=4e-6,
    shape="superformula",
    m=4,
    n1=3.5,
    n2=8.4,
    n3=8.4,
)

PyNJ checks that microelements stay inside the physical domain. Overlapping microelements are allowed only when they have the same refractive index.

Source Lines

The solver expects a 1D complex input line. PyNJ represents this with SourceLine.

There are two common workflows.

Full-Domain Source Line

Use this when you know the full input line:

src_line = domain.build_source_line(0.0)

Real values are interpreted as phase in radians and converted to exp(1j * phase). Complex values are used directly.

Narrow Source Chunks

If you have a narrow source, build it with its own x bounds and insert it into a full line:

phase = np.linspace(0.0, np.pi, 120)
source_item = pynj.build_source_line(
    phase,
    x_bounds=(-2e-6, 2e-6),
    ppum=30,
)

src_line = domain.build_empty_input_line()
src_line.insert_source((-2e-6, 2e-6), source_item)

This is important: domain.solve(...) does not guess where a narrow source belongs. The x-position is encoded by insert_source(...). Once inserted, the line has the full solver width and can be injected unambiguously.

SourceLine Attributes

Attribute Meaning
values Complex source samples.
x x-coordinate array in meters, if available.
length Number of samples.
resolution / dx Average x spacing in meters.
x_bounds (x_min, x_max) in meters.
physical_length x_max - x_min, in meters.
occupied_intervals Intervals inserted with insert_source.

SourceLine Methods

Method Meaning
insert_source(x_pos, source) Insert a scalar, array, or another SourceLine into an interval. Rejects overlapping insertions.
apply_mask(intervals) Keep only one interval or a list of intervals; set amplitude to zero elsewhere.
subset(x_pos) Return a new SourceLine containing only an interval.
preview(save_path=...) Plot the phase of the source line.
save(title=..., path=...) Save only the source line to .npz.

Load saved source lines with:

src_line = pynj.load_source_line("manual_input_line.npz")

load_source_line supports both new SourceLine.save(...) files and older simulation .npz files that contain a src_line key.

Time Reversal

domain.timereversal(...) computes a source line by back-propagating from a target point:

src_line = domain.timereversal(x_pnj=0.0, y_pnj=-2e-6)
src_line.apply_mask([(-9e-6, -1e-6), (1e-6, 9e-6)])

The returned object is a regular SourceLine, so it can be previewed, masked, saved, loaded, or passed to domain.solve(...).

Solving

Run a simulation with:

result = domain.solve(src_line, title="run_001", save_path="results")

Solve Parameters

Parameter Meaning
input_phase None, scalar phase, phase array, complex array, InputPhase, or SourceLine.
title Optional title. Also used to name saved .npz files when save_path is a directory.
save_path Output path for automatic result saving. Use None to disable automatic saving.

If save_path is a directory and title="run_001", the solver writes results/run_001.npz. If save_path=None, no automatic result file is written.

Where the Source Is Inserted

Inside the solver:

src_line = self._build_source_line(input_phase)
source = np.zeros_like(self.X, dtype=np.complex64)
source[:, self.j_src] = src_line

j_src is the y-index closest to domain.source_y. The x placement of narrow sources must already be represented in the full-width SourceLine.values.

Results

domain.solve(...) returns a SimulationResult.

Attribute Meaning
Hz Complex magnetic field.
Ex, Ey Complex electric field components.
Iz Normalized abs(Hz).
x, y Coordinate axes in meters, including PML.
src_line Injected full-width complex source line.
source Full 2D source array.
eps_r Relative permittivity map.
j_src y-grid index of the source line.
dx Grid spacing in meters.
lambda0, omega Wavelength and angular frequency.
NPML PML grid thickness as [NPMLx, NPMLy].

Save or plot a result:

result.preview(save_path="field.png")
result.save(path="result.npz", title="my_result")

Save methods write files and return None; they do not store hidden save_path state on the objects.

Saving and Loading Domains

Domains are saved as readable JSON:

domain.save(title="two_lenses", path="two_lenses.json")
domain2 = pynj.load_domain("two_lenses.json")

Save methods also accept positional arguments:

domain.save("two_lenses", "two_lenses.json")
src_line.save("manual_input_line", "manual_input_line.npz")
result.save("my_result", "result.npz")

The JSON stores all domain parameters and microelements needed to reconstruct the same domain.

Plotting

Each object has a small preview helper:

domain.preview(save_path="domain.png")
src_line.preview(save_path="phase.png")
result.preview(save_path="field.png")

If save_path is omitted, Matplotlib calls plt.show().

PyNJ sets a writable Matplotlib config directory automatically when it is imported, unless MPLCONFIGDIR is already set. The first plot can take a little longer because Matplotlib builds its font cache; after that it should be reused.

The domain preview uses a black/white mask-style view and draws the input phase line. The source preview plots phase in radians. The field preview plots the normalized magnetic-field magnitude.

Examples

Examples are installed with the package under pynj.examples and can be run with python -m:

python -m pynj.examples.domain_preview
python -m pynj.examples.source_lines
python -m pynj.examples.load_saved_source
python -m pynj.examples.time_reversal_forward

They write files to ./pynj_example_output.

The examples show:

  • domain creation with explicit bounds
  • square, circle, and superformula microelements
  • readable domain JSON files
  • manual source-line construction
  • insertion of narrow source chunks into full input lines
  • source-line saving/loading
  • time-reversal source generation
  • forward solving and result saving

Public API Summary

API Purpose
pynj.domain(...) Build a simulation domain.
domain.add_microelement(...) Add a lens/microelement.
domain.preview(...) Plot the domain mask and source line.
domain.save(...) Save a readable JSON domain.
pynj.load_domain(...) Load a saved domain JSON.
domain.build_empty_input_line() Build a full-width zero-amplitude SourceLine.
domain.build_source_line(values) Build a full-width SourceLine.
pynj.build_source_line(...) Build a standalone source chunk or line.
pynj.load_source_line(...) Load a saved source-line .npz.
domain.timereversal(...) Generate a time-reversal SourceLine.
domain.solve(...) Run a forward solve.
result.preview(...) Plot normalized abs(Hz).
result.save(...) Save result arrays to .npz.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pynj-0.2.2.tar.gz (17.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pynj-0.2.2-py3-none-any.whl (22.2 kB view details)

Uploaded Python 3

File details

Details for the file pynj-0.2.2.tar.gz.

File metadata

  • Download URL: pynj-0.2.2.tar.gz
  • Upload date:
  • Size: 17.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.9

File hashes

Hashes for pynj-0.2.2.tar.gz
Algorithm Hash digest
SHA256 563b7c39b9d4d3cb3ca7a36cf1a7af0d72de2d9aa4828e472a2d1ff6c7acfc41
MD5 7a46ca0d56f5e5ed971e883a873eef64
BLAKE2b-256 0bcef84305a13bd33bfdf0faf82164050e1fdb0f40b1516eb1d12698c4fe5bd1

See more details on using hashes here.

File details

Details for the file pynj-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: pynj-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 22.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.9

File hashes

Hashes for pynj-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 b301fcdd24c1b3589c6643851cbcfa79196da86af2100c8216bfd0407ab2dd09
MD5 90e3445ae751366f610c1599c6c114f2
BLAKE2b-256 656d26f7fdaf3d4db8854020e680d41de23095f20817f72df5afe066bb77f8a1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page