gratinglab 0.2.6__tar.gz → 0.2.7__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.
Files changed (196) hide show
  1. {gratinglab-0.2.6/src/gratinglab.egg-info → gratinglab-0.2.7}/PKG-INFO +25 -3
  2. {gratinglab-0.2.6 → gratinglab-0.2.7}/README.md +24 -2
  3. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/__init__.py +1 -1
  4. gratinglab-0.2.7/src/gratinglab/gui/comparison.py +361 -0
  5. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/integral_options.py +4 -2
  6. gratinglab-0.2.7/src/gratinglab/gui/qt/compare_tab.py +473 -0
  7. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/qt/integral_tab.py +6 -4
  8. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/qt/main_window.py +99 -5
  9. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/qt/worker.py +84 -0
  10. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/solvers/integral/__init__.py +17 -10
  11. {gratinglab-0.2.6 → gratinglab-0.2.7/src/gratinglab.egg-info}/PKG-INFO +25 -3
  12. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab.egg-info/SOURCES.txt +3 -0
  13. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_conditioning.py +7 -1
  14. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_cross_method.py +3 -3
  15. gratinglab-0.2.7/tests/test_gui_compare_tab.py +514 -0
  16. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_gui_integral_options.py +35 -11
  17. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_gui_qt.py +20 -9
  18. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_integral_graded.py +10 -4
  19. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_integral_solver.py +3 -0
  20. {gratinglab-0.2.6 → gratinglab-0.2.7}/LICENSE +0 -0
  21. {gratinglab-0.2.6 → gratinglab-0.2.7}/pyproject.toml +0 -0
  22. {gratinglab-0.2.6 → gratinglab-0.2.7}/setup.cfg +0 -0
  23. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/checks.py +0 -0
  24. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/compare.py +0 -0
  25. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/convergence.py +0 -0
  26. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/geometry.py +0 -0
  27. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/__init__.py +0 -0
  28. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/app.py +0 -0
  29. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/cmethod_options.py +0 -0
  30. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/diagram.py +0 -0
  31. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/diagram3d.py +0 -0
  32. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/docs.py +0 -0
  33. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/efficiency_plot.py +0 -0
  34. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/icon.py +0 -0
  35. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/launcher.py +0 -0
  36. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/mathtext.py +0 -0
  37. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/orders.py +0 -0
  38. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/provenance.py +0 -0
  39. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/qt/__init__.py +0 -0
  40. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/qt/cmethod_tab.py +0 -0
  41. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/qt/geometry_panel.py +0 -0
  42. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/qt/geometry_tab.py +0 -0
  43. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/qt/launcher.py +0 -0
  44. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/qt/profile_plot_panel.py +0 -0
  45. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/qt/rcwa_tab.py +0 -0
  46. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/qt/scalar_tab.py +0 -0
  47. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/qt/scene3d_panel.py +0 -0
  48. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/qt/setup_tab.py +0 -0
  49. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/qt/sizing.py +0 -0
  50. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/qt/theming.py +0 -0
  51. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/qt/theory_viewer.py +0 -0
  52. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/rcwa_options.py +0 -0
  53. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/richtext.py +0 -0
  54. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/scalar_options.py +0 -0
  55. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/state.py +0 -0
  56. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/gui/theme.py +0 -0
  57. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/illumination.py +0 -0
  58. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/io/__init__.py +0 -0
  59. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/io/boundary.py +0 -0
  60. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/io/efficiency_table.py +0 -0
  61. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/io/ggp.py +0 -0
  62. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/materials/__init__.py +0 -0
  63. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/materials/data/SOURCES.md +0 -0
  64. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/materials/fresnel.py +0 -0
  65. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/materials/optical.py +0 -0
  66. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/materials/refractiveindex.py +0 -0
  67. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/measured.py +0 -0
  68. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/__init__.py +0 -0
  69. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/analyzer.py +0 -0
  70. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/boundary/__init__.py +0 -0
  71. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/boundary/average.py +0 -0
  72. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/boundary/pipeline.py +0 -0
  73. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/boundary/swaths.py +0 -0
  74. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/cli.py +0 -0
  75. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/config.py +0 -0
  76. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/core/__init__.py +0 -0
  77. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/core/analysis.py +0 -0
  78. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/core/image_flatten.py +0 -0
  79. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/core/orientation.py +0 -0
  80. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/core/processing.py +0 -0
  81. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/core/region_mask.py +0 -0
  82. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/core/scars.py +0 -0
  83. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/core/tip.py +0 -0
  84. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/data/__init__.py +0 -0
  85. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/data/aggregation.py +0 -0
  86. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/gui/__init__.py +0 -0
  87. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/gui/icon.py +0 -0
  88. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/gui/qt/__init__.py +0 -0
  89. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/gui/qt/app.py +0 -0
  90. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/gui/qt/boundary_view.py +0 -0
  91. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/gui/qt/canvas.py +0 -0
  92. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/gui/qt/efficiency_dialog.py +0 -0
  93. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/gui/qt/import_view.py +0 -0
  94. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/gui/qt/layout.py +0 -0
  95. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/gui/qt/main_window.py +0 -0
  96. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/gui/qt/swath_dialog.py +0 -0
  97. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/gui/qt/wiki_view.py +0 -0
  98. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/gui/qt/worker.py +0 -0
  99. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/gui/state.py +0 -0
  100. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/io/__init__.py +0 -0
  101. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/io/file_io.py +0 -0
  102. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/io/ibw.py +0 -0
  103. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/io/metrics.py +0 -0
  104. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/io/spm.py +0 -0
  105. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/settings.py +0 -0
  106. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/stats/__init__.py +0 -0
  107. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/stats/analysis.py +0 -0
  108. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/stats/icc.py +0 -0
  109. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/visualization/__init__.py +0 -0
  110. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/visualization/diagnostics.py +0 -0
  111. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/visualization/profiles.py +0 -0
  112. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/visualization/statistics.py +0 -0
  113. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/wiki/__init__.py +0 -0
  114. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/wiki/facet-fitting.md +0 -0
  115. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/wiki/file-formats.md +0 -0
  116. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/wiki/flattening.md +0 -0
  117. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/wiki/groove-orientation.md +0 -0
  118. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/wiki/icc-correction.md +0 -0
  119. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/wiki/overview.md +0 -0
  120. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/wiki/reading-outputs.md +0 -0
  121. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/wiki/row-groups.md +0 -0
  122. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/wiki/scars-and-masking.md +0 -0
  123. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/wiki/swath-statistics.md +0 -0
  124. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/wiki/tip-correction.md +0 -0
  125. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/metrology/workflows.py +0 -0
  126. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/problem.py +0 -0
  127. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/profiles.py +0 -0
  128. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/resolution.py +0 -0
  129. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/result.py +0 -0
  130. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/solvers/__init__.py +0 -0
  131. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/solvers/_conditioning.py +0 -0
  132. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/solvers/_execution.py +0 -0
  133. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/solvers/base.py +0 -0
  134. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/solvers/cmethod/__init__.py +0 -0
  135. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/solvers/cmethod/_core.py +0 -0
  136. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/solvers/cmethod/_series.py +0 -0
  137. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/solvers/integral/_boundary.py +0 -0
  138. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/solvers/integral/_core.py +0 -0
  139. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/solvers/integral/_finite.py +0 -0
  140. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/solvers/integral/_greens.py +0 -0
  141. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/solvers/integral/_kernels.py +0 -0
  142. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/solvers/integral/_nystrom.py +0 -0
  143. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/solvers/rcwa/__init__.py +0 -0
  144. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/solvers/rcwa/_core.py +0 -0
  145. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/solvers/rcwa/_stack.py +0 -0
  146. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab/solvers/scalar.py +0 -0
  147. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab.egg-info/dependency_links.txt +0 -0
  148. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab.egg-info/entry_points.txt +0 -0
  149. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab.egg-info/requires.txt +0 -0
  150. {gratinglab-0.2.6 → gratinglab-0.2.7}/src/gratinglab.egg-info/top_level.txt +0 -0
  151. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_changelog.py +0 -0
  152. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_checks.py +0 -0
  153. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_cmethod.py +0 -0
  154. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_compare.py +0 -0
  155. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_convergence.py +0 -0
  156. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_efficiency_table.py +0 -0
  157. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_four_way.py +0 -0
  158. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_fresnel.py +0 -0
  159. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_geometry.py +0 -0
  160. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_ggp.py +0 -0
  161. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_gui_cmethod_options.py +0 -0
  162. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_gui_cmethod_tab.py +0 -0
  163. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_gui_diagram.py +0 -0
  164. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_gui_diagram3d.py +0 -0
  165. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_gui_docs.py +0 -0
  166. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_gui_launcher.py +0 -0
  167. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_gui_mathtext.py +0 -0
  168. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_gui_orders.py +0 -0
  169. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_gui_provenance.py +0 -0
  170. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_gui_rcwa_options.py +0 -0
  171. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_gui_richtext.py +0 -0
  172. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_gui_scalar_options.py +0 -0
  173. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_gui_state.py +0 -0
  174. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_gui_theme.py +0 -0
  175. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_illumination.py +0 -0
  176. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_integral_core.py +0 -0
  177. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_integral_corpus.py +0 -0
  178. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_integral_finite.py +0 -0
  179. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_integral_greens.py +0 -0
  180. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_integral_kernels.py +0 -0
  181. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_integral_operators.py +0 -0
  182. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_io_boundary.py +0 -0
  183. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_materials.py +0 -0
  184. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_measured.py +0 -0
  185. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_perturbation.py +0 -0
  186. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_profiles.py +0 -0
  187. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_rayleigh_anomaly.py +0 -0
  188. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_rcwa.py +0 -0
  189. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_rcwa_benchmarks.py +0 -0
  190. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_refractiveindex.py +0 -0
  191. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_resolution.py +0 -0
  192. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_result.py +0 -0
  193. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_scalar.py +0 -0
  194. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_solver_conformance.py +0 -0
  195. {gratinglab-0.2.6 → gratinglab-0.2.7}/tests/test_solver_execution.py +0 -0
  196. {gratinglab-0.2.6 → gratinglab-0.2.7}/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.6
