gratinglab 0.1.13__tar.gz → 0.2.1__tar.gz

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