gratinglab 0.3.3__tar.gz → 0.3.5__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.3.3/src/gratinglab.egg-info → gratinglab-0.3.5}/PKG-INFO +18 -7
- {gratinglab-0.3.3 → gratinglab-0.3.5}/README.md +17 -6
- {gratinglab-0.3.3 → gratinglab-0.3.5}/pyproject.toml +6 -6
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/__init__.py +1 -1
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/compare.py +47 -2
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/mathtext.py +83 -2
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/qt/setup_tab.py +5 -6
- gratinglab-0.3.5/src/gratinglab/gui/qt/theory_viewer.py +210 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/qt/worker.py +35 -3
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/richtext.py +50 -4
- gratinglab-0.3.5/src/gratinglab/io/__init__.py +17 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/io/ggp.py +64 -44
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/materials/__init__.py +21 -5
- gratinglab-0.3.5/src/gratinglab/materials/data/SOURCES.md +48 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/materials/optical.py +9 -0
- gratinglab-0.3.5/src/gratinglab/materials/typical.py +388 -0
- gratinglab-0.3.5/src/gratinglab/metrology/_analyzer_summary.py +385 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/analyzer.py +17 -368
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/reading-outputs.md +13 -6
- gratinglab-0.3.5/src/gratinglab/profiles/__init__.py +61 -0
- gratinglab-0.3.5/src/gratinglab/profiles/_base.py +91 -0
- gratinglab-0.3.5/src/gratinglab/profiles/_polyline.py +501 -0
- gratinglab-0.3.5/src/gratinglab/profiles/_shapes.py +288 -0
- gratinglab-0.3.5/src/gratinglab/profiles/_trapezoid.py +360 -0
- gratinglab-0.3.5/src/gratinglab/profiles/_types.py +119 -0
- gratinglab-0.3.5/src/gratinglab/solvers/_scalar_groove.py +556 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/solvers/cmethod/__init__.py +4 -1
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/solvers/integral/__init__.py +5 -2
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/solvers/integral/_kernels.py +14 -2
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/solvers/rcwa/__init__.py +10 -2
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/solvers/scalar.py +21 -517
- {gratinglab-0.3.3 → gratinglab-0.3.5/src/gratinglab.egg-info}/PKG-INFO +18 -7
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab.egg-info/SOURCES.txt +11 -2
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_cmethod.py +8 -7
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_four_way.py +8 -7
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_geometry.py +1 -1
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_ggp.py +37 -29
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_gui_mathtext.py +56 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_gui_qt.py +223 -8
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_gui_richtext.py +25 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_integral_measured_polygon.py +4 -2
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_materials.py +31 -31
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_measured.py +47 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_scalar.py +11 -8
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_trapezoidal_solvers.py +6 -5
- gratinglab-0.3.5/tests/test_typical.py +227 -0
- gratinglab-0.3.3/src/gratinglab/gui/qt/theory_viewer.py +0 -99
- gratinglab-0.3.3/src/gratinglab/io/__init__.py +0 -0
- gratinglab-0.3.3/src/gratinglab/materials/data/SOURCES.md +0 -53
- gratinglab-0.3.3/src/gratinglab/profiles.py +0 -1334
- {gratinglab-0.3.3 → gratinglab-0.3.5}/LICENSE +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/setup.cfg +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/checks.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/convergence.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/geometry.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/__init__.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/app.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/cmethod_options.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/comparison.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/diagram.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/diagram3d.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/docs.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/efficiency_plot.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/icon.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/integral_options.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/launcher.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/orders.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/provenance.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/qt/__init__.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/qt/cmethod_tab.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/qt/compare_tab.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/qt/geometry_panel.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/qt/geometry_tab.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/qt/integral_tab.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/qt/launcher.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/qt/main_window.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/qt/profile_plot_panel.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/qt/rcwa_tab.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/qt/scalar_tab.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/qt/scene3d_panel.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/qt/sizing.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/qt/theming.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/rcwa_options.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/scalar_options.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/state.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/gui/theme.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/illumination.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/io/boundary.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/io/efficiency_table.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/io/txl.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/materials/fresnel.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/materials/refractiveindex.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/measured.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/__init__.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/boundary/__init__.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/boundary/average.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/boundary/cross_section.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/boundary/pipeline.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/boundary/swaths.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/cli.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/config.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/core/__init__.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/core/analysis.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/core/image_flatten.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/core/orientation.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/core/processing.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/core/region_mask.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/core/scars.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/core/tip.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/data/__init__.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/data/aggregation.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/__init__.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/icon.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/__init__.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/app.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/boundary_view.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/canvas.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/efficiency_dialog.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/import_view.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/layout.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/main_window.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/swath_dialog.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/wiki_view.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/qt/worker.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/gui/state.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/io/__init__.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/io/file_io.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/io/ibw.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/io/metrics.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/io/sem.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/io/spm.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/settings.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/stats/__init__.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/stats/analysis.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/stats/icc.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/visualization/__init__.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/visualization/diagnostics.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/visualization/profiles.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/visualization/statistics.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/__init__.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/facet-fitting.md +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/file-formats.md +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/flattening.md +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/groove-orientation.md +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/icc-correction.md +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/overview.md +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/row-groups.md +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/scars-and-masking.md +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/swath-statistics.md +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/wiki/tip-correction.md +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/metrology/workflows.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/problem.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/resolution.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/result.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/solvers/__init__.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/solvers/_conditioning.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/solvers/_execution.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/solvers/base.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/solvers/cmethod/_core.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/solvers/cmethod/_series.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/solvers/integral/_boundary.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/solvers/integral/_core.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/solvers/integral/_finite.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/solvers/integral/_greens.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/solvers/integral/_nystrom.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/solvers/rcwa/_core.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab/solvers/rcwa/_stack.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab.egg-info/dependency_links.txt +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab.egg-info/entry_points.txt +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab.egg-info/requires.txt +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/src/gratinglab.egg-info/top_level.txt +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_changelog.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_checks.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_compare.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_conditioning.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_convergence.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_cross_method.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_efficiency_table.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_fresnel.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_gui_cmethod_options.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_gui_cmethod_tab.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_gui_compare_tab.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_gui_diagram.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_gui_diagram3d.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_gui_docs.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_gui_integral_options.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_gui_launcher.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_gui_orders.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_gui_provenance.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_gui_rcwa_options.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_gui_scalar_options.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_gui_state.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_gui_theme.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_illumination.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_integral_core.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_integral_corpus.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_integral_finite.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_integral_graded.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_integral_greens.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_integral_kernels.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_integral_operators.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_integral_solver.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_io_boundary.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_littrow.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_perturbation.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_profiles.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_rayleigh_anomaly.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_rcwa.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_rcwa_benchmarks.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_refractiveindex.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_resolution.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_result.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_solver_conformance.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_solver_execution.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_solver_progress.py +0 -0
- {gratinglab-0.3.3 → gratinglab-0.3.5}/tests/test_txl.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gratinglab
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
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
|
|
@@ -249,8 +249,9 @@ uv tool install "gratinglab[gui]" # gratinglab-gui as a standalone
|
|
|
249
249
|
|
|
250
250
|
From a checkout, use `pip install -e ".[dev,gui,metrology,refractiveindex]"` instead.
|
|
251
251
|
|
|
252
|
-
|
|
253
|
-
|
|
252
|
+
Neither the package nor its repository carries optical constant tables, so absolute
|
|
253
|
+
efficiencies, and the `coating="Au"` examples above, require tables supplied by the
|
|
254
|
+
user. A library is a directory of such tables, one
|
|
254
255
|
material per file, each named after its material:
|
|
255
256
|
|
|
256
257
|
| File | From | Range |
|
|
@@ -264,7 +265,14 @@ Saving one as `~/.gratinglab/materials/Pt.yml` makes `coating="Pt"` available.
|
|
|
264
265
|
`materials.from_cxro_file(path)` or `from_refractiveindex_file(path)` reads a single
|
|
265
266
|
file without a library. The YAML files require the `refractiveindex` extra.
|
|
266
267
|
[`SOURCES.md`](https://github.com/jamccoy/gratinglab/blob/master/src/gratinglab/materials/data/SOURCES.md)
|
|
267
|
-
explains why
|
|
268
|
+
explains why none are distributed.
|
|
269
|
+
|
|
270
|
+
Without a table, `coating="typical:Au"` (or `typical:SiO2`, `typical:C@2.0` with
|
|
271
|
+
a density) uses an approximate X-ray model built from physical facts, 30 eV to
|
|
272
|
+
30 keV, for Au, Pt, Ni, C, Si and O. Where it cannot answer for its index, near
|
|
273
|
+
an absorption edge or at low energy, the result's provenance says so.
|
|
274
|
+
[`docs/typical-materials.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/typical-materials.md)
|
|
275
|
+
has the model and how far it agrees with real tables.
|
|
268
276
|
|
|
269
277
|
The core install does not include a plotting stack, so a user who only runs the solvers
|
|
270
278
|
does not install one. A subpackage that needs an optional extra names it when it is
|
|
@@ -392,6 +400,7 @@ gratinglab` regenerates the icon from the artwork in `gratinglab.gui.icon`.
|
|
|
392
400
|
| [`docs/theory/scalar.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/theory/scalar.md) | The derivation of the scalar solver, its closed-form results and its limits |
|
|
393
401
|
| [`docs/theory/integral.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/theory/integral.md) | The boundary-integral solver: conical formulation, perfect and finite conductivity |
|
|
394
402
|
| [`docs/theory/metrology.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/theory/metrology.md) | The assumptions of the AFM pipeline, what it measures and what it does not, including what tip erosion can recover |
|
|
403
|
+
| [`docs/typical-materials.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/typical-materials.md) | The X-ray index model used without a table, what it is checked against, and its measured accuracy |
|
|
395
404
|
| [`docs/findings.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/findings.md) | Empirical results, each with the evidence for it |
|
|
396
405
|
| [`docs/references.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/references.md) | The literature each part of the package is based on |
|
|
397
406
|
| [`docs/mutation-testing.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/mutation-testing.md) | What is mutated, what is not, and how to interpret a surviving mutant |
|
|
@@ -514,9 +523,11 @@ no such question.
|
|
|
514
523
|
|
|
515
524
|
### What this licence does not cover
|
|
516
525
|
|
|
517
|
-
**Optical constant tables.** None are distributed with the package
|
|
518
|
-
|
|
519
|
-
refractiveindex.info
|
|
526
|
+
**Optical constant tables.** None are distributed with the package or kept in the
|
|
527
|
+
repository: the suite runs on an invented gold-sized table of its own, and its
|
|
528
|
+
refractiveindex.info reader fixtures are files that site dedicates CC0. A table you
|
|
529
|
+
add is governed by the terms of whoever published it, not by this licence. refractiveindex.info
|
|
530
|
+
dedicates its files CC0, CXRO states no terms at all, and a table exported from another program
|
|
520
531
|
carries whatever that program's terms attach to it.
|
|
521
532
|
[`SOURCES.md`](https://github.com/jamccoy/gratinglab/blob/master/src/gratinglab/materials/data/SOURCES.md)
|
|
522
533
|
records where each kind comes from and why none ships here.
|
|
@@ -204,8 +204,9 @@ uv tool install "gratinglab[gui]" # gratinglab-gui as a standalone
|
|
|
204
204
|
|
|
205
205
|
From a checkout, use `pip install -e ".[dev,gui,metrology,refractiveindex]"` instead.
|
|
206
206
|
|
|
207
|
-
|
|
208
|
-
|
|
207
|
+
Neither the package nor its repository carries optical constant tables, so absolute
|
|
208
|
+
efficiencies, and the `coating="Au"` examples above, require tables supplied by the
|
|
209
|
+
user. A library is a directory of such tables, one
|
|
209
210
|
material per file, each named after its material:
|
|
210
211
|
|
|
211
212
|
| File | From | Range |
|
|
@@ -219,7 +220,14 @@ Saving one as `~/.gratinglab/materials/Pt.yml` makes `coating="Pt"` available.
|
|
|
219
220
|
`materials.from_cxro_file(path)` or `from_refractiveindex_file(path)` reads a single
|
|
220
221
|
file without a library. The YAML files require the `refractiveindex` extra.
|
|
221
222
|
[`SOURCES.md`](https://github.com/jamccoy/gratinglab/blob/master/src/gratinglab/materials/data/SOURCES.md)
|
|
222
|
-
explains why
|
|
223
|
+
explains why none are distributed.
|
|
224
|
+
|
|
225
|
+
Without a table, `coating="typical:Au"` (or `typical:SiO2`, `typical:C@2.0` with
|
|
226
|
+
a density) uses an approximate X-ray model built from physical facts, 30 eV to
|
|
227
|
+
30 keV, for Au, Pt, Ni, C, Si and O. Where it cannot answer for its index, near
|
|
228
|
+
an absorption edge or at low energy, the result's provenance says so.
|
|
229
|
+
[`docs/typical-materials.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/typical-materials.md)
|
|
230
|
+
has the model and how far it agrees with real tables.
|
|
223
231
|
|
|
224
232
|
The core install does not include a plotting stack, so a user who only runs the solvers
|
|
225
233
|
does not install one. A subpackage that needs an optional extra names it when it is
|
|
@@ -347,6 +355,7 @@ gratinglab` regenerates the icon from the artwork in `gratinglab.gui.icon`.
|
|
|
347
355
|
| [`docs/theory/scalar.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/theory/scalar.md) | The derivation of the scalar solver, its closed-form results and its limits |
|
|
348
356
|
| [`docs/theory/integral.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/theory/integral.md) | The boundary-integral solver: conical formulation, perfect and finite conductivity |
|
|
349
357
|
| [`docs/theory/metrology.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/theory/metrology.md) | The assumptions of the AFM pipeline, what it measures and what it does not, including what tip erosion can recover |
|
|
358
|
+
| [`docs/typical-materials.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/typical-materials.md) | The X-ray index model used without a table, what it is checked against, and its measured accuracy |
|
|
350
359
|
| [`docs/findings.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/findings.md) | Empirical results, each with the evidence for it |
|
|
351
360
|
| [`docs/references.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/references.md) | The literature each part of the package is based on |
|
|
352
361
|
| [`docs/mutation-testing.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/mutation-testing.md) | What is mutated, what is not, and how to interpret a surviving mutant |
|
|
@@ -469,9 +478,11 @@ no such question.
|
|
|
469
478
|
|
|
470
479
|
### What this licence does not cover
|
|
471
480
|
|
|
472
|
-
**Optical constant tables.** None are distributed with the package
|
|
473
|
-
|
|
474
|
-
refractiveindex.info
|
|
481
|
+
**Optical constant tables.** None are distributed with the package or kept in the
|
|
482
|
+
repository: the suite runs on an invented gold-sized table of its own, and its
|
|
483
|
+
refractiveindex.info reader fixtures are files that site dedicates CC0. A table you
|
|
484
|
+
add is governed by the terms of whoever published it, not by this licence. refractiveindex.info
|
|
485
|
+
dedicates its files CC0, CXRO states no terms at all, and a table exported from another program
|
|
475
486
|
carries whatever that program's terms attach to it.
|
|
476
487
|
[`SOURCES.md`](https://github.com/jamccoy/gratinglab/blob/master/src/gratinglab/materials/data/SOURCES.md)
|
|
477
488
|
records where each kind comes from and why none ships here.
|
|
@@ -145,11 +145,11 @@ version = { attr = "gratinglab.__version__" }
|
|
|
145
145
|
[tool.setuptools.packages.find]
|
|
146
146
|
where = ["src"]
|
|
147
147
|
|
|
148
|
-
# Optical constant TABLES do not ship
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
#
|
|
148
|
+
# Optical constant TABLES do not ship, and the repository holds none: only
|
|
149
|
+
# data that is ours or in the public domain is committed, and no published
|
|
150
|
+
# table is clearly either. So `available()` is empty on install. The suite
|
|
151
|
+
# uses an invented table from `tests/data/materials/` instead, and
|
|
152
|
+
# release.yml asserts the wheel holds no table and that an install refuses
|
|
153
153
|
# `lookup("Au")` with the message that says what to do instead. A missing table
|
|
154
154
|
# raises rather than degrading, so the difference cannot pass for a result.
|
|
155
155
|
# SOURCES.md ships, because it is how a user finds out where to get one.
|
|
@@ -241,7 +241,7 @@ do_not_mutate = [
|
|
|
241
241
|
# are covered by tests/test_illumination.py and tests/test_profiles.py.
|
|
242
242
|
"*/illumination.py",
|
|
243
243
|
"*/problem.py",
|
|
244
|
-
"*/profiles
|
|
244
|
+
"*/profiles/*",
|
|
245
245
|
]
|
|
246
246
|
|
|
247
247
|
# corpus.toml, which the reference tests read.
|
|
@@ -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.3.
|
|
25
|
+
__version__ = "0.3.5"
|
|
26
26
|
|
|
27
27
|
__all__ = [
|
|
28
28
|
"Illumination",
|
|
@@ -173,6 +173,8 @@ class Residuals:
|
|
|
173
173
|
with no stated uncertainty cannot say how far off it is in those units.
|
|
174
174
|
``propagating`` is the *model's* flag: a measured order that the model
|
|
175
175
|
calls evanescent is a disagreement worth seeing, and its ``model`` is 0.
|
|
176
|
+
:meth:`evanescent_warning` says how many there are and in which orders,
|
|
177
|
+
because the residual alone cannot tell a wrong mount from a wrong model.
|
|
176
178
|
"""
|
|
177
179
|
|
|
178
180
|
measurement: str
|
|
@@ -201,8 +203,11 @@ class Residuals:
|
|
|
201
203
|
|
|
202
204
|
``chi2`` sums ``pull**2`` over the points that have a sigma, and
|
|
203
205
|
``n_with_sigma`` says how many that was, so the number is never read
|
|
204
|
-
without its denominator. ``
|
|
205
|
-
|
|
206
|
+
without its denominator. ``n_evanescent`` counts the measured points
|
|
207
|
+
on orders the model does not propagate, which are inside every other
|
|
208
|
+
figure here at a model value of exactly 0; see
|
|
209
|
+
:meth:`evanescent_warning`. ``at_wavelength`` and ``at_order`` locate
|
|
210
|
+
the largest absolute residual.
|
|
206
211
|
"""
|
|
207
212
|
residual = self.residual
|
|
208
213
|
pull = self.pull
|
|
@@ -214,10 +219,50 @@ class Residuals:
|
|
|
214
219
|
"chi2": float(np.sum(pull[finite] ** 2)),
|
|
215
220
|
"n_points": float(len(self)),
|
|
216
221
|
"n_with_sigma": float(np.count_nonzero(finite)),
|
|
222
|
+
"n_evanescent": float(np.count_nonzero(~self.propagating)),
|
|
217
223
|
"at_wavelength": float(self.wavelength[worst]),
|
|
218
224
|
"at_order": int(self.order[worst]),
|
|
219
225
|
}
|
|
220
226
|
|
|
227
|
+
def evanescent_warning(self) -> str | None:
|
|
228
|
+
"""Say which measured points the model cannot propagate, or None.
|
|
229
|
+
|
|
230
|
+
Something was measured at each of these points, so either the model
|
|
231
|
+
or the mount it was given is wrong there. Two shapes are common and
|
|
232
|
+
they mean different things. A point or two at the end of the arc,
|
|
233
|
+
the most negative or most positive order at its energy, can be a
|
|
234
|
+
real order close to its cutoff. Whole orders stranded at every
|
|
235
|
+
wavelength they were measured at is a mount problem, and the sign
|
|
236
|
+
of alpha is the first thing to check: the June 2017 SCIL campaign's angle file put 59
|
|
237
|
+
of 123 points there, and the only symptom without this count was an
|
|
238
|
+
RMS residual of 0.157 (``docs/findings.md``).
|
|
239
|
+
|
|
240
|
+
Nothing is decided here. The message gives the count per order,
|
|
241
|
+
against how many points that order has, and leaves the reading to
|
|
242
|
+
whoever runs the comparison. It is not raised as a Python warning,
|
|
243
|
+
because the edge-of-arc case is legitimate and a campaign that has
|
|
244
|
+
one would warn on every run.
|
|
245
|
+
"""
|
|
246
|
+
stranded = ~self.propagating
|
|
247
|
+
if not stranded.any():
|
|
248
|
+
return None
|
|
249
|
+
parts = []
|
|
250
|
+
for m in np.unique(self.order[stranded]):
|
|
251
|
+
in_order = self.order == m
|
|
252
|
+
parts.append(
|
|
253
|
+
f"m={int(m):+d} ({np.count_nonzero(stranded & in_order)} of "
|
|
254
|
+
f"{np.count_nonzero(in_order)})"
|
|
255
|
+
)
|
|
256
|
+
return (
|
|
257
|
+
f"{np.count_nonzero(stranded)} of {len(self)} measured points in "
|
|
258
|
+
f"{self.measurement!r} fall on orders {self.method!r} does not "
|
|
259
|
+
f"propagate, where it reads exactly 0: {', '.join(parts)}. A point "
|
|
260
|
+
"at the end of the arc can be a real order near its cutoff. A whole "
|
|
261
|
+
"order stranded usually means the mount is wrong: check the sign of "
|
|
262
|
+
"alpha first, and take the one under which the measured orders "
|
|
263
|
+
"propagate (docs/conventions.md, section 4)."
|
|
264
|
+
)
|
|
265
|
+
|
|
221
266
|
|
|
222
267
|
@dataclass(frozen=True, slots=True)
|
|
223
268
|
class Comparison:
|
|
@@ -173,7 +173,12 @@ def strip_boxed(latex: str) -> str:
|
|
|
173
173
|
|
|
174
174
|
|
|
175
175
|
def render_math_png(
|
|
176
|
-
latex: str,
|
|
176
|
+
latex: str,
|
|
177
|
+
*,
|
|
178
|
+
dpi: int = 140,
|
|
179
|
+
color: str = "black",
|
|
180
|
+
axis_pt: float | None = None,
|
|
181
|
+
min_height_pt: float = 0.0,
|
|
177
182
|
) -> bytes | None:
|
|
178
183
|
r"""Rasterize a LaTeX string (no `$` delimiters) to PNG bytes.
|
|
179
184
|
|
|
@@ -192,6 +197,20 @@ def render_math_png(
|
|
|
192
197
|
confirmed that mathtext's parser does not tolerate an embedded ``\n``. Any
|
|
193
198
|
run of whitespace is therefore collapsed to a single space before parsing,
|
|
194
199
|
which matches what plain LaTeX would do with the same source.
|
|
200
|
+
|
|
201
|
+
``axis_pt`` is for an equation set inside a line of text. Qt places an
|
|
202
|
+
image aligned ``vertical-align: middle`` with its centre a quarter of the
|
|
203
|
+
font's x-height above the baseline, so given that height in points of the
|
|
204
|
+
10 pt render, space is added above or below until the image's centre lands
|
|
205
|
+
there exactly when the equation's own baseline sits on the text's. Without
|
|
206
|
+
it the image's bottom edge sits on the baseline, and every symbol with a
|
|
207
|
+
descender rides above the line.
|
|
208
|
+
|
|
209
|
+
That rule holds only for an image at least about three x-heights tall;
|
|
210
|
+
Qt sets a shorter one higher, by an amount that depends on its height
|
|
211
|
+
(measured with PySide6 6.11 on 2026-11-12, by scanning solid images from
|
|
212
|
+
8 to 32 px against a 14 px font). ``min_height_pt`` pads the image evenly
|
|
213
|
+
above and below up to that height, which keeps the centre where it was.
|
|
195
214
|
"""
|
|
196
215
|
import io
|
|
197
216
|
import re
|
|
@@ -205,7 +224,69 @@ def render_math_png(
|
|
|
205
224
|
|
|
206
225
|
buffer = io.BytesIO()
|
|
207
226
|
try:
|
|
208
|
-
|
|
227
|
+
if axis_pt is None:
|
|
228
|
+
math_to_image(f"${normalized}$", buffer, dpi=dpi, format="png", color=color)
|
|
229
|
+
return buffer.getvalue()
|
|
230
|
+
return _inline_png(normalized, dpi, color, axis_pt, min_height_pt)
|
|
209
231
|
except ValueError:
|
|
210
232
|
return None
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _inline_png(
|
|
236
|
+
latex: str, dpi: int, color: str, axis_pt: float, min_height_pt: float
|
|
237
|
+
) -> bytes:
|
|
238
|
+
"""An inline equation as a transparent PNG, padded about its baseline.
|
|
239
|
+
|
|
240
|
+
The baseline is measured rather than taken from mathtext's box depth,
|
|
241
|
+
which is the layout box and not where the glyphs land: rasterised at
|
|
242
|
+
101 dpi the two differ by a pixel in either direction depending on the
|
|
243
|
+
expression. So the expression is rendered a second time with an upright
|
|
244
|
+
``x`` after it, which sits on the baseline, and the bottom of that ``x``
|
|
245
|
+
is read off. The two renders share one vertical frame whenever the ``x``
|
|
246
|
+
is no taller and no deeper than the expression, which is any expression
|
|
247
|
+
holding a letter or digit; otherwise the box depth is used.
|
|
248
|
+
"""
|
|
249
|
+
import io
|
|
250
|
+
|
|
251
|
+
import numpy as np
|
|
252
|
+
from matplotlib.colors import to_rgb
|
|
253
|
+
from matplotlib.image import imsave
|
|
254
|
+
from matplotlib.mathtext import MathTextParser
|
|
255
|
+
|
|
256
|
+
parser = MathTextParser("agg")
|
|
257
|
+
ink = np.asarray(parser.parse(f"${latex}$", dpi=dpi).image)
|
|
258
|
+
probe = parser.parse(f"${latex}" + r"\mathrm{x}$", dpi=dpi)
|
|
259
|
+
probe_ink = np.asarray(probe.image)
|
|
260
|
+
rows = ink.shape[0]
|
|
261
|
+
if probe_ink.shape[0] == rows:
|
|
262
|
+
# The rightmost couple of points of the x: its right leg ends on
|
|
263
|
+
# the baseline, and nothing of the expression reaches that far.
|
|
264
|
+
columns = np.flatnonzero(probe_ink.max(axis=0))
|
|
265
|
+
leg = probe_ink[:, columns[-1] - max(1, round(2 * dpi / 72)) + 1 : columns[-1] + 1]
|
|
266
|
+
baseline = int(np.flatnonzero(leg.max(axis=1) > 100)[-1]) + 1
|
|
267
|
+
else:
|
|
268
|
+
rendered = parser.parse(f"${latex}$", dpi=dpi)
|
|
269
|
+
baseline = int(rendered.height - rendered.depth) - 1
|
|
270
|
+
|
|
271
|
+
# Centre on the axis: (above - below) / 2 must equal the axis height.
|
|
272
|
+
scale = dpi / 72.0
|
|
273
|
+
above, below = float(baseline), float(rows - baseline)
|
|
274
|
+
excess = (above - below) - 2 * axis_pt * scale
|
|
275
|
+
if excess > 0:
|
|
276
|
+
below += excess
|
|
277
|
+
else:
|
|
278
|
+
above -= excess
|
|
279
|
+
short = min_height_pt * scale - (above + below)
|
|
280
|
+
if short > 0:
|
|
281
|
+
above += short / 2
|
|
282
|
+
below += short / 2
|
|
283
|
+
top = round(above) - baseline
|
|
284
|
+
bottom = round(below) - (rows - baseline)
|
|
285
|
+
|
|
286
|
+
alpha = np.pad(ink, ((top, bottom), (0, 0))).astype(float) / 255.0
|
|
287
|
+
rgba = np.empty(alpha.shape + (4,))
|
|
288
|
+
rgba[..., :3] = to_rgb(color)
|
|
289
|
+
rgba[..., 3] = alpha
|
|
290
|
+
buffer = io.BytesIO()
|
|
291
|
+
imsave(buffer, rgba, format="png")
|
|
211
292
|
return buffer.getvalue()
|
|
@@ -36,13 +36,12 @@ from PySide6.QtWidgets import (
|
|
|
36
36
|
QLabel,
|
|
37
37
|
QMessageBox,
|
|
38
38
|
QPushButton,
|
|
39
|
-
QTextBrowser,
|
|
40
39
|
QVBoxLayout,
|
|
41
40
|
QWidget,
|
|
42
41
|
)
|
|
43
42
|
|
|
44
43
|
from ..docs import TheoryPage
|
|
45
|
-
from .theory_viewer import render_into
|
|
44
|
+
from .theory_viewer import ReadingBrowser, render_into
|
|
46
45
|
|
|
47
46
|
#: What each reader's ``source`` string starts with, and what to cite for it.
|
|
48
47
|
_CITATIONS = {
|
|
@@ -193,9 +192,9 @@ code changes. Or load a table from anywhere without adding it:
|
|
|
193
192
|
|
|
194
193
|
{citations}
|
|
195
194
|
|
|
196
|
-
The package ships no tables:
|
|
197
|
-
|
|
198
|
-
directory records why. Each table's source names the file it was read from,
|
|
195
|
+
The package ships no tables: it carries only data that is its own or in the
|
|
196
|
+
public domain, and no published table is clearly either. `SOURCES.md` beside
|
|
197
|
+
the bundled directory records why. Each table's source names the file it was read from,
|
|
199
198
|
so a result's provenance says which library answered.
|
|
200
199
|
|
|
201
200
|
## Surface roughness
|
|
@@ -242,7 +241,7 @@ class SetupTab(QWidget):
|
|
|
242
241
|
|
|
243
242
|
def __init__(self, parent: QWidget | None = None) -> None:
|
|
244
243
|
super().__init__(parent)
|
|
245
|
-
self._browser =
|
|
244
|
+
self._browser = ReadingBrowser()
|
|
246
245
|
self._browser.setOpenExternalLinks(False)
|
|
247
246
|
self._browser.setOpenLinks(False)
|
|
248
247
|
# Links leave for the system browser; the page itself never navigates.
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""The read-only viewer for one theory or reference page.
|
|
2
|
+
|
|
3
|
+
The viewer is thin by design. :mod:`gratinglab.gui.richtext` turns the page
|
|
4
|
+
into HTML and a list of rasterised equations, and this class only passes both
|
|
5
|
+
to a `QTextBrowser`. Everything that could be wrong in a non-trivial way
|
|
6
|
+
(table structure, escaping, math placement, the approximate-method banner) is
|
|
7
|
+
decided and tested in that module, without a window.
|
|
8
|
+
|
|
9
|
+
Images are registered with `QTextDocument.addResource` instead of being
|
|
10
|
+
embedded as `data:` URIs, because `loadResource` does not handle `data:` by
|
|
11
|
+
default. This also removes the least elegant detail of the Tk viewer: a list
|
|
12
|
+
monkey-patched onto the window solely to keep `PhotoImage` objects alive
|
|
13
|
+
against garbage collection while Tk was still displaying them.
|
|
14
|
+
|
|
15
|
+
:class:`ReadingBrowser` is the page itself: a slightly larger font than the
|
|
16
|
+
widgets around it, and a text column kept to a readable width and centred,
|
|
17
|
+
so a wide window adds margin rather than longer lines. The math is sized
|
|
18
|
+
from that font, so an equation in a sentence matches the words beside it.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from PySide6.QtCore import Qt, QUrl
|
|
24
|
+
from PySide6.QtGui import (
|
|
25
|
+
QFontMetricsF,
|
|
26
|
+
QImage,
|
|
27
|
+
QTextBlockFormat,
|
|
28
|
+
QTextCursor,
|
|
29
|
+
QTextDocument,
|
|
30
|
+
QTextImageFormat,
|
|
31
|
+
)
|
|
32
|
+
from PySide6.QtWidgets import QTextBrowser, QVBoxLayout, QWidget
|
|
33
|
+
|
|
34
|
+
from .. import richtext
|
|
35
|
+
|
|
36
|
+
__all__ = ["ReadingBrowser", "TheoryViewer", "render_into"]
|
|
37
|
+
|
|
38
|
+
#: Body text relative to the application font. The pages are long prose, and
|
|
39
|
+
#: the 9 pt that suits a button is small for reading paragraphs of it.
|
|
40
|
+
READING_SCALE = 1.15
|
|
41
|
+
|
|
42
|
+
#: Widest the text column grows, in ems of the reading font: about 100
|
|
43
|
+
#: characters of prose a line. A display equation wider than that widens the
|
|
44
|
+
#: column to fit rather than scrolling sideways.
|
|
45
|
+
READING_WIDTH_EMS = 50
|
|
46
|
+
|
|
47
|
+
#: Space either side of the column, in pixels, when the window is narrower
|
|
48
|
+
#: than the column.
|
|
49
|
+
READING_MARGIN = 24
|
|
50
|
+
|
|
51
|
+
#: Space around tables, which Qt otherwise sets flush against the paragraph
|
|
52
|
+
#: above them.
|
|
53
|
+
_STYLE = "table { margin-top: 6px; margin-bottom: 10px; } th { text-align: left; }"
|
|
54
|
+
|
|
55
|
+
#: Extra space between lines, as a fraction of the font's pixel size. Added
|
|
56
|
+
#: as a fixed distance rather than CSS ``line-height: 135%``, which scales
|
|
57
|
+
#: each line's own height and so opens a wide gap around every line holding
|
|
58
|
+
#: an equation.
|
|
59
|
+
LEADING = 0.3
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ReadingBrowser(QTextBrowser):
|
|
63
|
+
"""A `QTextBrowser` laid out for reading a page rather than a log."""
|
|
64
|
+
|
|
65
|
+
def __init__(self, parent: QWidget | None = None) -> None:
|
|
66
|
+
super().__init__(parent)
|
|
67
|
+
font = self.font()
|
|
68
|
+
font.setPointSizeF(font.pointSizeF() * READING_SCALE)
|
|
69
|
+
self.setFont(font)
|
|
70
|
+
self.document().setDefaultFont(font)
|
|
71
|
+
self.document().setDefaultStyleSheet(_STYLE)
|
|
72
|
+
self._widest_image = 0.0
|
|
73
|
+
|
|
74
|
+
def resizeEvent(self, event) -> None: # noqa: N802 - Qt's spelling
|
|
75
|
+
super().resizeEvent(event)
|
|
76
|
+
self._centre_column()
|
|
77
|
+
|
|
78
|
+
def pixel_size(self) -> float:
|
|
79
|
+
"""The reading font's size in pixels.
|
|
80
|
+
|
|
81
|
+
From the point size and the screen's DPI rather than `QFontInfo`,
|
|
82
|
+
which reports the matched font and so reads near zero where no system
|
|
83
|
+
fonts are installed, as on the offscreen CI runs.
|
|
84
|
+
"""
|
|
85
|
+
return self.font().pointSizeF() * self.logicalDpiY() / 72.0
|
|
86
|
+
|
|
87
|
+
def _centre_column(self) -> None:
|
|
88
|
+
column = max(self.pixel_size() * READING_WIDTH_EMS, self._widest_image)
|
|
89
|
+
spare = self.viewport().width() - column
|
|
90
|
+
side = max(READING_MARGIN, spare / 2)
|
|
91
|
+
root = self.document().rootFrame()
|
|
92
|
+
frame = root.frameFormat()
|
|
93
|
+
if frame.leftMargin() == side and frame.rightMargin() == side:
|
|
94
|
+
return
|
|
95
|
+
frame.setLeftMargin(side)
|
|
96
|
+
frame.setRightMargin(side)
|
|
97
|
+
frame.setTopMargin(READING_MARGIN / 2)
|
|
98
|
+
frame.setBottomMargin(READING_MARGIN)
|
|
99
|
+
root.setFrameFormat(frame)
|
|
100
|
+
|
|
101
|
+
def setHtml(self, text: str) -> None: # noqa: N802 - Qt's spelling
|
|
102
|
+
# setHtml replaces the root frame's format along with the content.
|
|
103
|
+
super().setHtml(text)
|
|
104
|
+
leading = QTextBlockFormat()
|
|
105
|
+
leading.setLineHeight(
|
|
106
|
+
round(self.pixel_size() * LEADING),
|
|
107
|
+
QTextBlockFormat.LineHeightTypes.LineDistanceHeight.value,
|
|
108
|
+
)
|
|
109
|
+
cursor = QTextCursor(self.document())
|
|
110
|
+
cursor.select(QTextCursor.SelectionType.Document)
|
|
111
|
+
cursor.mergeBlockFormat(leading)
|
|
112
|
+
self._widest_image = _widest_image(self.document())
|
|
113
|
+
self._centre_column()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _widest_image(document: QTextDocument) -> float:
|
|
117
|
+
"""The widest image in a document, in pixels, tables included."""
|
|
118
|
+
widest = 0.0
|
|
119
|
+
block = document.begin()
|
|
120
|
+
while block.isValid():
|
|
121
|
+
fragments = block.begin()
|
|
122
|
+
while not fragments.atEnd():
|
|
123
|
+
fmt = fragments.fragment().charFormat()
|
|
124
|
+
if fmt.isImageFormat():
|
|
125
|
+
widest = max(widest, QTextImageFormat(fmt).width())
|
|
126
|
+
fragments += 1
|
|
127
|
+
block = block.next()
|
|
128
|
+
return widest
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def render_into(
|
|
132
|
+
browser: QTextBrowser, page, *, device_pixel_ratio: float, text_color: str
|
|
133
|
+
) -> None:
|
|
134
|
+
"""Render a page into an existing `QTextBrowser`, images and all.
|
|
135
|
+
|
|
136
|
+
It is shared by :class:`TheoryViewer` and :class:`~.setup_tab.SetupTab`,
|
|
137
|
+
both of which show "one markdown-ish page, typeset, in a browser," and the
|
|
138
|
+
second use is what justified factoring this function out instead of
|
|
139
|
+
copying it.
|
|
140
|
+
"""
|
|
141
|
+
font = browser.document().defaultFont()
|
|
142
|
+
pixels = font.pointSizeF() * browser.logicalDpiY() / 72.0
|
|
143
|
+
rendered = richtext.to_html(
|
|
144
|
+
page,
|
|
145
|
+
device_pixel_ratio=device_pixel_ratio,
|
|
146
|
+
color=text_color,
|
|
147
|
+
inline_dpi=richtext.math_dpi(pixels),
|
|
148
|
+
display_dpi=richtext.math_dpi(pixels, display=True),
|
|
149
|
+
**richtext.inline_metrics(pixels, QFontMetricsF(font).xHeight()),
|
|
150
|
+
)
|
|
151
|
+
document = browser.document()
|
|
152
|
+
for image in rendered.images:
|
|
153
|
+
document.addResource(
|
|
154
|
+
QTextDocument.ResourceType.ImageResource,
|
|
155
|
+
QUrl(image.key),
|
|
156
|
+
QImage.fromData(image.png, "PNG"),
|
|
157
|
+
)
|
|
158
|
+
browser.setHtml(rendered.html)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class TheoryViewer(QWidget):
|
|
162
|
+
"""One page, typeset."""
|
|
163
|
+
|
|
164
|
+
def __init__(self, page, parent: QWidget | None = None) -> None:
|
|
165
|
+
# A window in its own right rather than a modal dialog: reading the
|
|
166
|
+
# theory while adjusting the grating is the point.
|
|
167
|
+
super().__init__(parent)
|
|
168
|
+
self.setWindowFlag(Qt.WindowType.Window)
|
|
169
|
+
self.setWindowTitle(page.title)
|
|
170
|
+
self.resize(860, 720)
|
|
171
|
+
|
|
172
|
+
self._browser = ReadingBrowser()
|
|
173
|
+
self._browser.setOpenExternalLinks(False)
|
|
174
|
+
# Relative links between doc pages point at files on disk that this
|
|
175
|
+
# viewer has no route to open. Following them would empty the window.
|
|
176
|
+
self._browser.setOpenLinks(False)
|
|
177
|
+
|
|
178
|
+
layout = QVBoxLayout(self)
|
|
179
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
180
|
+
layout.addWidget(self._browser)
|
|
181
|
+
|
|
182
|
+
self.set_page(page)
|
|
183
|
+
from . import theming
|
|
184
|
+
|
|
185
|
+
# Equations are images rasterised in the text colour, so a theme
|
|
186
|
+
# change re-renders the page rather than only repainting it.
|
|
187
|
+
theming.subscribe(self._on_theme)
|
|
188
|
+
|
|
189
|
+
def _on_theme(self, _theme) -> None:
|
|
190
|
+
# A bound method so Qt disconnects it with the window (see SetupTab).
|
|
191
|
+
self.set_page(self._page)
|
|
192
|
+
|
|
193
|
+
def set_page(self, page) -> None:
|
|
194
|
+
"""Render a page into the browser, images and all."""
|
|
195
|
+
self._page = page
|
|
196
|
+
render_into(
|
|
197
|
+
self._browser,
|
|
198
|
+
page,
|
|
199
|
+
device_pixel_ratio=self.devicePixelRatioF(),
|
|
200
|
+
text_color=self._text_color(),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def _text_color(self) -> str:
|
|
204
|
+
"""Rasterise math in the palette's own text colour.
|
|
205
|
+
|
|
206
|
+
Equations are images, so they do not follow a stylesheet the way the
|
|
207
|
+
surrounding prose does. Black math on a dark theme would be unreadable
|
|
208
|
+
only because its colour had been fixed at render time.
|
|
209
|
+
"""
|
|
210
|
+
return self.palette().windowText().color().name()
|
|
@@ -26,6 +26,8 @@ and the clear is silently lost.
|
|
|
26
26
|
|
|
27
27
|
from __future__ import annotations
|
|
28
28
|
|
|
29
|
+
import inspect
|
|
30
|
+
import os
|
|
29
31
|
import threading
|
|
30
32
|
from typing import TYPE_CHECKING, NamedTuple
|
|
31
33
|
|
|
@@ -79,6 +81,32 @@ def _energy(scan) -> "EnergyReport":
|
|
|
79
81
|
return check_energy_balance(scan, tolerance=_ENERGY_DEVIATION_WARN)
|
|
80
82
|
|
|
81
83
|
|
|
84
|
+
#: Threads a scan's wavelengths are solved on, for the solvers that take
|
|
85
|
+
#: ``workers``. The library defaults to 1, and nothing in the window passed
|
|
86
|
+
#: anything else, so a scan used one core of however many there were. A
|
|
87
|
+
#: 32-wavelength integral scan on MC1 (16 cores) took 5.05 s at 1, 1.69 s at
|
|
88
|
+
#: 4, 1.34 s at 8 and 1.22 s at 12, identical to the serial result each time,
|
|
89
|
+
#: so 8 is where it stops paying. One core is left for the window itself.
|
|
90
|
+
_SCAN_WORKERS = max(1, min(8, (os.cpu_count() or 2) - 1))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _with_workers(method: str, options: dict) -> dict:
|
|
94
|
+
"""``options`` with :data:`_SCAN_WORKERS` added, if ``method`` takes it.
|
|
95
|
+
|
|
96
|
+
A ``workers`` already present is kept. The solver's own signature is what
|
|
97
|
+
decides, so a solver that gains the option later is covered without an
|
|
98
|
+
edit here, and one without it (scalar, the C-method) is never sent a
|
|
99
|
+
keyword it would refuse.
|
|
100
|
+
"""
|
|
101
|
+
from ...solvers.base import get_solver
|
|
102
|
+
|
|
103
|
+
if "workers" in options:
|
|
104
|
+
return options
|
|
105
|
+
if "workers" not in inspect.signature(get_solver(method).solve).parameters:
|
|
106
|
+
return options
|
|
107
|
+
return {**options, "workers": _SCAN_WORKERS}
|
|
108
|
+
|
|
109
|
+
|
|
82
110
|
class SolveWorker(QObject):
|
|
83
111
|
"""Run one solve at a time on a dedicated thread, for whichever solver asked.
|
|
84
112
|
|
|
@@ -123,7 +151,7 @@ class SolveWorker(QObject):
|
|
|
123
151
|
geometry.illumination,
|
|
124
152
|
geometry.wavelengths,
|
|
125
153
|
[method],
|
|
126
|
-
options={method: options},
|
|
154
|
+
options={method: _with_workers(method, options)},
|
|
127
155
|
progress=self._reporter(token, cancel),
|
|
128
156
|
)[0]
|
|
129
157
|
self.finished.emit(token, method, SolveResult(scan, _energy(scan)))
|
|
@@ -172,7 +200,11 @@ class SolveWorker(QObject):
|
|
|
172
200
|
geometry.illumination,
|
|
173
201
|
geometry.wavelengths,
|
|
174
202
|
progress=self._reporter(token, cancel),
|
|
175
|
-
**{
|
|
203
|
+
**{
|
|
204
|
+
k: v
|
|
205
|
+
for k, v in _with_workers(method, options).items()
|
|
206
|
+
if k != knob
|
|
207
|
+
},
|
|
176
208
|
)
|
|
177
209
|
self.converged.emit(
|
|
178
210
|
token,
|
|
@@ -238,7 +270,7 @@ class SolveWorker(QObject):
|
|
|
238
270
|
geometry.illumination,
|
|
239
271
|
geometry.wavelengths,
|
|
240
272
|
[method],
|
|
241
|
-
options={method: method_options},
|
|
273
|
+
options={method: _with_workers(method, method_options)},
|
|
242
274
|
progress=step,
|
|
243
275
|
)[0]
|
|
244
276
|
except Cancelled:
|