gratinglab 0.3.2__tar.gz → 0.3.4__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 (214) hide show
  1. {gratinglab-0.3.2/src/gratinglab.egg-info → gratinglab-0.3.4}/PKG-INFO +22 -11
  2. {gratinglab-0.3.2 → gratinglab-0.3.4}/README.md +19 -8
  3. {gratinglab-0.3.2 → gratinglab-0.3.4}/pyproject.toml +20 -7
  4. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/__init__.py +1 -1
  5. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/compare.py +47 -2
  6. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/geometry.py +42 -0
  7. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/qt/setup_tab.py +3 -3
  8. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/qt/worker.py +35 -3
  9. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/illumination.py +44 -1
  10. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/io/boundary.py +6 -2
  11. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/io/efficiency_table.py +1 -1
  12. gratinglab-0.3.4/src/gratinglab/io/ggp.py +824 -0
  13. gratinglab-0.3.4/src/gratinglab/io/txl.py +273 -0
  14. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/materials/__init__.py +21 -5
  15. gratinglab-0.3.4/src/gratinglab/materials/data/SOURCES.md +48 -0
  16. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/materials/optical.py +10 -1
  17. gratinglab-0.3.4/src/gratinglab/materials/typical.py +388 -0
  18. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/measured.py +10 -0
  19. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/config.py +1 -1
  20. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/gui/qt/boundary_view.py +3 -2
  21. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/settings.py +13 -0
  22. gratinglab-0.3.4/src/gratinglab/profiles/__init__.py +61 -0
  23. gratinglab-0.3.4/src/gratinglab/profiles/_base.py +91 -0
  24. gratinglab-0.3.4/src/gratinglab/profiles/_polyline.py +501 -0
  25. gratinglab-0.3.4/src/gratinglab/profiles/_shapes.py +288 -0
  26. gratinglab-0.3.4/src/gratinglab/profiles/_trapezoid.py +360 -0
  27. gratinglab-0.3.4/src/gratinglab/profiles/_types.py +119 -0
  28. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/solvers/cmethod/__init__.py +4 -1
  29. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/solvers/integral/__init__.py +4 -1
  30. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/solvers/integral/_kernels.py +14 -2
  31. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/solvers/rcwa/__init__.py +10 -2
  32. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/solvers/scalar.py +4 -0
  33. {gratinglab-0.3.2 → gratinglab-0.3.4/src/gratinglab.egg-info}/PKG-INFO +22 -11
  34. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab.egg-info/SOURCES.txt +12 -2
  35. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab.egg-info/requires.txt +2 -2
  36. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_cmethod.py +8 -7
  37. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_four_way.py +8 -7
  38. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_geometry.py +40 -1
  39. gratinglab-0.3.4/tests/test_ggp.py +757 -0
  40. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_gui_qt.py +223 -8
  41. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_integral_measured_polygon.py +4 -2
  42. gratinglab-0.3.4/tests/test_littrow.py +184 -0
  43. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_materials.py +31 -31
  44. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_measured.py +48 -1
  45. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_scalar.py +8 -6
  46. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_trapezoidal_solvers.py +6 -5
  47. gratinglab-0.3.4/tests/test_txl.py +326 -0
  48. gratinglab-0.3.4/tests/test_typical.py +227 -0
  49. gratinglab-0.3.2/src/gratinglab/io/ggp.py +0 -276
  50. gratinglab-0.3.2/src/gratinglab/materials/data/SOURCES.md +0 -53
  51. gratinglab-0.3.2/src/gratinglab/profiles.py +0 -1334
  52. gratinglab-0.3.2/tests/test_ggp.py +0 -254
  53. {gratinglab-0.3.2 → gratinglab-0.3.4}/LICENSE +0 -0
  54. {gratinglab-0.3.2 → gratinglab-0.3.4}/setup.cfg +0 -0
  55. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/checks.py +0 -0
  56. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/convergence.py +0 -0
  57. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/__init__.py +0 -0
  58. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/app.py +0 -0
  59. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/cmethod_options.py +0 -0
  60. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/comparison.py +0 -0
  61. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/diagram.py +0 -0
  62. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/diagram3d.py +0 -0
  63. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/docs.py +0 -0
  64. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/efficiency_plot.py +0 -0
  65. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/icon.py +0 -0
  66. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/integral_options.py +0 -0
  67. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/launcher.py +0 -0
  68. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/mathtext.py +0 -0
  69. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/orders.py +0 -0
  70. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/provenance.py +0 -0
  71. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/qt/__init__.py +0 -0
  72. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/qt/cmethod_tab.py +0 -0
  73. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/qt/compare_tab.py +0 -0
  74. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/qt/geometry_panel.py +0 -0
  75. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/qt/geometry_tab.py +0 -0
  76. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/qt/integral_tab.py +0 -0
  77. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/qt/launcher.py +0 -0
  78. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/qt/main_window.py +0 -0
  79. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/qt/profile_plot_panel.py +0 -0
  80. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/qt/rcwa_tab.py +0 -0
  81. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/qt/scalar_tab.py +0 -0
  82. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/qt/scene3d_panel.py +0 -0
  83. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/qt/sizing.py +0 -0
  84. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/qt/theming.py +0 -0
  85. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/qt/theory_viewer.py +0 -0
  86. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/rcwa_options.py +0 -0
  87. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/richtext.py +0 -0
  88. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/scalar_options.py +0 -0
  89. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/state.py +0 -0
  90. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/gui/theme.py +0 -0
  91. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/io/__init__.py +0 -0
  92. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/materials/fresnel.py +0 -0
  93. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/materials/refractiveindex.py +0 -0
  94. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/__init__.py +0 -0
  95. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/analyzer.py +0 -0
  96. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/boundary/__init__.py +0 -0
  97. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/boundary/average.py +0 -0
  98. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/boundary/cross_section.py +0 -0
  99. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/boundary/pipeline.py +0 -0
  100. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/boundary/swaths.py +0 -0
  101. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/cli.py +0 -0
  102. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/core/__init__.py +0 -0
  103. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/core/analysis.py +0 -0
  104. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/core/image_flatten.py +0 -0
  105. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/core/orientation.py +0 -0
  106. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/core/processing.py +0 -0
  107. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/core/region_mask.py +0 -0
  108. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/core/scars.py +0 -0
  109. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/core/tip.py +0 -0
  110. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/data/__init__.py +0 -0
  111. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/data/aggregation.py +0 -0
  112. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/gui/__init__.py +0 -0
  113. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/gui/icon.py +0 -0
  114. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/gui/qt/__init__.py +0 -0
  115. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/gui/qt/app.py +0 -0
  116. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/gui/qt/canvas.py +0 -0
  117. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/gui/qt/efficiency_dialog.py +0 -0
  118. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/gui/qt/import_view.py +0 -0
  119. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/gui/qt/layout.py +0 -0
  120. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/gui/qt/main_window.py +0 -0
  121. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/gui/qt/swath_dialog.py +0 -0
  122. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/gui/qt/wiki_view.py +0 -0
  123. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/gui/qt/worker.py +0 -0
  124. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/gui/state.py +0 -0
  125. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/io/__init__.py +0 -0
  126. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/io/file_io.py +0 -0
  127. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/io/ibw.py +0 -0
  128. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/io/metrics.py +0 -0
  129. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/io/sem.py +0 -0
  130. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/io/spm.py +0 -0
  131. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/stats/__init__.py +0 -0
  132. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/stats/analysis.py +0 -0
  133. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/stats/icc.py +0 -0
  134. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/visualization/__init__.py +0 -0
  135. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/visualization/diagnostics.py +0 -0
  136. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/visualization/profiles.py +0 -0
  137. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/visualization/statistics.py +0 -0
  138. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/wiki/__init__.py +0 -0
  139. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/wiki/facet-fitting.md +0 -0
  140. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/wiki/file-formats.md +0 -0
  141. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/wiki/flattening.md +0 -0
  142. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/wiki/groove-orientation.md +0 -0
  143. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/wiki/icc-correction.md +0 -0
  144. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/wiki/overview.md +0 -0
  145. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/wiki/reading-outputs.md +0 -0
  146. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/wiki/row-groups.md +0 -0
  147. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/wiki/scars-and-masking.md +0 -0
  148. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/wiki/swath-statistics.md +0 -0
  149. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/wiki/tip-correction.md +0 -0
  150. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/metrology/workflows.py +0 -0
  151. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/problem.py +0 -0
  152. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/resolution.py +0 -0
  153. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/result.py +0 -0
  154. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/solvers/__init__.py +0 -0
  155. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/solvers/_conditioning.py +0 -0
  156. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/solvers/_execution.py +0 -0
  157. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/solvers/base.py +0 -0
  158. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/solvers/cmethod/_core.py +0 -0
  159. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/solvers/cmethod/_series.py +0 -0
  160. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/solvers/integral/_boundary.py +0 -0
  161. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/solvers/integral/_core.py +0 -0
  162. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/solvers/integral/_finite.py +0 -0
  163. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/solvers/integral/_greens.py +0 -0
  164. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/solvers/integral/_nystrom.py +0 -0
  165. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/solvers/rcwa/_core.py +0 -0
  166. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab/solvers/rcwa/_stack.py +0 -0
  167. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab.egg-info/dependency_links.txt +0 -0
  168. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab.egg-info/entry_points.txt +0 -0
  169. {gratinglab-0.3.2 → gratinglab-0.3.4}/src/gratinglab.egg-info/top_level.txt +0 -0
  170. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_changelog.py +0 -0
  171. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_checks.py +0 -0
  172. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_compare.py +0 -0
  173. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_conditioning.py +0 -0
  174. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_convergence.py +0 -0
  175. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_cross_method.py +0 -0
  176. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_efficiency_table.py +0 -0
  177. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_fresnel.py +0 -0
  178. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_gui_cmethod_options.py +0 -0
  179. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_gui_cmethod_tab.py +0 -0
  180. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_gui_compare_tab.py +0 -0
  181. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_gui_diagram.py +0 -0
  182. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_gui_diagram3d.py +0 -0
  183. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_gui_docs.py +0 -0
  184. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_gui_integral_options.py +0 -0
  185. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_gui_launcher.py +0 -0
  186. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_gui_mathtext.py +0 -0
  187. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_gui_orders.py +0 -0
  188. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_gui_provenance.py +0 -0
  189. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_gui_rcwa_options.py +0 -0
  190. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_gui_richtext.py +0 -0
  191. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_gui_scalar_options.py +0 -0
  192. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_gui_state.py +0 -0
  193. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_gui_theme.py +0 -0
  194. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_illumination.py +0 -0
  195. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_integral_core.py +0 -0
  196. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_integral_corpus.py +0 -0
  197. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_integral_finite.py +0 -0
  198. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_integral_graded.py +0 -0
  199. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_integral_greens.py +0 -0
  200. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_integral_kernels.py +0 -0
  201. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_integral_operators.py +0 -0
  202. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_integral_solver.py +0 -0
  203. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_io_boundary.py +0 -0
  204. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_perturbation.py +0 -0
  205. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_profiles.py +0 -0
  206. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_rayleigh_anomaly.py +0 -0
  207. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_rcwa.py +0 -0
  208. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_rcwa_benchmarks.py +0 -0
  209. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_refractiveindex.py +0 -0
  210. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_resolution.py +0 -0
  211. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_result.py +0 -0
  212. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_solver_conformance.py +0 -0
  213. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_solver_execution.py +0 -0
  214. {gratinglab-0.3.2 → gratinglab-0.3.4}/tests/test_solver_progress.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gratinglab
