gratinglab 0.2.6__tar.gz → 0.2.8__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.6/src/gratinglab.egg-info → gratinglab-0.2.8}/PKG-INFO +36 -6
- {gratinglab-0.2.6 → gratinglab-0.2.8}/README.md +35 -5
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/__init__.py +1 -1
- gratinglab-0.2.8/src/gratinglab/gui/comparison.py +361 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/integral_options.py +94 -43
- gratinglab-0.2.8/src/gratinglab/gui/qt/compare_tab.py +473 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/qt/integral_tab.py +6 -4
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/qt/main_window.py +99 -5
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/qt/worker.py +84 -0
- gratinglab-0.2.8/src/gratinglab/metrology/io/sem.py +603 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/problem.py +8 -2
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/profiles.py +337 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/solvers/integral/__init__.py +17 -10
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/solvers/scalar.py +12 -8
- {gratinglab-0.2.6 → gratinglab-0.2.8/src/gratinglab.egg-info}/PKG-INFO +36 -6
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab.egg-info/SOURCES.txt +4 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_conditioning.py +7 -1
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_cross_method.py +3 -3
- gratinglab-0.2.8/tests/test_gui_compare_tab.py +514 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_gui_integral_options.py +56 -8
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_gui_qt.py +20 -9
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_integral_graded.py +10 -4
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_integral_solver.py +3 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_measured.py +28 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_profiles.py +145 -1
- {gratinglab-0.2.6 → gratinglab-0.2.8}/LICENSE +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/pyproject.toml +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/setup.cfg +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/checks.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/compare.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/convergence.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/geometry.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/__init__.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/app.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/cmethod_options.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/diagram.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/diagram3d.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/docs.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/efficiency_plot.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/icon.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/launcher.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/mathtext.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/orders.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/provenance.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/qt/__init__.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/qt/cmethod_tab.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/qt/geometry_panel.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/qt/geometry_tab.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/qt/launcher.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/qt/profile_plot_panel.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/qt/rcwa_tab.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/qt/scalar_tab.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/qt/scene3d_panel.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/qt/setup_tab.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/qt/sizing.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/qt/theming.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/qt/theory_viewer.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/rcwa_options.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/richtext.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/scalar_options.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/state.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/gui/theme.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/illumination.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/io/__init__.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/io/boundary.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/io/efficiency_table.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/io/ggp.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/materials/__init__.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/materials/data/SOURCES.md +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/materials/fresnel.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/materials/optical.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/materials/refractiveindex.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/measured.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/__init__.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/analyzer.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/boundary/__init__.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/boundary/average.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/boundary/pipeline.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/boundary/swaths.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/cli.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/config.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/core/__init__.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/core/analysis.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/core/image_flatten.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/core/orientation.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/core/processing.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/core/region_mask.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/core/scars.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/core/tip.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/data/__init__.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/data/aggregation.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/gui/__init__.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/gui/icon.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/gui/qt/__init__.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/gui/qt/app.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/gui/qt/boundary_view.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/gui/qt/canvas.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/gui/qt/efficiency_dialog.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/gui/qt/import_view.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/gui/qt/layout.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/gui/qt/main_window.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/gui/qt/swath_dialog.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/gui/qt/wiki_view.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/gui/qt/worker.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/gui/state.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/io/__init__.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/io/file_io.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/io/ibw.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/io/metrics.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/io/spm.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/settings.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/stats/__init__.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/stats/analysis.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/stats/icc.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/visualization/__init__.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/visualization/diagnostics.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/visualization/profiles.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/visualization/statistics.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/wiki/__init__.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/wiki/facet-fitting.md +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/wiki/file-formats.md +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/wiki/flattening.md +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/wiki/groove-orientation.md +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/wiki/icc-correction.md +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/wiki/overview.md +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/wiki/reading-outputs.md +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/wiki/row-groups.md +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/wiki/scars-and-masking.md +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/wiki/swath-statistics.md +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/wiki/tip-correction.md +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/metrology/workflows.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/resolution.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/result.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/solvers/__init__.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/solvers/_conditioning.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/solvers/_execution.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/solvers/base.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/solvers/cmethod/__init__.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/solvers/cmethod/_core.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/solvers/cmethod/_series.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/solvers/integral/_boundary.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/solvers/integral/_core.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/solvers/integral/_finite.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/solvers/integral/_greens.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/solvers/integral/_kernels.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/solvers/integral/_nystrom.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/solvers/rcwa/__init__.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/solvers/rcwa/_core.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab/solvers/rcwa/_stack.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab.egg-info/dependency_links.txt +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab.egg-info/entry_points.txt +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab.egg-info/requires.txt +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/src/gratinglab.egg-info/top_level.txt +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_changelog.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_checks.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_cmethod.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_compare.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_convergence.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_efficiency_table.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_four_way.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_fresnel.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_geometry.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_ggp.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_gui_cmethod_options.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_gui_cmethod_tab.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_gui_diagram.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_gui_diagram3d.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_gui_docs.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_gui_launcher.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_gui_mathtext.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_gui_orders.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_gui_provenance.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_gui_rcwa_options.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_gui_richtext.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_gui_scalar_options.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_gui_state.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_gui_theme.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_illumination.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_integral_core.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_integral_corpus.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_integral_finite.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_integral_greens.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_integral_kernels.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_integral_operators.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_io_boundary.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_materials.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_perturbation.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_rayleigh_anomaly.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_rcwa.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_rcwa_benchmarks.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_refractiveindex.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_resolution.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_result.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_scalar.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_solver_conformance.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_solver_execution.py +0 -0
- {gratinglab-0.2.6 → gratinglab-0.2.8}/tests/test_solver_progress.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gratinglab
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
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
|
|
@@ -115,6 +115,15 @@ beside them, both written in-house and both experimental: a rigorous coupled-wav
|
|
|
115
115
|
analysis (RCWA), which also models coated gratings through conformal layers, and
|
|
116
116
|
the C-method (Chandezon). Each has a tab in the efficiency window since 0.2.1.
|
|
117
117
|
|
|
118
|
+
**Every modelling method here still needs verification**, the rigorous ones
|
|
119
|
+
included. The checks in the table below show that each method reproduces the
|
|
120
|
+
cases it was checked on; they do not show that it is right on yours. Nor does
|
|
121
|
+
agreement between the solvers: they share the problem specification, the optical
|
|
122
|
+
constant tables and the angle conventions, so a mistake in any of those moves all
|
|
123
|
+
of them together and no comparison between them can see it. A number from any
|
|
124
|
+
solver is evidence to be checked against measurement or a published benchmark
|
|
125
|
+
before it is relied on.
|
|
126
|
+
|
|
118
127
|
## Scope
|
|
119
128
|
|
|
120
129
|
The metrology (`gratinglab.metrology`) reads AFM scans: Bruker/Veeco Nanoscope `.spm`
|
|
@@ -126,9 +135,17 @@ groove varies between horizontal swaths of the scan. It can also undo the broade
|
|
|
126
135
|
introduced by the AFM tip where the tip reached the surface, and it marks the regions
|
|
127
136
|
the tip could not reach instead of estimating them.
|
|
128
137
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
138
|
+
It also reads Zeiss SmartSEM electron micrographs (`metrology.io.sem`), with the
|
|
139
|
+
instrument's own pixel size, stage tilt and foreshortening. A cross-section is the
|
|
140
|
+
one groove measurement with no tip to correct for, and the only one that can see an
|
|
141
|
+
undercut or measure the width of a crest. Tracing a groove off a micrograph into a
|
|
142
|
+
profile is not written yet.
|
|
143
|
+
|
|
144
|
+
The efficiency side comprises the problem specification, the groove profiles
|
|
145
|
+
(blazed, lamellar, sinusoidal, trapezoidal with a sidewall angle and corner radius,
|
|
146
|
+
and any measured profile), the solvers, the comparison and convergence harnesses,
|
|
147
|
+
the materials layer, and a calculation of the resolving power implied by the number
|
|
148
|
+
of grooves measured.
|
|
132
149
|
|
|
133
150
|
The aim is gratings in general: reflection and transmission, metallic and
|
|
134
151
|
dielectric, bare and coated, from X-rays to the infrared. Every mount between the
|
|
@@ -147,7 +164,7 @@ The method backends, in order of development:
|
|
|
147
164
|
| Method | Status |
|
|
148
165
|
|---|---|
|
|
149
166
|
| Scalar (Kirchhoff, x-ray blaze form) | experimental; checked against closed-form results |
|
|
150
|
-
| Integral method (boundary integral, conical, undercut-capable) | experimental; perfect and finite conductivity (`conductivity="tabulated"`, Goray & Schmidt 2010), absolute efficiencies with the absorbed fraction recorded; a corner-graded mesh for profiles with corners (`corner_grading=4`), under either boundary condition; checked against published benchmark tables (Maystre & Popov; Goray & Schmidt), energy balance and reciprocity |
|
|
167
|
+
| Integral method (boundary integral, conical, undercut-capable) | experimental; perfect and finite conductivity (`conductivity="tabulated"`, Goray & Schmidt 2010), absolute efficiencies with the absorbed fraction recorded; a corner-graded mesh for profiles with corners (`corner_grading=4`, the default since 0.2.7; `0` is the equal-arc mesh), under either boundary condition; checked against published benchmark tables (Maystre & Popov; Goray & Schmidt), energy balance and reciprocity |
|
|
151
168
|
| RCWA (in-plane, conical, off-plane; Li factorization, S-matrix, layer stacks) | experimental, since 0.2.0; lamellar gratings exact and other profiles staircased, transmitted orders, and coatings through conformal layers (`Problem.layers`); checked against Fresnel, the Airy film formula, reciprocity, the integral solver, and published lamellar values (Granet; Lalanne & Hugonin; Gralak; Goray & Schmidt's conical tables). Not yet checked: staircase accuracy in metallic TM, and layers of a second material against an independent code |
|
|
152
169
|
| C-method (Chandezon) | experimental, since 0.2.0; perfect conductor in any mount, and one interface into an absorbing or lossless material, conical and coupled; checked against Goray & Schmidt's Table 3, Fresnel and the integral solver. Refuses vertical facets and undercuts; converges slowly on sawtooth corners, and loses precision at high truncation on profiles deeper than about half the period, both reported in provenance. No coatings |
|
|
153
170
|
|
|
@@ -310,8 +327,21 @@ hides it), and the window has one tab for each task:
|
|
|
310
327
|
own boundary-condition control (perfect conductivity or tabulated optical
|
|
311
328
|
constants) and a **Corner mesh** control: off, or a grading order that clusters
|
|
312
329
|
the boundary nodes at the profile's corners, which a cornered profile needs to
|
|
313
|
-
converge quickly (4
|
|
330
|
+
converge quickly (4, the default). It takes minutes where the scalar solver takes milliseconds, so the
|
|
314
331
|
window opens on the scalar solver and this tab computes only when asked to.
|
|
332
|
+
- **Compare:** the geometry solved by several solvers at once, each with the
|
|
333
|
+
settings on its own tab. Efficiency is drawn with order by colour and solver by
|
|
334
|
+
line style, above each solver's difference from a chosen reference, on a linear
|
|
335
|
+
or log scale. A solver that cannot model the problem, such as the C-method on a
|
|
336
|
+
lamellar groove, is named with its reason and the others still run.
|
|
337
|
+
|
|
338
|
+

|
|
339
|
+
|
|
340
|
+
*Four solvers on one problem: a 40 nm gold sinusoid, 5% deep, at 80° incidence,
|
|
341
|
+
unpolarized. The lower panel is each solver's difference from the C-method on a
|
|
342
|
+
log scale: the integral method and RCWA agree with it to within 2e-6, and the
|
|
343
|
+
scalar one is off by up to 0.018. That agreement is a consistency check between
|
|
344
|
+
methods, not a verification of any of them; see Status.*
|
|
315
345
|
- **Setup:** the material library, showing which optical constants are installed, the
|
|
316
346
|
range each covers, where each came from, and how to add others. The coating itself is
|
|
317
347
|
chosen in the geometry dock, because it is a `Problem` field, like the period, and the
|
|
@@ -70,6 +70,15 @@ beside them, both written in-house and both experimental: a rigorous coupled-wav
|
|
|
70
70
|
analysis (RCWA), which also models coated gratings through conformal layers, and
|
|
71
71
|
the C-method (Chandezon). Each has a tab in the efficiency window since 0.2.1.
|
|
72
72
|
|
|
73
|
+
**Every modelling method here still needs verification**, the rigorous ones
|
|
74
|
+
included. The checks in the table below show that each method reproduces the
|
|
75
|
+
cases it was checked on; they do not show that it is right on yours. Nor does
|
|
76
|
+
agreement between the solvers: they share the problem specification, the optical
|
|
77
|
+
constant tables and the angle conventions, so a mistake in any of those moves all
|
|
78
|
+
of them together and no comparison between them can see it. A number from any
|
|
79
|
+
solver is evidence to be checked against measurement or a published benchmark
|
|
80
|
+
before it is relied on.
|
|
81
|
+
|
|
73
82
|
## Scope
|
|
74
83
|
|
|
75
84
|
The metrology (`gratinglab.metrology`) reads AFM scans: Bruker/Veeco Nanoscope `.spm`
|
|
@@ -81,9 +90,17 @@ groove varies between horizontal swaths of the scan. It can also undo the broade
|
|
|
81
90
|
introduced by the AFM tip where the tip reached the surface, and it marks the regions
|
|
82
91
|
the tip could not reach instead of estimating them.
|
|
83
92
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
It also reads Zeiss SmartSEM electron micrographs (`metrology.io.sem`), with the
|
|
94
|
+
instrument's own pixel size, stage tilt and foreshortening. A cross-section is the
|
|
95
|
+
one groove measurement with no tip to correct for, and the only one that can see an
|
|
96
|
+
undercut or measure the width of a crest. Tracing a groove off a micrograph into a
|
|
97
|
+
profile is not written yet.
|
|
98
|
+
|
|
99
|
+
The efficiency side comprises the problem specification, the groove profiles
|
|
100
|
+
(blazed, lamellar, sinusoidal, trapezoidal with a sidewall angle and corner radius,
|
|
101
|
+
and any measured profile), the solvers, the comparison and convergence harnesses,
|
|
102
|
+
the materials layer, and a calculation of the resolving power implied by the number
|
|
103
|
+
of grooves measured.
|
|
87
104
|
|
|
88
105
|
The aim is gratings in general: reflection and transmission, metallic and
|
|
89
106
|
dielectric, bare and coated, from X-rays to the infrared. Every mount between the
|
|
@@ -102,7 +119,7 @@ The method backends, in order of development:
|
|
|
102
119
|
| Method | Status |
|
|
103
120
|
|---|---|
|
|
104
121
|
| Scalar (Kirchhoff, x-ray blaze form) | experimental; checked against closed-form results |
|
|
105
|
-
| Integral method (boundary integral, conical, undercut-capable) | experimental; perfect and finite conductivity (`conductivity="tabulated"`, Goray & Schmidt 2010), absolute efficiencies with the absorbed fraction recorded; a corner-graded mesh for profiles with corners (`corner_grading=4`), under either boundary condition; checked against published benchmark tables (Maystre & Popov; Goray & Schmidt), energy balance and reciprocity |
|
|
122
|
+
| Integral method (boundary integral, conical, undercut-capable) | experimental; perfect and finite conductivity (`conductivity="tabulated"`, Goray & Schmidt 2010), absolute efficiencies with the absorbed fraction recorded; a corner-graded mesh for profiles with corners (`corner_grading=4`, the default since 0.2.7; `0` is the equal-arc mesh), under either boundary condition; checked against published benchmark tables (Maystre & Popov; Goray & Schmidt), energy balance and reciprocity |
|
|
106
123
|
| RCWA (in-plane, conical, off-plane; Li factorization, S-matrix, layer stacks) | experimental, since 0.2.0; lamellar gratings exact and other profiles staircased, transmitted orders, and coatings through conformal layers (`Problem.layers`); checked against Fresnel, the Airy film formula, reciprocity, the integral solver, and published lamellar values (Granet; Lalanne & Hugonin; Gralak; Goray & Schmidt's conical tables). Not yet checked: staircase accuracy in metallic TM, and layers of a second material against an independent code |
|
|
107
124
|
| C-method (Chandezon) | experimental, since 0.2.0; perfect conductor in any mount, and one interface into an absorbing or lossless material, conical and coupled; checked against Goray & Schmidt's Table 3, Fresnel and the integral solver. Refuses vertical facets and undercuts; converges slowly on sawtooth corners, and loses precision at high truncation on profiles deeper than about half the period, both reported in provenance. No coatings |
|
|
108
125
|
|
|
@@ -265,8 +282,21 @@ hides it), and the window has one tab for each task:
|
|
|
265
282
|
own boundary-condition control (perfect conductivity or tabulated optical
|
|
266
283
|
constants) and a **Corner mesh** control: off, or a grading order that clusters
|
|
267
284
|
the boundary nodes at the profile's corners, which a cornered profile needs to
|
|
268
|
-
converge quickly (4
|
|
285
|
+
converge quickly (4, the default). It takes minutes where the scalar solver takes milliseconds, so the
|
|
269
286
|
window opens on the scalar solver and this tab computes only when asked to.
|
|
287
|
+
- **Compare:** the geometry solved by several solvers at once, each with the
|
|
288
|
+
settings on its own tab. Efficiency is drawn with order by colour and solver by
|
|
289
|
+
line style, above each solver's difference from a chosen reference, on a linear
|
|
290
|
+
or log scale. A solver that cannot model the problem, such as the C-method on a
|
|
291
|
+
lamellar groove, is named with its reason and the others still run.
|
|
292
|
+
|
|
293
|
+

|
|
294
|
+
|
|
295
|
+
*Four solvers on one problem: a 40 nm gold sinusoid, 5% deep, at 80° incidence,
|
|
296
|
+
unpolarized. The lower panel is each solver's difference from the C-method on a
|
|
297
|
+
log scale: the integral method and RCWA agree with it to within 2e-6, and the
|
|
298
|
+
scalar one is off by up to 0.018. That agreement is a consistency check between
|
|
299
|
+
methods, not a verification of any of them; see Status.*
|
|
270
300
|
- **Setup:** the material library, showing which optical constants are installed, the
|
|
271
301
|
range each covers, where each came from, and how to add others. The coating itself is
|
|
272
302
|
chosen in the geometry dock, because it is a `Problem` field, like the period, and the
|
|
@@ -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.8"
|
|
26
26
|
|
|
27
27
|
__all__ = [
|
|
28
28
|
"Illumination",
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
"""What the Compare tab draws and says, without a GUI toolkit.
|
|
2
|
+
|
|
3
|
+
The Compare tab runs one geometry through several solvers and puts the
|
|
4
|
+
answers on one pair of axes: efficiency above, the difference from a chosen
|
|
5
|
+
reference solver below. Like :mod:`gratinglab.gui.provenance` and
|
|
6
|
+
:mod:`gratinglab.gui.efficiency_plot`, the decisions live here, so they can be
|
|
7
|
+
tested without a window and the widget only paints them.
|
|
8
|
+
|
|
9
|
+
Colour means the order and line style means the solver, never the other way
|
|
10
|
+
round. The order colours are the ones every solver tab already uses
|
|
11
|
+
(:func:`~gratinglab.gui.theme.order_colors`), so m = +1 is the same orange on
|
|
12
|
+
the Compare tab as on the Scalar tab. A solver keeps its line style whichever
|
|
13
|
+
is the reference and whichever others are ticked, because the style is fixed
|
|
14
|
+
by the solver's name rather than by its position in the run.
|
|
15
|
+
|
|
16
|
+
A solver that refused the problem, or failed on it, is not dropped from the
|
|
17
|
+
picture without a word. :class:`ComparisonOutcome` carries every solver that
|
|
18
|
+
was asked for, and :func:`comparison_lines` names each one that produced
|
|
19
|
+
nothing, with the reason it gave.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from collections.abc import Collection, Sequence
|
|
25
|
+
from dataclasses import dataclass, field
|
|
26
|
+
from typing import TYPE_CHECKING
|
|
27
|
+
|
|
28
|
+
import numpy as np
|
|
29
|
+
|
|
30
|
+
from .provenance import Line
|
|
31
|
+
from .theme import Theme, order_colors, set_titles, style_axes
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
34
|
+
from ..checks import EnergyReport
|
|
35
|
+
from ..compare import Comparison
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"VERIFICATION_NOTE",
|
|
39
|
+
"ComparisonOutcome",
|
|
40
|
+
"line_style",
|
|
41
|
+
"pick_reference",
|
|
42
|
+
"draw_comparison",
|
|
43
|
+
"comparison_lines",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
#: Line styles in the order solvers are registered. Four solvers, four
|
|
47
|
+
#: styles: solid, dashed, dash-dot and dotted all stay distinct at 1.3 pt,
|
|
48
|
+
#: which no fifth matplotlib style does reliably.
|
|
49
|
+
_STYLES = ("-", "--", "-.", ":")
|
|
50
|
+
|
|
51
|
+
#: Closes every comparison the panel reports. Two solvers agreeing is evidence
|
|
52
|
+
#: that neither has a bug the other lacks, not that either is right: they can
|
|
53
|
+
#: share an assumption, a material table or a geometry convention. Every
|
|
54
|
+
#: method here is experimental, so the panel says so where agreement is shown,
|
|
55
|
+
#: dimmed because it is a fact about the methods, not a fault in this run.
|
|
56
|
+
VERIFICATION_NOTE = (
|
|
57
|
+
"\nagreement between solvers is not verification: every method here is "
|
|
58
|
+
"experimental and needs checking against measurement or a published "
|
|
59
|
+
"benchmark\n"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
#: More legend entries than this and the order legend takes a second column,
|
|
63
|
+
#: as the solver tabs' legend does.
|
|
64
|
+
_LEGEND_ROWS = 20
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True)
|
|
68
|
+
class ComparisonOutcome:
|
|
69
|
+
"""Everything one Compare run produced, across the thread boundary.
|
|
70
|
+
|
|
71
|
+
``requested`` is every solver that was ticked, in run order. Each of them
|
|
72
|
+
ends up in exactly one of ``comparison.methods``, ``refused`` or
|
|
73
|
+
``failed``. ``comparison`` is ``None`` when no solver produced a scan.
|
|
74
|
+
``refused`` holds solvers whose capabilities declined the problem
|
|
75
|
+
(an ``UnsupportedConfiguration``, or a form error on that solver's own
|
|
76
|
+
tab), ``failed`` those that raised anything else; both map a solver name
|
|
77
|
+
to the reason it gave.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
requested: tuple[str, ...]
|
|
81
|
+
comparison: "Comparison | None"
|
|
82
|
+
energies: dict[str, "EnergyReport"] = field(default_factory=dict)
|
|
83
|
+
refused: dict[str, str] = field(default_factory=dict)
|
|
84
|
+
failed: dict[str, str] = field(default_factory=dict)
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def solved(self) -> tuple[str, ...]:
|
|
88
|
+
return () if self.comparison is None else self.comparison.methods
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def line_style(name: str, solvers: Sequence[str]) -> str:
|
|
92
|
+
"""The line style of solver ``name``, fixed by its place in ``solvers``.
|
|
93
|
+
|
|
94
|
+
Pass every registered solver, not only the ticked ones, so that unticking
|
|
95
|
+
one never restyles the rest.
|
|
96
|
+
"""
|
|
97
|
+
try:
|
|
98
|
+
index = list(solvers).index(name)
|
|
99
|
+
except ValueError:
|
|
100
|
+
index = len(solvers)
|
|
101
|
+
return _STYLES[index % len(_STYLES)]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def pick_reference(wanted: str | None, solved: Sequence[str]) -> str | None:
|
|
105
|
+
"""The reference actually used: ``wanted`` if it solved, else the first
|
|
106
|
+
rigorous solver that did, else the first that did.
|
|
107
|
+
|
|
108
|
+
A reference that refused cannot be subtracted from anything, and falling
|
|
109
|
+
back silently would make the lower panel mean something other than its
|
|
110
|
+
title says, so the caller reports the substitution (see
|
|
111
|
+
:func:`comparison_lines`).
|
|
112
|
+
"""
|
|
113
|
+
if not solved:
|
|
114
|
+
return None
|
|
115
|
+
if wanted in solved:
|
|
116
|
+
return wanted
|
|
117
|
+
from ..solvers.base import get_solver
|
|
118
|
+
|
|
119
|
+
for name in solved:
|
|
120
|
+
try:
|
|
121
|
+
if get_solver(name).capabilities.rigorous:
|
|
122
|
+
return name
|
|
123
|
+
except KeyError:
|
|
124
|
+
continue
|
|
125
|
+
return solved[0]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _title(name: str) -> str:
|
|
129
|
+
from .docs import short_title
|
|
130
|
+
|
|
131
|
+
return short_title(name)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def draw_comparison(
|
|
135
|
+
efficiency_axes,
|
|
136
|
+
difference_axes,
|
|
137
|
+
comparison: "Comparison",
|
|
138
|
+
reference: str,
|
|
139
|
+
visible: Collection[int],
|
|
140
|
+
theme: Theme,
|
|
141
|
+
*,
|
|
142
|
+
solvers: Sequence[str],
|
|
143
|
+
log_difference: bool = False,
|
|
144
|
+
) -> dict[int, str]:
|
|
145
|
+
"""Draw ``comparison`` into the two axes; return the colour of each order.
|
|
146
|
+
|
|
147
|
+
The upper axes get each solver's efficiency in every visible order. The
|
|
148
|
+
lower get ``solver - reference`` for every other solver, or ``|solver -
|
|
149
|
+
reference|`` on a log scale when ``log_difference`` is set, which is what
|
|
150
|
+
makes a 1e-7 agreement between rigorous methods visible beside a 0.1
|
|
151
|
+
disagreement with the scalar one. Exact zeros are left off a log axis
|
|
152
|
+
rather than drawn at an invented floor.
|
|
153
|
+
"""
|
|
154
|
+
colors = order_colors((int(m) for m in comparison.orders), theme)
|
|
155
|
+
wavelengths = comparison.wavelengths
|
|
156
|
+
shown = [
|
|
157
|
+
(column, int(order))
|
|
158
|
+
for column, order in enumerate(comparison.orders)
|
|
159
|
+
if int(order) in visible
|
|
160
|
+
]
|
|
161
|
+
ref_index = comparison.methods.index(reference)
|
|
162
|
+
|
|
163
|
+
for axes in (efficiency_axes, difference_axes):
|
|
164
|
+
axes.clear()
|
|
165
|
+
style_axes(axes, theme)
|
|
166
|
+
axes.grid(alpha=0.35, color=theme.grid)
|
|
167
|
+
|
|
168
|
+
for index, name in enumerate(comparison.methods):
|
|
169
|
+
style = line_style(name, solvers)
|
|
170
|
+
for column, order in shown:
|
|
171
|
+
efficiency_axes.plot(
|
|
172
|
+
wavelengths, comparison.efficiency[index, :, column],
|
|
173
|
+
color=colors[order], ls=style, lw=1.3,
|
|
174
|
+
)
|
|
175
|
+
if index == ref_index:
|
|
176
|
+
continue
|
|
177
|
+
delta = comparison.efficiency[index] - comparison.efficiency[ref_index]
|
|
178
|
+
for column, order in shown:
|
|
179
|
+
values = delta[:, column]
|
|
180
|
+
if log_difference:
|
|
181
|
+
values = np.abs(values)
|
|
182
|
+
values = np.where(values > 0.0, values, np.nan)
|
|
183
|
+
difference_axes.plot(
|
|
184
|
+
wavelengths, values, color=colors[order], ls=style, lw=1.3,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
efficiency_axes.axhline(1.0, color=theme.bad, lw=0.8, ls=":", alpha=0.8)
|
|
188
|
+
efficiency_axes.set_ylabel("efficiency")
|
|
189
|
+
efficiency_axes.set_ylim(bottom=0)
|
|
190
|
+
count = len(comparison.methods)
|
|
191
|
+
set_titles(
|
|
192
|
+
efficiency_axes, "Efficiency by solver",
|
|
193
|
+
f"{count} solver{'s' if count != 1 else ''}, "
|
|
194
|
+
f"{len(shown)} of {len(comparison.orders)} orders shown",
|
|
195
|
+
theme,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
if log_difference:
|
|
199
|
+
difference_axes.set_yscale("log")
|
|
200
|
+
difference_axes.set_ylabel("|Δ efficiency|")
|
|
201
|
+
else:
|
|
202
|
+
difference_axes.set_yscale("linear")
|
|
203
|
+
difference_axes.axhline(0.0, color=theme.ink, lw=0.8, alpha=0.6)
|
|
204
|
+
difference_axes.set_ylabel("Δ efficiency")
|
|
205
|
+
difference_axes.set_xlabel("wavelength (nm)")
|
|
206
|
+
set_titles(
|
|
207
|
+
difference_axes, f"Difference from {_title(reference)}",
|
|
208
|
+
_worst_detail(comparison, reference, shown), theme,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
_legends(
|
|
212
|
+
efficiency_axes, difference_axes, comparison, shown, colors, theme,
|
|
213
|
+
solvers, reference,
|
|
214
|
+
)
|
|
215
|
+
return colors
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _worst_detail(comparison, reference: str, shown) -> str:
|
|
219
|
+
"""The largest disagreement over the drawn orders, for the title's right."""
|
|
220
|
+
others = [m for m in comparison.methods if m != reference]
|
|
221
|
+
if not others:
|
|
222
|
+
return "nothing to compare with"
|
|
223
|
+
if not shown:
|
|
224
|
+
return "no orders shown"
|
|
225
|
+
columns = [c for c, _ in shown]
|
|
226
|
+
ref = comparison.efficiency[comparison.methods.index(reference)][:, columns]
|
|
227
|
+
worst_name, worst = None, -1.0
|
|
228
|
+
for name in others:
|
|
229
|
+
delta = comparison.efficiency[comparison.methods.index(name)][:, columns] - ref
|
|
230
|
+
value = float(np.abs(delta).max())
|
|
231
|
+
if value > worst:
|
|
232
|
+
worst_name, worst = name, value
|
|
233
|
+
return f"largest |Δ| {worst:.2g} ({_title(worst_name)})"
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _legends(axes, difference_axes, comparison, shown, colors, theme, solvers, reference) -> None:
|
|
237
|
+
"""Two keys outside the axes: orders by colour above, solvers by style below.
|
|
238
|
+
|
|
239
|
+
Two keys rather than one entry per curve, because the curves are the
|
|
240
|
+
product of the two and a 4 x 10 legend would be forty entries saying the
|
|
241
|
+
same thing forty times. One key per axes, rather than both on one, so the
|
|
242
|
+
figure's constrained layout makes room for each: a second legend added
|
|
243
|
+
to the same axes is invisible to the layout and was clipped.
|
|
244
|
+
"""
|
|
245
|
+
from matplotlib.lines import Line2D
|
|
246
|
+
|
|
247
|
+
keys = []
|
|
248
|
+
if shown:
|
|
249
|
+
keys.append(axes.legend(
|
|
250
|
+
[Line2D([], [], color=colors[m], lw=2.0) for _, m in shown],
|
|
251
|
+
[f"m={m:+d}" for _, m in shown],
|
|
252
|
+
fontsize=7, loc="upper left", bbox_to_anchor=(1.01, 1.0),
|
|
253
|
+
borderaxespad=0.0, frameon=False,
|
|
254
|
+
ncol=1 if len(shown) <= _LEGEND_ROWS else 2,
|
|
255
|
+
))
|
|
256
|
+
keys.append(difference_axes.legend(
|
|
257
|
+
[Line2D([], [], color=theme.ink, ls=line_style(name, solvers), lw=1.3)
|
|
258
|
+
for name in comparison.methods],
|
|
259
|
+
[_title(name) + (" (ref)" if name == reference else "")
|
|
260
|
+
for name in comparison.methods],
|
|
261
|
+
fontsize=7, loc="upper left", bbox_to_anchor=(1.01, 1.0),
|
|
262
|
+
borderaxespad=0.0, frameon=False, handlelength=3.0,
|
|
263
|
+
))
|
|
264
|
+
for legend in keys:
|
|
265
|
+
for text in legend.get_texts():
|
|
266
|
+
text.set_color(theme.ink)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def comparison_lines(
|
|
270
|
+
outcome: ComparisonOutcome,
|
|
271
|
+
wanted_reference: str | None,
|
|
272
|
+
*,
|
|
273
|
+
cancelled: bool = False,
|
|
274
|
+
) -> tuple[Line, ...]:
|
|
275
|
+
"""What the Compare tab's panel says about a run, solver by solver.
|
|
276
|
+
|
|
277
|
+
One line per requested solver, in run order: the time it took and its
|
|
278
|
+
energy verdict if it solved, its reason if it refused or failed. Then the
|
|
279
|
+
headline numbers against the reference, over every order, so the panel
|
|
280
|
+
states the disagreement the plot shows. A refusal is reported in the
|
|
281
|
+
``warn`` colour, because it is the solver declining a problem it cannot
|
|
282
|
+
model, which is correct behaviour and not a fault; a failure is ``bad``.
|
|
283
|
+
"""
|
|
284
|
+
lines: list[Line] = []
|
|
285
|
+
if cancelled:
|
|
286
|
+
lines.append(Line(
|
|
287
|
+
"cancelled: the comparison stopped; showing the previous one\n", "dim",
|
|
288
|
+
))
|
|
289
|
+
for name in outcome.requested:
|
|
290
|
+
lines.append(Line(f"{_title(name)}: ", None))
|
|
291
|
+
if name in outcome.refused:
|
|
292
|
+
lines.append(Line(f"refused: {outcome.refused[name]}\n", "warn"))
|
|
293
|
+
elif name in outcome.failed:
|
|
294
|
+
lines.append(Line(f"failed: {outcome.failed[name]}\n", "bad"))
|
|
295
|
+
elif name in outcome.solved:
|
|
296
|
+
lines.extend(_solved_line(name, outcome))
|
|
297
|
+
else:
|
|
298
|
+
lines.append(Line("not run\n", "dim"))
|
|
299
|
+
|
|
300
|
+
reference = pick_reference(wanted_reference, outcome.solved)
|
|
301
|
+
if reference is None:
|
|
302
|
+
lines.append(Line("\nno solver produced a result to compare\n", "bad"))
|
|
303
|
+
return tuple(lines)
|
|
304
|
+
if wanted_reference is not None and reference != wanted_reference:
|
|
305
|
+
lines.append(Line(
|
|
306
|
+
f"\nreference: {_title(wanted_reference)} did not solve, so the "
|
|
307
|
+
f"differences are from {_title(reference)}\n", "warn",
|
|
308
|
+
))
|
|
309
|
+
else:
|
|
310
|
+
lines.append(Line(f"\nreference: {_title(reference)}\n", "dim"))
|
|
311
|
+
|
|
312
|
+
comparison = outcome.comparison
|
|
313
|
+
for name in comparison.methods:
|
|
314
|
+
if name == reference:
|
|
315
|
+
continue
|
|
316
|
+
summary = comparison.summary(name, reference)
|
|
317
|
+
lines.append(Line(
|
|
318
|
+
f" {_title(name)} − {_title(reference)}: max |Δ| "
|
|
319
|
+
f"{summary['max_abs_difference']:.3g} at "
|
|
320
|
+
f"{summary['at_wavelength']:.4g} nm, m={summary['at_order']:+d}; "
|
|
321
|
+
f"rms {summary['rms_difference']:.3g}\n", None,
|
|
322
|
+
))
|
|
323
|
+
lines.append(Line(VERIFICATION_NOTE, "dim"))
|
|
324
|
+
return tuple(lines)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _solved_line(name: str, outcome: ComparisonOutcome) -> list[Line]:
|
|
328
|
+
scan = outcome.comparison.scans[outcome.comparison.methods.index(name)]
|
|
329
|
+
provenance = scan.provenance
|
|
330
|
+
wall = provenance.wall_time_s
|
|
331
|
+
timing = "time not recorded" if wall is None else f"{wall * 1e3:.0f} ms"
|
|
332
|
+
parts = [Line(f"solved, {timing}", "dim")]
|
|
333
|
+
if not _rigorous(name):
|
|
334
|
+
parts.append(Line(", approximate method", "dim"))
|
|
335
|
+
energy = outcome.energies.get(name)
|
|
336
|
+
if energy is not None:
|
|
337
|
+
parts.append(Line(", energy balance ", "dim"))
|
|
338
|
+
if energy.passed:
|
|
339
|
+
parts.append(Line("ok", "ok"))
|
|
340
|
+
elif energy.lossless:
|
|
341
|
+
deviation = float(np.abs(energy.total - 1.0).max())
|
|
342
|
+
parts.append(Line(f"off unity by up to {deviation:.2g}", "bad"))
|
|
343
|
+
else:
|
|
344
|
+
parts.append(Line(f"EXCEEDS UNITY by {energy.max_excess:.2g}", "bad"))
|
|
345
|
+
parts.append(Line("\n"))
|
|
346
|
+
# The solver's own warnings, word for word and in the solver tabs' form.
|
|
347
|
+
# A verdict alone says that something is wrong; the warning is where the
|
|
348
|
+
# solver says why and what to change. Without it, the integral solver on
|
|
349
|
+
# an equal-arc mesh read "EXCEEDS UNITY by 2.3" here while its own tab
|
|
350
|
+
# said "pass corner_grading=4".
|
|
351
|
+
parts.extend(Line(f" ⚠ {warning}\n", "warn") for warning in provenance.warnings)
|
|
352
|
+
return parts
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _rigorous(name: str) -> bool:
|
|
356
|
+
from ..solvers.base import get_solver
|
|
357
|
+
|
|
358
|
+
try:
|
|
359
|
+
return get_solver(name).capabilities.rigorous
|
|
360
|
+
except KeyError:
|
|
361
|
+
return True
|