3
+ Version: 0.2.7
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`
@@ -147,7 +156,7 @@ The method backends, in order of development:
147
156
  | Method | Status |
148
157
  |---|---|
149
158
  | 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 |
159
+ | 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
160
  | 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
161
  | 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
162
 
@@ -310,8 +319,21 @@ hides it), and the window has one tab for each task:
310
319
  own boundary-condition control (perfect conductivity or tabulated optical
311
320
  constants) and a **Corner mesh** control: off, or a grading order that clusters
312
321
  the boundary nodes at the profile's corners, which a cornered profile needs to
313
- converge quickly (4 is recommended). It takes minutes where the scalar solver takes milliseconds, so the
322
+ converge quickly (4, the default). It takes minutes where the scalar solver takes milliseconds, so the
314
323
  window opens on the scalar solver and this tab computes only when asked to.
324
+ - **Compare:** the geometry solved by several solvers at once, each with the
325
+ settings on its own tab. Efficiency is drawn with order by colour and solver by
326
+ line style, above each solver's difference from a chosen reference, on a linear
327
+ or log scale. A solver that cannot model the problem, such as the C-method on a
328
+ lamellar groove, is named with its reason and the others still run.
329
+
330
+ ![The Compare tab: four solvers on a 40 nm gold sinusoid at 80 degrees, 2 to 6 nm](https://raw.githubusercontent.com/jamccoy/gratinglab/master/docs/images/compare-tab.png)
331
+
332
+ *Four solvers on one problem: a 40 nm gold sinusoid, 5% deep, at 80° incidence,
333
+ unpolarized. The lower panel is each solver's difference from the C-method on a
334
+ log scale: the integral method and RCWA agree with it to within 2e-6, and the
335
+ scalar one is off by up to 0.018. That agreement is a consistency check between
336
+ methods, not a verification of any of them; see Status.*
315
337
  - **Setup:** the material library, showing which optical constants are installed, the
316
338
  range each covers, where each came from, and how to add others. The coating itself is
317
339
  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`
