fibremodes/
├── solvers/ numerical eigenmode solver
├── analytical/
│ ├── LG/ Laguerre-Gaussian modes (ModesGen + toolbox)
│ └── HG/ Hermite-Gaussian modes (CPU, separable 1D)
├── utilities/ overlaps, transmission-matrix tools
└── tests/ notebooks and future pytest
Root-level ModesGen.py, mode_generation_core_library.py, and scalarmodesolver.py are deprecated compatibility shims.
Solve scalar wave equation under arbitrary refractive index profile (weakly guiding fibres).
- Module:
fibremodes.solvers.scalarmodesolver - Examples:
tests/scalarmodesolver_examples.ipynb
- High-level API:
fibremodes.analytical.LG.ModesGen(LGmodesclass) - Low-level toolbox:
fibremodes.analytical.LG.toolbox
LG modes for graded-index fibres use the standard near-field form
where
The expensive step is evaluating scipy.special.eval_genlaguerre mode by mode; for large mode groups and grid sizes (e.g. group 20 at 960×960) this is very slow because SciPy evaluates each polynomial independently over the full grid.
The GPU path (engine='GPU', default) avoids that loop by using the closed-form sum definition instead of SciPy's iterative evaluation:
Implementation in analytical/LG/toolbox/mode_generation_core_library.py:
-
Okernel(p, l, k)— precompute the factorial coefficients for all modes and$k$ indices (CPU). -
eval_genlaguerreGPU— build a power matrix(-1)^k x^kon the GPU once (shared across modes), multiply by the coefficients, and sum over$k$ in a single batched CuPy operation so all modes are computed together. -
LGmodes_GPU— apply the LG envelope (Gaussian, azimuthal phase, radial factor) and normalize, again vectorized over all modes.
This turns “$N_\mathrm{modes}$ sequential SciPy calls” into one GPU-friendly tensor contraction, which is where most of the speed-up comes from.
If GPU memory is tight, eval_genlaguerreGPU automatically falls back to chunked evaluation (see the memory check and block loop in that function). For very large grids or mode groups, reduce resolution, mode group, or ensure enough VRAM.
Example:
from fibremodes.analytical.LG.ModesGen import LGmodes
LGbases = LGmodes(
mfd=34, group=20, N=960, px_size=1,
generateModes=True, wholeSet=True,
engine="GPU", # uses eval_genlaguerreGPU + LGmodes_GPU
)fibremodes.utilities.overlaps— modal decomposition / reconstructionfibremodes.utilities.transmission_matrix_generator— synthetic MMF transmission matrices
- Module:
fibremodes.analytical.HG.fibremodes(makeHGModes)
Hermite-Gaussian modes are separable in Cartesian coordinates. Each 1D profile is
where scipy.special.eval_hermite),
The 2D mode is the outer product of two 1D profiles:
makeHGModes builds a triangular mode group of size
This path is CPU-only (NumPy/SciPy), but it stays fast because the expensive Hermite evaluation runs on 1D coordinate vectors only. For grid size
- Build 1D samples
$x = [-N_f/2,\ldots,N_f/2-1]$ . - Evaluate
$G$ distinct 1D profiles$\mathrm{HG}_J(x)$ once (makebasis/HG). - Form each 2D mode as an outer product with
einsum('...ki,...kj->kij', targetY, targetX)— no 2D Hermite loop.
So cost scales like
Example:
from fibremodes.analytical.HG.fibremodes import makeHGModes
# Nf x Nf grid, beam waist w0, G mode groups -> G*(G+1)/2 modes
HGmodes = makeHGModes(Nf=512, w0=17, G=16)
# shape: (mode_index, Nf, Nf)from fibremodes.solvers.scalarmodesolver import scalarmodeEigsSolver
from fibremodes.analytical.LG.ModesGen import LGmodes
from fibremodes.analytical.LG.toolbox import ComputeAllLGmodes_list, graded_index_fiber_coefs
from fibremodes.analytical.HG.fibremodes import makeHGModes
from fibremodes.utilities.overlaps import overlapspip install "fibremodes[gpu] @ git+https://github.com/MarKo7s/fibremodes.git@v1.0.0"Other packages can pin the same dependency in requirements.txt:
fibremodes[gpu] @ git+https://github.com/MarKo7s/fibremodes.git@v1.0.0
git clone git@github.com:MarKo7s/fibremodes.git
cd fibremodes
pip install -e ".[gpu,parallel,notebooks]"conda create -n fibremodes python=3.11 -y
conda activate fibremodes
pip install -e ".[gpu,parallel,notebooks]"cupy-cuda13x targets CUDA toolkit 13.x (e.g. 13.2). The toolkit installer sets system CUDA_PATH, but terminals/Jupyter kernels started before install (or outside conda) may not see it.
This env can set CUDA_PATH via conda env config vars and prepend %CUDA_PATH%\bin on conda activate fibremodes. Restart the Jupyter kernel after activating.
Register the Jupyter kernel (once):
python -m ipykernel install --user --name fibremodes --display-name "Python (fibremodes)"requirements.txt remains available for legacy workflows; prefer pip install -e ".[gpu]" for package installs.
The package version is defined in one place only: pyproject.toml → [project].version.
Do not edit __init__.py on each release. fibremodes.__version__ is read from pip metadata after install (importlib.metadata).
Check the installed version:
pip show fibremodes
python -c "import fibremodes; print(fibremodes.__version__)"Use semantic versioning: MAJOR.MINOR.PATCH (e.g. 1.0.0 → 1.0.1 for fixes, 1.1.0 for features).
- Add an entry for the new version at the top of
CHANGELOG.md. - Bump
versioninpyproject.toml. - Commit all changes (including the changelog).
- Run the release script from the repo root:
python scripts/release.py --from-changelogThe script reads the version from pyproject.toml, pushes main, creates annotated git tag vX.Y.Z, and pushes the tag. With --from-changelog, the tag message is taken from the matching CHANGELOG.md section. You can also pass a custom message with --message "..." (overrides --from-changelog).
After that, others can install with:
pip install "fibremodes[gpu] @ git+https://github.com/MarKo7s/fibremodes.git@vX.Y.Z"Dry run (no git changes):
python scripts/release.py --from-changelog --dry-runOptional: create a GitHub Release page with the same notes (gh CLI required):
gh release create vX.Y.Z --title "fibremodes X.Y.Z" --notes-file CHANGELOG.mdRequirements before release: clean working tree (all changes committed); tag vX.Y.Z must not already exist on GitHub.
After cloning, use editable install for development:
pip install -e ".[gpu,parallel,notebooks]"
