PyOptik 3.0.5__tar.gz → 3.2.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 (103) hide show
  1. {pyoptik-3.0.5 → pyoptik-3.2.0}/.github/workflows/deploy_PyPi.yml +3 -7
  2. {pyoptik-3.0.5 → pyoptik-3.2.0}/.github/workflows/deploy_anaconda.yml +6 -6
  3. {pyoptik-3.0.5 → pyoptik-3.2.0}/.github/workflows/deploy_coverage.yml +2 -1
  4. {pyoptik-3.0.5 → pyoptik-3.2.0}/.github/workflows/deploy_documentation.yml +2 -2
  5. pyoptik-3.2.0/.github/workflows/quality.yml +18 -0
  6. {pyoptik-3.0.5 → pyoptik-3.2.0}/.github/workflows/tests.yml +2 -2
  7. {pyoptik-3.0.5 → pyoptik-3.2.0}/.gitignore +5 -0
  8. pyoptik-3.2.0/CHANGELOG.md +84 -0
  9. pyoptik-3.2.0/CONTRIBUTING.md +10 -0
  10. {pyoptik-3.0.5 → pyoptik-3.2.0}/Makefile +14 -0
  11. {pyoptik-3.0.5 → pyoptik-3.2.0}/PKG-INFO +126 -1
  12. pyoptik-3.2.0/PyOptik/__init__.py +32 -0
  13. {pyoptik-3.0.5 → pyoptik-3.2.0}/PyOptik/__main__.py +11 -1
  14. {pyoptik-3.0.5 → pyoptik-3.2.0}/PyOptik/_version.py +3 -3
  15. {pyoptik-3.0.5 → pyoptik-3.2.0}/PyOptik/catalog.py +4 -5
  16. {pyoptik-3.0.5 → pyoptik-3.2.0}/PyOptik/directories.py +1 -0
  17. {pyoptik-3.0.5 → pyoptik-3.2.0}/PyOptik/material/__init__.py +1 -0
  18. {pyoptik-3.0.5 → pyoptik-3.2.0}/PyOptik/material/base_class.py +7 -1
  19. pyoptik-3.2.0/PyOptik/material/dataset.py +204 -0
  20. {pyoptik-3.0.5 → pyoptik-3.2.0}/PyOptik/material/sellmeier_class.py +64 -33
  21. {pyoptik-3.0.5 → pyoptik-3.2.0}/PyOptik/material/tabulated_class.py +93 -39
  22. pyoptik-3.2.0/PyOptik/thin_film.py +279 -0
  23. pyoptik-3.2.0/PyOptik/tui.py +105 -0
  24. {pyoptik-3.0.5 → pyoptik-3.2.0}/PyOptik/utils.py +1 -1
  25. {pyoptik-3.0.5 → pyoptik-3.2.0}/PyOptik.egg-info/PKG-INFO +126 -1
  26. {pyoptik-3.0.5 → pyoptik-3.2.0}/PyOptik.egg-info/SOURCES.txt +26 -2
  27. {pyoptik-3.0.5 → pyoptik-3.2.0}/PyOptik.egg-info/requires.txt +3 -0
  28. {pyoptik-3.0.5 → pyoptik-3.2.0}/PyOptik.egg-info/scm_file_list.json +26 -2
  29. pyoptik-3.2.0/PyOptik.egg-info/scm_version.json +8 -0
  30. {pyoptik-3.0.5 → pyoptik-3.2.0}/README.rst +123 -0
  31. {pyoptik-3.0.5 → pyoptik-3.2.0/conda.recipe}/meta.yaml +1 -1
  32. pyoptik-3.2.0/docs/examples/catalog/README.rst +6 -0
  33. pyoptik-3.2.0/docs/examples/catalog/plot_catalog_search.py +48 -0
  34. pyoptik-3.2.0/docs/examples/custom_materials/README.rst +6 -0
  35. pyoptik-3.2.0/docs/examples/custom_materials/plot_measured_material.py +54 -0
  36. pyoptik-3.2.0/docs/examples/interfaces/README.rst +6 -0
  37. pyoptik-3.2.0/docs/examples/interfaces/plot_fresnel_angles.py +42 -0
  38. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/examples/sellmeier/plot_water.py +0 -1
  39. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/examples/tabulated/plot_silver.py +0 -1
  40. pyoptik-3.2.0/docs/examples/thin_films/README.rst +6 -0
  41. pyoptik-3.2.0/docs/examples/thin_films/plot_antireflection_coating.py +46 -0
  42. pyoptik-3.2.0/docs/examples/thin_films/plot_bragg_mirror.py +50 -0
  43. pyoptik-3.2.0/docs/source/catalog_browser.rst +41 -0
  44. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/source/code.rst +58 -23
  45. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/source/conf.py +7 -1
  46. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/source/conventions.rst +17 -2
  47. pyoptik-3.2.0/docs/source/custom_materials.rst +101 -0
  48. pyoptik-3.2.0/docs/source/examples.rst +19 -0
  49. pyoptik-3.2.0/docs/source/getting_started.rst +66 -0
  50. pyoptik-3.2.0/docs/source/index.rst +70 -0
  51. pyoptik-3.2.0/docs/source/materials_and_catalog.rst +64 -0
  52. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/source/sg_execution_times.rst +27 -12
  53. pyoptik-3.2.0/docs/source/thin_films.rst +108 -0
  54. {pyoptik-3.0.5 → pyoptik-3.2.0}/pyproject.toml +4 -0
  55. {pyoptik-3.0.5 → pyoptik-3.2.0}/tests/conftest.py +2 -2
  56. {pyoptik-3.0.5 → pyoptik-3.2.0}/tests/test_base_material_decorator.py +1 -0
  57. pyoptik-3.2.0/tests/test_datasets_and_custom_materials.py +106 -0
  58. {pyoptik-3.0.5 → pyoptik-3.2.0}/tests/test_main_cli.py +13 -0
  59. pyoptik-3.2.0/tests/test_thin_film.py +124 -0
  60. pyoptik-3.2.0/tests/test_tui.py +11 -0
  61. {pyoptik-3.0.5 → pyoptik-3.2.0}/tests/test_utils_extra.py +2 -1
  62. pyoptik-3.2.0/tools/next_release_version.py +13 -0
  63. pyoptik-3.2.0/tools/release_tag.py +15 -0
  64. pyoptik-3.0.5/CHANGELOG.md +0 -47
  65. pyoptik-3.0.5/PyOptik/__init__.py +0 -20
  66. pyoptik-3.0.5/PyOptik.egg-info/scm_version.json +0 -8
  67. pyoptik-3.0.5/docs/source/examples.rst +0 -14
  68. pyoptik-3.0.5/docs/source/index.rst +0 -13
  69. {pyoptik-3.0.5 → pyoptik-3.2.0}/.coveragerc +0 -0
  70. {pyoptik-3.0.5 → pyoptik-3.2.0}/.flake8 +0 -0
  71. {pyoptik-3.0.5 → pyoptik-3.2.0}/.github/dependabot.yml +0 -0
  72. {pyoptik-3.0.5 → pyoptik-3.2.0}/.pre-commit-config.yaml +0 -0
  73. {pyoptik-3.0.5 → pyoptik-3.2.0}/LICENSE +0 -0
  74. {pyoptik-3.0.5 → pyoptik-3.2.0}/PyOptik/material_type.py +0 -0
  75. {pyoptik-3.0.5 → pyoptik-3.2.0}/PyOptik.egg-info/dependency_links.txt +0 -0
  76. {pyoptik-3.0.5 → pyoptik-3.2.0}/PyOptik.egg-info/entry_points.txt +0 -0
  77. {pyoptik-3.0.5 → pyoptik-3.2.0}/PyOptik.egg-info/top_level.txt +0 -0
  78. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/Makefile +0 -0
  79. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/examples/README.rst +0 -0
  80. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/examples/group_properties/README.rst +0 -0
  81. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/examples/group_properties/plot_group_properties.py +0 -0
  82. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/examples/sellmeier/README.rst +0 -0
  83. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/examples/sellmeier/plot_bk7.py +0 -0
  84. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/examples/sellmeier/plot_compare_glasses.py +0 -0
  85. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/examples/sellmeier/plot_silica.py +0 -0
  86. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/examples/tabulated/README.rst +0 -0
  87. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/examples/tabulated/plot_polyethylene.py +0 -0
  88. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/examples/tabulated/plot_silicon_nk.py +0 -0
  89. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/images/example_bk7.png +0 -0
  90. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/images/logo.svg +0 -0
  91. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/make.bat +0 -0
  92. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/source/_static/default.css +0 -0
  93. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/source/_static/favicon.png +0 -0
  94. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/source/_static/logo.svg +0 -0
  95. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/source/_static/thumbnail.png +0 -0
  96. {pyoptik-3.0.5 → pyoptik-3.2.0}/docs/source/references.rst +0 -0
  97. {pyoptik-3.0.5 → pyoptik-3.2.0}/pytest.ini +0 -0
  98. {pyoptik-3.0.5 → pyoptik-3.2.0}/setup.cfg +0 -0
  99. {pyoptik-3.0.5 → pyoptik-3.2.0}/tests/__init__.py +0 -0
  100. {pyoptik-3.0.5 → pyoptik-3.2.0}/tests/test_catalog.py +0 -0
  101. {pyoptik-3.0.5 → pyoptik-3.2.0}/tests/test_docstrings.py +0 -0
  102. {pyoptik-3.0.5 → pyoptik-3.2.0}/tests/test_material_models.py +0 -0
  103. {pyoptik-3.0.5 → pyoptik-3.2.0}/tests/test_validation_and_logging.py +0 -0