@@ -102,7 +111,7 @@ The method backends, in order of development:
102
111
  | Method | Status |
103
112
  |---|---|
104
113
  | 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 |
114
+ | 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
115
  | 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
116
  | 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
117
 
@@ -265,8 +274,21 @@ hides it), and the window has one tab for each task:
265
274
  own boundary-condition control (perfect conductivity or tabulated optical
266
275
  constants) and a **Corner mesh** control: off, or a grading order that clusters
267
276
  the boundary nodes at the profile's corners, which a cornered profile needs to
268
- converge quickly (4 is recommended). It takes minutes where the scalar solver takes milliseconds, so the
277
+ converge quickly (4, the default). It takes minutes where the scalar solver takes milliseconds, so the
269
278
  window opens on the scalar solver and this tab computes only when asked to.
279
+ - **Compare:** the geometry solved by several solvers at once, each with the
280
+ settings on its own tab. Efficiency is drawn with order by colour and solver by
281
+ line style, above each solver's difference from a chosen reference, on a linear
282
+ or log scale. A solver that cannot model the problem, such as the C-method on a
283
+ lamellar groove, is named with its reason and the others still run.
284
+
285
+ ![The Compare tab: four solvers on a 40 nm gold sinusoid at 80 degrees, 2 to 6 nm](https://raw.githubusercontent.com/jamccoy/gratinglab/master/docs/images/compare-tab.png)
286
+
287
+ *Four solvers on one problem: a 40 nm gold sinusoid, 5% deep, at 80° incidence,
288
+ unpolarized. The lower panel is each solver's difference from the C-method on a
289
+ log scale: the integral method and RCWA agree with it to within 2e-6, and the
290
+ scalar one is off by up to 0.018. That agreement is a consistency check between
291
+ methods, not a verification of any of them; see Status.*
270
292
  - **Setup:** the material library, showing which optical constants are installed, the
271
293
  range each covers, where each came from, and how to add others. The coating itself is
272
294
  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.6"
25
+ __version__ = "0.2.7"
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
@@ -81,7 +81,9 @@ CONDUCTIVITY_NOTES = {
81
81
 
82
82
 
83
83
  #: The corner-mesh choices as the form shows them: ``off`` is the equal-arc
84
- #: mesh, the solver's default, and a number is the grading order.
84
+ #: mesh and a number is the grading order. The solver's default, ``4``, is
85
+ #: the form's too (`IntegralOptionsState`), for the reason `CONDUCTIVITY_MODES`
86
+ #: gives; ``off`` is listed first only because the list reads in order.
85
87
  GRADING_CHOICES = ("off",) + tuple(str(p) for p in range(2, _MAX_GRADING + 1))
86
88
 
87
89
  #: What the choice means, shown beneath the selector, for the reason
@@ -107,7 +109,7 @@ class IntegralOptionsState:
107
109
 
108
110
  boundary_points: str = "400"
109
111
  conductivity: str = CONDUCTIVITY_MODES[0]
110
- corner_grading: str = GRADING_CHOICES[0]
112
+ corner_grading: str = "4"
111
113
 
112
114
  def with_field(self, name: str, value: Any) -> "IntegralOptionsState":
113
115
  """Return a copy with one field changed."""