gratinglab 0.2.0__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.2.0/src/gratinglab.egg-info → gratinglab-0.2.1}/PKG-INFO +1 -1
- {gratinglab-0.2.0 → gratinglab-0.2.1}/pyproject.toml +6 -2
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/__init__.py +1 -1
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/convergence.py +9 -2
- gratinglab-0.2.1/src/gratinglab/gui/cmethod_options.py +123 -0
- gratinglab-0.2.1/src/gratinglab/gui/qt/cmethod_tab.py +469 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/qt/geometry_panel.py +12 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/qt/main_window.py +27 -4
- gratinglab-0.2.1/src/gratinglab/gui/qt/rcwa_tab.py +466 -0
- gratinglab-0.2.1/src/gratinglab/gui/rcwa_options.py +164 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/state.py +56 -2
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/solvers/base.py +7 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/solvers/cmethod/__init__.py +60 -25
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/solvers/rcwa/__init__.py +51 -20
- {gratinglab-0.2.0 → gratinglab-0.2.1/src/gratinglab.egg-info}/PKG-INFO +1 -1
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab.egg-info/SOURCES.txt +7 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_cmethod.py +10 -4
- {gratinglab-0.2.0 → 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.2.0 → gratinglab-0.2.1}/tests/test_gui_qt.py +129 -0
- gratinglab-0.2.1/tests/test_gui_rcwa_options.py +95 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_gui_state.py +37 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/LICENSE +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/README.md +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/setup.cfg +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/checks.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/compare.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/geometry.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/__init__.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/app.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/diagram.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/diagram3d.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/docs.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/efficiency_plot.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/icon.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/integral_options.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/launcher.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/mathtext.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/orders.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/provenance.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/qt/__init__.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/qt/gc_guard.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/qt/geometry_tab.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/qt/integral_tab.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/qt/launcher.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/qt/profile_plot_panel.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/qt/scalar_tab.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/qt/scene3d_panel.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/qt/setup_tab.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/qt/sizing.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/qt/theming.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/qt/theory_viewer.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/qt/worker.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/richtext.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/scalar_options.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/gui/theme.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/illumination.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/io/__init__.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/io/boundary.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/io/efficiency_table.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/io/ggp.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/materials/__init__.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/materials/data/SOURCES.md +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/materials/fresnel.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/materials/optical.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/materials/refractiveindex.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/measured.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/__init__.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/analyzer.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/boundary/__init__.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/boundary/average.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/boundary/pipeline.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/boundary/swaths.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/cli.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/config.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/core/__init__.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/core/analysis.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/core/image_flatten.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/core/orientation.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/core/processing.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/core/region_mask.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/core/scars.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/core/tip.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/data/__init__.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/data/aggregation.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/__init__.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/icon.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/__init__.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/app.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/boundary_view.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/canvas.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/efficiency_dialog.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/import_view.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/main_window.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/swath_dialog.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/wiki_view.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/qt/worker.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/gui/state.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/io/__init__.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/io/file_io.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/io/ibw.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/io/metrics.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/io/spm.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/settings.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/stats/__init__.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/stats/analysis.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/stats/icc.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/visualization/__init__.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/visualization/diagnostics.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/visualization/profiles.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/visualization/statistics.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/__init__.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/facet-fitting.md +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/file-formats.md +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/flattening.md +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/groove-orientation.md +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/icc-correction.md +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/overview.md +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/reading-outputs.md +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/row-groups.md +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/scars-and-masking.md +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/swath-statistics.md +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/wiki/tip-correction.md +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/metrology/workflows.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/problem.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/profiles.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/resolution.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/result.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/solvers/__init__.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/solvers/_conditioning.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/solvers/_execution.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/solvers/cmethod/_core.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/solvers/cmethod/_series.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/solvers/integral/__init__.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/solvers/integral/_boundary.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/solvers/integral/_core.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/solvers/integral/_finite.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/solvers/integral/_greens.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/solvers/integral/_kernels.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/solvers/integral/_nystrom.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/solvers/rcwa/_core.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/solvers/rcwa/_stack.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab/solvers/scalar.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab.egg-info/dependency_links.txt +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab.egg-info/entry_points.txt +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab.egg-info/requires.txt +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/src/gratinglab.egg-info/top_level.txt +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_changelog.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_checks.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_compare.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_conditioning.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_cross_method.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_efficiency_table.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_fresnel.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_geometry.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_ggp.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_gui_diagram.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_gui_diagram3d.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_gui_docs.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_gui_integral_options.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_gui_launcher.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_gui_mathtext.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_gui_orders.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_gui_provenance.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_gui_richtext.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_gui_scalar_options.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_gui_theme.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_illumination.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_integral_core.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_integral_corpus.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_integral_finite.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_integral_graded.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_integral_greens.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_integral_kernels.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_integral_operators.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_integral_solver.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_io_boundary.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_materials.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_measured.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_perturbation.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_profiles.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_rayleigh_anomaly.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_rcwa.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_rcwa_benchmarks.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_refractiveindex.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_resolution.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_result.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_scalar.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_solver_conformance.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_solver_execution.py +0 -0
- {gratinglab-0.2.0 → gratinglab-0.2.1}/tests/test_solver_progress.py +0 -0
|
@@ -182,12 +182,16 @@ filterwarnings = [
|
|
|
182
182
|
"ignore:Enum value .Qt..ApplicationAttribute.AA_UseHighDpiPixmaps. is marked as deprecated:DeprecationWarning:matplotlib.backends.backend_qt",
|
|
183
183
|
# CI runs with PYTHONWARNDEFAULTENCODING=1, so any text open() here that
|
|
184
184
|
# leaves the encoding to the platform fails on every OS, not only on
|
|
185
|
-
# Windows with a non-ASCII file. These
|
|
185
|
+
# Windows with a non-ASCII file. These four are other projects' calls.
|
|
186
186
|
# numpy's savetxt touches its output with a bare open() whatever encoding
|
|
187
|
-
# 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.
|
|
188
191
|
"ignore::EncodingWarning:matplotlib.font_manager",
|
|
189
192
|
"ignore::EncodingWarning:numpy.lib._npyio_impl",
|
|
190
193
|
"ignore::EncodingWarning:numpy.lib._datasource",
|
|
194
|
+
"ignore::EncodingWarning:threadpoolctl",
|
|
191
195
|
]
|
|
192
196
|
# pytest-qt would otherwise probe every binding it knows. Verified clean under
|
|
193
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.2.
|
|
25
|
+
__version__ = "0.2.1"
|
|
26
26
|
|
|
27
27
|
__all__ = [
|
|
28
28
|
"Illumination",
|
|
@@ -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}
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
"""The C-method's own tab: its knob, its result, its provenance.
|
|
2
|
+
|
|
3
|
+
The third solver tab, written beside ``scalar_tab.py`` and ``integral_tab.py``
|
|
4
|
+
rather than on a base class, for the reason ``integral_tab.py`` gives: the
|
|
5
|
+
common structure is to be extracted once enough tabs show what it is, and the
|
|
6
|
+
duplication is that evidence. Against the integral tab this one drops the
|
|
7
|
+
corner-mesh selector, which the C-method has no counterpart for, and its knob
|
|
8
|
+
is ``truncation`` rather than ``boundary_points``.
|
|
9
|
+
|
|
10
|
+
Its warnings arrive the way every solver's do, as
|
|
11
|
+
``Provenance.warnings``, and the provenance panel prints them: corners
|
|
12
|
+
(slow convergence, an energy balance that overstates accuracy), a large
|
|
13
|
+
condition number (higher truncation can make the answer worse), and an energy
|
|
14
|
+
balance straying from unity. The tab adds none of its own.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import csv
|
|
20
|
+
import dataclasses
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Sequence
|
|
23
|
+
|
|
24
|
+
from PySide6.QtCore import Qt, Signal
|
|
25
|
+
from PySide6.QtWidgets import (
|
|
26
|
+
QAbstractItemView,
|
|
27
|
+
QComboBox,
|
|
28
|
+
QFileDialog,
|
|
29
|
+
QFormLayout,
|
|
30
|
+
QGroupBox,
|
|
31
|
+
QHBoxLayout,
|
|
32
|
+
QLabel,
|
|
33
|
+
QLineEdit,
|
|
34
|
+
QListWidget,
|
|
35
|
+
QListWidgetItem,
|
|
36
|
+
QMessageBox,
|
|
37
|
+
QProgressBar,
|
|
38
|
+
QPushButton,
|
|
39
|
+
QScrollArea,
|
|
40
|
+
QSplitter,
|
|
41
|
+
QTextEdit,
|
|
42
|
+
QVBoxLayout,
|
|
43
|
+
QWidget,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
from .. import orders as orders_module
|
|
47
|
+
from .. import provenance
|
|
48
|
+
from ..efficiency_plot import draw_efficiency
|
|
49
|
+
from ..theme import provenance_colors, style_figure
|
|
50
|
+
from . import theming
|
|
51
|
+
from ..cmethod_options import (
|
|
52
|
+
CONDUCTIVITY_MODES,
|
|
53
|
+
CONDUCTIVITY_NOTES,
|
|
54
|
+
CMethodOptionsState,
|
|
55
|
+
)
|
|
56
|
+
from ..cmethod_options import build_options as build_cmethod_options
|
|
57
|
+
from .sizing import fit_width_to_contents
|
|
58
|
+
|
|
59
|
+
__all__ = ["CMethodTab"]
|
|
60
|
+
|
|
61
|
+
_ORDER_ROLE = Qt.ItemDataRole.UserRole
|
|
62
|
+
#: Set on an order that never propagates in the scan, drawn in the muted colour.
|
|
63
|
+
_DIM_ROLE = Qt.ItemDataRole.UserRole + 1
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class CMethodTab(QWidget):
|
|
67
|
+
"""The C-method's own controls, result, and provenance."""
|
|
68
|
+
|
|
69
|
+
name = "cmethod"
|
|
70
|
+
|
|
71
|
+
solve_requested = Signal()
|
|
72
|
+
cancel_requested = Signal()
|
|
73
|
+
convergence_requested = Signal()
|
|
74
|
+
|
|
75
|
+
def __init__(self, parent: QWidget | None = None) -> None:
|
|
76
|
+
super().__init__(parent)
|
|
77
|
+
self._fields: dict[str, QWidget] = {}
|
|
78
|
+
self._scan = None
|
|
79
|
+
self._lines: tuple = ()
|
|
80
|
+
self._order_colors: dict[int, str] = {}
|
|
81
|
+
self._energy = None
|
|
82
|
+
self._lambda_over_period: float | None = None
|
|
83
|
+
self._visible_orders: frozenset[int] = frozenset()
|
|
84
|
+
self._previous_orders: frozenset[int] = frozenset()
|
|
85
|
+
self._build()
|
|
86
|
+
|
|
87
|
+
# -- construction ------------------------------------------------
|
|
88
|
+
|
|
89
|
+
def _build(self) -> None:
|
|
90
|
+
controls = QWidget()
|
|
91
|
+
column = QVBoxLayout(controls)
|
|
92
|
+
column.setContentsMargins(0, 0, 0, 0)
|
|
93
|
+
column.addWidget(self._build_options_group())
|
|
94
|
+
column.addWidget(self._build_orders_group())
|
|
95
|
+
column.addLayout(self._build_action_buttons())
|
|
96
|
+
column.addWidget(self._export_button)
|
|
97
|
+
column.addWidget(self._progress)
|
|
98
|
+
column.addStretch(1)
|
|
99
|
+
|
|
100
|
+
scroller = QScrollArea()
|
|
101
|
+
scroller.setWidget(controls)
|
|
102
|
+
scroller.setWidgetResizable(True)
|
|
103
|
+
fit_width_to_contents(scroller)
|
|
104
|
+
|
|
105
|
+
right = QSplitter(Qt.Orientation.Vertical)
|
|
106
|
+
right.addWidget(self._build_efficiency_plot())
|
|
107
|
+
right.addWidget(self._build_provenance())
|
|
108
|
+
right.setStretchFactor(0, 1)
|
|
109
|
+
right.setStretchFactor(1, 0)
|
|
110
|
+
|
|
111
|
+
body = QSplitter(Qt.Orientation.Horizontal)
|
|
112
|
+
body.addWidget(scroller)
|
|
113
|
+
body.addWidget(right)
|
|
114
|
+
body.setStretchFactor(0, 0)
|
|
115
|
+
body.setStretchFactor(1, 1)
|
|
116
|
+
body.setSizes([260, 700])
|
|
117
|
+
|
|
118
|
+
outer = QVBoxLayout(self)
|
|
119
|
+
outer.setContentsMargins(0, 0, 0, 0)
|
|
120
|
+
outer.addWidget(body)
|
|
121
|
+
|
|
122
|
+
self._check_fields_match_cmethod_options()
|
|
123
|
+
|
|
124
|
+
def _build_options_group(self) -> QWidget:
|
|
125
|
+
defaults = CMethodOptionsState()
|
|
126
|
+
group = QGroupBox("C-method (Chandezon)")
|
|
127
|
+
form = QFormLayout(group)
|
|
128
|
+
truncation = QLineEdit(defaults.truncation)
|
|
129
|
+
truncation.returnPressed.connect(self.solve_requested)
|
|
130
|
+
truncation.setToolTip(
|
|
131
|
+
"N: harmonics N either side of the centre of the propagating\n"
|
|
132
|
+
"orders, 2N + 1 in all -- the accuracy knob the convergence\n"
|
|
133
|
+
"sweep varies, from 1 to 256. Cost grows as N^3. On a profile\n"
|
|
134
|
+
"with corners convergence is slow, and on a deep one a high N\n"
|
|
135
|
+
"can lose digits: the provenance panel warns of both."
|
|
136
|
+
)
|
|
137
|
+
self._fields["truncation"] = truncation
|
|
138
|
+
form.addRow("Truncation N", truncation)
|
|
139
|
+
|
|
140
|
+
conductivity = QComboBox()
|
|
141
|
+
conductivity.addItems(CONDUCTIVITY_MODES)
|
|
142
|
+
conductivity.setToolTip(
|
|
143
|
+
"Which boundary condition the C-method matches.\n"
|
|
144
|
+
"perfect: perfect conductor in any mount -- efficiencies are\n"
|
|
145
|
+
" relative to a perfect reflector and sum to 1. A coating is\n"
|
|
146
|
+
" not consulted.\n"
|
|
147
|
+
"tabulated: one interface into the coating, read as a complex\n"
|
|
148
|
+
" index at every wavelength -- efficiencies are absolute,\n"
|
|
149
|
+
" absorption is recorded, and into a lossless material so are\n"
|
|
150
|
+
" the transmitted orders."
|
|
151
|
+
)
|
|
152
|
+
conductivity.currentTextChanged.connect(self._show_conductivity_note)
|
|
153
|
+
self._fields["conductivity"] = conductivity
|
|
154
|
+
form.addRow("Conductivity", conductivity)
|
|
155
|
+
|
|
156
|
+
self._conductivity_note = QLabel()
|
|
157
|
+
self._conductivity_note.setWordWrap(True)
|
|
158
|
+
form.addRow(self._conductivity_note)
|
|
159
|
+
self._show_conductivity_note(conductivity.currentText())
|
|
160
|
+
return group
|
|
161
|
+
|
|
162
|
+
def _show_conductivity_note(self, mode: str) -> None:
|
|
163
|
+
"""Say what the selected boundary condition means for the numbers."""
|
|
164
|
+
self._conductivity_note.setText(CONDUCTIVITY_NOTES.get(mode, ""))
|
|
165
|
+
|
|
166
|
+
def _build_orders_group(self) -> QWidget:
|
|
167
|
+
group = QGroupBox("Orders")
|
|
168
|
+
layout = QVBoxLayout(group)
|
|
169
|
+
|
|
170
|
+
self._order_list = QListWidget()
|
|
171
|
+
self._order_list.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
|
|
172
|
+
self._order_list.itemChanged.connect(self._on_order_item_changed)
|
|
173
|
+
layout.addWidget(self._order_list)
|
|
174
|
+
|
|
175
|
+
buttons = QHBoxLayout()
|
|
176
|
+
show_all = QPushButton("All")
|
|
177
|
+
show_all.clicked.connect(self._show_all_orders)
|
|
178
|
+
show_none = QPushButton("None")
|
|
179
|
+
show_none.clicked.connect(self._show_no_orders)
|
|
180
|
+
show_default = QPushButton("Default")
|
|
181
|
+
show_default.clicked.connect(self._show_default_orders)
|
|
182
|
+
for button in (show_all, show_none, show_default):
|
|
183
|
+
buttons.addWidget(button)
|
|
184
|
+
layout.addLayout(buttons)
|
|
185
|
+
|
|
186
|
+
self._order_count_label = QLabel("")
|
|
187
|
+
layout.addWidget(self._order_count_label)
|
|
188
|
+
return group
|
|
189
|
+
|
|
190
|
+
def _build_action_buttons(self) -> QVBoxLayout:
|
|
191
|
+
self._solve_button = QPushButton("Solve")
|
|
192
|
+
self._solve_button.clicked.connect(self.solve_requested)
|
|
193
|
+
self._cancel_button = QPushButton("Cancel")
|
|
194
|
+
self._cancel_button.clicked.connect(self.cancel_requested)
|
|
195
|
+
self._cancel_button.setEnabled(False)
|
|
196
|
+
self._export_button = QPushButton("Export CSV…")
|
|
197
|
+
self._export_button.clicked.connect(self.export_csv)
|
|
198
|
+
|
|
199
|
+
self._converge_button = QPushButton("Check convergence…")
|
|
200
|
+
self._converge_button.setToolTip(
|
|
201
|
+
"Sweep truncation (up to 256) until the answer stops moving, and "
|
|
202
|
+
"report the coarsest setting that can be defended."
|
|
203
|
+
)
|
|
204
|
+
self._converge_button.clicked.connect(self.convergence_requested)
|
|
205
|
+
|
|
206
|
+
self._progress = QProgressBar()
|
|
207
|
+
self._progress.setRange(0, 0)
|
|
208
|
+
self._progress.hide()
|
|
209
|
+
|
|
210
|
+
row = QHBoxLayout()
|
|
211
|
+
row.addWidget(self._solve_button)
|
|
212
|
+
row.addWidget(self._cancel_button)
|
|
213
|
+
|
|
214
|
+
column = QVBoxLayout()
|
|
215
|
+
column.addLayout(row)
|
|
216
|
+
column.addWidget(self._converge_button)
|
|
217
|
+
return column
|
|
218
|
+
|
|
219
|
+
def _build_efficiency_plot(self) -> QWidget:
|
|
220
|
+
from matplotlib.backends.backend_qtagg import (
|
|
221
|
+
FigureCanvasQTAgg,
|
|
222
|
+
NavigationToolbar2QT,
|
|
223
|
+
)
|
|
224
|
+
from matplotlib.figure import Figure
|
|
225
|
+
|
|
226
|
+
self._figure = Figure(figsize=(8, 3), layout="constrained")
|
|
227
|
+
self._axes = self._figure.add_subplot(1, 1, 1)
|
|
228
|
+
self._canvas = FigureCanvasQTAgg(self._figure)
|
|
229
|
+
style_figure(self._figure, theming.current())
|
|
230
|
+
theming.subscribe(self._on_theme)
|
|
231
|
+
|
|
232
|
+
panel = QWidget()
|
|
233
|
+
layout = QVBoxLayout(panel)
|
|
234
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
235
|
+
layout.addWidget(NavigationToolbar2QT(self._canvas, panel))
|
|
236
|
+
layout.addWidget(self._canvas)
|
|
237
|
+
return panel
|
|
238
|
+
|
|
239
|
+
def _build_provenance(self) -> QWidget:
|
|
240
|
+
panel = QWidget()
|
|
241
|
+
layout = QVBoxLayout(panel)
|
|
242
|
+
layout.setContentsMargins(8, 4, 8, 8)
|
|
243
|
+
heading = QLabel("Provenance")
|
|
244
|
+
# Bold by font, not stylesheet: a stylesheet freezes the label's
|
|
245
|
+
# palette at polish time, and it would keep the old theme's colour.
|
|
246
|
+
bold = heading.font()
|
|
247
|
+
bold.setBold(True)
|
|
248
|
+
heading.setFont(bold)
|
|
249
|
+
self._provenance = QTextEdit()
|
|
250
|
+
self._provenance.setReadOnly(True)
|
|
251
|
+
layout.addWidget(heading)
|
|
252
|
+
layout.addWidget(self._provenance)
|
|
253
|
+
return panel
|
|
254
|
+
|
|
255
|
+
def _check_fields_match_cmethod_options(self) -> None:
|
|
256
|
+
declared = {f.name for f in dataclasses.fields(CMethodOptionsState)}
|
|
257
|
+
if self._fields.keys() != declared:
|
|
258
|
+
raise AssertionError(
|
|
259
|
+
"cmethod-option fields do not match CMethodOptionsState: "
|
|
260
|
+
f"missing {sorted(declared - self._fields.keys())}, "
|
|
261
|
+
f"extra {sorted(self._fields.keys() - declared)}"
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# -- the MainWindow contract ---------------------------------------
|
|
265
|
+
|
|
266
|
+
def build_options(self, problem, illumination, wavelengths) -> dict:
|
|
267
|
+
options = CMethodOptionsState(
|
|
268
|
+
truncation=_value(self._fields["truncation"]),
|
|
269
|
+
conductivity=_value(self._fields["conductivity"]),
|
|
270
|
+
)
|
|
271
|
+
return build_cmethod_options(problem, illumination, wavelengths, options)
|
|
272
|
+
|
|
273
|
+
def set_running(self, running: bool) -> None:
|
|
274
|
+
self._solve_button.setEnabled(not running)
|
|
275
|
+
self._converge_button.setEnabled(not running)
|
|
276
|
+
self._cancel_button.setEnabled(running)
|
|
277
|
+
# Only the knob: the sweep chooses it, so the field it would come from
|
|
278
|
+
# is dead for the duration. `conductivity` is passed through to the
|
|
279
|
+
# sweep untouched, so it stays live, like scalar's model selectors.
|
|
280
|
+
self._fields["truncation"].setEnabled(not running)
|
|
281
|
+
if not running:
|
|
282
|
+
self._progress.hide()
|
|
283
|
+
|
|
284
|
+
def show_progress(self) -> None:
|
|
285
|
+
self._progress.show()
|
|
286
|
+
|
|
287
|
+
def show_progress_value(self, done: int, total: int) -> None:
|
|
288
|
+
if self._progress.maximum() != total:
|
|
289
|
+
self._progress.setRange(0, total)
|
|
290
|
+
self._progress.setValue(done)
|
|
291
|
+
|
|
292
|
+
def show_solving(self, wavelength_count: int) -> None:
|
|
293
|
+
self._progress.setRange(0, 0)
|
|
294
|
+
self._paint(provenance.solving_lines(self.name, wavelength_count))
|
|
295
|
+
|
|
296
|
+
def show_result(self, scan, energy, lambda_over_period: float) -> None:
|
|
297
|
+
self._scan, self._energy = scan, energy
|
|
298
|
+
self._lambda_over_period = lambda_over_period
|
|
299
|
+
|
|
300
|
+
summaries = orders_module.summarize(scan)
|
|
301
|
+
self._visible_orders = orders_module.carry_over(
|
|
302
|
+
self._visible_orders, self._previous_orders, summaries
|
|
303
|
+
)
|
|
304
|
+
self._previous_orders = frozenset(s.order for s in summaries)
|
|
305
|
+
self._rebuild_order_list(summaries)
|
|
306
|
+
|
|
307
|
+
self._draw_efficiency(scan)
|
|
308
|
+
self._paint(provenance.provenance_lines(scan, energy, lambda_over_period))
|
|
309
|
+
|
|
310
|
+
def show_convergence(self, scan, energy, lambda_over_period: float, report) -> None:
|
|
311
|
+
self.show_result(scan, energy, lambda_over_period)
|
|
312
|
+
if report.converged_at is not None:
|
|
313
|
+
self._fields["truncation"].setText(str(report.converged_at))
|
|
314
|
+
|
|
315
|
+
def show_cancelled(self) -> None:
|
|
316
|
+
if self._scan is None:
|
|
317
|
+
return
|
|
318
|
+
self._paint(
|
|
319
|
+
provenance.provenance_lines(
|
|
320
|
+
self._scan, self._energy, self._lambda_over_period, cancelled=True
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
def show_error(self, message: str) -> None:
|
|
325
|
+
self._paint((provenance.Line(f"solve failed: {message}\n", "bad"),))
|
|
326
|
+
|
|
327
|
+
def show_field_errors(self, errors) -> None:
|
|
328
|
+
self._paint(provenance.error_lines(errors))
|
|
329
|
+
|
|
330
|
+
# -- orders ----------------------------------------------------------
|
|
331
|
+
|
|
332
|
+
def _rebuild_order_list(self, summaries: Sequence["orders_module.OrderSummary"]) -> None:
|
|
333
|
+
self._order_list.blockSignals(True)
|
|
334
|
+
self._order_list.clear()
|
|
335
|
+
for summary in summaries:
|
|
336
|
+
item = QListWidgetItem(orders_module.describe(summary))
|
|
337
|
+
item.setData(_ORDER_ROLE, summary.order)
|
|
338
|
+
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
|
|
339
|
+
item.setCheckState(
|
|
340
|
+
Qt.CheckState.Checked
|
|
341
|
+
if summary.order in self._visible_orders
|
|
342
|
+
else Qt.CheckState.Unchecked
|
|
343
|
+
)
|
|
344
|
+
item.setData(_DIM_ROLE, not summary.ever_propagating)
|
|
345
|
+
self._order_list.addItem(item)
|
|
346
|
+
self._order_list.blockSignals(False)
|
|
347
|
+
self._apply_swatches()
|
|
348
|
+
self._update_order_count_label(len(summaries))
|
|
349
|
+
|
|
350
|
+
def _apply_swatches(self) -> None:
|
|
351
|
+
"""Give each listed order its line's colour, and dim the dead ones.
|
|
352
|
+
|
|
353
|
+
Both are redone on a theme change: the swatch because the colours
|
|
354
|
+
move, the dimming because it was the old palette's muted colour.
|
|
355
|
+
"""
|
|
356
|
+
# From the theme, not self.palette(): on a theme change this runs
|
|
357
|
+
# before Qt has propagated the new application palette to the widget.
|
|
358
|
+
from PySide6.QtGui import QColor
|
|
359
|
+
|
|
360
|
+
theme = theming.current()
|
|
361
|
+
muted, text = QColor(theme.muted), QColor(theme.text)
|
|
362
|
+
# Restyling an item emits itemChanged, whose handler redraws, which
|
|
363
|
+
# restyles again: blocked, as the checkbox resync already is.
|
|
364
|
+
self._order_list.blockSignals(True)
|
|
365
|
+
try:
|
|
366
|
+
for i in range(self._order_list.count()):
|
|
367
|
+
item = self._order_list.item(i)
|
|
368
|
+
color = self._order_colors.get(item.data(_ORDER_ROLE))
|
|
369
|
+
item.setIcon(theming.swatch(color) if color else theming.QIcon())
|
|
370
|
+
item.setForeground(muted if item.data(_DIM_ROLE) else text)
|
|
371
|
+
finally:
|
|
372
|
+
self._order_list.blockSignals(False)
|
|
373
|
+
|
|
374
|
+
def _update_order_count_label(self, total: int) -> None:
|
|
375
|
+
self._order_count_label.setText(
|
|
376
|
+
f"{len(self._visible_orders)} of {total} shown"
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
def _on_order_item_changed(self, item: QListWidgetItem) -> None:
|
|
380
|
+
order = item.data(_ORDER_ROLE)
|
|
381
|
+
if item.checkState() == Qt.CheckState.Checked:
|
|
382
|
+
self._visible_orders = self._visible_orders | {order}
|
|
383
|
+
else:
|
|
384
|
+
self._visible_orders = self._visible_orders - {order}
|
|
385
|
+
self._update_order_count_label(self._order_list.count())
|
|
386
|
+
if self._scan is not None:
|
|
387
|
+
self._draw_efficiency(self._scan)
|
|
388
|
+
|
|
389
|
+
def _show_all_orders(self) -> None:
|
|
390
|
+
self._visible_orders = frozenset(self._previous_orders)
|
|
391
|
+
self._resync_order_checkboxes()
|
|
392
|
+
|
|
393
|
+
def _show_no_orders(self) -> None:
|
|
394
|
+
self._visible_orders = frozenset()
|
|
395
|
+
self._resync_order_checkboxes()
|
|
396
|
+
|
|
397
|
+
def _show_default_orders(self) -> None:
|
|
398
|
+
if self._scan is None:
|
|
399
|
+
return
|
|
400
|
+
self._visible_orders = orders_module.default_visible(
|
|
401
|
+
orders_module.summarize(self._scan)
|
|
402
|
+
)
|
|
403
|
+
self._resync_order_checkboxes()
|
|
404
|
+
|
|
405
|
+
def _resync_order_checkboxes(self) -> None:
|
|
406
|
+
self._order_list.blockSignals(True)
|
|
407
|
+
for i in range(self._order_list.count()):
|
|
408
|
+
item = self._order_list.item(i)
|
|
409
|
+
item.setCheckState(
|
|
410
|
+
Qt.CheckState.Checked
|
|
411
|
+
if item.data(_ORDER_ROLE) in self._visible_orders
|
|
412
|
+
else Qt.CheckState.Unchecked
|
|
413
|
+
)
|
|
414
|
+
self._order_list.blockSignals(False)
|
|
415
|
+
self._update_order_count_label(self._order_list.count())
|
|
416
|
+
if self._scan is not None:
|
|
417
|
+
self._draw_efficiency(self._scan)
|
|
418
|
+
|
|
419
|
+
# -- drawing -----------------------------------------------------
|
|
420
|
+
|
|
421
|
+
def _paint(self, lines) -> None:
|
|
422
|
+
self._lines = tuple(lines)
|
|
423
|
+
self._provenance.setHtml(
|
|
424
|
+
provenance.to_html(self._lines, provenance_colors(theming.current()))
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
def _on_theme(self, theme) -> None:
|
|
428
|
+
style_figure(self._figure, theme)
|
|
429
|
+
if self._scan is not None:
|
|
430
|
+
self._draw_efficiency(self._scan)
|
|
431
|
+
else:
|
|
432
|
+
self._canvas.draw_idle()
|
|
433
|
+
if self._lines:
|
|
434
|
+
self._paint(self._lines)
|
|
435
|
+
self._apply_swatches()
|
|
436
|
+
|
|
437
|
+
def _draw_efficiency(self, scan) -> None:
|
|
438
|
+
self._order_colors = draw_efficiency(
|
|
439
|
+
self._axes, scan, self._visible_orders, theming.current()
|
|
440
|
+
)
|
|
441
|
+
self._apply_swatches()
|
|
442
|
+
self._canvas.draw_idle()
|
|
443
|
+
|
|
444
|
+
# -- export ------------------------------------------------------
|
|
445
|
+
|
|
446
|
+
def export_csv(self) -> None:
|
|
447
|
+
if self._scan is None:
|
|
448
|
+
QMessageBox.information(self, "Nothing to export", "Solve first.")
|
|
449
|
+
return
|
|
450
|
+
path, _ = QFileDialog.getSaveFileName(
|
|
451
|
+
self, "Export efficiencies", "efficiency.csv", "CSV (*.csv)"
|
|
452
|
+
)
|
|
453
|
+
if not path:
|
|
454
|
+
return
|
|
455
|
+
rows = self._scan.to_records()
|
|
456
|
+
with open(path, "w", newline="", encoding="utf-8") as handle:
|
|
457
|
+
writer = csv.DictWriter(handle, fieldnames=list(rows[0]))
|
|
458
|
+
writer.writeheader()
|
|
459
|
+
writer.writerows(rows)
|
|
460
|
+
QMessageBox.information(
|
|
461
|
+
self, "Exported", f"{len(rows)} rows to {Path(path).name}"
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def _value(widget: QWidget) -> str:
|
|
466
|
+
"""The text of an input, whichever kind it is."""
|
|
467
|
+
if isinstance(widget, QComboBox):
|
|
468
|
+
return widget.currentText()
|
|
469
|
+
return widget.text()
|
|
@@ -156,6 +156,18 @@ class GeometryPanel(QWidget):
|
|
|
156
156
|
|
|
157
157
|
entry(surface_form, "Roughness σ (nm)", "roughness")
|
|
158
158
|
|
|
159
|
+
layers = entry(surface_form, "Layers", "layers")
|
|
160
|
+
layers.setPlaceholderText("none, e.g. Au 20, Cr 5")
|
|
161
|
+
layers.setToolTip(
|
|
162
|
+
"Conformal layers on the grating, innermost first: a material\n"
|
|
163
|
+
"and a thickness in nm each, separated by commas. Each follows the\n"
|
|
164
|
+
"surface beneath it as its true offset, so convex corners are\n"
|
|
165
|
+
"rounded and concave ones stay sharp. The grating itself is cut\n"
|
|
166
|
+
"into the Coating material.\n"
|
|
167
|
+
"Only the RCWA tab models layers; the others refuse a problem\n"
|
|
168
|
+
"that has any rather than solve the bare grating beneath them."
|
|
169
|
+
)
|
|
170
|
+
|
|
159
171
|
scan = QGroupBox("Wavelengths (nm)")
|
|
160
172
|
scan_form = QFormLayout(scan)
|
|
161
173
|
entry(scan_form, "Start", "wavelength_start")
|