PyFiberModes 0.17.7__tar.gz → 1.0.1__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 (179) hide show
  1. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/.zenodo.json +2 -2
  2. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/CHANGELOG.md +9 -1
  3. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PKG-INFO +34 -16
  4. pyfibermodes-0.17.7/PyFiberModes/fiber.py → pyfibermodes-1.0.1/PyFiberModes/_fiber.py +2 -2
  5. pyfibermodes-0.17.7/PyFiberModes/field.py → pyfibermodes-1.0.1/PyFiberModes/_field.py +2 -2
  6. pyfibermodes-1.0.1/PyFiberModes/_version.py +5 -0
  7. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/analysis.py +45 -19
  8. pyfibermodes-1.0.1/PyFiberModes/coordinates.py +153 -0
  9. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/factory.py +14 -13
  10. pyfibermodes-1.0.1/PyFiberModes/fiber.py +447 -0
  11. pyfibermodes-1.0.1/PyFiberModes/field.py +181 -0
  12. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fundamentals.py +1 -3
  13. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/loader.py +2 -1
  14. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/materials.py +43 -22
  15. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/models.py +9 -6
  16. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/optimization.py +39 -11
  17. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/plotting.py +1 -1
  18. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/propagation.py +36 -10
  19. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/services.py +1 -1
  20. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/solver/base_solver.py +4 -2
  21. pyfibermodes-1.0.1/PyFiberModes/source.py +100 -0
  22. pyfibermodes-1.0.1/PyFiberModes/units.py +67 -0
  23. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes.egg-info/PKG-INFO +34 -16
  24. pyfibermodes-1.0.1/PyFiberModes.egg-info/SOURCES.txt +83 -0
  25. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes.egg-info/requires.txt +7 -1
  26. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/README.rst +27 -14
  27. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/pyproject.toml +15 -8
  28. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/tests/test_advanced_features.py +11 -10
  29. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/tests/test_api.py +7 -5
  30. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/tests/test_architecture_enhancements.py +9 -8
  31. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/tests/test_coordinates.py +28 -27
  32. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/tests/test_fiber.py +7 -5
  33. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/tests/test_field.py +2 -2
  34. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/tests/test_file_loading.py +3 -2
  35. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/tests/test_physics_regression.py +6 -5
  36. pyfibermodes-1.0.1/tests/test_release_tools.py +32 -0
  37. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/tests/test_three_layer_solver.py +4 -2
  38. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/tests/test_two_layer_solver.py +3 -1
  39. pyfibermodes-1.0.1/tests/test_units.py +247 -0
  40. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/tests/test_validation.py +7 -5
  41. pyfibermodes-0.17.7/.coveragerc +0 -22
  42. pyfibermodes-0.17.7/.github/dependabot.yml +0 -10
  43. pyfibermodes-0.17.7/.github/workflows/deploy_coverage.yml +0 -21
  44. pyfibermodes-0.17.7/.github/workflows/deploy_documentation.yml +0 -20
  45. pyfibermodes-0.17.7/.github/workflows/deploy_release.yml +0 -35
  46. pyfibermodes-0.17.7/.github/workflows/quality.yml +0 -21
  47. pyfibermodes-0.17.7/.github/workflows/tests.yml +0 -19
  48. pyfibermodes-0.17.7/.gitignore +0 -44
  49. pyfibermodes-0.17.7/CONTRIBUTING.md +0 -9
  50. pyfibermodes-0.17.7/Makefile +0 -61
  51. pyfibermodes-0.17.7/PyFiberModes/_version.py +0 -34
  52. pyfibermodes-0.17.7/PyFiberModes/source.py +0 -81
  53. pyfibermodes-0.17.7/PyFiberModes.egg-info/SOURCES.txt +0 -168
  54. pyfibermodes-0.17.7/benchmarks/README.md +0 -11
  55. pyfibermodes-0.17.7/benchmarks/test_field_performance.py +0 -32
  56. pyfibermodes-0.17.7/conda.recipe/meta.yaml +0 -64
  57. pyfibermodes-0.17.7/docs/Makefile +0 -19
  58. pyfibermodes-0.17.7/docs/examples/README.rst +0 -8
  59. pyfibermodes-0.17.7/docs/examples/SMF28/README.rst +0 -6
  60. pyfibermodes-0.17.7/docs/examples/SMF28/plot_smf28.py +0 -26
  61. pyfibermodes-0.17.7/docs/examples/SMF28/plot_smf28_dispersion_vs_wavelength.py +0 -39
  62. pyfibermodes-0.17.7/docs/examples/SMF28/plot_smf28_group_index_vs_wavelength.py +0 -39
  63. pyfibermodes-0.17.7/docs/examples/analysis/README.rst +0 -6
  64. pyfibermodes-0.17.7/docs/examples/analysis/plot_coupled_mode_exchange.py +0 -22
  65. pyfibermodes-0.17.7/docs/examples/analysis/plot_guided_mode_discovery.py +0 -22
  66. pyfibermodes-0.17.7/docs/examples/analysis/plot_structured_wavelength_sweep.py +0 -27
  67. pyfibermodes-0.17.7/docs/examples/basic/README.rst +0 -6
  68. pyfibermodes-0.17.7/docs/examples/basic/plot_U_vs_V.py +0 -48
  69. pyfibermodes-0.17.7/docs/examples/basic/plot_comparison_solvers.py +0 -54
  70. pyfibermodes-0.17.7/docs/examples/basic/plot_dispersion.py +0 -50
  71. pyfibermodes-0.17.7/docs/examples/basic/plot_effective_index.py +0 -45
  72. pyfibermodes-0.17.7/docs/examples/basic/plot_fiber_LP_modes.py +0 -51
  73. pyfibermodes-0.17.7/docs/examples/basic/plot_group_index.py +0 -50
  74. pyfibermodes-0.17.7/docs/examples/basic/plot_mode_field.py +0 -47
  75. pyfibermodes-0.17.7/docs/examples/basic/plot_mode_field_1.py +0 -37
  76. pyfibermodes-0.17.7/docs/examples/basic/plot_mode_field_2.py +0 -49
  77. pyfibermodes-0.17.7/docs/examples/basic/plot_neff_tapered_fiber.py +0 -67
  78. pyfibermodes-0.17.7/docs/examples/benchmarks/README.rst +0 -7
  79. pyfibermodes-0.17.7/docs/examples/benchmarks/plot_component_batching_speedup.py +0 -33
  80. pyfibermodes-0.17.7/docs/examples/benchmarks/plot_field_grid_scaling.py +0 -28
  81. pyfibermodes-0.17.7/docs/examples/benchmarks/plot_modal_cache_speedup.py +0 -28
  82. pyfibermodes-0.17.7/docs/examples/fields/README.rst +0 -7
  83. pyfibermodes-0.17.7/docs/examples/fields/plot_confinement_by_radius.py +0 -28
  84. pyfibermodes-0.17.7/docs/examples/fields/plot_polarization_rotation.py +0 -27
  85. pyfibermodes-0.17.7/docs/examples/fields/plot_vector_components.py +0 -24
  86. pyfibermodes-0.17.7/docs/examples/specialty_fibers/README.rst +0 -7
  87. pyfibermodes-0.17.7/docs/examples/specialty_fibers/plot_refractive_index_profile.py +0 -26
  88. pyfibermodes-0.17.7/docs/examples/specialty_fibers/plot_specialty_fiber_catalog.py +0 -33
  89. pyfibermodes-0.17.7/docs/examples/specialty_fibers/plot_specialty_mode_indices.py +0 -25
  90. pyfibermodes-0.17.7/docs/examples/validation/README.rst +0 -7
  91. pyfibermodes-0.17.7/docs/examples/validation/plot_effective_index_bounds.py +0 -25
  92. pyfibermodes-0.17.7/docs/examples/validation/plot_field_area_convergence.py +0 -26
  93. pyfibermodes-0.17.7/docs/examples/validation/plot_normalized_effective_index.py +0 -31
  94. pyfibermodes-0.17.7/docs/make.bat +0 -35
  95. pyfibermodes-0.17.7/docs/redirect.html +0 -12
  96. pyfibermodes-0.17.7/docs/source/_static/default.css +0 -43
  97. pyfibermodes-0.17.7/docs/source/_static/favicon.png +0 -0
  98. pyfibermodes-0.17.7/docs/source/_static/logo.png +0 -0
  99. pyfibermodes-0.17.7/docs/source/_static/thumbnail.png +0 -0
  100. pyfibermodes-0.17.7/docs/source/api_policy.rst +0 -64
  101. pyfibermodes-0.17.7/docs/source/architecture.rst +0 -42
  102. pyfibermodes-0.17.7/docs/source/code/analysis.rst +0 -38
  103. pyfibermodes-0.17.7/docs/source/code/fibers.rst +0 -15
  104. pyfibermodes-0.17.7/docs/source/code/fields.rst +0 -11
  105. pyfibermodes-0.17.7/docs/source/code/fundamentals.rst +0 -22
  106. pyfibermodes-0.17.7/docs/source/code.rst +0 -45
  107. pyfibermodes-0.17.7/docs/source/conf.py +0 -41
  108. pyfibermodes-0.17.7/docs/source/examples.rst +0 -73
  109. pyfibermodes-0.17.7/docs/source/getting_started.rst +0 -69
  110. pyfibermodes-0.17.7/docs/source/guide.rst +0 -37
  111. pyfibermodes-0.17.7/docs/source/index.rst +0 -50
  112. pyfibermodes-0.17.7/docs/source/migration_guide.rst +0 -84
  113. pyfibermodes-0.17.7/docs/source/performance.rst +0 -20
  114. pyfibermodes-0.17.7/docs/source/public_api_contract.rst +0 -164
  115. pyfibermodes-0.17.7/docs/source/references.rst +0 -12
  116. pyfibermodes-0.17.7/docs/source/resources.rst +0 -62
  117. pyfibermodes-0.17.7/docs/source/theory.rst +0 -64
  118. pyfibermodes-0.17.7/docs/source/troubleshooting.rst +0 -33
  119. pyfibermodes-0.17.7/docs/source/user_guide/fibers.rst +0 -78
  120. pyfibermodes-0.17.7/docs/source/user_guide/fields.rst +0 -71
  121. pyfibermodes-0.17.7/docs/source/user_guide/index.rst +0 -24
  122. pyfibermodes-0.17.7/docs/source/user_guide/modes.rst +0 -70
  123. pyfibermodes-0.17.7/docs/source/user_guide/reproducibility.rst +0 -53
  124. pyfibermodes-0.17.7/docs/source/user_guide/sweeps.rst +0 -101
  125. pyfibermodes-0.17.7/docs/source/user_guide/units.rst +0 -59
  126. pyfibermodes-0.17.7/docs/source/workflows.rst +0 -35
  127. pyfibermodes-0.17.7/pytest.ini +0 -21
  128. pyfibermodes-0.17.7/tests/__init__.py +0 -1
  129. pyfibermodes-0.17.7/tests/api_snapshot.json +0 -72
  130. pyfibermodes-0.17.7/tests/helpers.py +0 -13
  131. pyfibermodes-0.17.7/tests/test_release_tools.py +0 -66
  132. pyfibermodes-0.17.7/tools/check_release.py +0 -88
  133. pyfibermodes-0.17.7/tools/next_release_version.py +0 -55
  134. pyfibermodes-0.17.7/tools/release_tag.py +0 -136
  135. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/CITATION.cff +0 -0
  136. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/LICENSE +0 -0
  137. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/MANIFEST.in +0 -0
  138. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/__init__.py +0 -0
  139. /pyfibermodes-0.17.7/PyFiberModes/coordinates.py → /pyfibermodes-1.0.1/PyFiberModes/_coordinates.py +0 -0
  140. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/directories.py +0 -0
  141. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/exceptions.py +0 -0
  142. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fiber_files/1550BHP.yaml +0 -0
  143. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fiber_files/DCF13.yaml +0 -0
  144. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fiber_files/DCF1300S_20.yaml +0 -0
  145. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fiber_files/DCF1300S_26.yaml +0 -0
  146. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fiber_files/DCF1300S_33.yaml +0 -0
  147. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fiber_files/DCF1300S_42.yaml +0 -0
  148. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fiber_files/F2028M12.yaml +0 -0
  149. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fiber_files/F2028M21.yaml +0 -0
  150. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fiber_files/F2028M24.yaml +0 -0
  151. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fiber_files/F2058G1.yaml +0 -0
  152. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fiber_files/F2058L1.yaml +0 -0
  153. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fiber_files/HI1060.yaml +0 -0
  154. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fiber_files/HP630.yaml +0 -0
  155. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fiber_files/SM1950.yaml +0 -0
  156. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fiber_files/SMF28.yaml +0 -0
  157. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fiber_files/fluorine_doped_1%_capillary.yaml +0 -0
  158. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fiber_files/fluorine_doped_2%_capillary.yaml +0 -0
  159. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fiber_files/test_fiber.yaml +0 -0
  160. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/fiber_files/test_multimode_fiber.yaml +0 -0
  161. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/mode.py +0 -0
  162. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/mode_instances.py +0 -0
  163. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/solver/__init__.py +0 -0
  164. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/solver/multilayer/__init__.py +0 -0
  165. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/solver/multilayer/effective_index.py +0 -0
  166. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/solver/protocols.py +0 -0
  167. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/solver/results.py +0 -0
  168. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/solver/three_layer/__init__.py +0 -0
  169. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/solver/three_layer/cutoff.py +0 -0
  170. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/solver/two_layer/__init__.py +0 -0
  171. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/solver/two_layer/cutoff.py +0 -0
  172. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/solver/two_layer/effective_index.py +0 -0
  173. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes/stepindex.py +0 -0
  174. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes.egg-info/dependency_links.txt +0 -0
  175. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/PyFiberModes.egg-info/top_level.txt +0 -0
  176. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/setup.cfg +0 -0
  177. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/tests/test_architecture.py +0 -0
  178. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/tests/test_docstrings.py +0 -0
  179. {pyfibermodes-0.17.7 → pyfibermodes-1.0.1}/tests/test_public_api.py +0 -0