3
- Version: 0.3.2
3
+ Version: 0.3.4
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
@@ -25,11 +25,11 @@ Requires-Dist: pydantic>=2.6
25
25
  Requires-Dist: endstation>=0.2.6
26
26
  Requires-Dist: threadpoolctl>=3.1
27
27
  Provides-Extra: dev
28
- Requires-Dist: pytest>=7; extra == "dev"
28
+ Requires-Dist: pytest>=9.2; extra == "dev"
29
29
  Requires-Dist: pytest-qt>=4.5; extra == "dev"
30
30
  Requires-Dist: matplotlib>=3.8; extra == "dev"
31
31
  Requires-Dist: pandas>=2.1; extra == "dev"
32
- Requires-Dist: ruff>=0.16; extra == "dev"
32
+ Requires-Dist: ruff>=0.17; extra == "dev"
33
33
  Provides-Extra: gui
34
34
  Requires-Dist: matplotlib>=3.8; extra == "gui"
35
35
  Requires-Dist: PySide6-Essentials!=6.12.0,>=6.7; python_version < "3.12" and extra == "gui"
@@ -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
- No optical constant tables are distributed with the package, so absolute efficiencies
253
- require tables supplied by the user. A library is a directory of such tables, one
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 no tables are distributed.
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
@@ -287,8 +295,8 @@ same applies to the private reference corpus of efficiency tables:
287
295
 