@@ -2,18 +2,14 @@ name: Deploy to PyPi
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [ "master" ]
6
- tags:
7
- - '*'
8
- pull_request:
9
- branches: [ "master" ]
5
+ tags: [ "v*" ]
10
6
 
11
7
  permissions:
12
- contents: write
8
+ contents: read
13
9
 
14
10
  jobs:
15
11
  pure_package:
16
- uses: MartinPdeS/MPSActions/.github/workflows/publish_pure_package_to_PyPi.yml@master
12
+ uses: MartinPdeS/MPSActions/.github/workflows/publish_pure_package_to_PyPi.yml@v5
17
13
  with:
18
14
  python-version: "3.11"
19
15
  secrets:
@@ -2,20 +2,20 @@ name: Deploy Conda Packages
2
2
 
3
3
  on:
4
4
  push:
5
- branches: [ "master" ]
6
- tags: '*'
7
- pull_request:
8
- branches: [ "master" ]
5
+ tags: [ "v*" ]
9
6
 
10
7
  permissions:
11
- contents: write
8
+ contents: read
12
9
 
13
10
  jobs:
14
11
  linux:
15
- uses: MartinPdeS/MPSActions/.github/workflows/publish_compiled_package_to_anaconda.yml@master
12
+ uses: MartinPdeS/MPSActions/.github/workflows/publish_compiled_package_to_anaconda.yml@v5
16
13
  with:
17
14
  python_versions: '["3.13"]'
18
15
  apt_package: ""
19
16
  os_list: '["ubuntu-latest"]'
17
+ package_name: pyoptik
18
+ recipe_path: conda.recipe
19
+ test_import: PyOptik
20
20
  secrets:
21
21
  ANACONDA_API_TOKEN: ${{ secrets.ANACONDA_API_TOKEN }}
@@ -17,9 +17,10 @@ permissions:
17
17
 
18
18
  jobs:
19
19
  ManyLinux_x86_64:
20
- uses: MartinPdeS/MPSActions/.github/workflows/publish_coverage.yml@master
20
+ uses: MartinPdeS/MPSActions/.github/workflows/publish_coverage.yml@v5
21
21
  with:
22
22
  python-version: "3.11"
23
23
  apt-package: xvfb
24
+ coverage-package: PyOptik
24
25
  secrets:
25
26
  _GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -16,8 +16,8 @@ permissions:
16
16
 
17
17
  jobs:
18
18
  ManyLinux_x86_64:
19
- uses: MartinPdeS/MPSActions/.github/workflows/publish_documentation.yml@master
19
+ uses: MartinPdeS/MPSActions/.github/workflows/publish_documentation.yml@v5
20
20
  with:
21
21
  python-version: "3.11"
22
22
  package-name: "PyOptik"
23
- apt-package: xvfb jq
23
+ apt-package: xvfb jq
@@ -0,0 +1,18 @@
1
+ name: Static quality checks
2
+ on:
3
+ push:
4
+ branches: ["master"]
5
+ pull_request:
6
+ branches: ["master"]
7
+ permissions:
8
+ contents: read
9
+ jobs:
10
+ flake8:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09
14
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
15
+ with:
16
+ python-version: "3.11"
17
+ - run: python -m pip install "flake8==7.1.1"
18
+ - run: python -m flake8 PyOptik tests
@@ -15,8 +15,8 @@ jobs:
15
15
  python-version: ["3.10", "3.11", "3.12", "3.13"]