@@ -9,8 +9,8 @@
9
9
  "license": "MIT",
10
10
  "upload_type": "software",
11
11
  "access_right": "open",
12
- "version": "0.17.7",
13
- "publication_date": "2026-10-23",
12
+ "version": "1.0.1",
13
+ "publication_date": "2026-10-27",
14
14
  "keywords": [
15
15
  "optical fibers",
16
16
  "guided modes",
@@ -6,6 +6,12 @@ use [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.0.1] - 2026-10-27
10
+
11
+ ## [1.0.0] - 2026-10-23
12
+
13
+ - Require TypedUnit/Pint quantities for physical inputs and return unit-bearing fiber, field, sweep, material, design and propagation results. Numerical solvers continue to operate in SI internally.
14
+
9
15
  ## [0.17.7] - 2026-10-23
10
16
 
11
17
  ## [0.17.6] - 2026-10-22
@@ -175,7 +181,9 @@ use [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
175
181
 
176
182
  - Established the current tagged baseline for subsequent documented releases.
177
183
 
178
- [Unreleased]: https://github.com/MartinPdeS/PyFiberModes/compare/v0.17.7...HEAD
184
+ [Unreleased]: https://github.com/MartinPdeS/PyFiberModes/compare/v1.0.1...HEAD
185
+ [1.0.1]: https://github.com/MartinPdeS/PyFiberModes/releases/tag/v1.0.1
186
+ [1.0.0]: https://github.com/MartinPdeS/PyFiberModes/releases/tag/v1.0.0
179
187
  [0.17.7]: https://github.com/MartinPdeS/PyFiberModes/releases/tag/v0.17.7
180
188
  [0.17.6]: https://github.com/MartinPdeS/PyFiberModes/releases/tag/v0.17.6
181
189
  [0.17.5]: https://github.com/MartinPdeS/PyFiberModes/releases/tag/v0.17.5
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyFiberModes
3
- Version: 0.17.7
3
+ Version: 1.0.1
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
@@ -699,10 +699,15 @@ Requires-Python: >=3.11
699
699
  Description-Content-Type: text/x-rst
700
700
  License-File: LICENSE
701
701
  Requires-Dist: PyFinitDiff
702
+ Requires-Dist: TypedUnit>=0.2.9
703
+ Requires-Dist: pint<0.26,>=0.24
702
704
  Requires-Dist: pyyaml
703
705
  Requires-Dist: numpy
704
706
  Requires-Dist: scipy
707
+ Provides-Extra: release
708
+ Requires-Dist: martinpdes-maintenance==0.2.2; extra == "release"
705
709
  Provides-Extra: testing
710
+ Requires-Dist: martinpdes-maintenance==0.2.2; extra == "testing"
706
711
  Requires-Dist: pytest<10.0,>=7.4; extra == "testing"
707
712
  Requires-Dist: pytest-cov<8,>=2; extra == "testing"
708
713
  Requires-Dist: pytest-json-report~=1.5; extra == "testing"
@@ -715,7 +720,7 @@ Requires-Dist: matplotlib>=3.8; extra == "plotting"
715
720
  Provides-Extra: benchmarking
716
721
  Requires-Dist: pytest-benchmark<6,>=5; extra == "benchmarking"
717
722
  Provides-Extra: documentation
718
- Requires-Dist: MartinPdeS-docs<0.3,>=0.2.5; extra == "documentation"
723
+ Requires-Dist: MartinPdeS-docs<0.5,>=0.4.0; extra == "documentation"
719
724
  Requires-Dist: matplotlib>=3.8; extra == "documentation"
720
725
  Provides-Extra: dev
721
726
  Requires-Dist: ruff<1,>=0.8; extra == "dev"
@@ -818,22 +823,25 @@ First mode calculation
818
823
  ----------------------
819
824
 
820
825
  Load the bundled SMF-28 definition at 1550 nm and solve its fundamental LP
821
- mode. Lengths and wavelengths are expressed in SI units.
826
+ mode. Physical inputs require Pint quantities; lengths may use any compatible unit.
827
+ Results also carry units, using the shared TypedUnit registry as in PyMieSim.
822
828
 
823
829
  .. code-block:: python
824
830
 
831
+ from PyFiberModes.units import ureg
832
+
825
833
  from PyFiberModes import LP01
826
834
  from PyFiberModes.fiber import load_fiber
827
835
 
828
- fiber = load_fiber("SMF28", wavelength=1550e-9)
836
+ fiber = load_fiber("SMF28", wavelength=1550e-9 * ureg.meter)
829
837
 
830
838
  effective_index = fiber.get_effective_index(LP01)
831
839
  propagation_constant = fiber.get_propagation_constant(LP01)
832
840
  dispersion = fiber.get_dispersion(LP01)
833
841
 
834
842
  print(f"n_eff = {effective_index:.9f}")
835
- print(f"beta = {propagation_constant:.6e} rad/m")
836
- print(f"D = {dispersion:.3f} ps/(nm km)")
843
+ print(f"beta = {propagation_constant:.6e}")
844
+ print(f"D = {dispersion:.3f}")
837
845
 
838
846
  ``load_fiber`` accepts the stem of any YAML definition in
839
847
  ``PyFiberModes/fiber_files``. A fiber can also be assembled programmatically
@@ -848,6 +856,8 @@ fiber index:
848
856
 
849
857
  .. code-block:: python
850
858
 
859
+ from PyFiberModes.units import ureg
860
+
851
861
  modes = fiber.find_modes(
852
862
  families=("LP",),
853
863
  max_nu=4,
@@ -869,9 +879,11 @@ values and whose columns correspond to modes.
869
879
 
870
880
  .. code-block:: python
871
881
 
882
+ from PyFiberModes.units import ureg
883
+
872
884
  import numpy as np
873
885
 
874
- wavelengths = np.linspace(1260e-9, 1650e-9, 101)
886
+ wavelengths = np.linspace(1260e-9, 1650e-9, 101) * ureg.meter
875
887
  result = fiber.sweep(
876
888
  wavelengths,
877
889
  modes=modes,
@@ -892,16 +904,18 @@ Create a two-dimensional field grid from a solved mode:
892
904
 
893
905
  .. code-block:: python
894
906
 
907
+ from PyFiberModes.units import ureg
908
+
895
909
  field = fiber.get_mode_field(
896
910
  mode=LP01,
897
- limit=20e-6,
911
+ limit=20e-6 * ureg.meter,
898
912
  n_point=201,
899
913
  )
900
914
 
901
915
  electric_x = field.Ex()
902
916
  intensity = field.Emod() ** 2
903
917
  effective_area = field.get_effective_area()
904
- confinement = field.get_confinement_factor(radius=4.1e-6)
918
+ confinement = field.get_confinement_factor(radius=4.1e-6 * ureg.meter)
905
919
  power = field.get_power()
906
920
  sx, sy, sz = field.get_poynting_vector()
907
921
 
@@ -921,17 +935,19 @@ scalar value to minimize.
921
935
 
922
936
  .. code-block:: python
923
937
 
938
+ from PyFiberModes.units import ureg
939
+
924
940
  from PyFiberModes import DesignParameter, optimize_fiber
925
941
 
926
942
  core_radius = DesignParameter.layer_radius(
927
943
  layer=0,
928
- bounds=(3.0e-6, 6.0e-6),
944
+ bounds=(3.0e-6 * ureg.meter, 6.0e-6 * ureg.meter),
929
945
  )
930
946
 
931
947
  design = optimize_fiber(
932
948
  fiber,
933
949
  parameters=[core_radius],
934
- objective=lambda candidate: abs(candidate.get_dispersion(LP01)),
950
+ objective=lambda candidate: abs(candidate.get_dispersion(LP01).to("ps / nm / km").magnitude),
935
951
  constraints=(
936
952
  lambda candidate: candidate.get_effective_index(LP01)
937
953
  - candidate.last_layer.refractive_index,
@@ -956,10 +972,12 @@ electric field. Modal amplitudes then evolve according to
956
972
 
957
973
  .. code-block:: python
958
974
 
975
+ from PyFiberModes.units import ureg
976
+
959
977
  import numpy as np
960
978
  from PyFiberModes import CoupledModeSystem
961
979
 
962
- multimode_fiber = load_fiber("test_multimode_fiber", wavelength=1550e-9)
980
+ multimode_fiber = load_fiber("test_multimode_fiber", wavelength=1550e-9 * ureg.meter)
963
981
  multimode_modes = multimode_fiber.find_modes(
964
982
  families=("LP",),
965
983
  max_nu=3,
@@ -968,16 +986,16 @@ electric field. Modal amplitudes then evolve according to
968
986
  selected_modes = multimode_modes[:2]
969
987
  fields = [multimode_fiber.get_mode_field(mode, n_point=151) for mode in selected_modes]
970
988
  betas = [multimode_fiber.get_propagation_constant(mode) for mode in selected_modes]
971
- relative_betas = np.asarray(betas) - betas[0]
989
+ relative_betas = ureg.Quantity.from_list(betas) - betas[0]
972
990
  mode_overlap = fields[0].overlap(fields[1])
973
991
 
974
992
  coupling = np.array([
975
993
  [0.0, 0.25],
976
994
  [0.25, 0.0],
977
- ])
995
+ ]) / ureg.meter
978
996
  system = CoupledModeSystem(betas=relative_betas, coupling=coupling)
979
997
 
980
- z = np.linspace(0, 20, 500)
998
+ z = np.linspace(0, 20, 500) * ureg.meter
981
999
  propagation = system.propagate(initial=[1.0, 0.0], z=z)
982
1000
  modal_power = propagation.powers
983
1001
 
@@ -1054,7 +1072,7 @@ Releases and changelog
1054
1072
 
1055
1073
  User-visible changes are recorded in
1056
1074
  `CHANGELOG.md <https://github.com/MartinPdeS/PyFiberModes/blob/master/CHANGELOG.md>`_.
1057
- Release helpers keep the Python package, Conda recipe, generated version,
1075
+ Release helpers keep the Python package, Conda recipe, checked-in version,
1058
1076
  Zenodo metadata, changelog, and annotated Git tag synchronized.
1059
1077
 
1060
1078
  .. code-block:: bash
@@ -12,7 +12,7 @@ from scipy.constants import c
12
12
  from PyFiberModes.stepindex import StepIndex
13
13
  from PyFiberModes import Mode
14
14
  from PyFinitDiff.finite_difference_1D import get_function_derivative
15
- from PyFiberModes.field import Field
15
+ from PyFiberModes._field import Field
16
16
 
17
17
  from PyFiberModes.fundamentals import (
18
18
  get_radial_field,
@@ -20,7 +20,7 @@ from PyFiberModes.fundamentals import (
20
20
  )
21
21
 
22
22
  from PyFiberModes import loader
23
- from PyFiberModes.coordinates import CylindricalCoordinates
23
+ from PyFiberModes._coordinates import CylindricalCoordinates
24
24
  from PyFiberModes.exceptions import ConvergenceError, ValidationError
25
25
  from PyFiberModes.materials import DEFAULT_MATERIALS, MaterialRegistry
26
26
  from PyFiberModes.services import FieldAnalysis, ModalAnalysis
@@ -7,8 +7,8 @@ import numpy as np
7
7
  from PyFiberModes.mode_instances import HE11
8
8
  from PyFiberModes.mode import Mode
9
9
  from dataclasses import dataclass
10
- from PyFiberModes.coordinates import CartesianCoordinates
11
- from PyFiberModes.coordinates import CylindricalCoordinates as CylindricalCoordinates # noqa: F401
10
+ from PyFiberModes._coordinates import CartesianCoordinates
11
+ from PyFiberModes._coordinates import CylindricalCoordinates as CylindricalCoordinates # noqa: F401
12
12
 
13
13
 
14
14
  @dataclass
@@ -0,0 +1,5 @@
1
+ """Release version maintained by martinpdes-maintenance."""
2
+
3
+ __version__ = version = "1.0.1"
4
+ __version_tuple__ = version_tuple = tuple(int(part) for part in version.split("."))
5
+ __commit_id__ = commit_id = None
@@ -8,6 +8,7 @@ import numpy as np
8
8
 
9
9
  from PyFiberModes.exceptions import ConvergenceError, ValidationError
10
10
  from PyFiberModes.mode import Mode
11
+ from PyFiberModes.units import MODAL_UNITS, Quantity, magnitude, quantity
11
12
 
12
13
 
13
14
  @dataclass(frozen=True)
@@ -30,9 +31,9 @@ class ModeSweepResult:
30
31
  """
31
32
 
32
33
  parameter_name: str
33
- parameters: np.ndarray
34
+ parameters: np.ndarray | Quantity
34
35
  modes: tuple[Mode, ...]
35
- values: dict[str, np.ndarray]
36
+ values: dict[str, np.ndarray | Quantity]
36
37
 
37
38
  def __getitem__(self, metric: str) -> np.ndarray:
38
39
  """Return the array associated with a metric name.
@@ -74,9 +75,11 @@ class ModeSweepResult:
74
75
  One record for every parameter and mode combination.
75
76
  """
76
77
  return [
77
- {self.parameter_name: float(parameter), "mode": mode, **{
78
- name: float(array[i, j]) for name, array in self.values.items()
79
- }}
78
+ {
79
+ self.parameter_name: parameter,
80
+ "mode": mode,
81
+ **{name: array[i, j] for name, array in self.values.items()},
82
+ }
80
83
  for i, parameter in enumerate(self.parameters)
81
84
  for j, mode in enumerate(self.modes)
82
85
  ]
@@ -137,11 +140,7 @@ def find_modes(fiber, families=("LP",), max_nu=6, max_m=6) -> tuple[Mode, ...]:
137
140
  for mode in candidate_modes(families, max_nu, max_m):
138
141
  try:
139
142
  effective_index = fiber.get_effective_index(mode)
140
- guided = (
141
- fiber.last_layer.refractive_index
142
- < effective_index
143
- <= fiber.maximum_index
144
- )
143
+ guided = fiber.last_layer.refractive_index < effective_index <= fiber.maximum_index
145
144
  if np.isfinite(effective_index) and guided:
146
145
  found.append(mode)
147
146
  except ConvergenceError:
@@ -167,8 +166,8 @@ def sweep_modes(
167
166
  ----------
168
167
  fiber : Fiber
169
168
  Fiber used as the immutable sweep template.
170
- parameters : iterable of float
171
- Ordered parameter values.
169
+ parameters : pint.Quantity or iterable of pint.Quantity
170
+ Ordered parameter values with explicit units for public fibers.
172
171
  modes : sequence of Mode, optional
173
172
  Modes to evaluate. When omitted, modes are discovered automatically.
174
173
  parameter_name : str, optional
@@ -195,7 +194,26 @@ def sweep_modes(
195
194
  If ``parameters`` is empty or not one-dimensional, or when a custom
196
195
  parameter is requested without a setter.
197
196
  """
198
- parameters = np.asarray(tuple(parameters), dtype=float)
197
+ unit_aware = hasattr(fiber, "_si")
198
+ parameter_unit = None
199
+ if unit_aware:
200
+ if isinstance(parameters, Quantity):
201
+ parameter_unit = parameters.units
202
+ if setter is None:
203
+ magnitude(parameters, "meter", "parameters")
204
+ parameters = np.asarray(parameters.magnitude, dtype=float)
205
+ else:
206
+ items = tuple(parameters)
207
+ if not items:
208
+ raise ValueError("parameters must be a non-empty one-dimensional sequence")
209
+ parameter_unit = items[0].units if isinstance(items[0], Quantity) else "meter"
210
+ parameters = np.asarray(
211
+ [magnitude(item, parameter_unit, "parameters") for item in items], dtype=float
212
+ )
213
+ if setter is None:
214
+ quantity(parameters, parameter_unit).to("meter")
215
+ else:
216
+ parameters = np.asarray(tuple(parameters), dtype=float)
199
217
  if parameters.ndim != 1 or not len(parameters):
200
218
  raise ValueError("parameters must be a non-empty one-dimensional sequence")
201
219
  if setter is None:
@@ -205,11 +223,14 @@ def sweep_modes(
205
223
  def setter(current_fiber, value):
206
224
  """Update wavelength for the default sweep behavior."""
207
225
  current_fiber.update_wavelength(value)
226
+
208
227
  if modes is None:
209
228
  discovered = []
210
229
  for parameter in parameters:
211
230
  parameter_fiber = deepcopy(fiber)
212
- setter(parameter_fiber, float(parameter))
231
+ setter(
232
+ parameter_fiber, quantity(parameter, parameter_unit) if unit_aware else parameter
233
+ )
213
234
  for mode in find_modes(parameter_fiber, families, max_nu, max_m):
214
235
  if mode not in discovered:
215
236
  discovered.append(mode)
@@ -218,17 +239,22 @@ def sweep_modes(
218
239
  values = {metric: np.full((len(parameters), len(modes)), np.nan) for metric in metrics}
219
240
  parameter_fibers = tuple(deepcopy(fiber) for _ in parameters)
220
241
  for parameter_fiber, parameter in zip(parameter_fibers, parameters, strict=True):
221
- setter(parameter_fiber, float(parameter))
242
+ setter(parameter_fiber, quantity(parameter, parameter_unit) if unit_aware else parameter)
222
243
 
223
244
  for i, parameter_fiber in enumerate(parameter_fibers):
224
- metric_functions = {
225
- metric: getattr(parameter_fiber, f"get_{metric}") for metric in metrics
226
- }
245
+ metric_functions = {metric: getattr(parameter_fiber, f"get_{metric}") for metric in metrics}
227
246
  for metric, metric_function in metric_functions.items():
228
247
  for j, mode in enumerate(modes):
229
248
  try:
230
249
  value = metric_function(mode)
231
- values[metric][i, j] = value if np.isfinite(value) else np.nan
250
+ values[metric][i, j] = (
251
+ (value.to(MODAL_UNITS[metric]).magnitude if unit_aware else value)
252
+ if np.isfinite(value)
253
+ else np.nan
254
+ )
232
255
  except ConvergenceError:
233
256
  pass
257
+ if unit_aware:
258
+ parameters = quantity(parameters, parameter_unit)
259
+ values = {metric: quantity(array, MODAL_UNITS[metric]) for metric, array in values.items()}
234
260
  return ModeSweepResult(parameter_name, parameters, modes, values)
@@ -0,0 +1,153 @@
1
+ """Length-valued Cartesian and cylindrical coordinate grids."""
2
+
3
+ from dataclasses import dataclass
4
+ import numpy as np
5
+
6
+ from PyFiberModes import _coordinates
7
+ from PyFiberModes.units import Angle, Length, magnitude, quantity
8
+
9
+
10
+ @dataclass
11
+ class CylindricalCoordinates:
12
+ """Cylindrical positions with length-valued rho/z and angle-valued phi."""
13
+
14
+ rho: Length
15
+ phi: Angle
16
+ z: Length
17
+
18
+ def __post_init__(self):
19
+ """Validate and normalize coordinate quantities."""
20
+ self.rho = quantity(magnitude(self.rho, "meter", "rho"), "meter")
21
+ self.phi = quantity(magnitude(self.phi, "radian", "phi"), "radian")
22
+ self.z = quantity(magnitude(self.z, "meter", "z"), "meter")
23
+
24
+ def to_cartesian(self):
25
+ """Convert cylindrical positions to Cartesian positions."""
26
+ return CartesianCoordinates(
27
+ x=self.rho * np.cos(self.phi),
28
+ y=self.rho * np.sin(self.phi),
29
+ z=self.z,
30
+ )
31
+
32
+ def to_cylindrical(self):
33
+ """Return these cylindrical positions."""
34
+ return self
35
+
36
+
37
+ @dataclass
38
+ class CartesianCoordinates:
39
+ """Cartesian positions with explicit length units on every axis."""
40
+
41
+ x: Length
42
+ y: Length
43
+ z: Length
44
+ is_structured: bool = False
45
+ is_3D: bool = False
46
+
47
+ def __post_init__(self):
48
+ """Validate lengths and normalize numerical coordinate arrays to meters."""
49
+ for name in ("x", "y", "z"):
50
+ setattr(
51
+ self,
52
+ name,
53
+ quantity(
54
+ np.asarray(magnitude(getattr(self, name), "meter", name), dtype=float), "meter"
55
+ ),
56
+ )
57
+
58
+ @classmethod
59
+ def _from_si(cls, coordinates):
60
+ """Attach length units to a numerical coordinate grid."""
61
+ return cls(
62
+ x=quantity(coordinates.x, "meter"),
63
+ y=quantity(coordinates.y, "meter"),
64
+ z=quantity(coordinates.z, "meter"),
65
+ is_structured=coordinates.is_structured,
66
+ is_3D=coordinates.is_3D,
67
+ )
68
+
69
+ @property
70
+ def x_boundaries(self):
71
+ """Minimum and maximum x positions."""
72
+ return self.x.min(), self.x.max()
73
+
74
+ @property
75
+ def y_boundaries(self):
76
+ """Minimum and maximum y positions."""
77
+ return self.y.min(), self.y.max()
78
+
79
+ @property
80
+ def dx(self):
81
+ """Structured grid spacing along x."""
82
+ if not self.is_structured:
83
+ raise ValueError("dx value cannot be inferred from an unstructured mesh")
84
+ return quantity(np.diff(np.unique(self.x.magnitude))[0], self.x.units)
85
+
86
+ @property
87
+ def dy(self):
88
+ """Structured grid spacing along y."""
89
+ if not self.is_structured:
90
+ raise ValueError("dy value cannot be inferred from an unstructured mesh")
91
+ return quantity(np.diff(np.unique(self.y.magnitude))[0], self.y.units)
92
+
93
+ @classmethod
94
+ def generate_from_square(cls, length: Length, center=None, n_points: int = 100):
95
+ """Generate a square grid with a length-valued side and center."""
96
+ center_si = (
97
+ (0.0, 0.0)
98
+ if center is None
99
+ else tuple(magnitude(item, "meter", "center") for item in center)
100
+ )
101
+ return cls._from_si(
102
+ _coordinates.CartesianCoordinates.generate_from_square(
103
+ length=magnitude(length, "meter", "length"),
104
+ center=center_si,
105
+ n_points=n_points,
106
+ )
107
+ )
108
+
109
+ @classmethod
110
+ def generate_from_boundaries(cls, x_limits, y_limits, z_limits, x_points, y_points, z_points):
111
+ """Generate a structured grid from length-valued axis limits."""
112
+ limits = [
113
+ tuple(magnitude(item, "meter", "limits") for item in axis)
114
+ for axis in (x_limits, y_limits, z_limits)
115
+ ]
116
+ return cls._from_si(
117
+ _coordinates.CartesianCoordinates.generate_from_boundaries(
118
+ x_limits=limits[0],
119
+ y_limits=limits[1],
120
+ z_limits=limits[2],
121
+ x_points=x_points,
122
+ y_points=y_points,
123
+ z_points=z_points,
124
+ )
125
+ )
126
+
127
+ def to_cylindrical(self):
128
+ """Convert positions to cylindrical coordinates."""
129
+ return CylindricalCoordinates(
130
+ rho=np.sqrt(self.x**2 + self.y**2),
131
+ phi=np.arctan2(self.y, self.x),
132
+ z=self.z,
133
+ )
134
+
135
+ def centering(self):
136
+ """Center each axis in place."""
137
+ self.x -= self.x.mean()
138
+ self.y -= self.y.mean()
139
+ self.z -= self.z.mean()
140
+
141
+ def shift_coordinates(self, shift):
142
+ """Shift the grid by three length-valued displacements."""
143
+ # Validate all inputs before mutating the grid.
144
+ values = [quantity(magnitude(item, "meter", "shift"), "meter") for item in shift]
145
+ self.x += values[0]
146
+ self.y += values[1]
147
+ self.z += values[2]
148
+
149
+ def scale_coordinates(self, scale):
150
+ """Scale axes by dimensionless factors."""
151
+ self.x *= float(scale[0])
152
+ self.y *= float(scale[1])
153
+ self.z *= float(scale[2])
@@ -4,6 +4,7 @@ from itertools import product
4
4
  from dataclasses import dataclass, field
5
5
  import numpy as np
6
6
  from PyFiberModes.fiber import Fiber
7
+ from PyFiberModes.units import Length, RefractiveIndex, magnitude, quantity, ureg
7
8
 
8
9
 
9
10
  @dataclass
@@ -15,9 +16,9 @@ class ProxyLayer:
15
16
  ----------
16
17
  name : str
17
18
  Name of the layer.
18
- radius : list[float]
19
+ radius : pint.Quantity
19
20
  Radius values for the layer (in meters).
20
- index : list[float]
21
+ index : pint.Quantity
21
22
  Refractive index values for the layer.
22
23
  """
23
24
  name: str
@@ -27,8 +28,8 @@ class ProxyLayer:
27
28
  def __post_init__(self):
28
29
  """Normalize scalar layer parameters into one-dimensional arrays."""
29
30
  self.name = [self.name]
30
- self.radius = np.atleast_1d(self.radius)
31
- self.index = np.atleast_1d(self.index)
31
+ self.radius = quantity(np.atleast_1d(magnitude(self.radius, "meter", "radius")), "meter")
32
+ self.index = quantity(np.atleast_1d(magnitude(self.index, "dimensionless", "index")), "dimensionless")
32
33
 
33
34
  def get_generator(self):
34
35
  """
@@ -48,40 +49,40 @@ class FiberFactory:
48
49
 
49
50
  Parameters
50
51
  ----------
51
- wavelength : float
52
+ wavelength : TypedUnit.Length
52
53
  Wavelength (in meters) used for fiber simulations.
53
54
 
54
55
  Attributes
55
56
  ----------
56
57
  layers_list : list[ProxyLayer]
57
58
  List of ProxyLayer objects representing the layers of the fiber.
58
- wavelength : float
59
+ wavelength : TypedUnit.Length
59
60
  Wavelength used in simulations.
60
61
  """
61
62
 
62
- def __init__(self, wavelength: float):
63
+ def __init__(self, wavelength: Length):
63
64
  """Initialize an empty fiber-configuration factory.
64
65
 
65
66
  Parameters
66
67
  ----------
67
- wavelength : float
68
+ wavelength : TypedUnit.Length
68
69
  Vacuum wavelength in meters for generated fibers.
69
70
  """
70
71
  self.layers_list = []
71
- self.wavelength = wavelength
72
+ self.wavelength = quantity(magnitude(wavelength, "meter", "wavelength"), "meter")
72
73
 
73
- def add_layer(self, index: float, name: str = "", radius: float = 0.0):
74
+ def add_layer(self, index: RefractiveIndex, name: str = "", radius: Length = np.inf * ureg.meter):
74
75
  """
75
76
  Add a new layer to the fiber factory.
76
77
 
77
78
  Parameters
78
79
  ----------
79
- index : float
80
+ index : TypedUnit.RefractiveIndex
80
81
  Refractive index of the layer.
81
82
  name : str, optional
82
83
  Name of the layer (default is an empty string).
83
- radius : float, optional
84
- Radius of the layer (default is 0.0 meters).
84
+ radius : TypedUnit.Length, optional
85
+ Radius of the layer (defaults to an unbounded outer layer).
85
86
  """
86
87
  layer = ProxyLayer(name=name, radius=radius, index=index)
87
88
  self.layers_list.append(layer)