gratinglab 0.1.6__tar.gz → 0.1.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 (170) hide show
  1. gratinglab-0.1.7/PKG-INFO +372 -0
  2. gratinglab-0.1.7/README.md +333 -0
  3. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/__init__.py +2 -2
  4. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/checks.py +20 -20
  5. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/compare.py +28 -26
  6. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/convergence.py +93 -90
  7. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/geometry.py +108 -102
  8. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/app.py +19 -18
  9. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/diagram.py +78 -71
  10. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/diagram3d.py +68 -62
  11. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/docs.py +33 -32
  12. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/icon.py +8 -7
  13. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/integral_options.py +23 -22
  14. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/mathtext.py +52 -50
  15. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/orders.py +39 -35
  16. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/provenance.py +42 -41
  17. gratinglab-0.1.7/src/gratinglab/gui/qt/__init__.py +22 -0
  18. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/qt/gc_guard.py +1 -1
  19. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/qt/geometry_panel.py +20 -19
  20. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/qt/geometry_tab.py +50 -44
  21. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/qt/integral_tab.py +12 -10
  22. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/qt/main_window.py +88 -83
  23. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/qt/profile_plot_panel.py +7 -7
  24. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/qt/scalar_tab.py +29 -26
  25. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/qt/scene3d_panel.py +46 -43
  26. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/qt/setup_tab.py +16 -16
  27. gratinglab-0.1.7/src/gratinglab/gui/qt/sizing.py +26 -0
  28. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/qt/theory_viewer.py +16 -16
  29. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/qt/worker.py +57 -55
  30. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/richtext.py +63 -54
  31. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/scalar_options.py +19 -18
  32. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/state.py +31 -32
  33. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/illumination.py +9 -9
  34. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/io/boundary.py +12 -10
  35. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/io/efficiency_table.py +16 -16
  36. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/io/ggp.py +24 -22
  37. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/materials/__init__.py +26 -23
  38. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/materials/fresnel.py +71 -69
  39. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/materials/optical.py +60 -58
  40. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/materials/refractiveindex.py +4 -4
  41. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/__init__.py +17 -16
  42. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/analyzer.py +28 -24
  43. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/boundary/__init__.py +4 -4
  44. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/boundary/average.py +7 -6
  45. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/boundary/pipeline.py +35 -33
  46. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/boundary/swaths.py +13 -12
  47. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/core/__init__.py +3 -2
  48. gratinglab-0.1.7/src/gratinglab/metrology/core/image_flatten.py +136 -0
  49. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/core/processing.py +11 -10
  50. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/core/tip.py +58 -56
  51. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/data/__init__.py +3 -2
  52. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/data/aggregation.py +3 -2
  53. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/gui/__init__.py +16 -14
  54. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/gui/icon.py +20 -18
  55. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/gui/qt/__init__.py +6 -6
  56. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/gui/qt/boundary_view.py +21 -18
  57. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/gui/qt/efficiency_dialog.py +19 -16
  58. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/gui/qt/import_view.py +9 -8
  59. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/gui/qt/main_window.py +5 -4
  60. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/gui/qt/wiki_view.py +9 -7
  61. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/gui/qt/worker.py +13 -12
  62. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/gui/state.py +9 -8
  63. gratinglab-0.1.7/src/gratinglab/metrology/io/__init__.py +9 -0
  64. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/io/file_io.py +6 -5
  65. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/io/metrics.py +5 -5
  66. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/io/spm.py +23 -21
  67. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/settings.py +7 -6
  68. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/stats/__init__.py +3 -2
  69. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/stats/analysis.py +10 -8
  70. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/stats/icc.py +4 -4
  71. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/visualization/__init__.py +3 -2
  72. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/visualization/diagnostics.py +3 -2
  73. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/visualization/profiles.py +7 -6
  74. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/visualization/statistics.py +17 -17
  75. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/wiki/__init__.py +13 -11
  76. gratinglab-0.1.7/src/gratinglab/metrology/wiki/facet-fitting.md +99 -0
  77. gratinglab-0.1.7/src/gratinglab/metrology/wiki/file-formats.md +122 -0
  78. gratinglab-0.1.7/src/gratinglab/metrology/wiki/flattening.md +109 -0
  79. gratinglab-0.1.7/src/gratinglab/metrology/wiki/icc-correction.md +112 -0
  80. gratinglab-0.1.7/src/gratinglab/metrology/wiki/overview.md +55 -0
  81. gratinglab-0.1.7/src/gratinglab/metrology/wiki/reading-outputs.md +182 -0
  82. gratinglab-0.1.7/src/gratinglab/metrology/wiki/row-groups.md +78 -0
  83. gratinglab-0.1.7/src/gratinglab/metrology/wiki/swath-statistics.md +66 -0
  84. gratinglab-0.1.7/src/gratinglab/metrology/wiki/tip-correction.md +180 -0
  85. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/workflows.py +14 -11
  86. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/problem.py +8 -7
  87. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/profiles.py +36 -34
  88. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/resolution.py +37 -34
  89. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/result.py +17 -17
  90. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/solvers/base.py +56 -53
  91. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/solvers/integral/__init__.py +40 -38
  92. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/solvers/integral/_boundary.py +5 -5
  93. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/solvers/integral/_core.py +12 -10
  94. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/solvers/integral/_finite.py +53 -48
  95. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/solvers/integral/_greens.py +25 -22
  96. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/solvers/integral/_kernels.py +66 -58
  97. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/solvers/integral/_nystrom.py +89 -80
  98. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/solvers/scalar.py +209 -195
  99. gratinglab-0.1.7/src/gratinglab.egg-info/PKG-INFO +372 -0
  100. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_cross_method.py +2 -2
  101. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_integral_corpus.py +7 -5
  102. gratinglab-0.1.6/PKG-INFO +0 -348
  103. gratinglab-0.1.6/README.md +0 -309
  104. gratinglab-0.1.6/src/gratinglab/gui/qt/__init__.py +0 -20
  105. gratinglab-0.1.6/src/gratinglab/gui/qt/sizing.py +0 -25
  106. gratinglab-0.1.6/src/gratinglab/metrology/core/image_flatten.py +0 -131
  107. gratinglab-0.1.6/src/gratinglab/metrology/io/__init__.py +0 -8
  108. gratinglab-0.1.6/src/gratinglab/metrology/wiki/facet-fitting.md +0 -95
  109. gratinglab-0.1.6/src/gratinglab/metrology/wiki/file-formats.md +0 -116
  110. gratinglab-0.1.6/src/gratinglab/metrology/wiki/flattening.md +0 -105
  111. gratinglab-0.1.6/src/gratinglab/metrology/wiki/icc-correction.md +0 -111
  112. gratinglab-0.1.6/src/gratinglab/metrology/wiki/overview.md +0 -51
  113. gratinglab-0.1.6/src/gratinglab/metrology/wiki/reading-outputs.md +0 -172
  114. gratinglab-0.1.6/src/gratinglab/metrology/wiki/row-groups.md +0 -75
  115. gratinglab-0.1.6/src/gratinglab/metrology/wiki/swath-statistics.md +0 -61
  116. gratinglab-0.1.6/src/gratinglab/metrology/wiki/tip-correction.md +0 -173
  117. gratinglab-0.1.6/src/gratinglab.egg-info/PKG-INFO +0 -348
  118. {gratinglab-0.1.6 → gratinglab-0.1.7}/LICENSE +0 -0
  119. {gratinglab-0.1.6 → gratinglab-0.1.7}/pyproject.toml +0 -0
  120. {gratinglab-0.1.6 → gratinglab-0.1.7}/setup.cfg +0 -0
  121. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/gui/__init__.py +0 -0
  122. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/io/__init__.py +0 -0
  123. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/materials/data/SOURCES.md +0 -0
  124. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/cli.py +0 -0
  125. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/config.py +0 -0
  126. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/core/analysis.py +0 -0
  127. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/gui/qt/app.py +0 -0
  128. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/gui/qt/canvas.py +0 -0
  129. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/metrology/gui/qt/swath_dialog.py +0 -0
  130. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab/solvers/__init__.py +0 -0
  131. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab.egg-info/SOURCES.txt +0 -0
  132. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab.egg-info/dependency_links.txt +0 -0
  133. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab.egg-info/entry_points.txt +0 -0
  134. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab.egg-info/requires.txt +0 -0
  135. {gratinglab-0.1.6 → gratinglab-0.1.7}/src/gratinglab.egg-info/top_level.txt +0 -0
  136. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_checks.py +0 -0
  137. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_compare.py +0 -0
  138. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_convergence.py +0 -0
  139. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_efficiency_table.py +0 -0
  140. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_fresnel.py +0 -0
  141. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_geometry.py +0 -0
  142. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_ggp.py +0 -0
  143. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_gui_diagram.py +0 -0
  144. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_gui_diagram3d.py +0 -0
  145. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_gui_docs.py +0 -0
  146. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_gui_integral_options.py +0 -0
  147. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_gui_mathtext.py +0 -0
  148. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_gui_orders.py +0 -0
  149. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_gui_provenance.py +0 -0
  150. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_gui_qt.py +0 -0
  151. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_gui_richtext.py +0 -0
  152. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_gui_scalar_options.py +0 -0
  153. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_gui_state.py +0 -0
  154. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_illumination.py +0 -0
  155. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_integral_core.py +0 -0
  156. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_integral_finite.py +0 -0
  157. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_integral_greens.py +0 -0
  158. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_integral_kernels.py +0 -0
  159. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_integral_operators.py +0 -0
  160. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_integral_solver.py +0 -0
  161. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_io_boundary.py +0 -0
  162. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_materials.py +0 -0
  163. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_perturbation.py +0 -0
  164. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_profiles.py +0 -0
  165. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_refractiveindex.py +0 -0
  166. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_resolution.py +0 -0
  167. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_result.py +0 -0
  168. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_scalar.py +0 -0
  169. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_solver_conformance.py +0 -0
  170. {gratinglab-0.1.6 → gratinglab-0.1.7}/tests/test_solver_progress.py +0 -0