288
296
  | Data | Environment variable | Default |
289
297
  |---|---|---|
290
- | AFM scans | `GRATINGLAB_AFM_DIR` | `~/Documents/afm_scans` |
291
- | Reference efficiency tables | `GRATINGLAB_REF_DIR` | `~/Documents/diffraction_efficiency` |
298
+ | AFM scans | `GRATINGLAB_AFM_DIR` | `~/Documents/Research/afm_scans` |
299
+ | Reference efficiency tables | `GRATINGLAB_REF_DIR` | `~/Documents/Research/diffraction_efficiency` |
292
300
 
293
301
  Tests that need either are skipped when it is absent, so the suite passes on a fresh
294
302
  clone. Most of the metrology tests do not need it: they run against a committed
@@ -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, and a table you
518
- add is governed by the terms of whoever published it, not by this licence. CXRO and
519
- refractiveindex.info each state their own, and a table exported from another program
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
- No optical constant tables are distributed with the package, so absolute efficiencies
208
- require tables supplied by the user. A library is a directory of such tables, one
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 no tables are distributed.
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
@@ -242,8 +250,8 @@ same applies to the private reference corpus of efficiency tables:
242
250
 
243
251
  | Data | Environment variable | Default |
244
252
  |---|---|---|
245
- | AFM scans | `GRATINGLAB_AFM_DIR` | `~/Documents/afm_scans` |
246
- | Reference efficiency tables | `GRATINGLAB_REF_DIR` | `~/Documents/diffraction_efficiency` |
253
+ | AFM scans | `GRATINGLAB_AFM_DIR` | `~/Documents/Research/afm_scans` |
254
+ | Reference efficiency tables | `GRATINGLAB_REF_DIR` | `~/Documents/Research/diffraction_efficiency` |
247
255
 
