PyFiberModes 0.14.3__tar.gz → 0.15.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/.zenodo.json +1 -1
  2. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/CHANGELOG.md +20 -1
  3. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PKG-INFO +1 -1
  4. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/__init__.py +7 -2
  5. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/_version.py +2 -2
  6. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/analysis.py +5 -2
  7. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/exceptions.py +4 -0
  8. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber.py +16 -3
  9. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fundamentals.py +8 -2
  10. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/mode.py +26 -7
  11. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/services.py +12 -3
  12. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/solver/multilayer/effective_index.py +14 -1
  13. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/solver/three_layer/cutoff.py +11 -1
  14. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/solver/two_layer/cutoff.py +11 -1
  15. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/solver/two_layer/effective_index.py +37 -15
  16. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes.egg-info/PKG-INFO +1 -1
  17. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes.egg-info/SOURCES.txt +1 -0
  18. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/conda.recipe/meta.yaml +1 -1
  19. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/api_policy.rst +5 -0
  20. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/code.rst +1 -1
  21. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/index.rst +1 -0
  22. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/migration_guide.rst +15 -0
  23. pyfibermodes-0.15.0/docs/source/public_api_contract.rst +164 -0
  24. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/pyproject.toml +1 -1
  25. pyfibermodes-0.15.0/tests/api_snapshot.json +72 -0
  26. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tests/test_advanced_features.py +24 -2
  27. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tests/test_public_api.py +38 -1
  28. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tests/test_validation.py +25 -1
  29. pyfibermodes-0.14.3/tests/api_snapshot.json +0 -54
  30. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/.coveragerc +0 -0
  31. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/.github/dependabot.yml +0 -0
  32. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/.github/workflows/deploy_PyPi.yml +0 -0
  33. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/.github/workflows/deploy_anaconda.yml +0 -0
  34. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/.github/workflows/deploy_coverage.yml +0 -0
  35. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/.github/workflows/deploy_documentation.yml +0 -0
  36. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/.github/workflows/quality.yml +0 -0
  37. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/.github/workflows/tests.yml +0 -0
  38. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/.gitignore +0 -0
  39. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/CITATION.cff +0 -0
  40. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/CONTRIBUTING.md +0 -0
  41. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/LICENSE +0 -0
  42. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/MANIFEST.in +0 -0
  43. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/Makefile +0 -0
  44. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/coordinates.py +0 -0
  45. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/directories.py +0 -0
  46. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/factory.py +0 -0
  47. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber_files/1550BHP.yaml +0 -0
  48. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber_files/DCF13.yaml +0 -0
  49. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber_files/DCF1300S_20.yaml +0 -0
  50. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber_files/DCF1300S_26.yaml +0 -0
  51. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber_files/DCF1300S_33.yaml +0 -0
  52. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber_files/DCF1300S_42.yaml +0 -0
  53. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber_files/F2028M12.yaml +0 -0
  54. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber_files/F2028M21.yaml +0 -0
  55. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber_files/F2028M24.yaml +0 -0
  56. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber_files/F2058G1.yaml +0 -0
  57. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber_files/F2058L1.yaml +0 -0
  58. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber_files/HI1060.yaml +0 -0
  59. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber_files/HP630.yaml +0 -0
  60. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber_files/SM1950.yaml +0 -0
  61. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber_files/SMF28.yaml +0 -0
  62. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber_files/fluorine_doped_1%_capillary.yaml +0 -0
  63. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber_files/fluorine_doped_2%_capillary.yaml +0 -0
  64. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber_files/test_fiber.yaml +0 -0
  65. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/fiber_files/test_multimode_fiber.yaml +0 -0
  66. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/field.py +0 -0
  67. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/loader.py +0 -0
  68. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/materials.py +0 -0
  69. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/mode_instances.py +0 -0
  70. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/models.py +0 -0
  71. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/optimization.py +0 -0
  72. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/plotting.py +0 -0
  73. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/propagation.py +0 -0
  74. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/solver/__init__.py +0 -0
  75. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/solver/base_solver.py +0 -0
  76. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/solver/multilayer/__init__.py +0 -0
  77. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/solver/protocols.py +0 -0
  78. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/solver/results.py +0 -0
  79. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/solver/three_layer/__init__.py +0 -0
  80. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/solver/two_layer/__init__.py +0 -0
  81. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/source.py +0 -0
  82. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes/stepindex.py +0 -0
  83. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes.egg-info/dependency_links.txt +0 -0
  84. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes.egg-info/requires.txt +0 -0
  85. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/PyFiberModes.egg-info/top_level.txt +0 -0
  86. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/README.rst +0 -0
  87. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/benchmarks/README.md +0 -0
  88. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/benchmarks/test_field_performance.py +0 -0
  89. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/Makefile +0 -0
  90. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/README.rst +0 -0
  91. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/SMF28/README.rst +0 -0
  92. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/SMF28/plot_smf28.py +0 -0
  93. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/SMF28/plot_smf28_dispersion_vs_wavelength.py +0 -0
  94. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/SMF28/plot_smf28_group_index_vs_wavelength.py +0 -0
  95. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/analysis/README.rst +0 -0
  96. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/analysis/plot_coupled_mode_exchange.py +0 -0
  97. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/analysis/plot_guided_mode_discovery.py +0 -0
  98. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/analysis/plot_structured_wavelength_sweep.py +0 -0
  99. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/basic/README.rst +0 -0
  100. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/basic/plot_U_vs_V.py +0 -0
  101. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/basic/plot_comparison_solvers.py +0 -0
  102. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/basic/plot_dispersion.py +0 -0
  103. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/basic/plot_effective_index.py +0 -0
  104. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/basic/plot_fiber_LP_modes.py +0 -0
  105. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/basic/plot_group_index.py +0 -0
  106. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/basic/plot_mode_field.py +0 -0
  107. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/basic/plot_mode_field_1.py +0 -0
  108. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/basic/plot_mode_field_2.py +0 -0
  109. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/basic/plot_neff_tapered_fiber.py +0 -0
  110. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/benchmarks/README.rst +0 -0
  111. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/benchmarks/plot_component_batching_speedup.py +0 -0
  112. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/benchmarks/plot_field_grid_scaling.py +0 -0
  113. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/benchmarks/plot_modal_cache_speedup.py +0 -0
  114. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/fields/README.rst +0 -0
  115. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/fields/plot_confinement_by_radius.py +0 -0
  116. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/fields/plot_polarization_rotation.py +0 -0
  117. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/fields/plot_vector_components.py +0 -0
  118. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/specialty_fibers/README.rst +0 -0
  119. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/specialty_fibers/plot_refractive_index_profile.py +0 -0
  120. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/specialty_fibers/plot_specialty_fiber_catalog.py +0 -0
  121. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/specialty_fibers/plot_specialty_mode_indices.py +0 -0
  122. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/validation/README.rst +0 -0
  123. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/validation/plot_effective_index_bounds.py +0 -0
  124. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/validation/plot_field_area_convergence.py +0 -0
  125. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/examples/validation/plot_normalized_effective_index.py +0 -0
  126. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/make.bat +0 -0
  127. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/_static/default.css +0 -0
  128. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/_static/favicon.png +0 -0
  129. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/_static/logo.png +0 -0
  130. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/_static/thumbnail.png +0 -0
  131. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/architecture.rst +0 -0
  132. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/code/analysis.rst +0 -0
  133. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/code/fibers.rst +0 -0
  134. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/code/fields.rst +0 -0
  135. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/code/fundamentals.rst +0 -0
  136. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/conf.py +0 -0
  137. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/examples.rst +0 -0
  138. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/getting_started.rst +0 -0
  139. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/performance.rst +0 -0
  140. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/references.rst +0 -0
  141. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/theory.rst +0 -0
  142. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/troubleshooting.rst +0 -0
  143. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/user_guide/fibers.rst +0 -0
  144. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/user_guide/fields.rst +0 -0
  145. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/user_guide/index.rst +0 -0
  146. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/user_guide/modes.rst +0 -0
  147. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/user_guide/reproducibility.rst +0 -0
  148. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/user_guide/sweeps.rst +0 -0
  149. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/user_guide/units.rst +0 -0
  150. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/docs/source/workflows.rst +0 -0
  151. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/pytest.ini +0 -0
  152. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/setup.cfg +0 -0
  153. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tests/__init__.py +0 -0
  154. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tests/helpers.py +0 -0
  155. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tests/test_api.py +0 -0
  156. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tests/test_architecture.py +0 -0
  157. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tests/test_architecture_enhancements.py +0 -0
  158. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tests/test_coordinates.py +0 -0
  159. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tests/test_docstrings.py +0 -0
  160. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tests/test_fiber.py +0 -0
  161. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tests/test_field.py +0 -0
  162. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tests/test_file_loading.py +0 -0
  163. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tests/test_physics_regression.py +0 -0
  164. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tests/test_release_tools.py +0 -0
  165. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tests/test_three_layer_solver.py +0 -0
  166. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tests/test_two_layer_solver.py +0 -0
  167. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tools/check_release.py +0 -0
  168. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tools/next_release_version.py +0 -0
  169. {pyfibermodes-0.14.3 → pyfibermodes-0.15.0}/tools/release_tag.py +0 -0