@@ -0,0 +1,372 @@
1
+ Metadata-Version: 2.4
2
+ Name: gratinglab
3
+ Version: 0.1.7
4
+ Summary: Open platform for diffraction grating groove metrology and efficiency analysis
5
+ Author: Jake McCoy
6
+ License: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/jamccoy/gratinglab
8
+ Keywords: diffraction,grating,efficiency,rcwa,x-ray,spectroscopy,afm,metrology,blaze,pcgrate
9
+ Classifier: Development Status :: 2 - Pre-Alpha
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: License :: OSI Approved :: BSD License
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Scientific/Engineering :: Physics
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: numpy>=1.26
19
+ Requires-Dist: scipy>=1.11
20
+ Requires-Dist: pydantic>=2.6
21
+ Requires-Dist: endstation>=0.2.2
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest>=7; extra == "dev"
24
+ Requires-Dist: pytest-qt>=4.5; extra == "dev"
25
+ Requires-Dist: matplotlib>=3.8; extra == "dev"
26
+ Requires-Dist: pandas>=2.1; extra == "dev"
27
+ Requires-Dist: ruff>=0.16; extra == "dev"
28
+ Provides-Extra: gui
29
+ Requires-Dist: matplotlib>=3.8; extra == "gui"
30
+ Requires-Dist: PySide6-Essentials>=6.7; extra == "gui"
31
+ Requires-Dist: endstation[qt]>=0.2.2; extra == "gui"
32
+ Provides-Extra: metrology
33
+ Requires-Dist: matplotlib>=3.9; extra == "metrology"
34
+ Provides-Extra: refractiveindex
35
+ Requires-Dist: pyyaml>=6; extra == "refractiveindex"
36
+ Provides-Extra: mut
37
+ Requires-Dist: mutmut<3.3,>=3.2; extra == "mut"
38
+ Dynamic: license-file
39
+
40
+ # gratinglab
41
+
42
+ [![PyPI](https://img.shields.io/pypi/v/gratinglab?cacheSeconds=3600)](https://pypi.org/project/gratinglab/)
43
+ [![Python](https://img.shields.io/pypi/pyversions/gratinglab?cacheSeconds=3600)](https://pypi.org/project/gratinglab/)
44
+ [![tests](https://github.com/jamccoy/gratinglab/actions/workflows/test.yml/badge.svg)](https://github.com/jamccoy/gratinglab/actions/workflows/test.yml)
45
+ [![License: BSD-3-Clause](https://img.shields.io/badge/license-BSD--3--Clause-blue)](https://github.com/jamccoy/gratinglab/blob/master/LICENSE)
46
+
47
+ **[PyPI](https://pypi.org/project/gratinglab/)** · **[Documentation](https://github.com/jamccoy/gratinglab/wiki)** · **[Releases](https://github.com/jamccoy/gratinglab/releases)** · **[Issues](https://github.com/jamccoy/gratinglab/issues)**
48
+
49
+ gratinglab is a Python package for measuring the groove profiles of diffraction
50
+ gratings from atomic force microscope (AFM) scans and for computing the diffraction
51
+ efficiency of a grating from such a profile.
52
+
53
+ The AFM metrology is the part of the package in regular use. It measures blaze angles
54
+ from a scan, averages the grooves into a single profile, reports how much that profile
55
+ varies down the scan, corrects for the shape of the AFM tip where the data allow it,
56
+ and exports the averaged groove as a boundary profile. The efficiency solvers are
57
+ **experimental**: they pass their test suite but have not yet been used for real
58
+ analysis, and their results should be treated as provisional.
59
+
60
+ Rigorous methods for computing grating efficiency are well established, and open codes
61
+ exist for several of them, although each carries its own conventions and
62
+ normalizations. gratinglab is intended to fill two gaps that remain: running the same
63
+ physical problem through several methods so that their results can be compared
64
+ directly, and starting that problem from a measured surface rather than an idealised
65
+ one. To this end, the physical problem and the numerical method are kept separate. A
66
+ `Problem` describes the grating and can be saved to a file, and the same `Problem` is
67
+ passed to every solver, so any difference between their results is attributable to
68
+ the methods themselves.
69
+
70
+ ```
71
+ AFM scan -> BoundaryProfile (gratinglab.metrology)
72
+ | measured period, averaged groove, facet fit
73
+ v
74
+ Problem (period + profile + materials; no solver fields)
75
+ Illumination (wavelength + direction + polarization)
76
+ |
77
+ v
78
+ Solver (plugin) -> Result (+ provenance)
79
+ ```
80
+
81
+ The metrology and the solvers are distributed as one package so that no information is
82
+ lost between them. A plain boundary file records the groove shape but not the grating
83
+ period, which must then be entered again by hand. `BoundaryProfile.to_problem()`
84
+ carries the period measured from the scan, together with the fitted facet angle. A
85
+ profile can be saved with that information intact in gratinglab's own `.json` format
86
+ (`gratinglab.io.boundary.write_boundary_json`); reading it back gives the same
87
+ problem, and "Load boundary profile" in the efficiency window takes the period from
88
+ it. The Boundary tab of the metrology window exports `.json`, `.csv`, or a `.ggp`
89
+ boundary file for PCGrate, and its "Send to efficiency window" button opens the groove
90
+ in the efficiency window directly.
91
+
92
+ ```python
93
+ from gratinglab.metrology.boundary import build_boundary_profile
94
+
95
+ profile = build_boundary_profile(data, scan_x_size, settings)
96
+ problem = profile.to_problem(coating="Au", blaze_angle=29.5)
97
+ ```
98
+
99
+ ## Status
100
+
101
+ The project is at a pre-alpha stage. The AFM metrology is the more mature of its two
102
+ parts. On the efficiency side, the scalar and integral solvers, the comparison and
103
+ convergence harnesses and the materials layer work end to end. They are checked
104
+ against closed-form results, published benchmark tables, energy balance and
105
+ reciprocity, but remain experimental until they have been used on real problems.
106
+ Efficiencies are absolute when a coating is named, using an optical constant table
107
+ supplied by the user (see Install). A rigorous coupled-wave analysis (RCWA) backend
108
+ has been contributed but is not yet integrated.
109
+
110
+ ## Scope
111
+
112
+ The metrology (`gratinglab.metrology`) reads AFM scans, either Nanoscope `.spm` files
113
+ or Gwyddion text exports. It produces blaze angles for each groove with statistics
114
+ over groups of scan rows and a correction for the correlation between those groups,
115
+ an averaged groove normalised to one period, and statistics describing how that
116
+ groove varies between horizontal swaths of the scan. It can also undo the broadening
117
+ introduced by the AFM tip where the tip reached the surface, and it marks the regions
118
+ the tip could not reach instead of estimating them.
119
+
120
+ The efficiency side comprises the problem specification, the solvers, the comparison
121
+ and convergence harnesses, the materials layer, and a calculation of the resolving
122
+ power implied by the number of grooves measured.
123
+
124
+ Two mounting geometries are supported:
125
+
126
+ - **In-plane (classical):** ultraviolet to near-infrared optical engineering.
127
+ - **Extreme off-plane (conical) at grazing incidence:** soft x-ray spectroscopy, where
128
+ the integral method is the established approach and general-purpose RCWA codes are
129
+ least reliable.
130
+
131
+ The method backends, in order of development:
132
+
133
+ | Method | Status |
134
+ |---|---|
135
+ | Scalar (Kirchhoff, x-ray blaze form) | experimental; checked against closed-form results |
136
+ | Integral method (boundary integral, conical, undercut-capable) | experimental; perfect and finite conductivity (`conductivity="tabulated"`, Goray & Schmidt 2010), absolute efficiencies with the absorbed fraction recorded; checked against published benchmark tables (Maystre & Popov; Goray & Schmidt), energy balance and reciprocity |
137
+ | RCWA (in-plane, conical, off-plane; Li factorization, S-matrix) | contributed, not yet integrated |
138
+ | C-method (Chandezon) | planned |
139
+
140
+ The comparison harness also accepts an efficiency table already computed by an
141
+ external program as one of its methods, so that comparison plots can be made without
142
+ a licence for the program that produced the reference data:
143
+
144
+ ```python
145
+ reference = read_scan("run.txt", method="integral")
146
+ align(sweep(problem, illumination, wavelengths, ["scalar", reference]))
147
+ ```
148
+
149
+ Because the file does not record the physics behind it, the caller states which method
150
+ produced an imported table. Comparison legends then name the method, for example
151
+ `scalar vs integral`, while `Provenance.version` records the program and version that
152
+ generated the file.
153
+
154
+ ### Resolving power from a measured surface
155
+
156
+ A scan records the number of grooves it covers, so resolving power can be computed
157
+ without entering anything by hand:
158
+
159
+ ```python
160
+ problem = profile.to_problem(coating="Au") # n_grooves = the grooves averaged
161
+ resolving_power(problem, illumination, wavelength=2.0, order=3).r
162
+ ```
163
+
164
+ The resolving power follows from the Rayleigh criterion as `R = |m|·N`, and
165
+ `line_profile` gives the line shape for a finite number of grooves from which that
166
+ result is derived. A problem whose `n_grooves` is not set is refused rather than
167
+ treated as having infinitely many grooves. Resolving power degraded by errors in groove
168
+ placement is planned; until it is implemented, the ideal formula is not used as an
169
+ approximation for it.
170
+
171
+ ### Correcting for the AFM tip
172
+
173
+ An AFM image is the true surface dilated by the shape of the tip.
174
+ `tip_correction='erosion'` reverses this dilation where it can be reversed (Villarrubia
175
+ 1997) and reports a certainty map showing where it cannot. The tip is modelled as a
176
+ spherical apex on two flanks whose angles may differ, so a pyramidal probe such as
177
+ Bruker's ScanAsyst-Air can be represented. No method can recover the bottom of a
178
+ trough the tip did not enter, and the correction leaves such regions as upper bounds
179
+ instead of filling them in.
180
+
181
+ On one real scan, the groove depth and the fitted facet angle disagree by 7.6°.
182
+ Erosion with the probe at its nominal radius recovers none of that difference, and
183
+ reproducing it by simulation requires an apex worn to about 80 nm, which excludes the
184
+ nominal tip as the cause
185
+ ([findings](https://github.com/jamccoy/gratinglab/blob/master/docs/findings.md)).
186
+
187
+ ## Install
188
+
189
+ ```bash
190
+ pip install gratinglab # solvers only: numpy, scipy, pydantic
191
+ pip install "gratinglab[metrology]" # + AFM readers and the boundary pipeline
192
+ pip install "gratinglab[gui]" # + the Qt windows
193
+ pip install "gratinglab[refractiveindex]" # + refractiveindex.info tables
194
+ ```
195
+
196
+ With [uv](https://docs.astral.sh/uv/), the same extras work:
197
+
198
+ ```bash
199
+ uv pip install "gratinglab[refractiveindex]" # into the active environment
200
+ uv add gratinglab # into a uv project
201
+ uv tool install "gratinglab[gui]" # gratinglab-gui as a standalone command
202
+ ```
203
+
204
+ From a checkout, use `pip install -e ".[dev,gui,metrology,refractiveindex]"` instead.
205
+
206
+ No optical constant tables are distributed with the package, so absolute efficiencies
207
+ require tables supplied by the user. A library is a directory of such tables, one
208
+ material per file, each named after its material:
209
+
210
+ | File | From | Range |
211
+ |---|---|---|
212
+ | `Pt.txt` | a [CXRO](https://henke.lbl.gov/optical_constants/) export | x-ray |
213
+ | `Pt.yml` | a [refractiveindex.info](https://refractiveindex.info) file, renamed from its author's name | ultraviolet to infrared |
214
+ | `Pt.ari` | a PCGrate table | whatever it covers |
215
+
216
+ Saving one as `~/.gratinglab/materials/Pt.yml` makes `coating="Pt"` available.
217
+ `GRATINGLAB_MATERIALS` points to a different directory, and
218
+ `materials.from_cxro_file(path)` or `from_refractiveindex_file(path)` reads a single
219
+ file without a library. The YAML files require the `refractiveindex` extra.
220
+ [`SOURCES.md`](https://github.com/jamccoy/gratinglab/blob/master/src/gratinglab/materials/data/SOURCES.md)
221
+ explains why no tables are distributed.
222
+
223
+ The core install does not include a plotting stack, so a user who only runs the solvers
224
+ does not install one. A subpackage that needs an optional extra names it when it is
225
+ missing, instead of failing with a bare `ModuleNotFoundError`.
226
+
227
+ Apart from numpy, scipy and pydantic, the only dependency is
228
+ [endstation](https://github.com/jamccoy/endstation), a library of shared components for
229
+ desktop scientific applications by the same author. gratinglab uses its guards for
230
+ optional extras (the messages described above), form validation, Qt layout sizing,
231
+ test helpers, and the tools that build the icon and the macOS application and publish
232
+ the wiki. The core of endstation has no dependencies of its own, so it adds nothing to
233
+ a solver-only install; its Qt components are installed only with the `gui` extra.
234
+
235
+ ### Where the measurement data lives
236
+
237
+ AFM scans are not included in the repository, because they are research data. The
238
+ same applies to the private reference corpus of efficiency tables:
239
+
240
+ | Data | Environment variable | Default |
241
+ |---|---|---|
242
+ | AFM scans | `GRATINGLAB_AFM_DIR` | `~/Documents/afm_scans` |
243
+ | Reference efficiency tables | `GRATINGLAB_REF_DIR` | `~/Documents/diffraction_efficiency` |
244
+
245
+ Tests that need either are skipped when it is absent, so the suite passes on a fresh
246
+ clone. Most of the metrology tests do not need it: they run against a committed
247
+ synthetic scan of a grating with known geometry
248
+ (`tools/metrology/make_synthetic_scan.py`), which also allows a test to check that the
249
+ pipeline recovers the period and blaze angle used to generate it.
250
+
251
+ ## GUI
252
+
253
+ ```bash
254
+ .venv/bin/python -m pip install -e ".[gui,metrology]"
255
+ gratinglab-gui # geometry, solvers, comparison
256
+ gratinglab-metrology-gui # AFM import, blaze angles, boundary profiles
257
+ ```
258
+
259
+ The windows are built with Qt (PySide6), so `pip install "gratinglab[gui]"` needs no
260
+ system toolkit to be installed first. PySide6 is licensed under the LGPLv3 and is an
261
+ optional, dynamically linked extra, so this package remains under the BSD-3 licence.
262
+
263
+ The grating geometry is set in a dock on the left of the efficiency window (Ctrl+G
264
+ hides it), and the window has one tab for each task:
265
+
266
+ - **Grating Geometry:** a rotatable 3D view of the diffraction cone, in which every wave
267
+ vector is drawn as an exact unit vector. In the reference off-plane geometry the
268
+ orders separate in azimuth rather than in polar angle (93.8° of azimuthal spread at
269
+ 0.0° of polar spread), so the view looking down the cone axis, combined with a uniform
270
+ 27× zoom, makes them distinguishable at true scale. The grazing angle γ is not
271
+ exaggerated: a uniform zoom preserves every angle in the view, and the magnification
272
+ is stated on the canvas. Alongside it are the cross-section in the dispersion plane, a
273
+ `sin β` diagram that includes the evanescent orders, and the groove profile. The view
274
+ updates as values are typed.
275
+ - **Scalar (Kirchhoff):** the scalar solution, the efficiency of each order with
276
+ per-order visibility, and a provenance panel that lists every validity warning, the
277
+ summed-efficiency check, and whether convergence was demonstrated. A result that
278
+ violates energy conservation is flagged. "Check convergence…" runs a convergence
279
+ sweep and reports the coarsest setting that meets the criterion, with a progress bar
280
+ and a Cancel button. Because solvers report progress for each wavelength, Cancel
281
+ stops the computation itself.
282
+ - **Integral method:** the rigorous backend, with the same panel and provenance and its
283
+ own boundary-condition control (perfect conductivity or tabulated optical
284
+ constants). It takes minutes where the scalar solver takes milliseconds, so the
285
+ window opens on the scalar solver and this tab computes only when asked to.
286
+ - **Setup:** the material library, showing which optical constants are installed, the
287
+ range each covers, where each came from, and how to add others. The coating itself is
288
+ chosen in the geometry dock, because it is a `Problem` field, like the period, and the
289
+ same `Problem` is used by every tab.
290
+
291
+ Each registered solver receives a tab generated from the solver registry, so a
292
+ contributed backend appears in the window once it is registered.
293
+
294
+ The Help menu contains the theory pages, rendered with typeset equations and tables.
295
+
296
+ A macOS `.app` can be built with `python tools/make_app.py` (macOS 13 or later, the
297
+ minimum supported by the PySide6 wheels), and the icon is regenerated with
298
+ `python tools/make_icon.py`.
299
+
300
+ ## Documentation
301
+
302
+ | Document | Contents |
303
+ |---|---|
304
+ | [`docs/roadmap.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/roadmap.md) | The current state of the project, planned work and open questions; the place to start |
305
+ | [`docs/conventions.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/conventions.md) | The normative definitions: signs, units, normalizations, and errata in the references |
306
+ | [`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 |
307
+ | [`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 |
308
+ | [`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 |
309
+ | [`docs/findings.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/findings.md) | Empirical results, each with the evidence for it |
310
+ | [`docs/references.md`](https://github.com/jamccoy/gratinglab/blob/master/docs/references.md) | The literature each part of the package is based on |
311
+ | [`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 |
312
+ | [`benchmarks/corpus.toml`](https://github.com/jamccoy/gratinglab/blob/master/benchmarks/corpus.toml) | Geometry for the reference corpus, most of it recovered from the data |
313
+
314
+ `conventions.md` should be read before writing a solver or reporting a bug involving a
315
+ sign. In brief: the time convention is `exp(-iωt)`, the complex refractive index is
316
+ *n* = n′ + ik, the grating equation is `sin α + sin β_m = mλ/(p sin γ)`, efficiencies
317
+ are absolute, lengths are in nm, and angles are in degrees at the API boundary.
318
+
319
+ Each solver has a theory page stating its assumptions, its derivation in the notation
320
+ of the code, and the conditions under which its results cease to be reliable.
321
+
322
+ The [wiki](https://github.com/jamccoy/gratinglab/wiki) is a published copy of `docs/`
323
+ and of the metrology window's own help pages, which appear there with an "AFM" prefix.
324
+ Changes are made to the sources in the repository and then republished:
325
+
326
+ ```bash
327
+ git clone https://github.com/jamccoy/gratinglab.wiki.git ../gratinglab.wiki
328
+ .venv/bin/python tools/publish_wiki.py --wiki ../gratinglab.wiki # then review and push
329
+ ```
330
+
331
+ ## Development
332
+
333
+ ```bash
334
+ .venv/bin/python -m pip install -e ".[dev,gui,metrology,refractiveindex]"
335
+ .venv/bin/python -m pytest
336
+ .venv/bin/ruff check .
337
+ ```
338
+
339
+ The test suite relies on three practices in particular:
340
+
341
+ - **Physical self-checks.** `checks.check_reciprocity` and `checks.check_energy_balance`
342
+ test properties that any correct model must satisfy, independently of how the
343
+ solver computes its result. They require no reference data and apply to every
344
+ backend.
345
+ - **Demonstrated convergence.** `convergence.check_convergence` varies whatever a
346
+ solver declares as its accuracy parameter and reports the coarsest setting that
347
+ meets the criterion, or reports that none did. It requires three consecutive values
348
+ to agree, because the quadrature error of a blazed profile does not decrease
349
+ monotonically, so a single agreement can be coincidental
350
+ ([findings](https://github.com/jamccoy/gratinglab/blob/master/docs/findings.md#quadrature-error-is-not-monotone-in-the-number-of-points)).
351
+
352
+ ```python
353
+ report = check_convergence(scalar, problem, illumination, wavelengths)
354
+ report.converged_at # 4096 -- what a production run should use
355
+ report.scan.provenance.converged # True
356
+ ```
357
+ - **Mutation testing.** The suite is run against deliberately altered copies of the
358
+ physics code to check that the changes are detected. Coverage shows that a line was
359
+ executed; mutation testing shows whether any test would fail if that line were wrong.
360
+ Both runs so far have found real gaps in the tests; see
361
+ [findings](https://github.com/jamccoy/gratinglab/blob/master/docs/findings.md#mutation-testing-found-a-real-gap)
362
+ and [docs/mutation-testing.md](https://github.com/jamccoy/gratinglab/blob/master/docs/mutation-testing.md)
363
+ for how to run it.
364
+
365
+ ```bash
366
+ .venv/bin/python -m pip install -e ".[mut]"
367
+ .venv/bin/mutmut run
368
+ ```
369
+
370
+ ## License
371
+
372
+ BSD-3-Clause.