248
256
  Tests that need either are skipped when it is absent, so the suite passes on a fresh
249
257
  clone. Most of the metrology tests do not need it: they run against a committed
@@ -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, and a table you
473
- add is governed by the terms of whoever published it, not by this licence. CXRO and
474
- refractiveindex.info each state their own, and a table exported from another program
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.
@@ -62,7 +62,20 @@ dependencies = [
62
62
  ]
63
63
 
64
64
  [project.optional-dependencies]
65
- dev = ["pytest>=7", "pytest-qt>=4.5", "matplotlib>=3.8", "pandas>=2.1", "ruff>=0.16"]
65
+ # The two floors that are not "the oldest version that works": pytest and ruff
66
+ # are the gate the suite is judged by, and a local copy older than the one CI
67
+ # resolves makes that gate quietly permissive rather than merely different.
68
+ # `pytest>=7` let a local 9.1.1 accept `pytest.approx` over a list of tuples
69
+ # that 9.2 refuses, so tests/test_txl.py passed here and failed on all four CI
70
+ # legs; ruff gains rules between minors the same way, so an older copy passes
71
+ # what `ruff check .` rejects there. Both floors therefore track what CI
72
+ # installs, and raising them is part of taking a new version of either.
73
+ #
74
+ # The library floors below stay as genuine minimums. CI installs the newest of
75
+ # everything on purpose -- that is what caught the pytest change -- and pinning
76
+ # matplotlib or pandas to whatever it resolved this week would throw that away
77
+ # while excluding environments the package supports.
78
+ dev = ["pytest>=9.2", "pytest-qt>=4.5", "matplotlib>=3.8", "pandas>=2.1", "ruff>=0.17"]
66
79
  # The GUI is an optional extra: `pip install gratinglab` must never require
67
80
  # a toolkit.
68
81
  #
@@ -132,11 +145,11 @@ version = { attr = "gratinglab.__version__" }
132
145
  [tool.setuptools.packages.find]
133
146
  where = ["src"]
134
147
 
135
- # Optical constant TABLES do not ship: whether CXRO's may be redistributed is
136
- # not settled, so the wheel and sdist carry none and `available()` is empty on
137
- # install. That is the one place a checkout and an install differ on purpose,
138
- # and both halves are checked: the suite uses `data/Au.txt` from the checkout,
139
- # and release.yml asserts the wheel holds no table and that an install refuses
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
140
153
  # `lookup("Au")` with the message that says what to do instead. A missing table
141
154
  # raises rather than degrading, so the difference cannot pass for a result.
142
155
  # SOURCES.md ships, because it is how a user finds out where to get one.