16
16
 
17
17
  steps:
18
- - uses: actions/checkout@v4
19
- - uses: actions/setup-python@v5
18
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09
19
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
20
20
  with:
21
21
  python-version: ${{ matrix.python-version }}
22
22
  cache: pip
@@ -4,10 +4,14 @@
4
4
 
5
5
  # Except ...
6
6
  !.github
7
+ !conda.recipe
8
+ !conda.recipe/meta.yaml
7
9
  !PyOptik
8
10
  !development
9
11
  !docs
10
12
  !tests
13
+ !tools
14
+ !tools/*.py
11
15
  !.flake8
12
16
  !.gitignore
13
17
  !.pre-commit-config.yaml
@@ -21,6 +25,7 @@
21
25
  !pytest.ini
22
26
  !.coveragerc
23
27
  !CHANGELOG.md
28
+ !CONTRIBUTING.md
24
29
 
25
30
  # But ignore docs/build inside docs and code
26
31
  docs/build/
@@ -0,0 +1,84 @@
1
+ # Changelog
2
+
3
+ All notable changes to PyOptik are documented in this file.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [3.2.0] - 2026-09-16
11
+
12
+ ### Added
13
+
14
+ - Task-oriented getting-started and material-catalog guides.
15
+ - Executable gallery examples for measured-material import and YAML round
16
+ trips, Fresnel polarization and Brewster angle, quarter-wave antireflection
17
+ coatings, dielectric Bragg mirrors, and catalog provenance workflows.
18
+ - Direct README links to the principal guides, examples, and API reference.
19
+
20
+ ### Changed
21
+
22
+ - Documentation navigation now groups content into Start here, User guide,
23
+ Learn by example, and Reference sections.
24
+ - The documentation landing page, page titles, API hierarchy, and gallery
25
+ descriptions now use clearer task-oriented language.
26
+
27
+ ## [3.1.0] - 2026-09-16
28
+
29
+ ### Added
30
+
31
+ - Support for all nine RefractiveIndex.INFO dispersion formula types.
32
+ - `MaterialCatalog.search()` with hierarchy, source, reference, and local
33
+ availability filters.
34
+ - `MaterialPage.provenance()` for serializable material-source records.
35
+ - `MaterialCatalog.verify_integrity()` for SHA-256 verification of cached
36
+ material data.
37
+ - `compute_group_delay_wavelength_slope()` for the explicit wavelength-space
38
+ derivative, `dτ_g/dλ`.
39
+ - Convenience APIs for real index, extinction coefficient, relative
40
+ permittivity, and absorption coefficient.
41
+ - Split tabulated ``n`` and ``k`` YAML support and opt-in monotonic PCHIP
42
+ interpolation.
43
+ - A physical conventions and provenance documentation guide.
44
+ - Numerical regression, unit-equivalence, catalog-search, and cache-integrity
45
+ test coverage.
46
+ - A new PyOptik prism logo and documentation favicon.
47
+ - Typed, validated material documents for formula and tabulated optical data.
48
+ - User-defined material construction from arrays, CSV files, and formula
49
+ coefficients, with atomic RefractiveIndex.INFO-compatible YAML export.
50
+ - An optional Textual terminal browser for searching the local material
51
+ catalog and inspecting provenance.
52
+ - Fresnel reflection and transmission calculations for s and p polarization,
53
+ including Brewster and critical-angle helpers.
54
+ - Coherent transfer-matrix calculations for isotropic multilayer thin films,
55
+ supporting constant complex indices and wavelength-dependent PyOptik
56
+ material models.
57
+ - Numerical reference tests for interfaces, total internal reflection,
58
+ antireflection coatings, absorbing films, custom material round trips, and
59
+ malformed input paths.
60
+
61
+ ### Changed
62
+
63
+ - `compute_group_delay_dispersion()` now returns conventional frequency-domain
64
+ GDD, `dτ_g/dω`, with time-squared units.
65
+ - Material plots use a consistent built-in Matplotlib layout and typography.
66
+ - Public catalog and group-delay APIs use expanded NumPy-style docstrings.
67
+ - Material YAML loading and export share one typed parser and validation layer.
68
+ - Wavelength validity endpoints tolerate floating-point round-off introduced
69
+ by unit conversions.
70
+
71
+ ### Fixed
72
+
73
+ - Formula type 6 now accumulates every gas-dispersion term instead of
74
+ overwriting earlier terms.
75
+
76
+ ### Removed
77
+
78
+ - The `MPSPlots` runtime dependency and its use in examples and material
79
+ plotting helpers.
80
+ - Retired logo assets.
81
+
82
+ [Unreleased]: https://github.com/MartinPdeS/PyOptik/compare/v3.2.0...HEAD
83
+ [3.2.0]: https://github.com/MartinPdeS/PyOptik/compare/v3.1.0...v3.2.0
84
+ [3.1.0]: https://github.com/MartinPdeS/PyOptik/compare/v3.0.5...v3.1.0
@@ -0,0 +1,10 @@
1
+ # Contributing to PyOptik
2
+
3
+ Contributions should preserve PyOptik's offline material-catalogue workflow.
4
+ Add tests for behaviour changes, document data provenance and physical units,
5
+ and avoid committing generated assets, caches, or `PyOptik/_version.py`.
6
+
7
+ Use `make quality`, `make test`, and `make docs` as appropriate before a pull
8
+ request. Semantic release commands (`make release patch`, `minor`, or `major`)
9
+ create and push the release commit and exact tag; `make tag VERSION=vX.Y.Z`
10
+ creates a local-only tag.
@@ -3,10 +3,13 @@ BUILD_DIR ?= build
3
3
  DIST_DIR ?= dist
4
4
  DOCS_DIR ?= docs
5
5
  DOCS_BUILD_DIR ?= $(DOCS_DIR)/build
6
+ RELEASE_KIND := $(filter major minor patch,$(MAKECMDGOALS))
6
7
 
7
8
  .PHONY: help quality test docs build install uninstall quick rebuild editable \
8
9
  setup download-all clean
9
10
 
11
+ .PHONY: release tag major minor patch
12
+
10
13
  help:
11
14
  @echo "PyOptik development targets:"
12
15
  @echo " make quality Run static checks"
@@ -20,6 +23,7 @@ help:
20
23
  @echo " make quick Run quality checks, tests, and build"
21
24
  @echo " make rebuild Clean, then run the quick workflow"
22
25
  @echo " make clean Remove generated build and test artifacts"
26
+ @echo " make release patch Create and push the next patch release"
23
27
 
24
28
  quality:
25
29
  $(PYTHON) -m flake8 PyOptik tests
@@ -55,6 +59,16 @@ setup:
55
59
  download-all:
56
60
  $(PYTHON) -m PyOptik download-all $(ARGS)
57
61
 
62
+ tag:
63
+ $(PYTHON) tools/release_tag.py "$(VERSION)"
64
+
65
+ release:
66
+ @test "$(words $(RELEASE_KIND))" -eq 1 || { echo "usage: make release [patch|minor|major]" >&2; exit 2; }
67
+ @set -eu; tag="$$($(PYTHON) tools/next_release_version.py $(RELEASE_KIND))"; $(PYTHON) tools/release_tag.py "$$tag"; git push origin HEAD "refs/tags/$$tag"
68
+
69
+ major minor patch:
70
+ @:
71
+
58
72
  clean:
59
73
  rm -rf $(BUILD_DIR) $(DIST_DIR) $(DOCS_BUILD_DIR) \
60
74
  .pytest_cache htmlcov .coverage
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyOptik
3
- Version: 3.0.5
3
+ Version: 3.2.0
4
4
  Summary: A package for refractive index values.
5
5
  Author-email: Martin Poinsinet de Sivry-Houle <martin.poinsinet.de.sivry@gmail.com>
6
6
  License: MIT License
@@ -55,6 +55,8 @@ Requires-Dist: sphinx-rtd-theme==2.0.0; extra == "documentation"
55
55
  Requires-Dist: pydata-sphinx-theme==0.14.1; extra == "documentation"
56
56
  Provides-Extra: dev
57
57
  Requires-Dist: flake8==7.1.1; extra == "dev"
58
+ Provides-Extra: ui
59
+ Requires-Dist: textual>=1.0; extra == "ui"
58
60
  Dynamic: license-file
59
61
 
60
62
  |logo|
@@ -94,6 +96,24 @@ catalog interface for the hierarchical `RefractiveIndex.INFO
94
96
  The library is designed for optical design, photonics simulations,
95
97
  electromagnetic modeling, and experimental data analysis.
96
98
 
99
+ Documentation
100
+ -------------
101
+
102
+ The full documentation is organized by task:
103
+
104
+ * `Getting started <https://martinpdes.github.io/PyOptik/getting_started.html>`_
105
+ for installation and a first unit-aware calculation.
106
+ * `Materials and catalog <https://martinpdes.github.io/PyOptik/materials_and_catalog.html>`_
107
+ for model selection, search, provenance, and cache integrity.
108
+ * `Custom materials <https://martinpdes.github.io/PyOptik/custom_materials.html>`_
109
+ for arrays, CSV import, coefficients, validation, and YAML export.
110
+ * `Interfaces and thin films <https://martinpdes.github.io/PyOptik/thin_films.html>`_
111
+ for Fresnel coefficients and coherent multilayer design.
112
+ * `Example gallery <https://martinpdes.github.io/PyOptik/examples.html>`_
113
+ for complete, executable workflows.
114
+ * `API reference <https://martinpdes.github.io/PyOptik/code.html>`_ for public
115
+ classes and functions.
116
+
97
117
  Features
98
118
  --------
99
119
 
@@ -101,6 +121,7 @@ Features
101
121
  * Tabulated complex refractive index data, ``n + i k``.
102
122
  * Unit-aware wavelength calculations through ``TypedUnit`` and Pint.
103
123
  * Group index, group velocity, group delay, and group-delay dispersion.
124
+ * Fresnel interfaces and coherent multilayer thin-film calculations.
104
125
  * NumPy-compatible scalar and array evaluation.
105
126
  * Plotting helpers for dispersion and absorption data.
106
127
  * Hierarchical catalog access using upstream ``shelf / book / page`` identity.
@@ -197,6 +218,35 @@ Use ``material.n(wavelength)``, ``material.k(wavelength)``,
197
218
  See the documentation's physical-conventions page for the vacuum-wavelength,
198
219
  ``n + i k``, GDD, interpolation, and extrapolation conventions.
199
220
 
221
+ User-defined materials
222
+ ~~~~~~~~~~~~~~~~~~~~~~
223
+
224
+ Create tabulated materials directly from measured arrays or a CSV file, then
225
+ export them in the same validated YAML format used by the catalog:
226
+
227
+ .. code-block:: python
228
+
229
+ from TypedUnit import ureg
230
+ from PyOptik import TabulatedMaterial
231
+
232
+ sample = TabulatedMaterial.from_arrays(
233
+ "sample",
234
+ [400, 500, 600] * ureg.nanometer,
235
+ n=[1.40, 1.45, 1.50],
236
+ k=[0.01, 0.02, 0.04],
237
+ reference="Laboratory measurement",
238
+ )
239
+ sample.to_yaml("sample.yml")
240
+
241
+ # CSV headers default to: wavelength,n,k
242
+ imported = TabulatedMaterial.from_csv(
243
+ "sample.csv",
244
+ wavelength_unit=ureg.nanometer,
245
+ )
246
+
247
+ Formula materials can be authored in the same way with
248
+ ``SellmeierMaterial.from_coefficients(...)`` and exported with ``to_yaml()``.
249
+
200
250
  Validity ranges
201
251
  ~~~~~~~~~~~~~~~
202
252
 
@@ -241,6 +291,55 @@ These methods accept scalar or array wavelengths and return unit-aware values.
241
291
  ``dτ_g/dω`` (typically expressed in fs²); use
242
292
  ``compute_group_delay_wavelength_slope`` for ``dτ_g/dλ``.
243
293
 
294
+ Interfaces and thin films
295
+ -------------------------
296
+
297
+ Calculate s- or p-polarized Fresnel coefficients at a single interface:
298
+
299
+ .. code-block:: python
300
+
301
+ from TypedUnit import ureg
302
+ from PyOptik import fresnel_coefficients, brewster_angle, critical_angle
303
+
304
+ interface = fresnel_coefficients(
305
+ 1.0,
306
+ 1.5,
307
+ angle=45 * ureg.degree,
308
+ polarization="p",
309
+ )
310
+ print(interface.reflectance, interface.transmittance)
311
+
312
+ print(brewster_angle(1.0, 1.5).to(ureg.degree))
313
+ print(critical_angle(1.5, 1.0).to(ureg.degree))
314
+
315
+ For coherent multilayers, supply constant indices or PyOptik material models.
316
+ Each thickness should carry units:
317
+
318
+ .. code-block:: python
319
+
320
+ import numpy
321
+ from PyOptik import ThinFilmLayer, thin_film_stack
322
+
323
+ wavelength = 600 * ureg.nanometer
324
+ substrate_index = 1.5
325
+ coating_index = numpy.sqrt(substrate_index)
326
+ coating = ThinFilmLayer(
327
+ coating_index,
328
+ wavelength / (4 * coating_index),
329
+ )
330
+ spectrum = thin_film_stack(
331
+ [500, 550, 600, 650, 700] * ureg.nanometer,
332
+ [coating],
333
+ incident_index=1.0,
334
+ substrate_index=substrate_index,
335
+ polarization="s",
336
+ )
337
+
338
+ The returned results contain complex amplitude coefficients and the power
339
+ fractions ``reflectance``, ``transmittance``, and ``absorptance``. The model is
340
+ coherent and isotropic; it does not model roughness, anisotropy, or incoherent
341
+ thick substrates.
342
+
244
343
  Plotting
245
344
  --------
246
345
 
@@ -262,6 +361,20 @@ Matplotlib backend before importing plotting code:
262
361
  import matplotlib
263
362
  matplotlib.use("Agg")
264
363
 
364
+ Worked examples
365
+ ---------------
366
+
367
+ The documentation gallery includes downloadable Python scripts and notebooks
368
+ for:
369
+
370
+ * Sellmeier and tabulated material dispersion;
371
+ * group index and group velocity;
372
+ * importing, interpolating, exporting, and reloading measured optical data;
373
+ * s- and p-polarized Fresnel reflectance and Brewster angle;
374
+ * quarter-wave antireflection coatings;
375
+ * multilayer dielectric Bragg mirrors; and
376
+ * catalog search, canonical source selection, and provenance capture.
377
+
265
378
  Hierarchical material catalog
266
379
  -----------------------------
267
380
 
@@ -325,6 +438,18 @@ The command-line interface provides the canonical catalog workflow:
325
438
  After installation, the equivalent console command is ``pyoptik
326
439
  download-all --data-root ./refractiveindex-data``.
327
440
 
441
+ An optional terminal interface provides live search and a provenance pane for
442
+ browsing the downloaded database:
443
+
444
+ .. code-block:: bash
445
+
446
+ python -m pip install "PyOptik[ui]"
447
+ pyoptik setup
448
+ pyoptik browse
449
+
450
+ Use the arrow keys or mouse to select a page, ``/`` to focus search, and ``q``
451
+ to quit. Pass ``--data-root`` when browsing a non-default snapshot location.
452
+
328
453
  For a beginner-friendly first-time setup, use:
329
454
 
330
455
  .. code-block:: bash
@@ -0,0 +1,32 @@
1
+ try:
2
+ from ._version import version as __version__ # noqa: F401
3
+
4
+ except ImportError:
5
+ __version__ = "0.0.0"
6
+
7
+ import logging
8
+
9
+ logging.getLogger(__name__).addHandler(logging.NullHandler())
10
+
11
+
12
+ from .material_type import MaterialType # noqa: E402
13
+ from .catalog import MaterialCatalog, MaterialId, MaterialPage, download_snapshot # noqa: E402
14
+
15
+ from .material import TabulatedMaterial # noqa: E402
16
+ from .material import SellmeierMaterial # noqa: E402
17
+ from .material import ( # noqa: E402
18
+ FormulaDataset, MaterialDocument, MaterialMetadata, TabulatedDataset, parse_material,
19
+ )
20
+ from .thin_film import ( # noqa: E402
21
+ FresnelResult,
22
+ ThinFilmLayer,
23
+ ThinFilmResult,
24
+ brewster_angle,
25
+ critical_angle,
26
+ fresnel_coefficients,
27
+ thin_film_stack,
28
+ )
29
+ from .material import base_class # noqa: E402
30
+
31
+
32
+ TIMEOUT = 10 # Default timeout for requests in seconds
@@ -22,7 +22,7 @@ def main() -> None:
22
22
  "command",
23
23
  nargs="?",
24
24
  default="setup",
25
- choices=("setup", "download-all"),
25
+ choices=("setup", "download-all", "browse"),
26
26
  help="Canonical catalog command (default: setup)",
27
27
  )
28
28
  parser.add_argument(
@@ -70,6 +70,16 @@ def main() -> None:
70
70
  format="%(asctime)s %(levelname)s %(name)s: %(message)s",
71
71
  )
72
72
 
73
+ if args.command == "browse":
74
+ if args.force or args.fail_fast or args.source != "snapshot" or args.workers != 8 or args.no_progress:
75
+ parser.error("download options cannot be used with browse")
76
+ from PyOptik.tui import run_browser
77
+ try:
78
+ run_browser(args.data_root)
79
+ except (FileNotFoundError, RuntimeError) as error:
80
+ parser.error(str(error))
81
+ return
82
+
73
83
  if args.command == "setup" and args.source != "snapshot":
74
84
  parser.error("setup always uses the upstream snapshot; use download-all for page mode")
75
85
  source = "snapshot" if args.command == "setup" else args.source
@@ -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 = '3.0.5'
22
- __version_tuple__ = version_tuple = (3, 0, 5)
21
+ __version__ = version = '3.2.0'
22
+ __version_tuple__ = version_tuple = (3, 2, 0)
23
23
 
24
- __commit_id__ = commit_id = 'g3091905a0'
24
+ __commit_id__ = commit_id = 'g7c8410160'
@@ -145,17 +145,16 @@ class MaterialPage:
145
145
  """
146
146
  from PyOptik.material.sellmeier_class import SellmeierMaterial
147
147
  from PyOptik.material.tabulated_class import TabulatedMaterial
148
+ from PyOptik.material.dataset import parse_material
148
149
 
149
150
  if self.local_path is not None and self.local_path.exists():
150
- with self.local_path.open("r") as stream:
151
- document = yaml.safe_load(stream) or {}
152
- entries = document.get("DATA", [])
153
- if any("formula" in str(entry.get("type", "")) for entry in entries):
151
+ document = parse_material(self.local_path)
152
+ if document.formula_datasets:
154
153
  material = SellmeierMaterial(self.name, file_path=self.local_path)
155
154
  material.catalog_id = self.id.key
156
155
  material.source_url = self.source_url
157
156
  return material
158
- if any("tabulated" in str(entry.get("type", "")) for entry in entries):
157
+ if document.tabulated_datasets:
159
158
  material = TabulatedMaterial(
160
159
  self.name,
161
160
  file_path=self.local_path,
@@ -54,6 +54,7 @@ user_data_path = _default_user_data_path()
54
54
  user_sellmeier_data_path = user_data_path / "sellmeier"
55
55
  user_tabulated_data_path = user_data_path / "tabulated"
56
56
 
57
+
57
58
  def material_paths(material_type):
58
59
  """Return the user data directory for a material type.
59
60
 
@@ -1,3 +1,4 @@
1
1
  from .base_class import BaseMaterial
2
2
  from .sellmeier_class import SellmeierMaterial
3
3
  from .tabulated_class import TabulatedMaterial
4
+ from .dataset import FormulaDataset, MaterialDocument, MaterialMetadata, TabulatedDataset, parse_material
@@ -10,6 +10,7 @@ from TypedUnit import Length, AnyUnit, Time, ureg, validate_units
10
10
 
11
11
  logger = logging.getLogger(__name__)
12
12
 
13
+
13
14
  class BaseMaterial(object):
14
15
  """Common interface for refractive-index material models.
15
16
 
@@ -195,8 +196,13 @@ class BaseMaterial(object):
195
196
  raise ValueError("out_of_range must be 'warn', 'raise', or 'clip'.")
196
197
  if self.wavelength_bound is not None:
197
198
  min_value, max_value = self.wavelength_bound
199
+ values = wavelength.to(ureg.meter).magnitude
200
+ lower = min_value.to(ureg.meter).magnitude
201
+ upper = max_value.to(ureg.meter).magnitude
202
+ below = (values < lower) & ~numpy.isclose(values, lower, rtol=1e-12, atol=0.0)
203
+ above = (values > upper) & ~numpy.isclose(values, upper, rtol=1e-12, atol=0.0)
198
204
 
199
- if numpy.any((wavelength < min_value) | (wavelength > max_value)):
205
+ if numpy.any(below | above):
200
206
  message = (
201
207
  f"Wavelength range goes from {wavelength.min().to_compact()} to {wavelength.max().to_compact()} "
202
208
  f"which is outside the allowable range of {min_value.to_compact()} to {max_value.to_compact()} µm. "