gratinglab 0.1.13__tar.gz → 0.2.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.
- {gratinglab-0.1.13/src/gratinglab.egg-info → gratinglab-0.2.1}/PKG-INFO +2 -1
- {gratinglab-0.1.13 → gratinglab-0.2.1}/pyproject.toml +13 -2
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/__init__.py +1 -1
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/checks.py +5 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/convergence.py +9 -2
- gratinglab-0.2.1/src/gratinglab/gui/cmethod_options.py +123 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/docs.py +13 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/efficiency_plot.py +11 -2
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/provenance.py +13 -1
- gratinglab-0.2.1/src/gratinglab/gui/qt/cmethod_tab.py +469 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/qt/geometry_panel.py +12 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/qt/main_window.py +36 -23
- gratinglab-0.2.1/src/gratinglab/gui/qt/rcwa_tab.py +466 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/qt/theming.py +48 -2
- gratinglab-0.2.1/src/gratinglab/gui/rcwa_options.py +164 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/state.py +56 -2
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/boundary_view.py +31 -7
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/canvas.py +46 -13
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/efficiency_dialog.py +26 -3
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/import_view.py +16 -3
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/main_window.py +16 -1
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/swath_dialog.py +18 -1
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/problem.py +27 -1
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/result.py +101 -35
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/solvers/__init__.py +6 -0
- gratinglab-0.2.1/src/gratinglab/solvers/_conditioning.py +131 -0
- gratinglab-0.2.1/src/gratinglab/solvers/_execution.py +153 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/solvers/base.py +19 -0
- gratinglab-0.2.1/src/gratinglab/solvers/cmethod/__init__.py +453 -0
- gratinglab-0.2.1/src/gratinglab/solvers/cmethod/_core.py +405 -0
- gratinglab-0.2.1/src/gratinglab/solvers/cmethod/_series.py +196 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/solvers/integral/__init__.py +155 -54
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/solvers/integral/_core.py +6 -2
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/solvers/integral/_finite.py +9 -3
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/solvers/integral/_nystrom.py +18 -0
- gratinglab-0.2.1/src/gratinglab/solvers/rcwa/__init__.py +446 -0
- gratinglab-0.2.1/src/gratinglab/solvers/rcwa/_core.py +437 -0
- gratinglab-0.2.1/src/gratinglab/solvers/rcwa/_stack.py +364 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1/src/gratinglab.egg-info}/PKG-INFO +2 -1
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab.egg-info/SOURCES.txt +21 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab.egg-info/requires.txt +1 -0
- gratinglab-0.2.1/tests/test_cmethod.py +529 -0
- gratinglab-0.2.1/tests/test_conditioning.py +201 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_convergence.py +22 -0
- gratinglab-0.2.1/tests/test_gui_cmethod_options.py +142 -0
- gratinglab-0.2.1/tests/test_gui_cmethod_tab.py +262 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_gui_qt.py +135 -2
- gratinglab-0.2.1/tests/test_gui_rcwa_options.py +95 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_gui_state.py +37 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_gui_theme.py +24 -0
- gratinglab-0.2.1/tests/test_rayleigh_anomaly.py +180 -0
- gratinglab-0.2.1/tests/test_rcwa.py +807 -0
- gratinglab-0.2.1/tests/test_rcwa_benchmarks.py +468 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_result.py +45 -1
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_solver_conformance.py +43 -5
- gratinglab-0.2.1/tests/test_solver_execution.py +252 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/LICENSE +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/README.md +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/setup.cfg +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/compare.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/geometry.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/__init__.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/app.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/diagram.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/diagram3d.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/icon.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/integral_options.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/launcher.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/mathtext.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/orders.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/qt/__init__.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/qt/gc_guard.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/qt/geometry_tab.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/qt/integral_tab.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/qt/launcher.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/qt/profile_plot_panel.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/qt/scalar_tab.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/qt/scene3d_panel.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/qt/setup_tab.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/qt/sizing.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/qt/theory_viewer.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/qt/worker.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/richtext.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/scalar_options.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/gui/theme.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/illumination.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/io/__init__.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/io/boundary.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/io/efficiency_table.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/io/ggp.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/materials/__init__.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/materials/data/SOURCES.md +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/materials/fresnel.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/materials/optical.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/materials/refractiveindex.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/measured.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/__init__.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/analyzer.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/boundary/__init__.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/boundary/average.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/boundary/pipeline.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/boundary/swaths.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/cli.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/config.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/core/__init__.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/core/analysis.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/core/image_flatten.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/core/orientation.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/core/processing.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/core/region_mask.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/core/scars.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/core/tip.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/data/__init__.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/data/aggregation.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/__init__.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/icon.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/__init__.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/app.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/wiki_view.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/worker.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/state.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/io/__init__.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/io/file_io.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/io/ibw.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/io/metrics.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/io/spm.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/settings.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/stats/__init__.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/stats/analysis.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/stats/icc.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/visualization/__init__.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/visualization/diagnostics.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/visualization/profiles.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/visualization/statistics.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/__init__.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/facet-fitting.md +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/file-formats.md +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/flattening.md +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/groove-orientation.md +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/icc-correction.md +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/overview.md +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/reading-outputs.md +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/row-groups.md +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/scars-and-masking.md +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/swath-statistics.md +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/tip-correction.md +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/metrology/workflows.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/profiles.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/resolution.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/solvers/integral/_boundary.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/solvers/integral/_greens.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/solvers/integral/_kernels.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab/solvers/scalar.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab.egg-info/dependency_links.txt +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab.egg-info/entry_points.txt +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/src/gratinglab.egg-info/top_level.txt +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_changelog.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_checks.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_compare.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_cross_method.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_efficiency_table.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_fresnel.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_geometry.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_ggp.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_gui_diagram.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_gui_diagram3d.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_gui_docs.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_gui_integral_options.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_gui_launcher.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_gui_mathtext.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_gui_orders.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_gui_provenance.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_gui_richtext.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_gui_scalar_options.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_illumination.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_integral_core.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_integral_corpus.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_integral_finite.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_integral_graded.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_integral_greens.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_integral_kernels.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_integral_operators.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_integral_solver.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_io_boundary.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_materials.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_measured.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_perturbation.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_profiles.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_refractiveindex.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_resolution.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_scalar.py +0 -0
- {gratinglab-0.1.13 → gratinglab-0.2.1}/tests/test_solver_progress.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gratinglab
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Open platform for diffraction grating groove metrology and efficiency analysis
|
|
5
5
|
Author: Jake McCoy, Aditi Gangadharan
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -23,6 +23,7 @@ Requires-Dist: numpy>=1.26
|
|
|
23
23
|
Requires-Dist: scipy>=1.11
|
|
24
24
|
Requires-Dist: pydantic>=2.6
|
|
25
25
|
Requires-Dist: endstation>=0.2.2
|
|
26
|
+
Requires-Dist: threadpoolctl>=3.1
|
|
26
27
|
Provides-Extra: dev
|
|
27
28
|
Requires-Dist: pytest>=7; extra == "dev"
|
|
28
29
|
Requires-Dist: pytest-qt>=4.5; extra == "dev"
|
|
@@ -52,6 +52,13 @@ dependencies = [
|
|
|
52
52
|
# so this line adds no transitive weight -- the Qt and matplotlib parts sit
|
|
53
53
|
# behind its own extras, which our `gui` extra asks for by name.
|
|
54
54
|
"endstation>=0.2.2",
|
|
55
|
+
# The rigorous solvers cap BLAS threads for the length of a solve
|
|
56
|
+
# (solvers/_execution.py): OpenBLAS's thread-per-core default made RCWA
|
|
57
|
+
# up to 135x slower on a busy machine, measured in benchmarks/
|
|
58
|
+
# solver_speed.py. NumPy and SciPy each load their own OpenBLAS, and
|
|
59
|
+
# this is the one maintained way to reach both. Pure Python, BSD-3, no
|
|
60
|
+
# dependencies of its own.
|
|
61
|
+
"threadpoolctl>=3.1",
|
|
55
62
|
]
|
|
56
63
|
|
|
57
64
|
[project.optional-dependencies]
|
|
@@ -175,12 +182,16 @@ filterwarnings = [
|
|
|
175
182
|
"ignore:Enum value .Qt..ApplicationAttribute.AA_UseHighDpiPixmaps. is marked as deprecated:DeprecationWarning:matplotlib.backends.backend_qt",
|
|
176
183
|
# CI runs with PYTHONWARNDEFAULTENCODING=1, so any text open() here that
|
|
177
184
|
# leaves the encoding to the platform fails on every OS, not only on
|
|
178
|
-
# Windows with a non-ASCII file. These
|
|
185
|
+
# Windows with a non-ASCII file. These four are other projects' calls.
|
|
179
186
|
# numpy's savetxt touches its output with a bare open() whatever encoding
|
|
180
|
-
# it is given.
|
|
187
|
+
# it is given. threadpoolctl reads /proc/self/maps with a bare open(), so
|
|
188
|
+
# on Linux only, every rigorous solve (which caps BLAS through it) failed
|
|
189
|
+
# here: master's CI run 40624677314, both ubuntu jobs. Inert without the
|
|
190
|
+
# flag.
|
|
181
191
|
"ignore::EncodingWarning:matplotlib.font_manager",
|
|
182
192
|
"ignore::EncodingWarning:numpy.lib._npyio_impl",
|
|
183
193
|
"ignore::EncodingWarning:numpy.lib._datasource",
|
|
194
|
+
"ignore::EncodingWarning:threadpoolctl",
|
|
184
195
|
]
|
|
185
196
|
# pytest-qt would otherwise probe every binding it knows. Verified clean under
|
|
186
197
|
# `python -W error -c "import PySide6.QtWidgets"`, so `filterwarnings = error`
|
|
@@ -22,7 +22,7 @@ from .measured import MeasuredEfficiency, MeasurementProvenance
|
|
|
22
22
|
from .resolution import ResolvingPower, resolving_power
|
|
23
23
|
from .result import EfficiencyScan, OrderEfficiency, Provenance
|
|
24
24
|
|
|
25
|
-
__version__ = "0.1
|
|
25
|
+
__version__ = "0.2.1"
|
|
26
26
|
|
|
27
27
|
__all__ = [
|
|
28
28
|
"Illumination",
|
|
@@ -238,6 +238,9 @@ def check_energy_balance(
|
|
|
238
238
|
|
|
239
239
|
Notes
|
|
240
240
|
-----
|
|
241
|
+
Transmitted orders, when the scan records them, are added to the sum: the
|
|
242
|
+
power a lossless substrate carries away is accounted for, not lost.
|
|
243
|
+
|
|
241
244
|
When the scan records an absorbed fraction (``scan.absorption``, from a
|
|
242
245
|
solver that computes one independently), the conserved quantity is the
|
|
243
246
|
sum plus the absorption, and that is what is checked. ``R + A = 1`` is a
|
|
@@ -253,6 +256,8 @@ def check_energy_balance(
|
|
|
253
256
|
reached 3.6.
|
|
254
257
|
"""
|
|
255
258
|
total = np.asarray(scan.total, dtype=np.float64)
|
|
259
|
+
if scan.transmission is not None:
|
|
260
|
+
total = total + scan.transmission.total
|
|
256
261
|
if scan.absorption is not None:
|
|
257
262
|
total = total + scan.absorption
|
|
258
263
|
return EnergyReport(
|
|
@@ -201,7 +201,10 @@ def default_ladder(solver) -> tuple[int, ...]:
|
|
|
201
201
|
"""A ladder straddling the solver's own default for its accuracy knob.
|
|
202
202
|
|
|
203
203
|
The default is read from the signature and is not hardcoded, so a backend
|
|
204
|
-
that changes its default does not leave this sweeping the wrong range.
|
|
204
|
+
that changes its default does not leave this sweeping the wrong range. A
|
|
205
|
+
backend declaring an ``accuracy_ceiling`` has the ladder cut there: a sweep
|
|
206
|
+
that has not plateaued by the ceiling reports that, instead of failing on
|
|
207
|
+
a rung the solver refuses.
|
|
205
208
|
"""
|
|
206
209
|
knob = _knob_of(solver)
|
|
207
210
|
default = inspect.signature(solver.solve).parameters[knob].default
|
|
@@ -211,7 +214,11 @@ def default_ladder(solver) -> tuple[int, ...]:
|
|
|
211
214
|
f"{knob!r} ({default!r}), so a ladder cannot be inferred -- pass "
|
|
212
215
|
"`values` explicitly"
|
|
213
216
|
)
|
|
214
|
-
|
|
217
|
+
ladder = doubling_ladder(max(1, default // 2**_RUNGS_BELOW_DEFAULT), _DEFAULT_RUNGS)
|
|
218
|
+
ceiling = solver.capabilities.accuracy_ceiling
|
|
219
|
+
if ceiling is not None:
|
|
220
|
+
ladder = tuple(value for value in ladder if value <= ceiling)
|
|
221
|
+
return ladder
|
|
215
222
|
|
|
216
223
|
|
|
217
224
|
def check_convergence(
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""The C-method's own form: boundary condition, truncation, and their guards.
|
|
2
|
+
|
|
3
|
+
The third options module, written to the pattern of ``integral_options.py``:
|
|
4
|
+
its own file, its own frozen state and its own ``build_options`` raising
|
|
5
|
+
:class:`~gratinglab.gui.state.FormErrors`, and no toolkit import.
|
|
6
|
+
|
|
7
|
+
The form has two settings, because the solver has two. ``conductivity`` is
|
|
8
|
+
named and offered as the integral solver offers it, so one geometry reaches
|
|
9
|
+
either method with the same choice: ``"perfect"`` (relative efficiency, no
|
|
10
|
+
material) or ``"tabulated"`` (one interface into the coating, read per
|
|
11
|
+
wavelength, with absolute efficiency, absorption and, into a lossless
|
|
12
|
+
material, the transmitted orders). ``truncation`` is the accuracy knob: ``N``
|
|
13
|
+
harmonics either side of the centre of the propagating orders.
|
|
14
|
+
|
|
15
|
+
The guards mirror the solver's refusals so an error lands on the field that
|
|
16
|
+
caused it instead of arriving as a ValueError from the worker thread. The
|
|
17
|
+
truncation floor is not re-derived here: it is
|
|
18
|
+
:func:`gratinglab.solvers.cmethod.truncation_floor`, the function the solver
|
|
19
|
+
itself applies, so the form and the solver cannot disagree about it.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from dataclasses import dataclass, replace
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
import numpy as np
|
|
28
|
+
from numpy.typing import NDArray
|
|
29
|
+
|
|
30
|
+
from ..illumination import Illumination
|
|
31
|
+
from ..problem import Problem
|
|
32
|
+
from ..solvers.cmethod import _MAX_TRUNCATION, truncation_floor
|
|
33
|
+
from .integral_options import _tabulated_index
|
|
34
|
+
from .state import FieldError, FormErrors, _number
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"CONDUCTIVITY_MODES",
|
|
38
|
+
"CONDUCTIVITY_NOTES",
|
|
39
|
+
"CMethodOptionsState",
|
|
40
|
+
"build_options",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
#: Selectable boundary conditions, the solver's default first.
|
|
44
|
+
CONDUCTIVITY_MODES = ("perfect", "tabulated")
|
|
45
|
+
|
|
46
|
+
#: What each mode means, shown beneath the selector, for the reason
|
|
47
|
+
#: ``integral_options.CONDUCTIVITY_NOTES`` gives: a claim about the result
|
|
48
|
+
#: must not be hardcoded beside a control that can falsify it.
|
|
49
|
+
CONDUCTIVITY_NOTES = {
|
|
50
|
+
"perfect": (
|
|
51
|
+
"Perfect conductor in any mount: efficiencies are relative to a "
|
|
52
|
+
"perfect reflector and sum to 1. A coating named in the geometry "
|
|
53
|
+
"panel is not consulted (the provenance panel says so)."
|
|
54
|
+
),
|
|
55
|
+
"tabulated": (
|
|
56
|
+
"One interface into the coating named in the geometry panel, read "
|
|
57
|
+
"as a complex index at every wavelength. Efficiencies are absolute "
|
|
58
|
+
"and absorption is recorded; into a lossless material the "
|
|
59
|
+
"transmitted orders are recorded too, and R + T + A = 1 is a check."
|
|
60
|
+
),
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True, slots=True)
|
|
65
|
+
class CMethodOptionsState:
|
|
66
|
+
"""The C-method's own form fields, exactly as typed."""
|
|
67
|
+
|
|
68
|
+
truncation: str = "32"
|
|
69
|
+
conductivity: str = CONDUCTIVITY_MODES[0]
|
|
70
|
+
|
|
71
|
+
def with_field(self, name: str, value: Any) -> "CMethodOptionsState":
|
|
72
|
+
"""Return a copy with one field changed."""
|
|
73
|
+
return replace(self, **{name: value})
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def build_options(
|
|
77
|
+
problem: Problem,
|
|
78
|
+
illumination: Illumination,
|
|
79
|
+
wavelengths: NDArray[np.float64],
|
|
80
|
+
form: CMethodOptionsState,
|
|
81
|
+
) -> dict[str, Any]:
|
|
82
|
+
"""Parse the C-method options and check the truncation holds every order.
|
|
83
|
+
|
|
84
|
+
Raises
|
|
85
|
+
------
|
|
86
|
+
FormErrors
|
|
87
|
+
Same contract as :func:`gratinglab.gui.state.build`.
|
|
88
|
+
"""
|
|
89
|
+
errors: list[FieldError] = []
|
|
90
|
+
truncation = _number(
|
|
91
|
+
form.truncation, "truncation", errors, minimum=1, integer=True
|
|
92
|
+
)
|
|
93
|
+
if truncation is not None and truncation > _MAX_TRUNCATION:
|
|
94
|
+
errors.append(
|
|
95
|
+
FieldError(
|
|
96
|
+
"truncation",
|
|
97
|
+
f"at most {_MAX_TRUNCATION}: the eigenproblem is dense and its "
|
|
98
|
+
"cost grows as N^3, so a sweep that has not settled by then is "
|
|
99
|
+
"the answer",
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
if form.conductivity not in CONDUCTIVITY_MODES:
|
|
103
|
+
errors.append(
|
|
104
|
+
FieldError(
|
|
105
|
+
"conductivity",
|
|
106
|
+
f"must be one of {', '.join(CONDUCTIVITY_MODES)}",
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
if errors:
|
|
110
|
+
raise FormErrors(tuple(errors))
|
|
111
|
+
assert truncation is not None
|
|
112
|
+
|
|
113
|
+
indices = None
|
|
114
|
+
if form.conductivity == "tabulated":
|
|
115
|
+
_name, indices, error = _tabulated_index(problem, wavelengths)
|
|
116
|
+
if error is not None:
|
|
117
|
+
raise FormErrors((error,))
|
|
118
|
+
|
|
119
|
+
floor = truncation_floor(problem, illumination, wavelengths, indices)
|
|
120
|
+
if truncation < floor.needed:
|
|
121
|
+
raise FormErrors((FieldError("truncation", floor.message),))
|
|
122
|
+
|
|
123
|
+
return {"truncation": int(truncation), "conductivity": form.conductivity}
|
|
@@ -44,8 +44,10 @@ __all__ = [
|
|
|
44
44
|
#: name for anything not listed here, so a new solver still gets a menu entry
|
|
45
45
|
#: before anyone remembers to add a nicer title.
|
|
46
46
|
_TITLES = {
|
|
47
|
+
"cmethod": "C-method (Chandezon)",
|
|
47
48
|
"scalar": "Scalar (Kirchhoff)",
|
|
48
49
|
"integral": "Integral (boundary integral equation)",
|
|
50
|
+
"rcwa": "RCWA (Fourier modal method)",
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
#: Non-solver reference pages: (key, title, filename relative to docs/).
|
|
@@ -105,6 +107,17 @@ def display_title(name: str) -> str:
|
|
|
105
107
|
return _TITLES.get(name, name)
|
|
106
108
|
|
|
107
109
|
|
|
110
|
+
def short_title(name: str) -> str:
|
|
111
|
+
"""A solver's tab label: its display title without the bracketed gloss.
|
|
112
|
+
|
|
113
|
+
``"Integral (boundary integral equation)"`` becomes ``"Integral"``. The
|
|
114
|
+
full title was the tab label until 2026-10-27, and with four tabs plus the
|
|
115
|
+
geometry dock it already overflowed the bar, so every new solver made it
|
|
116
|
+
worse. The full title stays in the Help menu and the tab's tooltip.
|
|
117
|
+
"""
|
|
118
|
+
return display_title(name).split(" (", 1)[0]
|
|
119
|
+
|
|
120
|
+
|
|
108
121
|
def find_theory_root(start: Path | None = None) -> Path | None:
|
|
109
122
|
"""Search upward from ``start`` for a ``docs/theory`` directory.
|
|
110
123
|
|
|
@@ -50,6 +50,14 @@ def draw_efficiency(
|
|
|
50
50
|
scan.wavelengths, scan.total,
|
|
51
51
|
color=theme.ink, ls="--", lw=1.0, alpha=0.7, label="Σ",
|
|
52
52
|
)
|
|
53
|
+
transmitted = (
|
|
54
|
+
scan.transmission.total if scan.transmission is not None else 0.0
|
|
55
|
+
)
|
|
56
|
+
if scan.transmission is not None:
|
|
57
|
+
axes.plot(
|
|
58
|
+
scan.wavelengths, scan.transmission.total,
|
|
59
|
+
color=theme.ink, lw=1.0, ls=":", alpha=0.9, label="T",
|
|
60
|
+
)
|
|
53
61
|
if scan.absorption is not None:
|
|
54
62
|
# Under a finite-conductivity solve Σ alone is the reflected fraction
|
|
55
63
|
# and sits below the unity line by exactly the absorbed one. Drawing
|
|
@@ -61,8 +69,9 @@ def draw_efficiency(
|
|
|
61
69
|
color=theme.warn, lw=1.0, ls="-.", alpha=0.9, label="A",
|
|
62
70
|
)
|
|
63
71
|
axes.plot(
|
|
64
|
-
scan.wavelengths, scan.total + scan.absorption,
|
|
65
|
-
color=theme.ink, lw=1.0, ls="-", alpha=0.5,
|
|
72
|
+
scan.wavelengths, scan.total + transmitted + scan.absorption,
|
|
73
|
+
color=theme.ink, lw=1.0, ls="-", alpha=0.5,
|
|
74
|
+
label="Σ + T + A" if scan.transmission is not None else "Σ + A",
|
|
66
75
|
)
|
|
67
76
|
axes.axhline(1.0, color=theme.bad, lw=0.8, ls=":", alpha=0.8)
|
|
68
77
|
axes.set_xlabel("wavelength (nm)")
|
|
@@ -147,6 +147,16 @@ def provenance_lines(
|
|
|
147
147
|
lines.append(Line("normalization: ", "dim"))
|
|
148
148
|
lines.append(Line(f"{normalization} ({detail})\n", "dim"))
|
|
149
149
|
|
|
150
|
+
if scan.transmission is not None:
|
|
151
|
+
lines.append(Line("transmission: ", "dim"))
|
|
152
|
+
lines.append(
|
|
153
|
+
Line(
|
|
154
|
+
f"T ∈ [{scan.transmission.total.min():.4f}, "
|
|
155
|
+
f"{scan.transmission.total.max():.4f}]\n",
|
|
156
|
+
"dim",
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
|
|
150
160
|
if scan.absorption is not None:
|
|
151
161
|
# A physical result, not a problem: `dim`, never an alarm tag. It also
|
|
152
162
|
# explains the deficit in Σ below, which would otherwise read as a
|
|
@@ -163,7 +173,9 @@ def provenance_lines(
|
|
|
163
173
|
# `check_energy_balance` folds absorption into the total when a scan
|
|
164
174
|
# carries one, so the conserved quantity is R + A there. Naming it Σ
|
|
165
175
|
# anyway would collide with the Σ curve on the plot, which is R alone.
|
|
166
|
-
conserved = "Σ +
|
|
176
|
+
conserved = "Σ" + (" + T" if scan.transmission is not None else "") + (
|
|
177
|
+
" + A" if scan.absorption is not None else ""
|
|
178
|
+
)
|
|
167
179
|
lines.append(Line("energy balance: ", "dim"))
|
|
168
180
|
lines.append(
|
|
169
181
|
Line(
|