@@ -228,7 +241,7 @@ do_not_mutate = [
228
241
  # are covered by tests/test_illumination.py and tests/test_profiles.py.
229
242
  "*/illumination.py",
230
243
  "*/problem.py",
231
- "*/profiles.py",
244
+ "*/profiles/*",
232
245
  ]
233
246
 
234
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.2"
25
+ __version__ = "0.3.4"
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. ``at_wavelength`` and ``at_order`` locate the
205
- largest absolute residual.
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:
@@ -27,6 +27,7 @@ __all__ = [
27
27
  "horizon_weights",
28
28
  "blaze_direction",
29
29
  "blaze_wavelength",
30
+ "littrow_alpha",
30
31
  "interference_factor",
31
32
  ]
32
33
 
@@ -466,6 +467,47 @@ def blaze_wavelength(
466
467
  return np.where(m != 0, numerator / m, np.inf)
467
468
 
468
469
 
470
+ def littrow_alpha(order: int, wavelength: float, period: float, gamma: float) -> float:
471
+ r"""Azimuthal incidence angle that sends order ``order`` back along itself, in radians.
472
+
473
+ .. math::
474
+ \sin\alpha_L = \frac{m\lambda}{2 p \sin\gamma}
475
+
476
+ This is the grating equation with :math:`\beta_m = \alpha`, so the
477
+ diffracted order returns along the azimuth it arrived on. It is also the
478
+ facet angle a sawtooth needs to blaze into that order there, since
479
+ :func:`blaze_direction` gives :math:`\beta_b = 2\delta - \alpha = \alpha`
480
+ exactly when :math:`\delta = \alpha`.
481
+
482
+ Ported from PyXFocus ``grating.blazeYaw``, whose ``inc`` is the graze onto
483
+ the grating surface and whose yaw is measured in the surface from the
484
+ grooves. Neither is :math:`\gamma` or :math:`\alpha`; the two frames share
485
+ only the dispersion cosine, :math:`\cos(\text{inc})\sin\psi = \sin\gamma\sin\alpha`.
486
+ ``docs/conventions.md`` §10 records the mapping.
487
+
488
+ Raises
489
+ ------
490
+ ValueError
491
+ When :math:`|m\lambda| \ge 2 p \sin\gamma`, where no incidence angle
492
+ short of grazing reaches Littrow.
493
+ """
494
+ if period <= 0:
495
+ raise ValueError(f"period must be positive, got {period}")
496
+ if wavelength <= 0:
497
+ raise ValueError(f"wavelength must be positive, got {wavelength}")
498
+ if not 0 < gamma <= np.pi / 2:
499
+ raise ValueError(f"gamma must lie in (0, pi/2], got {gamma}")
500
+
501
+ s = order * wavelength / (2.0 * period * np.sin(gamma))
502
+ if not -1.0 < s < 1.0:
503
+ raise ValueError(
504
+ f"order {order} at wavelength {wavelength} cannot reach Littrow on period "
505
+ f"{period} at gamma {np.degrees(gamma):g} deg: it needs sin(alpha) = {s:.4g}. "
506
+ "Littrow is reachable only while |m * wavelength| < 2 * period * sin(gamma)."
507
+ )
508
+ return float(np.arcsin(s))
509
+
510
+
469
511
  def interference_factor(s: ArrayLike, n_grooves: int) -> NDArray[np.float64]:
470
512
  r"""The finite-:math:`N` grating interference function, ISSI eq. (8).
471
513
 
@@ -193,9 +193,9 @@ code changes. Or load a table from anywhere without adding it:
193
193
 
194
194
  {citations}
195
195
 
196
- The package ships no tables: CXRO's terms for redistributing them inside a
197
- BSD-3 package have not been confirmed, and `SOURCES.md` beside the bundled
198
- directory records why. Each table's source names the file it was read from,
196
+ The package ships no tables: it carries only data that is its own or in the
197
+ public domain, and no published table is clearly either. `SOURCES.md` beside
198
+ the bundled directory records why. Each table's source names the file it was read from,
199
199
  so a result's provenance says which library answered.
200
200
 
201
201
  ## Surface roughness
@@ -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
- **{k: v for k, v in options.items() if k != knob},
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:
@@ -5,11 +5,13 @@ incidence angle and the half-cone angle, because every mount reduces to it and i
5
5
  serialises readably. Direction cosines are available as a derived property.
6
6
 
7
7
  Three constructors exist so that each community can pass the angles it
8
- uses; they all resolve to the same internal state:
8
+ uses; they all resolve to the same internal state. A fourth places a mount at
9
+ Littrow for one order:
9
10
 
10
11
  >>> Illumination.classical(alpha=10.0) # in-plane
11
12
  >>> Illumination.conical(theta=10.0, phi=45.0) # RCWA-style polar/azimuth
12
13
  >>> Illumination.offplane(graze=1.5, azimuth=0.97) # extreme off-plane, X-ray
14
+ >>> Illumination.littrow(1, 1.55, 400.0, graze=1.73) # order 1 back on itself
13
15
 
14
16
  See ``docs/conventions.md`` §3 for the frame and the sign of ``alpha``.
15
17
  """
@@ -21,6 +23,8 @@ from typing import Literal
21
23
  import numpy as np
22
24
  from pydantic import BaseModel, ConfigDict, Field, field_validator
23
25
 
26
+ from .geometry import littrow_alpha
27
+
24
28
  __all__ = ["Illumination", "Polarization"]
25
29
 
26
30
  Polarization = Literal["TE", "TM", "unpolarized"]
@@ -133,6 +137,45 @@ class Illumination(BaseModel):
133
137
  """
134
138
  return cls(alpha_deg=azimuth, gamma_deg=graze, polarization=polarization)
135
139
 
140
+ @classmethod
141
+ def littrow(
142
+ cls,
143
+ order: int,
144
+ wavelength: float,
145
+ period: float,
146
+ graze: float = 90.0,
147
+ polarization: Polarization = "TE",
148
+ ) -> "Illumination":
149
+ """The Littrow mount for one order at one wavelength.
150
+
151
+ Order ``order`` leaves along the azimuth it arrived on,
152
+ :math:`\\beta_m = \\alpha`. The default ``graze = 90`` is the in-plane
153
+ Littrow mount; a small ``graze`` is the off-plane one, with ``graze``
154
+ the half-cone angle :math:`\\gamma` exactly as in :meth:`offplane`.
155
+ The azimuth found is also the facet angle that blazes into this order
156
+ here; see :func:`gratinglab.geometry.littrow_alpha`.
157
+
158
+ Parameters
159
+ ----------
160
+ order
161
+ Diffraction order :math:`m`.
162
+ wavelength, period
163
+ In nm, or any unit the two share.
164
+ graze
165
+ Half-cone angle :math:`\\gamma`, degrees.
166
+
167
+ Raises
168
+ ------
169
+ ValueError
170
+ When the order cannot reach Littrow at this wavelength and cone.
171
+ """
172
+ alpha = littrow_alpha(order, wavelength, period, float(np.radians(graze)))
173
+ return cls(
174
+ alpha_deg=float(np.degrees(alpha)),
175
+ gamma_deg=graze,
176
+ polarization=polarization,
177
+ )
178
+
136
179
  @classmethod
137
180
  def from_direction_cosines(
138
181
  cls, ux: float, uy: float, uz: float, polarization: Polarization = "TE"
@@ -225,8 +225,12 @@ def read_profile_file(path: "str | Path"):
225
225
 
226
226
  The format is chosen by suffix, because the two are unrelated formats and
227
227
  sniffing the content would only turn a clear refusal into a confusing one.
228
- Only the JSON carries a period; read it with :func:`read_boundary_json`
229
- to get that too.
228
+ Only the JSON carries a period *in nm*; a ``.ggp``'s control string may
229
+ carry the number without its unit, and ``read_ggp`` reports it. Read the
230
+ JSON with :func:`read_boundary_json` to get a period you can use.
231
+
232
+ All five of PCGrate's border profile types reach a profile through here --
233
+ see :mod:`gratinglab.io.ggp` -- so this is not polygonal-only.
230
234
  """
231
235
  path = Path(path)
232
236
  if path.suffix.lower() == ".json":
@@ -32,7 +32,7 @@ Format, confirmed by inspection of PCGrate-SX 6.7.1 output::
32
32
  Every line is tab-separated and right-padded with empty fields.
33
33
 
34
34
  Order indices come from the column headers, never from a positional offset.
35
- The prototype scripts in ``~/Documents/diffraction_efficiency`` read columns as
35
+ The prototype scripts in ``~/Documents/Research/diffraction_efficiency`` read columns as
36
36
  ``data[i+11]``, which works only because ``np.genfromtxt`` splits the quoted
37
37
  label ``" Wav., nm"`` into two whitespace-delimited fields. That offset is
38
38
  different for every file and silently wrong if the order range changes.