PyOptik 3.0.4__tar.gz → 3.1.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.
- {pyoptik-3.0.4 → pyoptik-3.1.0}/.github/workflows/deploy_PyPi.yml +3 -7
- {pyoptik-3.0.4 → pyoptik-3.1.0}/.github/workflows/deploy_anaconda.yml +6 -6
- {pyoptik-3.0.4 → pyoptik-3.1.0}/.github/workflows/deploy_coverage.yml +2 -1
- {pyoptik-3.0.4 → pyoptik-3.1.0}/.github/workflows/deploy_documentation.yml +2 -2
- pyoptik-3.1.0/.github/workflows/quality.yml +18 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/.github/workflows/tests.yml +2 -2
- {pyoptik-3.0.4 → pyoptik-3.1.0}/.gitignore +5 -0
- pyoptik-3.1.0/CHANGELOG.md +66 -0
- pyoptik-3.1.0/CONTRIBUTING.md +10 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/Makefile +14 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/PKG-INFO +100 -1
- {pyoptik-3.0.4 → pyoptik-3.1.0}/PyOptik/__init__.py +10 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/PyOptik/__main__.py +11 -1
- {pyoptik-3.0.4 → pyoptik-3.1.0}/PyOptik/_version.py +3 -3
- {pyoptik-3.0.4 → pyoptik-3.1.0}/PyOptik/catalog.py +23 -8
- {pyoptik-3.0.4 → pyoptik-3.1.0}/PyOptik/material/__init__.py +1 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/PyOptik/material/base_class.py +113 -1
- pyoptik-3.1.0/PyOptik/material/dataset.py +204 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/PyOptik/material/sellmeier_class.py +67 -31
- pyoptik-3.1.0/PyOptik/material/tabulated_class.py +342 -0
- pyoptik-3.1.0/PyOptik/thin_film.py +279 -0
- pyoptik-3.1.0/PyOptik/tui.py +105 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/PyOptik.egg-info/PKG-INFO +100 -1
- {pyoptik-3.0.4 → pyoptik-3.1.0}/PyOptik.egg-info/SOURCES.txt +16 -2
- {pyoptik-3.0.4 → pyoptik-3.1.0}/PyOptik.egg-info/requires.txt +3 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/PyOptik.egg-info/scm_file_list.json +16 -2
- pyoptik-3.1.0/PyOptik.egg-info/scm_version.json +8 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/README.rst +97 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0/conda.recipe}/meta.yaml +1 -1
- pyoptik-3.1.0/docs/source/catalog_browser.rst +42 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/source/code.rst +42 -0
- pyoptik-3.1.0/docs/source/conventions.rst +64 -0
- pyoptik-3.1.0/docs/source/custom_materials.rst +102 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/source/index.rst +4 -0
- pyoptik-3.1.0/docs/source/thin_films.rst +108 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/pyproject.toml +4 -0
- pyoptik-3.1.0/tests/test_datasets_and_custom_materials.py +106 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/tests/test_main_cli.py +13 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/tests/test_material_models.py +24 -0
- pyoptik-3.1.0/tests/test_thin_film.py +124 -0
- pyoptik-3.1.0/tests/test_tui.py +11 -0
- pyoptik-3.1.0/tools/next_release_version.py +13 -0
- pyoptik-3.1.0/tools/release_tag.py +15 -0
- pyoptik-3.0.4/CHANGELOG.md +0 -42
- pyoptik-3.0.4/PyOptik/material/tabulated_class.py +0 -227
- pyoptik-3.0.4/PyOptik.egg-info/scm_version.json +0 -8
- {pyoptik-3.0.4 → pyoptik-3.1.0}/.coveragerc +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/.flake8 +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/.github/dependabot.yml +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/.pre-commit-config.yaml +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/LICENSE +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/PyOptik/directories.py +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/PyOptik/material_type.py +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/PyOptik/utils.py +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/PyOptik.egg-info/dependency_links.txt +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/PyOptik.egg-info/entry_points.txt +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/PyOptik.egg-info/top_level.txt +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/Makefile +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/examples/README.rst +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/examples/group_properties/README.rst +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/examples/group_properties/plot_group_properties.py +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/examples/sellmeier/README.rst +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/examples/sellmeier/plot_bk7.py +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/examples/sellmeier/plot_compare_glasses.py +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/examples/sellmeier/plot_silica.py +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/examples/sellmeier/plot_water.py +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/examples/tabulated/README.rst +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/examples/tabulated/plot_polyethylene.py +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/examples/tabulated/plot_silicon_nk.py +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/examples/tabulated/plot_silver.py +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/images/example_bk7.png +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/images/logo.svg +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/make.bat +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/source/_static/default.css +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/source/_static/favicon.png +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/source/_static/logo.svg +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/source/_static/thumbnail.png +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/source/conf.py +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/source/examples.rst +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/source/references.rst +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/docs/source/sg_execution_times.rst +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/pytest.ini +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/setup.cfg +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/tests/__init__.py +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/tests/conftest.py +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/tests/test_base_material_decorator.py +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/tests/test_catalog.py +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/tests/test_docstrings.py +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.0}/tests/test_utils_extra.py +0 -0
- {pyoptik-3.0.4 → pyoptik-3.1.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
|
-
|
|
6
|
-
tags:
|
|
7
|
-
- '*'
|
|
8
|
-
pull_request:
|
|
9
|
-
branches: [ "master" ]
|
|
5
|
+
tags: [ "v*" ]
|
|
10
6
|
|
|
11
7
|
permissions:
|
|
12
|
-
|
|
8
|
+
contents: read
|
|
13
9
|
|
|
14
10
|
jobs:
|
|
15
11
|
pure_package:
|
|
16
|
-
uses: MartinPdeS/MPSActions/.github/workflows/publish_pure_package_to_PyPi.yml@
|
|
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
|
-
|
|
6
|
-
tags: '*'
|
|
7
|
-
pull_request:
|
|
8
|
-
branches: [ "master" ]
|
|
5
|
+
tags: [ "v*" ]
|
|
9
6
|
|
|
10
7
|
permissions:
|
|
11
|
-
|
|
8
|
+
contents: read
|
|
12
9
|
|
|
13
10
|
jobs:
|
|
14
11
|
linux:
|
|
15
|
-
uses: MartinPdeS/MPSActions/.github/workflows/publish_compiled_package_to_anaconda.yml@
|
|
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@
|
|
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@
|
|
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@
|
|
19
|
-
- uses: actions/setup-python@
|
|
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,66 @@
|
|
|
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.1.0] - 2026-09-16
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Support for all nine RefractiveIndex.INFO dispersion formula types.
|
|
15
|
+
- `MaterialCatalog.search()` with hierarchy, source, reference, and local
|
|
16
|
+
availability filters.
|
|
17
|
+
- `MaterialPage.provenance()` for serializable material-source records.
|
|
18
|
+
- `MaterialCatalog.verify_integrity()` for SHA-256 verification of cached
|
|
19
|
+
material data.
|
|
20
|
+
- `compute_group_delay_wavelength_slope()` for the explicit wavelength-space
|
|
21
|
+
derivative, `dτ_g/dλ`.
|
|
22
|
+
- Convenience APIs for real index, extinction coefficient, relative
|
|
23
|
+
permittivity, and absorption coefficient.
|
|
24
|
+
- Split tabulated ``n`` and ``k`` YAML support and opt-in monotonic PCHIP
|
|
25
|
+
interpolation.
|
|
26
|
+
- A physical conventions and provenance documentation guide.
|
|
27
|
+
- Numerical regression, unit-equivalence, catalog-search, and cache-integrity
|
|
28
|
+
test coverage.
|
|
29
|
+
- A new PyOptik prism logo and documentation favicon.
|
|
30
|
+
- Typed, validated material documents for formula and tabulated optical data.
|
|
31
|
+
- User-defined material construction from arrays, CSV files, and formula
|
|
32
|
+
coefficients, with atomic RefractiveIndex.INFO-compatible YAML export.
|
|
33
|
+
- An optional Textual terminal browser for searching the local material
|
|
34
|
+
catalog and inspecting provenance.
|
|
35
|
+
- Fresnel reflection and transmission calculations for s and p polarization,
|
|
36
|
+
including Brewster and critical-angle helpers.
|
|
37
|
+
- Coherent transfer-matrix calculations for isotropic multilayer thin films,
|
|
38
|
+
supporting constant complex indices and wavelength-dependent PyOptik
|
|
39
|
+
material models.
|
|
40
|
+
- Numerical reference tests for interfaces, total internal reflection,
|
|
41
|
+
antireflection coatings, absorbing films, custom material round trips, and
|
|
42
|
+
malformed input paths.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- `compute_group_delay_dispersion()` now returns conventional frequency-domain
|
|
47
|
+
GDD, `dτ_g/dω`, with time-squared units.
|
|
48
|
+
- Material plots use a consistent built-in Matplotlib layout and typography.
|
|
49
|
+
- Public catalog and group-delay APIs use expanded NumPy-style docstrings.
|
|
50
|
+
- Material YAML loading and export share one typed parser and validation layer.
|
|
51
|
+
- Wavelength validity endpoints tolerate floating-point round-off introduced
|
|
52
|
+
by unit conversions.
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- Formula type 6 now accumulates every gas-dispersion term instead of
|
|
57
|
+
overwriting earlier terms.
|
|
58
|
+
|
|
59
|
+
### Removed
|
|
60
|
+
|
|
61
|
+
- The `MPSPlots` runtime dependency and its use in examples and material
|
|
62
|
+
plotting helpers.
|
|
63
|
+
- Retired logo assets.
|
|
64
|
+
|
|
65
|
+
[Unreleased]: https://github.com/MartinPdeS/PyOptik/compare/v3.1.0...HEAD
|
|
66
|
+
[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
|
|
3
|
+
Version: 3.1.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|
|
|
@@ -101,6 +103,7 @@ Features
|
|
|
101
103
|
* Tabulated complex refractive index data, ``n + i k``.
|
|
102
104
|
* Unit-aware wavelength calculations through ``TypedUnit`` and Pint.
|
|
103
105
|
* Group index, group velocity, group delay, and group-delay dispersion.
|
|
106
|
+
* Fresnel interfaces and coherent multilayer thin-film calculations.
|
|
104
107
|
* NumPy-compatible scalar and array evaluation.
|
|
105
108
|
* Plotting helpers for dispersion and absorption data.
|
|
106
109
|
* Hierarchical catalog access using upstream ``shelf / book / page`` identity.
|
|
@@ -191,6 +194,41 @@ Tabulated materials interpolate complex optical constants:
|
|
|
191
194
|
The real part is the refractive index ``n`` and the imaginary part is the
|
|
192
195
|
extinction coefficient ``k``.
|
|
193
196
|
|
|
197
|
+
Use ``material.n(wavelength)``, ``material.k(wavelength)``,
|
|
198
|
+
``material.relative_permittivity(wavelength)``, and
|
|
199
|
+
``material.absorption_coefficient(wavelength)`` for common derived properties.
|
|
200
|
+
See the documentation's physical-conventions page for the vacuum-wavelength,
|
|
201
|
+
``n + i k``, GDD, interpolation, and extrapolation conventions.
|
|
202
|
+
|
|
203
|
+
User-defined materials
|
|
204
|
+
~~~~~~~~~~~~~~~~~~~~~~
|
|
205
|
+
|
|
206
|
+
Create tabulated materials directly from measured arrays or a CSV file, then
|
|
207
|
+
export them in the same validated YAML format used by the catalog:
|
|
208
|
+
|
|
209
|
+
.. code-block:: python
|
|
210
|
+
|
|
211
|
+
from TypedUnit import ureg
|
|
212
|
+
from PyOptik import TabulatedMaterial
|
|
213
|
+
|
|
214
|
+
sample = TabulatedMaterial.from_arrays(
|
|
215
|
+
"sample",
|
|
216
|
+
[400, 500, 600] * ureg.nanometer,
|
|
217
|
+
n=[1.40, 1.45, 1.50],
|
|
218
|
+
k=[0.01, 0.02, 0.04],
|
|
219
|
+
reference="Laboratory measurement",
|
|
220
|
+
)
|
|
221
|
+
sample.to_yaml("sample.yml")
|
|
222
|
+
|
|
223
|
+
# CSV headers default to: wavelength,n,k
|
|
224
|
+
imported = TabulatedMaterial.from_csv(
|
|
225
|
+
"sample.csv",
|
|
226
|
+
wavelength_unit=ureg.nanometer,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
Formula materials can be authored in the same way with
|
|
230
|
+
``SellmeierMaterial.from_coefficients(...)`` and exported with ``to_yaml()``.
|
|
231
|
+
|
|
194
232
|
Validity ranges
|
|
195
233
|
~~~~~~~~~~~~~~~
|
|
196
234
|
|
|
@@ -235,6 +273,55 @@ These methods accept scalar or array wavelengths and return unit-aware values.
|
|
|
235
273
|
``dτ_g/dω`` (typically expressed in fs²); use
|
|
236
274
|
``compute_group_delay_wavelength_slope`` for ``dτ_g/dλ``.
|
|
237
275
|
|
|
276
|
+
Interfaces and thin films
|
|
277
|
+
-------------------------
|
|
278
|
+
|
|
279
|
+
Calculate s- or p-polarized Fresnel coefficients at a single interface:
|
|
280
|
+
|
|
281
|
+
.. code-block:: python
|
|
282
|
+
|
|
283
|
+
from TypedUnit import ureg
|
|
284
|
+
from PyOptik import fresnel_coefficients, brewster_angle, critical_angle
|
|
285
|
+
|
|
286
|
+
interface = fresnel_coefficients(
|
|
287
|
+
1.0,
|
|
288
|
+
1.5,
|
|
289
|
+
angle=45 * ureg.degree,
|
|
290
|
+
polarization="p",
|
|
291
|
+
)
|
|
292
|
+
print(interface.reflectance, interface.transmittance)
|
|
293
|
+
|
|
294
|
+
print(brewster_angle(1.0, 1.5).to(ureg.degree))
|
|
295
|
+
print(critical_angle(1.5, 1.0).to(ureg.degree))
|
|
296
|
+
|
|
297
|
+
For coherent multilayers, supply constant indices or PyOptik material models.
|
|
298
|
+
Each thickness should carry units:
|
|
299
|
+
|
|
300
|
+
.. code-block:: python
|
|
301
|
+
|
|
302
|
+
import numpy
|
|
303
|
+
from PyOptik import ThinFilmLayer, thin_film_stack
|
|
304
|
+
|
|
305
|
+
wavelength = 600 * ureg.nanometer
|
|
306
|
+
substrate_index = 1.5
|
|
307
|
+
coating_index = numpy.sqrt(substrate_index)
|
|
308
|
+
coating = ThinFilmLayer(
|
|
309
|
+
coating_index,
|
|
310
|
+
wavelength / (4 * coating_index),
|
|
311
|
+
)
|
|
312
|
+
spectrum = thin_film_stack(
|
|
313
|
+
[500, 550, 600, 650, 700] * ureg.nanometer,
|
|
314
|
+
[coating],
|
|
315
|
+
incident_index=1.0,
|
|
316
|
+
substrate_index=substrate_index,
|
|
317
|
+
polarization="s",
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
The returned results contain complex amplitude coefficients and the power
|
|
321
|
+
fractions ``reflectance``, ``transmittance``, and ``absorptance``. The model is
|
|
322
|
+
coherent and isotropic; it does not model roughness, anisotropy, or incoherent
|
|
323
|
+
thick substrates.
|
|
324
|
+
|
|
238
325
|
Plotting
|
|
239
326
|
--------
|
|
240
327
|
|
|
@@ -319,6 +406,18 @@ The command-line interface provides the canonical catalog workflow:
|
|
|
319
406
|
After installation, the equivalent console command is ``pyoptik
|
|
320
407
|
download-all --data-root ./refractiveindex-data``.
|
|
321
408
|
|
|
409
|
+
An optional terminal interface provides live search and a provenance pane for
|
|
410
|
+
browsing the downloaded database:
|
|
411
|
+
|
|
412
|
+
.. code-block:: bash
|
|
413
|
+
|
|
414
|
+
python -m pip install "PyOptik[ui]"
|
|
415
|
+
pyoptik setup
|
|
416
|
+
pyoptik browse
|
|
417
|
+
|
|
418
|
+
Use the arrow keys or mouse to select a page, ``/`` to focus search, and ``q``
|
|
419
|
+
to quit. Pass ``--data-root`` when browsing a non-default snapshot location.
|
|
420
|
+
|
|
322
421
|
For a beginner-friendly first-time setup, use:
|
|
323
422
|
|
|
324
423
|
.. code-block:: bash
|
|
@@ -14,6 +14,16 @@ from .catalog import MaterialCatalog, MaterialId, MaterialPage, download_snapsho
|
|
|
14
14
|
|
|
15
15
|
from .material import TabulatedMaterial
|
|
16
16
|
from .material import SellmeierMaterial
|
|
17
|
+
from .material import FormulaDataset, MaterialDocument, MaterialMetadata, TabulatedDataset, parse_material
|
|
18
|
+
from .thin_film import (
|
|
19
|
+
FresnelResult,
|
|
20
|
+
ThinFilmLayer,
|
|
21
|
+
ThinFilmResult,
|
|
22
|
+
brewster_angle,
|
|
23
|
+
critical_angle,
|
|
24
|
+
fresnel_coefficients,
|
|
25
|
+
thin_film_stack,
|
|
26
|
+
)
|
|
17
27
|
from .material import base_class
|
|
18
28
|
|
|
19
29
|
|
|
@@ -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
|
|
22
|
-
__version_tuple__ = version_tuple = (3,
|
|
21
|
+
__version__ = version = '3.1.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (3, 1, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g705a5ec86'
|
|
@@ -122,9 +122,15 @@ class MaterialPage:
|
|
|
122
122
|
state = "available" if self.available else "missing"
|
|
123
123
|
return f"MaterialPage(id={self.id.key!r}, name={self.name!r}, data={state!r})"
|
|
124
124
|
|
|
125
|
-
def load(self):
|
|
125
|
+
def load(self, *, interpolation: str = "linear"):
|
|
126
126
|
"""Load this page as the appropriate PyOptik material object.
|
|
127
127
|
|
|
128
|
+
Parameters
|
|
129
|
+
----------
|
|
130
|
+
interpolation : {"linear", "pchip"}, optional
|
|
131
|
+
Tabulated-data interpolation method. Formula materials ignore this
|
|
132
|
+
option.
|
|
133
|
+
|
|
128
134
|
Returns
|
|
129
135
|
-------
|
|
130
136
|
SellmeierMaterial or TabulatedMaterial
|
|
@@ -139,15 +145,24 @@ class MaterialPage:
|
|
|
139
145
|
"""
|
|
140
146
|
from PyOptik.material.sellmeier_class import SellmeierMaterial
|
|
141
147
|
from PyOptik.material.tabulated_class import TabulatedMaterial
|
|
148
|
+
from PyOptik.material.dataset import parse_material
|
|
142
149
|
|
|
143
150
|
if self.local_path is not None and self.local_path.exists():
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
+
document = parse_material(self.local_path)
|
|
152
|
+
if document.formula_datasets:
|
|
153
|
+
material = SellmeierMaterial(self.name, file_path=self.local_path)
|
|
154
|
+
material.catalog_id = self.id.key
|
|
155
|
+
material.source_url = self.source_url
|
|
156
|
+
return material
|
|
157
|
+
if document.tabulated_datasets:
|
|
158
|
+
material = TabulatedMaterial(
|
|
159
|
+
self.name,
|
|
160
|
+
file_path=self.local_path,
|
|
161
|
+
interpolation=interpolation,
|
|
162
|
+
)
|
|
163
|
+
material.catalog_id = self.id.key
|
|
164
|
+
material.source_url = self.source_url
|
|
165
|
+
return material
|
|
151
166
|
raise ValueError(f"No supported optical dataset found in {self.local_path}")
|
|
152
167
|
|
|
153
168
|
raise FileNotFoundError(
|
|
@@ -17,6 +17,113 @@ class BaseMaterial(object):
|
|
|
17
17
|
unit handling, validity-range checks, and group-delay calculations.
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
+
@property
|
|
21
|
+
def provenance(self) -> dict:
|
|
22
|
+
"""Return source and validity metadata for this material.
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
dict
|
|
27
|
+
Material name, source file, reference, conditions, validity range,
|
|
28
|
+
and catalog identity when the material was loaded from a catalog.
|
|
29
|
+
"""
|
|
30
|
+
return {
|
|
31
|
+
"name": self.filename,
|
|
32
|
+
"file_path": str(getattr(self, "file_path", "")) or None,
|
|
33
|
+
"reference": getattr(self, "reference", None),
|
|
34
|
+
"conditions": getattr(self, "conditions", None),
|
|
35
|
+
"comments": getattr(self, "comments", None),
|
|
36
|
+
"wavelength_range": getattr(self, "wavelength_bound", None),
|
|
37
|
+
"catalog_id": getattr(self, "catalog_id", None),
|
|
38
|
+
"source_url": getattr(self, "source_url", None),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
def refractive_index(self, wavelength: Length, **kwargs):
|
|
42
|
+
"""Return the complex refractive index ``n + i k``.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
wavelength : Length
|
|
47
|
+
Vacuum wavelength.
|
|
48
|
+
**kwargs
|
|
49
|
+
Forwarded to :meth:`compute_refractive_index`.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
complex or numpy.ndarray
|
|
54
|
+
Complex refractive index.
|
|
55
|
+
"""
|
|
56
|
+
return self.compute_refractive_index(wavelength, **kwargs)
|
|
57
|
+
|
|
58
|
+
def n(self, wavelength: Length, **kwargs):
|
|
59
|
+
"""Return the real refractive index.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
wavelength : Length
|
|
64
|
+
Vacuum wavelength.
|
|
65
|
+
**kwargs
|
|
66
|
+
Forwarded to :meth:`compute_refractive_index`.
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
float or numpy.ndarray
|
|
71
|
+
Real part of the complex refractive index.
|
|
72
|
+
"""
|
|
73
|
+
return self.compute_refractive_index(wavelength, **kwargs).real
|
|
74
|
+
|
|
75
|
+
def k(self, wavelength: Length, **kwargs):
|
|
76
|
+
"""Return the extinction coefficient.
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
wavelength : Length
|
|
81
|
+
Vacuum wavelength.
|
|
82
|
+
**kwargs
|
|
83
|
+
Forwarded to :meth:`compute_refractive_index`.
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
float or numpy.ndarray
|
|
88
|
+
Imaginary part of the complex refractive index.
|
|
89
|
+
"""
|
|
90
|
+
return self.compute_refractive_index(wavelength, **kwargs).imag
|
|
91
|
+
|
|
92
|
+
def relative_permittivity(self, wavelength: Length, **kwargs):
|
|
93
|
+
"""Return relative complex permittivity, ``(n + i k)²``.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
wavelength : Length
|
|
98
|
+
Vacuum wavelength.
|
|
99
|
+
**kwargs
|
|
100
|
+
Forwarded to :meth:`compute_refractive_index`.
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
complex or numpy.ndarray
|
|
105
|
+
Relative complex permittivity.
|
|
106
|
+
"""
|
|
107
|
+
return self.compute_refractive_index(wavelength, **kwargs) ** 2
|
|
108
|
+
|
|
109
|
+
@validate_units
|
|
110
|
+
def absorption_coefficient(self, wavelength: Length, **kwargs) -> AnyUnit:
|
|
111
|
+
"""Return the intensity absorption coefficient, ``α = 4πk / λ``.
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
wavelength : Length
|
|
116
|
+
Vacuum wavelength.
|
|
117
|
+
**kwargs
|
|
118
|
+
Forwarded to :meth:`k`.
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
-------
|
|
122
|
+
AnyUnit
|
|
123
|
+
Absorption coefficient with inverse-length units.
|
|
124
|
+
"""
|
|
125
|
+
return 4 * numpy.pi * self.k(wavelength, **kwargs) / wavelength
|
|
126
|
+
|
|
20
127
|
def __eq__(self, other) -> bool:
|
|
21
128
|
"""Compare materials by concrete type and filename.
|
|
22
129
|
|
|
@@ -88,8 +195,13 @@ class BaseMaterial(object):
|
|
|
88
195
|
raise ValueError("out_of_range must be 'warn', 'raise', or 'clip'.")
|
|
89
196
|
if self.wavelength_bound is not None:
|
|
90
197
|
min_value, max_value = self.wavelength_bound
|
|
198
|
+
values = wavelength.to(ureg.meter).magnitude
|
|
199
|
+
lower = min_value.to(ureg.meter).magnitude
|
|
200
|
+
upper = max_value.to(ureg.meter).magnitude
|
|
201
|
+
below = (values < lower) & ~numpy.isclose(values, lower, rtol=1e-12, atol=0.0)
|
|
202
|
+
above = (values > upper) & ~numpy.isclose(values, upper, rtol=1e-12, atol=0.0)
|
|
91
203
|
|
|
92
|
-
if numpy.any(
|
|
204
|
+
if numpy.any(below | above):
|
|
93
205
|
message = (
|
|
94
206
|
f"Wavelength range goes from {wavelength.min().to_compact()} to {wavelength.max().to_compact()} "
|
|
95
207
|
f"which is outside the allowable range of {min_value.to_compact()} to {max_value.to_compact()} µm. "
|