@@ -9,7 +9,7 @@
9
9
  "license": "MIT",
10
10
  "upload_type": "software",
11
11
  "access_right": "open",
12
- "version": "0.14.3",
12
+ "version": "0.15.0",
13
13
  "publication_date": "2026-09-23",
14
14
  "keywords": [
15
15
  "optical fibers",
@@ -6,6 +6,24 @@ use [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.15.0] - 2026-09-23
10
+
11
+ ### Added
12
+
13
+ - Published the definitive 1.0 public API table with units, mutation rules,
14
+ failure semantics, compatibility names, and CI-enforced signatures/defaults.
15
+ - Added ``UnsupportedGeometryError`` to distinguish valid but unsupported
16
+ calculations from invalid input and numerical non-convergence.
17
+
18
+ ### Changed
19
+
20
+ - ``Mode`` accepts both strings and ``Family`` members, requires positive
21
+ radial order, and raises ``ValidationError`` instead of runtime assertions.
22
+ - Standardized expected missing roots, unsupported geometry, invalid input,
23
+ derivative failures, and structured solver diagnostics across analysis APIs.
24
+ - Completed two-layer and multilayer solver assumptions, characteristic
25
+ equation residual documentation, units, and primary-reference attribution.
26
+
9
27
  ## [0.14.3] - 2026-09-23
10
28
 
11
29
  ### Fixed
@@ -123,7 +141,8 @@ use [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
123
141
 
124
142
  - Established the current tagged baseline for subsequent documented releases.
125
143
 
126
- [Unreleased]: https://github.com/MartinPdeS/PyFiberModes/compare/v0.14.3...HEAD
144
+ [Unreleased]: https://github.com/MartinPdeS/PyFiberModes/compare/v0.15.0...HEAD
145
+ [0.15.0]: https://github.com/MartinPdeS/PyFiberModes/releases/tag/v0.15.0
127
146
  [0.14.3]: https://github.com/MartinPdeS/PyFiberModes/releases/tag/v0.14.3
128
147
  [0.14.2]: https://github.com/MartinPdeS/PyFiberModes/releases/tag/v0.14.2
129
148
  [0.14.1]: https://github.com/MartinPdeS/PyFiberModes/releases/tag/v0.14.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyFiberModes
3
- Version: 0.14.3
3
+ Version: 0.15.0
4
4
  Summary: Guided-mode analysis and design for circular optical fibers.
5
5
  Author-email: Martin Poinsinet de Sivry-Houle <martin.poinsinet.de.sivry@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -26,7 +26,12 @@ from PyFiberModes.factory import FiberFactory
26
26
  from PyFiberModes.fiber import Fiber, load_fiber
27
27
  from PyFiberModes.field import Field
28
28
  from PyFiberModes.coordinates import CartesianCoordinates, CylindricalCoordinates
29
- from PyFiberModes.exceptions import ConvergenceError, SolverError, ValidationError
29
+ from PyFiberModes.exceptions import (
30
+ ConvergenceError,
31
+ SolverError,
32
+ UnsupportedGeometryError,
33
+ ValidationError,
34
+ )
30
35
  from PyFiberModes.models import LayerSpec, SolverSettings
31
36
  from PyFiberModes.solver.results import SolverResult
32
37
  from PyFiberModes.analysis import ModeSweepResult, find_modes, sweep_modes
@@ -57,7 +62,7 @@ __all__ = [
57
62
  'Field',
58
63
  'CartesianCoordinates', 'CylindricalCoordinates',
59
64
  'LayerSpec', 'SolverSettings', 'SolverResult',
60
- 'ValidationError', 'SolverError', 'ConvergenceError',
65
+ 'ValidationError', 'SolverError', 'ConvergenceError', 'UnsupportedGeometryError',
61
66
  'ModeSweepResult', 'find_modes', 'sweep_modes',
62
67
  'DesignParameter', 'DesignResult', 'optimize_fiber',
63
68
  'CoupledModeSystem', 'PropagationResult', 'coupling_matrix', 'overlap',
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.14.3'
22
- __version_tuple__ = version_tuple = (0, 14, 3)
21
+ __version__ = version = '0.15.0'
22
+ __version_tuple__ = version_tuple = (0, 15, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -6,6 +6,7 @@ from typing import Callable, Iterable, Sequence
6
6
 
7
7
  import numpy as np
8
8
 
9
+ from PyFiberModes.exceptions import ConvergenceError, ValidationError
9
10
  from PyFiberModes.mode import Mode
10
11
 
11
12
 
@@ -100,6 +101,8 @@ def candidate_modes(
100
101
  tuple of Mode
101
102
  Candidates ordered by family, azimuthal order, and radial order.
102
103
  """
104
+ if max_nu < 0 or max_m < 1:
105
+ raise ValidationError("max_nu must be non-negative and max_m must be positive")
103
106
  modes = []
104
107
  for family in families:
105
108
  nus = (0,) if family in ("TE", "TM") else range(max_nu + 1)
@@ -141,7 +144,7 @@ def find_modes(fiber, families=("LP",), max_nu=6, max_m=6) -> tuple[Mode, ...]:
141
144
  )
142
145
  if np.isfinite(effective_index) and guided:
143
146
  found.append(mode)
144
- except (ArithmeticError, AssertionError, RuntimeError, ValueError):
147
+ except ConvergenceError:
145
148
  continue
146
149
  return tuple(found)
147
150
 
@@ -226,6 +229,6 @@ def sweep_modes(
226
229
  try:
227
230
  value = metric_function(mode)
228
231
  values[metric][i, j] = value if np.isfinite(value) else np.nan
229
- except (ArithmeticError, AssertionError, RuntimeError, ValueError):
232
+ except ConvergenceError:
230
233
  pass
231
234
  return ModeSweepResult(parameter_name, parameters, modes, values)
@@ -15,3 +15,7 @@ class SolverError(PyFiberModesError, RuntimeError):
15
15
 
16
16
  class ConvergenceError(SolverError):
17
17
  """Indicate that a numerical solver did not converge to a valid root."""
18
+
19
+
20
+ class UnsupportedGeometryError(SolverError):
21
+ """Indicate that a requested calculation does not support the fiber geometry."""
@@ -21,7 +21,7 @@ from PyFiberModes.fundamentals import (
21
21
 
22
22
  from PyFiberModes import loader
23
23
  from PyFiberModes.coordinates import CylindricalCoordinates
24
- from PyFiberModes.exceptions import ValidationError
24
+ from PyFiberModes.exceptions import ConvergenceError, ValidationError
25
25
  from PyFiberModes.materials import DEFAULT_MATERIALS, MaterialRegistry
26
26
  from PyFiberModes.services import FieldAnalysis, ModalAnalysis
27
27
 
@@ -668,7 +668,10 @@ class Fiber(object):
668
668
  function_kwargs=dict(fiber=self, mode=mode)
669
669
  )
670
670
 
671
- return derivative * constants.c
671
+ value = derivative * constants.c
672
+ if not numpy.isfinite(value):
673
+ raise ConvergenceError(f"group-index derivative failed for {mode}")
674
+ return value
672
675
 
673
676
  def get_group_velocity(self, mode: Mode) -> float:
674
677
  """Compute the correctly named modal group velocity.
@@ -712,6 +715,10 @@ class Fiber(object):
712
715
  function_kwargs=dict(fiber=self, mode=mode)
713
716
  )
714
717
 
718
+ if not numpy.isfinite(derivative):
719
+ raise ConvergenceError(
720
+ f"group-velocity-dispersion derivative failed for {mode}"
721
+ )
715
722
  return derivative
716
723
 
717
724
  def get_dispersion(self, mode: Mode) -> float:
@@ -778,6 +785,9 @@ class Fiber(object):
778
785
  function_kwargs=dict(fiber=self, mode=mode)
779
786
  )
780
787
 
788
+ if not numpy.isfinite(derivative):
789
+ raise ConvergenceError(f"dispersion-slope derivative failed for {mode}")
790
+
781
791
  factor = 2 * numpy.pi * constants.c / self.wavelength**2
782
792
 
783
793
  return 1e-3 * derivative * factor**2
@@ -965,7 +975,10 @@ class Fiber(object):
965
975
  print(first_line, "-" * len(first_line))
966
976
  for mode in mode_list:
967
977
  data_type_string = f"get_{data_type.lower()}"
968
- data = getattr(self, data_type_string)(mode=mode)
978
+ try:
979
+ data = getattr(self, data_type_string)(mode=mode)
980
+ except ConvergenceError:
981
+ data = numpy.nan
969
982
  output_string = f"{mode = } \t {data_type}: {data}"
970
983
  print(output_string)
971
984
 
@@ -10,6 +10,7 @@ from PyFiberModes.mode_instances import HE11, LP01
10
10
  from scipy.constants import c
11
11
 
12
12
  from PyFiberModes.coordinates import CylindricalCoordinates
13
+ from PyFiberModes.exceptions import ConvergenceError, UnsupportedGeometryError
13
14
 
14
15
  if TYPE_CHECKING:
15
16
  from PyFiberModes.solver.results import SolverResult
@@ -139,7 +140,10 @@ def get_U_parameter(
139
140
  The \(U\) parameter.
140
141
  """
141
142
  from PyFiberModes import solver
142
- assert fiber.n_layer == 2, "U-parameter can only be calculated for two-layer fibers."
143
+ if fiber.n_layer != 2:
144
+ raise UnsupportedGeometryError(
145
+ "U-parameter calculations require exactly two fiber layers"
146
+ )
143
147
 
144
148
  effective_index_solver = solver.two_layer.EffectiveIndexSolver(
145
149
  fiber=fiber, wavelength=wavelength
@@ -253,7 +257,7 @@ def get_mode_cutoff_v0(
253
257
  case 3:
254
258
  cutoff_solver = solver.three_layer.CutoffSolver(fiber=fiber, wavelength=wavelength)
255
259
  case _:
256
- raise NotImplementedError(
260
+ raise UnsupportedGeometryError(
257
261
  "Cutoff calculations are currently available for two- and three-layer fibers"
258
262
  )
259
263
 
@@ -306,6 +310,8 @@ def get_radial_field(
306
310
  wavelength=fiber.wavelength,
307
311
  mode=mode
308
312
  )
313
+ if not numpy.isfinite(neff):
314
+ raise ConvergenceError(f"no guided effective-index solution for {mode}")
309
315
 
310
316
  kwargs = dict(
311
317
  nu=mode.nu,
@@ -3,7 +3,15 @@
3
3
  from enum import Enum
4
4
  from dataclasses import dataclass
5
5
 
6
- Family = Enum('Family', 'LP HE EH TE TM', module=__name__)
6
+
7
+ class Family(str, Enum):
8
+ """Supported scalar and vector mode families."""
9
+
10
+ LP = "LP"
11
+ HE = "HE"
12
+ EH = "EH"
13
+ TE = "TE"
14
+ TM = "TM"
7
15
 
8
16
 
9
17
  @dataclass(frozen=True, eq=True)
@@ -19,7 +27,7 @@ class Mode():
19
27
  m : int
20
28
  Non-negative radial order.
21
29
  """
22
- family: str
30
+ family: str | Family
23
31
  """ Family of the mode """
24
32
  nu: int
25
33
  """ Parameter of the mode. It often corresponds to the parameter of the radial Bessel functions. """
@@ -31,14 +39,25 @@ class Mode():
31
39
 
32
40
  Raises
33
41
  ------
34
- AssertionError
35
- If the family is unknown or either modal order is negative.
42
+ ValidationError
43
+ If the family is unknown, ``nu`` is negative, or ``m`` is not a
44
+ positive integer.
36
45
  """
37
- assert self.family in ['LP', 'HE', 'EH', 'TE', 'TM'], f'Unexpected mode family: {self.family}'
46
+ from PyFiberModes.exceptions import ValidationError
38
47
 
39
- assert self.nu >= 0, 'Unexpected negative nu value'
48
+ try:
49
+ family = self.family if isinstance(self.family, Family) else Family(self.family)
50
+ except (TypeError, ValueError) as error:
51
+ choices = ", ".join(member.value for member in Family)
52
+ raise ValidationError(
53
+ f"unknown mode family {self.family!r}; expected one of {choices}"
54
+ ) from error
55
+ if not isinstance(self.nu, int) or isinstance(self.nu, bool) or self.nu < 0:
56
+ raise ValidationError("nu must be a non-negative integer")
57
+ if not isinstance(self.m, int) or isinstance(self.m, bool) or self.m < 1:
58
+ raise ValidationError("m must be a positive integer")
40
59
 
41
- assert self.m >= 0, 'Unexpected negative m value'
60
+ object.__setattr__(self, "family", family.value)
42
61
 
43
62
  def __repr__(self) -> str:
44
63
  """Return the compact family-and-order representation.
@@ -2,7 +2,12 @@
2
2
 
3
3
  import numpy as np
4
4
 
5
- from PyFiberModes.exceptions import ConvergenceError
5
+ from PyFiberModes.exceptions import (
6
+ ConvergenceError,
7
+ UnsupportedGeometryError,
8
+ ValidationError,
9
+ )
10
+ from PyFiberModes.mode import Mode
6
11
  from PyFiberModes.models import SolverSettings
7
12
  from PyFiberModes.solver.results import SolverResult
8
13
 
@@ -45,6 +50,8 @@ class ModalAnalysis:
45
50
  SolverResult[float]
46
51
  Cached value and convergence status.
47
52
  """
53
+ if not isinstance(mode, Mode):
54
+ raise ValidationError("mode must be a Mode instance")
48
55
  key = self._key("effective_index", mode)
49
56
  if key in self._cache:
50
57
  return self._cache[key]
@@ -58,6 +65,8 @@ class ModalAnalysis:
58
65
  mode=mode,
59
66
  delta_neff=self.settings.effective_index_step,
60
67
  )
68
+ except (ValidationError, UnsupportedGeometryError):
69
+ raise
61
70
  except (ArithmeticError, RuntimeError, ValueError) as error:
62
71
  result = SolverResult(value=None, converged=False, message=str(error))
63
72
 
@@ -100,7 +109,7 @@ class ModalAnalysis:
100
109
  converged=converged,
101
110
  message="converged" if converged else f"no cutoff solution for {mode}",
102
111
  )
103
- except (ArithmeticError, RuntimeError, ValueError) as error:
112
+ except (ArithmeticError, ValueError) as error:
104
113
  self._cache[key] = SolverResult(
105
114
  value=None, converged=False, message=str(error)
106
115
  )
@@ -145,7 +154,7 @@ class FieldAnalysis:
145
154
  from PyFiberModes.field import Field
146
155
 
147
156
  if n_point < 2:
148
- raise ValueError("n_point must be at least two")
157
+ raise ValidationError("n_point must be at least two")
149
158
  return Field(
150
159
  fiber=self.fiber,
151
160
  mode=mode,
@@ -1,4 +1,11 @@
1
- """Effective-index solver for multilayer step-index fibers."""
1
+ """Transfer-matrix solver for isotropic concentric step-index fibers.
2
+
3
+ Each homogeneous annulus is represented by cylindrical Bessel or modified
4
+ Bessel solutions and tangential fields are matched at every interface. The
5
+ model assumes circular symmetry, scalar real refractive indices, lossless
6
+ materials, and a longitudinally invariant cross-section. See Snyder and Love,
7
+ *Optical Waveguide Theory*, chapters 12 and 19.
8
+ """
2
9
 
3
10
  import numpy
4
11
  from scipy.special import kn, kvp, k0, k1, jn, jvp, yn, yvp, iv, ivp
@@ -20,6 +27,12 @@ class EffectiveIndexSolver(BaseSolver):
20
27
  Multilayer fiber to solve.
21
28
  wavelength : float
22
29
  Vacuum wavelength in meters.
30
+
31
+ Notes
32
+ -----
33
+ LP, TE, TM, HE, and EH families are supported. The solver does not model
34
+ anisotropy, material loss, stress birefringence, non-circular interfaces,
35
+ or longitudinal tapering within a solve.
23
36
  """
24
37
  def get_neff_lower_boundary(self, mode: Mode, delta_neff: float = 1e-6) -> float:
25
38
  """Gets the lower boundary for neff value.
@@ -1,4 +1,9 @@
1
- """Modal cutoff solver for three-layer step-index fibers."""
1
+ """Cutoff solver for isotropic three-layer step-index fibers.
2
+
3
+ The solver matches cylindrical solutions across two concentric interfaces and
4
+ returns dimensionless normalized cutoff frequencies. It assumes homogeneous,
5
+ lossless, isotropic layers with scalar real refractive indices.
6
+ """
2
7
 
3
8
  from PyFiberModes.mode import Mode
4
9
  from PyFiberModes.mode_instances import HE11, LP01, LP11, TE01
@@ -35,6 +40,11 @@ class CutoffSolver(BaseSolver):
35
40
  Three-layer fiber to solve.
36
41
  wavelength : float
37
42
  Reference vacuum wavelength in meters.
43
+
44
+ Notes
45
+ -----
46
+ Only cutoff calculations are provided by this backend. Effective indices
47
+ and fields for the same geometry are handled by the multilayer solver.
38
48
  """
39
49
 
40
50
  def get_lower_neff_mode(self, mode: Mode) -> Mode:
@@ -1,4 +1,9 @@
1
- """Modal cutoff solver for two-layer step-index fibers."""
1
+ """Analytical cutoff solver for isotropic two-layer step-index fibers.
2
+
3
+ Cutoffs are zeros of the appropriate cylindrical Bessel characteristic
4
+ equation and are returned as dimensionless normalized frequencies. The
5
+ formulation follows Snyder and Love, *Optical Waveguide Theory*, chapter 12.
6
+ """
2
7
 
3
8
  import numpy
4
9
  import logging
@@ -21,6 +26,11 @@ class CutoffSolver(BaseSolver):
21
26
  Standard step-index fiber.
22
27
  wavelength : float
23
28
  Reference vacuum wavelength in meters.
29
+
30
+ Notes
31
+ -----
32
+ The fiber must contain exactly one circular core and one homogeneous
33
+ exterior cladding. LP cutoffs use the weak-guidance approximation.
24
34
  """
25
35
  logger = logging.getLogger(__name__)
26
36
 
@@ -1,4 +1,11 @@
1
- """Effective-index and radial-field solver for two-layer fibers."""
1
+ """Exact scalar and vector solver for isotropic two-layer step-index fibers.
2
+
3
+ The implementation matches cylindrical Bessel solutions across one circular
4
+ core-cladding interface. LP equations use the weak-guidance approximation;
5
+ TE, TM, HE, and EH equations retain the vector boundary conditions. Material
6
+ indices are real, lossless, and evaluated at one vacuum wavelength. See
7
+ Snyder and Love, *Optical Waveguide Theory*, chapters 11--12.
8
+ """
2
9
 
3
10
  import numpy
4
11
  import logging
@@ -22,6 +29,12 @@ class EffectiveIndexSolver(BaseSolver):
22
29
  Standard step-index fiber.
23
30
  wavelength : float
24
31
  Vacuum wavelength in meters.
32
+
33
+ Notes
34
+ -----
35
+ The solver requires exactly two concentric, homogeneous, isotropic layers
36
+ with a higher-index inner core. Characteristic functions are dimensionless;
37
+ their roots give the dimensionless effective index.
25
38
  """
26
39
 
27
40
  def get_mode_with_lower_neff(self, mode: Mode) -> Mode:
@@ -542,10 +555,11 @@ class EffectiveIndexSolver(BaseSolver):
542
555
  return value
543
556
 
544
557
  def get_TE_equation(self, neff: float, nu: int) -> float:
545
- """Return the value of the phase matching equation for TE mode.
558
+ r"""Return the TE characteristic-equation residual.
546
559
 
547
560
  .. math::
548
- U * j_0(U) * k_1(W) + W * j_1(U) * k_0(W)
561
+
562
+ U J_0(U) K_1(W) + W J_1(U) K_0(W)
549
563
 
550
564
  Parameters
551
565
  ----------
@@ -557,17 +571,20 @@ class EffectiveIndexSolver(BaseSolver):
557
571
  Returns
558
572
  -------
559
573
  float
560
- Dont know
574
+ Dimensionless boundary-condition residual. A guided TE solution
575
+ occurs at a root.
561
576
  """
562
577
  U, W, _ = self.get_U_W_V_parameter(neff=neff)
563
578
 
564
579
  return U * j0(U) * k1(W) + W * j1(U) * k0(W)
565
580
 
566
581
  def get_TM_equation(self, neff: float, nu: int) -> float:
567
- """Return the value of the phase matching equation for TM mode.
582
+ r"""Return the TM characteristic-equation residual.
568
583
 
569
584
  .. math::
570
- U * j_0(U) * k_1(W) * n_{clad}^2 + W * j_1(U) * k_0(W) * n_{core}^2
585
+
586
+ U J_0(U) K_1(W)n_\mathrm{clad}^2
587
+ + W J_1(U) K_0(W)n_\mathrm{core}^2
571
588
 
572
589
  Parameters
573
590
  ----------
@@ -579,7 +596,8 @@ class EffectiveIndexSolver(BaseSolver):
579
596
  Returns
580
597
  -------
581
598
  float
582
- Dont know
599
+ Dimensionless boundary-condition residual. A guided TM solution
600
+ occurs at a root.
583
601
  """
584
602
  core, clad = self.fiber.layers
585
603
 
@@ -591,8 +609,8 @@ class EffectiveIndexSolver(BaseSolver):
591
609
 
592
610
  return U * j0(U) * k1(W) * n_clad**2 + W * j1(U) * k0(W) * n_core**2
593
611
 
594
- def get_HE_EH_terms(self, neff, nu: int) -> float:
595
- """Return the value of the terms for the equation for HE or EH mode.
612
+ def get_HE_EH_terms(self, neff, nu: int) -> tuple[float, float]:
613
+ """Return the common and splitting terms of the hybrid equations.
596
614
 
597
615
  Parameters
598
616
  ----------
@@ -603,8 +621,10 @@ class EffectiveIndexSolver(BaseSolver):
603
621
 
604
622
  Returns
605
623
  -------
606
- float
607
- Dont know
624
+ tuple of float
625
+ Dimensionless common boundary term and non-negative hybrid
626
+ splitting term. Their sum and difference form the HE and EH
627
+ characteristic residuals, respectively.
608
628
  """
609
629
  core, clad = self.fiber.layers
610
630
 
@@ -628,7 +648,7 @@ class EffectiveIndexSolver(BaseSolver):
628
648
  return term_0, term_4
629
649
 
630
650
  def get_HE_equation(self, neff: float, nu: int) -> float:
631
- """Return the value of the phase matching equation for HE mode.
651
+ """Return the dimensionless HE characteristic-equation residual.
632
652
 
633
653
  Parameters
634
654
  ----------
@@ -640,7 +660,8 @@ class EffectiveIndexSolver(BaseSolver):
640
660
  Returns
641
661
  -------
642
662
  float
643
- Dont know
663
+ Boundary-condition residual; a guided HE solution occurs at a
664
+ root.
644
665
  """
645
666
  term_0, term_1 = self.get_HE_EH_terms(
646
667
  neff=neff,
@@ -652,7 +673,7 @@ class EffectiveIndexSolver(BaseSolver):
652
673
  return value
653
674
 
654
675
  def get_EH_equation(self, neff: float, nu: int) -> float:
655
- """Return the value of the phase matching equation for EH mode.
676
+ """Return the dimensionless EH characteristic-equation residual.
656
677
 
657
678
  Parameters
658
679
  ----------
@@ -664,7 +685,8 @@ class EffectiveIndexSolver(BaseSolver):
664
685
  Returns
665
686
  -------
666
687
  float
667
- Dont know
688
+ Boundary-condition residual; a guided EH solution occurs at a
689
+ root.
668
690
  """
669
691
  term_0, term_1 = self.get_HE_EH_terms(
670
692
  neff=neff,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyFiberModes
3
- Version: 0.14.3
3
+ Version: 0.15.0
4
4
  Summary: Guided-mode analysis and design for circular optical fibers.
5
5
  Author-email: Martin Poinsinet de Sivry-Houle <martin.poinsinet.de.sivry@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -123,6 +123,7 @@ docs/source/getting_started.rst
123
123
  docs/source/index.rst
124
124
  docs/source/migration_guide.rst
125
125
  docs/source/performance.rst
126
+ docs/source/public_api_contract.rst
126
127
  docs/source/references.rst
127
128
  docs/source/theory.rst
128
129
  docs/source/troubleshooting.rst
@@ -1,6 +1,6 @@
1
1
  package:
2
2
  name: pyfibermodes
3
- version: 0.14.3
3
+ version: 0.15.0
4
4
 
5
5
  source:
6
6
  path: ../
@@ -15,6 +15,11 @@ The public surface is stored in ``tests/api_snapshot.json`` and checked in CI.
15
15
  An intentional API change must update that snapshot, the changelog, and the
16
16
  :doc:`migration_guide` in the same pull request.
17
17
 
18
+ The definitive object-by-object contract, including units, mutation, and
19
+ failure behavior, is published in :doc:`public_api_contract`. CI freezes the
20
+ parameter order and important defaults of the primary constructors, functions,
21
+ and :class:`~PyFiberModes.fiber.Fiber` façade methods.
22
+
18
23
  Internal modules
19
24
  ----------------
20
25
 
@@ -6,7 +6,7 @@ API reference
6
6
  The public API is grouped by task. NumPyDoc descriptions, signatures,
7
7
  inheritance, and source links are generated directly from the package.
8
8
  The supported import boundary and compatibility guarantees are defined in the
9
- :doc:`api_policy`; user-visible changes are collected in the
9
+ :doc:`api_policy` and :doc:`public_api_contract`; user-visible changes are collected in the
10
10
  :doc:`migration_guide`.
11
11
 
12
12
  .. toctree::
@@ -17,6 +17,7 @@
17
17
  Theory <theory.rst>
18
18
  Architecture <architecture.rst>
19
19
  API policy <api_policy.rst>
20
+ Public API contract <public_api_contract.rst>
20
21
  Migration <migration_guide.rst>
21
22
  Workflows <workflows.rst>
22
23
  Performance <performance.rst>
@@ -10,6 +10,21 @@ even though those modules are outside the stable public API.
10
10
  Unreleased
11
11
  ----------
12
12
 
13
+ Mode validation and failure semantics
14
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
15
+
16
+ ``Mode`` now accepts either strings or ``Family`` members and normalizes the
17
+ stored family to its string value. Radial order ``m`` must be at least one;
18
+ invalid families and orders raise ``ValidationError`` instead of relying on
19
+ Python assertions.
20
+
21
+ Unsupported cutoff or normalized-parameter geometries raise
22
+ ``UnsupportedGeometryError``. Non-finite wavelength derivatives raise
23
+ ``ConvergenceError``. Structured solver methods continue to represent an
24
+ unguided or unconverged mode with ``SolverResult(converged=False)``; scalar
25
+ effective-index and cutoff conveniences continue to return ``NaN`` for that
26
+ expected absence.
27
+
13
28
  Material loading
14
29
  ~~~~~~~~~~~~~~~~
15
30