easyreflectometry 1.8.0__tar.gz → 2.0.0__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 (349) hide show
  1. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/copilot-instructions.md +0 -1
  2. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/pypi-test.yml +8 -0
  3. easyreflectometry-2.0.0/.planning/codebase/ARCHITECTURE.md +311 -0
  4. easyreflectometry-2.0.0/.planning/codebase/CONCERNS.md +280 -0
  5. easyreflectometry-2.0.0/.planning/codebase/INTEGRATIONS.md +203 -0
  6. easyreflectometry-2.0.0/.planning/codebase/STACK.md +142 -0
  7. easyreflectometry-2.0.0/.planning/codebase/STRUCTURE.md +245 -0
  8. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/CHANGELOG.md +393 -0
  9. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/PKG-INFO +38 -9
  10. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/README.md +33 -5
  11. easyreflectometry-2.0.0/docs/docs/api-reference/contrasts.md +75 -0
  12. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/index.md +9 -0
  13. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/inequality_constraints.md +2 -3
  14. easyreflectometry-2.0.0/docs/docs/api-reference/volume_fraction.md +1 -0
  15. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/installation-and-setup/index.md +2 -2
  16. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/advancedfitting/bayesian_bumps.ipynb +4 -4
  17. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/advancedfitting/constraints.ipynb +4 -4
  18. easyreflectometry-2.0.0/docs/docs/tutorials/advancedfitting/contrasts.ipynb +579 -0
  19. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/advancedfitting/multi_contrast.ipynb +44 -27
  20. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/advancedfitting/polarized_fitting.ipynb +91 -13
  21. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/basic/model.md +6 -8
  22. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/fitting/monolayer.ipynb +1 -1
  23. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/fitting/repeating.ipynb +1 -1
  24. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/index.md +5 -0
  25. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/simulation/bilayer.ipynb +78 -6
  26. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/simulation/magnetism.ipynb +113 -24
  27. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/user-guide/index.md +75 -2
  28. easyreflectometry-2.0.0/docs/docs/user-guide/magnetism.md +213 -0
  29. easyreflectometry-2.0.0/docs/docs/user-guide/resolution.md +154 -0
  30. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/mkdocs.yml +5 -0
  31. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/notebooks/ess_hercules_2026.ipynb +3 -3
  32. easyreflectometry-2.0.0/notebooks/polarized_demo_data/fe_on_si_dd.dat +161 -0
  33. easyreflectometry-2.0.0/notebooks/polarized_demo_data/fe_on_si_du.dat +161 -0
  34. easyreflectometry-2.0.0/notebooks/polarized_demo_data/fe_on_si_ud.dat +161 -0
  35. easyreflectometry-2.0.0/notebooks/polarized_demo_data/fe_on_si_uu.dat +161 -0
  36. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/notebooks/polarized_fitting.ipynb +2 -2
  37. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/notebooks/zero_variance_fitting.ipynb +7 -7
  38. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/pixi.lock +7732 -8538
  39. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/pyproject.toml +5 -3
  40. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/__init__.py +2 -0
  41. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/_bumps_constraints.py +8 -10
  42. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/analysis/bayesian.py +65 -18
  43. easyreflectometry-2.0.0/src/easyreflectometry/calculators/calculator_base.py +244 -0
  44. easyreflectometry-2.0.0/src/easyreflectometry/calculators/factory.py +168 -0
  45. easyreflectometry-2.0.0/src/easyreflectometry/calculators/refl1d/calculator.py +17 -0
  46. easyreflectometry-2.0.0/src/easyreflectometry/calculators/refl1d/wrapper.py +289 -0
  47. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/calculators/refnx/calculator.py +0 -19
  48. easyreflectometry-2.0.0/src/easyreflectometry/calculators/refnx/wrapper.py +77 -0
  49. easyreflectometry-2.0.0/src/easyreflectometry/calculators/stateless_wrapper_base.py +276 -0
  50. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/constraints.py +2 -2
  51. easyreflectometry-2.0.0/src/easyreflectometry/contrasts.py +450 -0
  52. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/data/__init__.py +4 -0
  53. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/data/data_store.py +18 -3
  54. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/data/measurement.py +142 -0
  55. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/data/polarized.py +2 -0
  56. easyreflectometry-2.0.0/src/easyreflectometry/fit_settings.py +399 -0
  57. easyreflectometry-2.0.0/src/easyreflectometry/fitting.py +1434 -0
  58. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/inequality_constraints.py +5 -5
  59. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/model/model.py +69 -22
  60. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/model/model_collection.py +5 -2
  61. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/model/resolution_functions.py +14 -4
  62. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/orso_utils.py +172 -43
  63. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/plot.py +63 -0
  64. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/project.py +1207 -296
  65. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/__init__.py +8 -0
  66. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/assemblies/bilayer.py +22 -39
  67. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/assemblies/multilayer.py +1 -5
  68. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/assemblies/surfactant_layer.py +9 -26
  69. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/base_core.py +40 -33
  70. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/collections/base_collection.py +60 -36
  71. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/collections/layer_collection.py +1 -2
  72. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/collections/material_collection.py +2 -3
  73. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/collections/sample.py +36 -5
  74. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/elements/layers/layer.py +110 -19
  75. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/elements/layers/layer_area_per_molecule.py +1 -1
  76. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/elements/materials/material_density.py +122 -47
  77. easyreflectometry-2.0.0/src/easyreflectometry/sample/elements/materials/material_magnetic.py +188 -0
  78. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/elements/materials/material_mixture.py +2 -20
  79. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/elements/materials/material_solvated.py +1 -1
  80. easyreflectometry-2.0.0/src/easyreflectometry/sample/paths.py +75 -0
  81. easyreflectometry-2.0.0/src/easyreflectometry/sample/references.py +224 -0
  82. easyreflectometry-2.0.0/src/easyreflectometry/sample/volume_fraction.py +468 -0
  83. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/special/calculations.py +50 -0
  84. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/summary/html_templates.py +8 -4
  85. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/summary/summary.py +273 -132
  86. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/utils.py +3 -3
  87. easyreflectometry-2.0.0/tests/_static/PROVENANCE.md +43 -0
  88. easyreflectometry-2.0.0/tests/_static/e361r.txt +99 -0
  89. easyreflectometry-2.0.0/tests/_static/validation_test4.dat +104 -0
  90. easyreflectometry-2.0.0/tests/calculators/baselines/refl1d.npz +0 -0
  91. easyreflectometry-2.0.0/tests/calculators/baselines/refnx.npz +0 -0
  92. easyreflectometry-2.0.0/tests/calculators/refl1d/test_refl1d_calculator.py +125 -0
  93. easyreflectometry-2.0.0/tests/calculators/refl1d/test_refl1d_wrapper.py +456 -0
  94. easyreflectometry-2.0.0/tests/calculators/refnx/test_refnx_calculator.py +98 -0
  95. easyreflectometry-2.0.0/tests/calculators/refnx/test_refnx_wrapper.py +280 -0
  96. easyreflectometry-2.0.0/tests/calculators/test_calculator_baselines.py +302 -0
  97. easyreflectometry-2.0.0/tests/calculators/test_calculator_membership.py +169 -0
  98. easyreflectometry-2.0.0/tests/calculators/test_magnetism_reconciliation.py +223 -0
  99. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/calculators/test_polarization_interface.py +3 -4
  100. easyreflectometry-2.0.0/tests/calculators/test_recycled_names.py +109 -0
  101. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/calculators/test_resolution_conventions.py +16 -44
  102. easyreflectometry-2.0.0/tests/calculators/test_validation_suite.py +101 -0
  103. easyreflectometry-2.0.0/tests/calculators/test_volume_fraction_profile.py +88 -0
  104. easyreflectometry-2.0.0/tests/integration/convergence/reference.md +22 -0
  105. easyreflectometry-2.0.0/tests/integration/convergence/test_convergence.py +227 -0
  106. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/integration/test_cross_engine_resolution.py +20 -61
  107. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/integration/test_ort_file_fitting.py +3 -4
  108. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/model/test_model.py +45 -41
  109. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/model/test_model_collection.py +7 -7
  110. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/sample/assemblies/test_base_assembly.py +2 -2
  111. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/sample/assemblies/test_bilayer.py +32 -9
  112. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/sample/assemblies/test_gradient_layer.py +2 -2
  113. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/sample/assemblies/test_multilayer.py +21 -15
  114. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/sample/assemblies/test_repeating_multilayer.py +21 -15
  115. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/sample/assemblies/test_surfactant_layer.py +33 -5
  116. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/sample/collections/test_base_collection.py +5 -22
  117. easyreflectometry-2.0.0/tests/sample/collections/test_collection_fields.py +70 -0
  118. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/sample/collections/test_layer_collection.py +70 -1
  119. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/sample/collections/test_material_collection.py +1 -1
  120. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/sample/collections/test_sample.py +1 -1
  121. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/sample/elements/layers/test_layer.py +6 -5
  122. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/sample/elements/layers/test_layer_area_per_molecule.py +2 -2
  123. easyreflectometry-2.0.0/tests/sample/elements/layers/test_layer_magnetism.py +476 -0
  124. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/sample/elements/materials/test_material.py +1 -1
  125. easyreflectometry-2.0.0/tests/sample/elements/materials/test_material_density.py +334 -0
  126. easyreflectometry-2.0.0/tests/sample/elements/materials/test_material_magnetic.py +134 -0
  127. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/sample/elements/materials/test_material_mixture.py +10 -13
  128. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/sample/elements/materials/test_material_solvated.py +2 -2
  129. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/sample/test_base_core.py +9 -29
  130. easyreflectometry-2.0.0/tests/sample/test_volume_fraction.py +331 -0
  131. easyreflectometry-2.0.0/tests/summary/test_summary.py +535 -0
  132. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/test_bayesian.py +224 -5
  133. easyreflectometry-2.0.0/tests/test_contrasts.py +580 -0
  134. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/test_data.py +1 -2
  135. easyreflectometry-2.0.0/tests/test_fit_settings.py +520 -0
  136. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/test_fitting.py +13 -12
  137. easyreflectometry-2.0.0/tests/test_guide_field.py +320 -0
  138. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/test_limits.py +19 -19
  139. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/test_measurement_comprehensive.py +1 -2
  140. easyreflectometry-2.0.0/tests/test_model_removal.py +167 -0
  141. easyreflectometry-2.0.0/tests/test_orso_magnetism.py +272 -0
  142. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/test_orso_support.py +7 -5
  143. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/test_orso_utils.py +1 -2
  144. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/test_ort_file.py +1 -2
  145. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/test_parameter_utils.py +2 -2
  146. easyreflectometry-2.0.0/tests/test_per_dataset_resolution.py +1166 -0
  147. easyreflectometry-2.0.0/tests/test_plot.py +61 -0
  148. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/test_polarized_fitting.py +46 -24
  149. easyreflectometry-2.0.0/tests/test_prepared_fit.py +297 -0
  150. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/test_project.py +173 -94
  151. easyreflectometry-2.0.0/tests/test_public_api.py +13 -0
  152. easyreflectometry-2.0.0/tests/test_shared_persistence.py +298 -0
  153. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/test_utils.py +10 -1
  154. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/unit/test_bumps_constraints_shim.py +57 -15
  155. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/unit/test_constraints.py +86 -58
  156. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/unit/test_derived_parameters.py +6 -6
  157. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/unit/test_fitting_mcmc.py +113 -8
  158. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/unit/test_inequality_constraints.py +18 -18
  159. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/unit/test_material_and_calculations.py +6 -6
  160. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/unit/test_project_core.py +9 -12
  161. easyreflectometry-2.0.0/tests/unit/test_summary_goodness_of_fit.py +92 -0
  162. easyreflectometry-2.0.0/tools/benchmark_calculators.py +224 -0
  163. easyreflectometry-1.8.0/.coveragerc +0 -3
  164. easyreflectometry-1.8.0/CONTRIBUTING.rst +0 -114
  165. easyreflectometry-1.8.0/Makefile +0 -79
  166. easyreflectometry-1.8.0/notebooks/polarized_demo_data/fe_on_si_dd.dat +0 -161
  167. easyreflectometry-1.8.0/notebooks/polarized_demo_data/fe_on_si_du.dat +0 -161
  168. easyreflectometry-1.8.0/notebooks/polarized_demo_data/fe_on_si_ud.dat +0 -161
  169. easyreflectometry-1.8.0/notebooks/polarized_demo_data/fe_on_si_uu.dat +0 -161
  170. easyreflectometry-1.8.0/src/easyreflectometry/calculators/calculator_base.py +0 -332
  171. easyreflectometry-1.8.0/src/easyreflectometry/calculators/factory.py +0 -84
  172. easyreflectometry-1.8.0/src/easyreflectometry/calculators/refl1d/calculator.py +0 -38
  173. easyreflectometry-1.8.0/src/easyreflectometry/calculators/refl1d/wrapper.py +0 -609
  174. easyreflectometry-1.8.0/src/easyreflectometry/calculators/refnx/wrapper.py +0 -224
  175. easyreflectometry-1.8.0/src/easyreflectometry/calculators/wrapper_base.py +0 -442
  176. easyreflectometry-1.8.0/src/easyreflectometry/fitting.py +0 -948
  177. easyreflectometry-1.8.0/tests/calculators/refl1d/test_refl1d_calculator.py +0 -219
  178. easyreflectometry-1.8.0/tests/calculators/refl1d/test_refl1d_wrapper.py +0 -680
  179. easyreflectometry-1.8.0/tests/calculators/refnx/test_refnx_calculator.py +0 -133
  180. easyreflectometry-1.8.0/tests/calculators/refnx/test_refnx_wrapper.py +0 -577
  181. easyreflectometry-1.8.0/tests/sample/elements/layers/test_layer_magnetism.py +0 -227
  182. easyreflectometry-1.8.0/tests/sample/elements/materials/test_material_density.py +0 -181
  183. easyreflectometry-1.8.0/tests/summary/test_summary.py +0 -351
  184. easyreflectometry-1.8.0/tests/unit/test_summary_goodness_of_fit.py +0 -86
  185. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.badgery.yaml +0 -0
  186. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.codecov.yml +0 -0
  187. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.copier-answers.yml +0 -0
  188. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.gitattributes +0 -0
  189. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/actions/download-artifact/action.yml +0 -0
  190. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/actions/github-script/action.yml +0 -0
  191. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/actions/setup-easyscience-bot/action.yml +0 -0
  192. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/actions/setup-pixi/action.yml +0 -0
  193. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/actions/upload-artifact/action.yml +0 -0
  194. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/actions/upload-codecov/action.yml +0 -0
  195. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/configs/pages-deployment.json +0 -0
  196. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/configs/rulesets-develop.json +0 -0
  197. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/configs/rulesets-gh-pages.json +0 -0
  198. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/configs/rulesets-master.json +0 -0
  199. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/release-drafter.yml +0 -0
  200. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/scripts/backmerge-conflict-issue.js +0 -0
  201. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/scripts/publish-dashboard.sh +0 -0
  202. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/backmerge.yml +0 -0
  203. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/cleanup.yml +0 -0
  204. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/codeql-analysis.yml +0 -0
  205. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/coverage.yml +0 -0
  206. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/dashboard.yml +0 -0
  207. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/docs.yml +0 -0
  208. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/issues-labels.yml +0 -0
  209. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/lint-format.yml +0 -0
  210. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/ossar-analysis.yml +0 -0
  211. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/pr-labels.yml +0 -0
  212. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/pypi-publish.yml +0 -0
  213. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/python-ci.yml +0 -0
  214. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/python-package.yml +0 -0
  215. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/python-publish.yml +0 -0
  216. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/release-drafter-verify-pr-labels.yml +0 -0
  217. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/release-drafter.yml +0 -0
  218. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/release-notes.yml +0 -0
  219. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/release-pr.yml +0 -0
  220. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/security.yml +0 -0
  221. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/test-trigger.yml +0 -0
  222. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/test.yml +0 -0
  223. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/tutorial-tests-trigger.yml +0 -0
  224. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.github/workflows/tutorial-tests.yml +0 -0
  225. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.gitignore +0 -0
  226. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.pre-commit-config.yaml +0 -0
  227. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/.prettierignore +0 -0
  228. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/AUTHORS.rst +0 -0
  229. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/CONTRIBUTING.md +0 -0
  230. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/LICENSE +0 -0
  231. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/codecov.yml +0 -0
  232. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/assemblies/gradient_layer.md +0 -0
  233. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/assemblies/multilayer.md +0 -0
  234. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/assemblies/repeating_multilayer.md +0 -0
  235. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/assemblies/surfactant_layer.md +0 -0
  236. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/bayesian.md +0 -0
  237. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/calculators.md +0 -0
  238. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/constraints.md +0 -0
  239. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/data.md +0 -0
  240. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/elements/layer.md +0 -0
  241. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/elements/layer_area_per_molecule.md +0 -0
  242. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/elements/layer_magnetism.md +0 -0
  243. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/elements/material.md +0 -0
  244. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/elements/material_density.md +0 -0
  245. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/elements/material_mixture.md +0 -0
  246. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/elements/material_solvated.md +0 -0
  247. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/fitting.md +0 -0
  248. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/limits.md +0 -0
  249. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/model.md +0 -0
  250. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/orso.md +0 -0
  251. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/plot.md +0 -0
  252. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/project.md +0 -0
  253. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/sample.md +0 -0
  254. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/api-reference/summary.md +0 -0
  255. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/assets/images/favicon.png +0 -0
  256. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/assets/images/logo_dark.svg +0 -0
  257. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/assets/images/logo_light.svg +0 -0
  258. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/assets/javascripts/extra.js +0 -0
  259. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/assets/javascripts/mathjax.js +0 -0
  260. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/assets/stylesheets/extra.css +0 -0
  261. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/index.md +0 -0
  262. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/introduction/index.md +0 -0
  263. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/advancedfitting/example.ort +0 -0
  264. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/advancedfitting/multiple.ort +0 -0
  265. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/basic/assemblies_library.md +0 -0
  266. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/basic/layer_library.md +0 -0
  267. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/basic/material_library.md +0 -0
  268. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/fitting/d70d2o.ort +0 -0
  269. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/fitting/dspc.png +0 -0
  270. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/fitting/eq_monolayer.svg +0 -0
  271. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/fitting/eq_solvated.svg +0 -0
  272. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/fitting/example.ort +0 -0
  273. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/fitting/material_solvated.ipynb +0 -0
  274. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/fitting/monolayer.png +0 -0
  275. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/fitting/monolayer.svg +0 -0
  276. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/fitting/polymer_film.png +0 -0
  277. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/fitting/polymer_film.svg +0 -0
  278. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/fitting/repeating.png +0 -0
  279. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/fitting/repeating.svg +0 -0
  280. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/fitting/repeating_layers.ort +0 -0
  281. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/fitting/simple_fitting.ipynb +0 -0
  282. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/simulation/mod_pointwise_two_layer_sample_dq-0.0.ort +0 -0
  283. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/simulation/mod_pointwise_two_layer_sample_dq-1.0.ort +0 -0
  284. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/simulation/mod_pointwise_two_layer_sample_dq-10.0.ort +0 -0
  285. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/simulation/resolution_functions.ipynb +0 -0
  286. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/simulation/two_layers.png +0 -0
  287. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/docs/tutorials/simulation/two_layers.svg +0 -0
  288. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/includes/abbreviations.md +0 -0
  289. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/overrides/.icons/app.svg +0 -0
  290. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/overrides/.icons/easyreflectometry.svg +0 -0
  291. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/overrides/.icons/easyscience.svg +0 -0
  292. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/overrides/.icons/google-colab.svg +0 -0
  293. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/overrides/main.html +0 -0
  294. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/docs/overrides/partials/logo.html +0 -0
  295. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/notebooks/reflectivity_geomgrid.ort +0 -0
  296. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/package-lock.json +0 -0
  297. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/package.json +0 -0
  298. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/pixi.toml +0 -0
  299. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/prettierrc.toml +0 -0
  300. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/analysis/__init__.py +0 -0
  301. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/calculators/__init__.py +0 -0
  302. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/calculators/polarization.py +0 -0
  303. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/limits.py +0 -0
  304. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/main.py +0 -0
  305. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/model/__init__.py +0 -0
  306. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/assemblies/__init__.py +0 -0
  307. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/assemblies/base_assembly.py +0 -0
  308. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/assemblies/gradient_layer.py +0 -0
  309. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/assemblies/repeating_multilayer.py +0 -0
  310. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/collections/__init__.py +0 -0
  311. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/elements/__init__.py +0 -0
  312. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/elements/layers/__init__.py +0 -0
  313. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/elements/layers/layer_magnetism.py +0 -0
  314. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/elements/materials/__init__.py +0 -0
  315. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/sample/elements/materials/material.py +0 -0
  316. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/special/parsing.py +0 -0
  317. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/src/easyreflectometry/summary/__init__.py +0 -0
  318. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/_static/Ni_example.ort +0 -0
  319. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/_static/amor_reduced_iofq.ort +0 -0
  320. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/_static/example.orb +0 -0
  321. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/_static/example.ort +0 -0
  322. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/_static/fwhm_sqz.ort +0 -0
  323. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/_static/nan_sqz.ort +0 -0
  324. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/_static/nm_units.ort +0 -0
  325. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/_static/partial_nan_sqz.ort +0 -0
  326. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/_static/polarized_2ch.ort +0 -0
  327. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/_static/ref_concat_1.txt +0 -0
  328. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/_static/ref_five_col.txt +0 -0
  329. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/_static/ref_zero_var.txt +0 -0
  330. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/_static/test_example1.ort +0 -0
  331. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/_static/test_example1.txt +0 -0
  332. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/_static/test_example2.ort +0 -0
  333. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/_static/test_example3.ort +0 -0
  334. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/_static/test_example4.ort +0 -0
  335. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/data/test_data_store.py +0 -0
  336. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/data/test_polarized.py +0 -0
  337. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/functional/test_dummy.py +0 -0
  338. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/integration/fitting/test_dummy.py +0 -0
  339. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/integration/scipp-analysis/test_dummy.py +0 -0
  340. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/model/test_resolution_functions.py +0 -0
  341. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/package_test.py +0 -0
  342. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/special/test_calculations.py +0 -0
  343. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/test_topmost_nesting.py +0 -0
  344. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tests/unit/test_dummy.py +0 -0
  345. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tools/license_headers.py +0 -0
  346. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tools/update_docs_assets.py +0 -0
  347. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/tools/update_github_labels.py +0 -0
  348. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/vscode-template/README.rst +0 -0
  349. {easyreflectometry-1.8.0 → easyreflectometry-2.0.0}/vscode-template/settings.json +0 -0
@@ -174,7 +174,6 @@ tox
174
174
  ```
175
175
  src/easyreflectometry/ # Main package source code
176
176
  ├── calculators/ # Calculator implementations (refnx, refl1d)
177
- │ └── bornagain/ # BornAgain calculator (not yet functional)
178
177
  ├── model/ # Reflectometry models
179
178
  ├── sample/ # Sample structures and materials
180
179
  ├── special/ # Special calculations and parsing
@@ -47,6 +47,14 @@ jobs:
47
47
  - name: Init pixi project
48
48
  run: pixi init easyreflectometry
49
49
 
50
+ # A fresh pixi project assumes macOS 13.0 on osx-arm64, which rejects
51
+ # scipp's macosx_14_0 wheels and forces a failing source build. Match
52
+ # the macOS minimum set in the repository's pixi.toml.
53
+ - name: Require macOS 14 (matches scipp wheels)
54
+ working-directory: easyreflectometry
55
+ shell: bash
56
+ run: printf '\n[system-requirements]\nmacos = "14.0"\n' >> pixi.toml
57
+
50
58
  - name: Add Python 3.13 from Conda
51
59
  working-directory: easyreflectometry
52
60
  run: pixi add "python=3.13"
@@ -0,0 +1,311 @@
1
+ # Architecture
2
+
3
+ **Analysis Date:** 2026-10-15 **Branch analysed:** `develop` @
4
+ `66adaf54` **Revised:** 2026-11-08 for the easyscience 3 port
5
+ (`core-3.0`)
6
+
7
+ ## Pattern Overview
8
+
9
+ **Overall:** Layered library with two facades. `Project` is the
10
+ stateful, GUI-oriented facade (experiments, models, persistence,
11
+ constraints, contrasts); `MultiFitter` is the fitting facade that turns
12
+ datasets into a `PreparedFit` and delegates execution to EasyScience's
13
+ `MultiFitter` / `Sampler`, which drive LMFit, BUMPS or DFO. Reflectivity
14
+ is computed by pluggable engine wrappers (refnx, refl1d) behind
15
+ `CalculatorFactory`.
16
+
17
+ **Key Characteristics:**
18
+
19
+ - Prepare / execute / record split: `Project.prepare_fit` →
20
+ `PreparedFit` (immutable snapshot of settings, arrays, bound fit
21
+ functions, configured core fitter) → `execute()` or a GUI worker →
22
+ `Project.record_fit` → `FitRun`
23
+ - Resolution follows the dataset, not the calculator: each
24
+ `DataSet1D.resolution_function` is bound into its fit function;
25
+ `CalculatorBase` keeps one resolution per model id
26
+ - Parameters are EasyScience `Parameter`s; all coupling (conformal
27
+ thickness, contrasts, user constraints, sum constraints) is expressed
28
+ as parameter dependencies and persisted by structural path
29
+ - Inequality constraints are enforced by patching the BUMPS driver in
30
+ the core's namespace, because the core offers no hook
31
+ - Shared objects (one material in two layers, one assembly in two
32
+ models) serialize once through `sample/references.py`
33
+ (`file_format` 4)
34
+
35
+ ## Layers
36
+
37
+ **Project Facade:**
38
+
39
+ - Purpose: Hold the session state an application needs and expose
40
+ index-based accessors for plots and tables
41
+ - Location: `src/easyreflectometry/project.py` (`Project`,
42
+ `ModelRemoval`, spin-asymmetry helpers)
43
+ - Contains: experiments `Dict[int, DataSet1D | PolarizedDataSet]`,
44
+ `ModelCollection`, `MaterialCollection` palette, `FitSettings`,
45
+ inequality specs, link records, `last_fit`, load/save, ORSO import,
46
+ contrasts, polarized loading, `spin_asymmetry_for_experiment_at_index`
47
+ - Depends on: every other package in the library
48
+ - Used by: EasyReflectometry GUI (ERA app), tutorials, `Summary`
49
+
50
+ **Fitting Layer:**
51
+
52
+ - Purpose: Zero-variance handling, per-dataset resolution binding,
53
+ settings snapshot, constraint enforcement, goodness-of-fit metrics
54
+ - Location: `src/easyreflectometry/fitting.py`, `fit_settings.py`,
55
+ `_bumps_constraints.py`, `inequality_constraints.py`
56
+ - Contains: `MultiFitter` (`fit`, `fit_single_data_set_1d`,
57
+ `fit_polarized`, `for_experiments`, `prepare`, `mcmc_sample`,
58
+ `switch_minimizer`, chi-square properties), `PreparedFit`, `FitRun`,
59
+ `_ConstrainedEasyScienceMultiFitter`, `FitSettings`,
60
+ `MinimizerOption`, `InequalitySpec`, `build_constraints_factory`
61
+ - Depends on: `easyscience.fitting` (`MultiFitter`, `Sampler`,
62
+ `FitResults`), `easyscience.Minimizers`, `asteval`, `scipp`
63
+
64
+ **Bayesian Analysis Layer:**
65
+
66
+ - Purpose: Post-hoc posterior analysis and chain persistence
67
+ - Location: `src/easyreflectometry/analysis/bayesian.py`
68
+ - Contains: `PosteriorResults` (summary, corner, trace, distribution,
69
+ credible*interval, gelman_rubin, save), `posterior_predictive*\*`,
70
+ `save_posterior`/`load_posterior` with fingerprinted sidecar
71
+ - Depends on: plotly (required), arviz (optional), numpy
72
+
73
+ **Model / Sample Layer:**
74
+
75
+ - Purpose: Physical description of the sample and measurement
76
+ - Location: `src/easyreflectometry/model/`,
77
+ `src/easyreflectometry/sample/`
78
+ - Contains: `Model` (scale, background, resolution, `total_thickness`
79
+ derived parameter), `Sample` and assemblies/layers/materials,
80
+ `LayerMagnetism`, `MaterialDensity.sld_coupled`, `volume_fraction`,
81
+ `paths`, `references`
82
+ - Depends on: `easyscience.base_classes`, `easyscience.variable`,
83
+ periodictable (via `special/`)
84
+
85
+ **Constraints and Contrasts Layer:**
86
+
87
+ - Purpose: User-level coupling between parameters and between models
88
+ - Location: `src/easyreflectometry/constraints.py`, `contrasts.py`
89
+ - Contains: `constrain*`, `derived_parameter`, `clamp_sum_partners`;
90
+ `derive_contrast`, `ReplaceMaterial`, `ReplaceFormula`, `plan_link` /
91
+ `LinkPlan` / `LinkRecord`, `tie_corresponding`, `snapshot` / `restore`
92
+ - Depends on: EasyScience dependency mechanism; `USER_CONSTRAINT_FLAG`
93
+ marks constraints the project should persist
94
+
95
+ **Data Layer:**
96
+
97
+ - Purpose: Load, merge and describe measured data
98
+ - Location: `src/easyreflectometry/data/`, `orso_utils.py`
99
+ - Contains: `DataSet1D`, `PolarizedDataSet`, `load`, `load_as_dataset`,
100
+ `merge_datasets`, `resolution_from_dataset`, channel detection, ORSO
101
+ read/write and model-language conversion
102
+
103
+ **Calculator Layer:**
104
+
105
+ - Purpose: Pluggable reflectivity engines
106
+ - Location: `src/easyreflectometry/calculators/`
107
+ - Contains: `CalculatorBase` (thin layer over the wrapper: model
108
+ membership, resolution, magnetism flag and channel),
109
+ `StatelessWrapperBase` (one entry per served model, held weakly, with
110
+ its resolution and cache; magnetism reconciliation; abstract engine
111
+ API incl. `calculate_channel`, `calculate_polarized`,
112
+ `magnetic_sld_profile`), `CalculatorFactory` (`fit_func`,
113
+ `fit_func_for(channel, resolution_function)`), `PolarizationChannel`
114
+ - Stateless: each evaluation builds the engine structure from the model
115
+ and drops it again; no value is copied into a backend, so model edits
116
+ need no mirroring
117
+ - Implementations: `refnx/` (unpolarized), `refl1d/` (magnetism,
118
+ polarized probes with a per-model cache, oversampled resolution)
119
+
120
+ **EasyScience Core (external, `>=3.0.2,<4`):**
121
+
122
+ - `Fitter` / `MultiFitter` (reshaping of multi-dataset arrays, minimizer
123
+ dispatch), `Sampler` / `SamplingResults` (DREAM chain, `extend`,
124
+ `save`, `load_state`), minimizer wrappers, `Minimizers`
125
+
126
+ ## Data Flow
127
+
128
+ **Classical Fit (project path):**
129
+
130
+ 1. GUI/user calls
131
+ `Project.prepare_fit(experiments=None, objective=None, skip_invalid=False)`
132
+ 2. `_fit_scope` selects experiments with `include_in_fit` and a model;
133
+ `_fitter_for` builds `MultiFitter.for_experiments(...)` with a deep
134
+ copy of `fit_settings` and a weak `constraints_factory_provider`
135
+ 3. `MultiFitter.prepare` applies the objective (`hybrid` default:
136
+ Mighell for zero-variance points, WLS otherwise), binds each
137
+ dataset's fit function to its own resolution
138
+ (`_resolution_for_fitted_points` for `legacy_mask`), collects
139
+ `added_roots` (free parameters of left-out models that fitted
140
+ parameters follow), builds the core fitter via `_core_fitter_for` +
141
+ `FitSettings.configure`, checks preconditions (finite bounds for
142
+ engines that need them, warns on starts at bounds)
143
+ 4. Caller runs `prepared.execute(**kwargs)` or `core_fitter.fit(...)` in
144
+ a thread; the `_ConstrainedEasyScienceMultiFitter.fit` property wraps
145
+ the call in `_bumps_constraints.applied(factory)`
146
+ 5. `Project.record_fit(prepared, results, status)` computes classical
147
+ metrics from the measured arrays (`prepared.finalize`), stores a
148
+ `FitRun` on `last_fit` and pushes results to `project.fitter`
149
+
150
+ **Classical Fit (library path):**
151
+
152
+ 1. `MultiFitter(model, ...)` and `fitter.fit(data: sc.DataGroup)`
153
+ 2. `_datagroup_inputs` pairs curve `Qz_<n>` with model/dataset `n` in
154
+ numeric order
155
+ 3. `prepare` → `execute` → results written back into the `DataGroup`
156
+ (model curves, SLD profiles, pooled chi-square)
157
+
158
+ **Polarized Fit:**
159
+
160
+ - `fit_polarized(polarized_dataset)` or `for_experiments([...])`: one
161
+ fit function per spin channel, all against one model, each bound to
162
+ its channel via `CalculatorFactory.fit_func_for(channel, resolution)`.
163
+ Structural parameters shared, magnetic parameters constrained by all
164
+ channels.
165
+
166
+ **Bayesian MCMC:**
167
+
168
+ 1. `Project.prepare_sample()` → `for_experiments` fitter with settings
169
+ snapshot; or `fitter.mcmc_sample(data)` directly
170
+ 2. `mcmc_sample` requires a BUMPS minimizer
171
+ (`minimizer.package == 'bumps'`), refuses all-zero variances unless
172
+ `objective='mighell'`, calls `prepare(action='sampling')`
173
+ 3. Builds `Sampler(core_fitter, x, y, weights)` inside the constraints
174
+ context, patches `sampler.extend` to re-enter it, keeps the sampler
175
+ on `fitter.sampler`, runs
176
+ `sampler.sample(samples, burn, thin, population, sampler_kwargs, progress_callback, abort_test)`
177
+ 4. Returns `{'draws', 'param_names', 'state', 'logp'}`; user wraps in
178
+ `PosteriorResults` or calls `save_posterior`
179
+
180
+ **Persistence:**
181
+
182
+ - `Project.as_dict` writes models, materials in order, experiments by
183
+ model index with per-dataset resolution, fit settings, inequality
184
+ specs, user-constraint records by path, contrast provenance, links.
185
+ `references.writing()` replaces repeated objects with `@ref`. Always
186
+ `file_format` 4.
187
+ - `from_dict` refuses anything but format 4 first (files from 1.x carry
188
+ 2, 3 or none), then rebuilds shared objects (`references.reading`),
189
+ restores constraints by path, reports problems in `load_report`.
190
+
191
+ **State Management:**
192
+
193
+ - `Project._sync_parameter_states` disables physically meaningless
194
+ parameters (superphase thickness/roughness, subphase thickness) and
195
+ applies default limits to enabled parameters created from project
196
+ defaults
197
+ - `PreparedFit` owns copies of the measured arrays; model parameters and
198
+ constraints stay live because they are what the fit changes
199
+ - Parameter values saved/restored around posterior predictive evaluation
200
+ (`_save_parameter_state` / `_restore_parameter_state`)
201
+
202
+ ## Key Abstractions
203
+
204
+ **`PreparedFit` (fitting.py):** Snapshot pattern. Everything a run
205
+ needs, fixed at prepare time; owns copies of measured arrays, the
206
+ configured core fitter and both full-resolution (`curve_funcs`) and
207
+ fitted-point (`fit_funcs`) evaluators.
208
+
209
+ **`FitRun` (fitting.py):** Frozen record of a run (status, minimizer,
210
+ objective, inputs by experiment key, pooled and per-dataset statistics).
211
+ Outlives model changes; `remapped()` follows experiment re-keying.
212
+
213
+ **`FitSettings` (fit_settings.py):** Serializable configuration
214
+ (minimizer, mode, objective, tolerance, budget, per-minimizer options
215
+ validated by `MinimizerOption` schemas). `configure(fitter)` and
216
+ `engine_kwargs()` are the two consumers.
217
+
218
+ **`_ConstrainedEasyScienceMultiFitter` / `_bumps_constraints`:**
219
+ Property override plus monkeypatch. The subclass overrides the core's
220
+ read-only `fit` property so the raw `easy_science_multi_fitter.fit(...)`
221
+ path resolves the owner's constraints provider in the calling thread and
222
+ consumes an explicit `constraints_factory` keyword (the core would
223
+ swallow it via `**kwargs`). `install()`, run at import of
224
+ `_bumps_constraints`, wraps `minimizer_bumps.FitDriver` so each
225
+ `FitProblem` gets `constraints = factory(pars)` before the driver runs.
226
+ Non-BUMPS engines raise `NON_BUMPS_ERROR`.
227
+
228
+ **`InequalitySpec` (inequality_constraints.py):** Declarative
229
+ `lhs op rhs` over parameter paths, evaluated with asteval in a
230
+ restricted symbol table; `build_constraints_factory` returns BUMPS
231
+ penalty callables.
232
+
233
+ **Contrasts (`contrasts.py`):** A contrast is an ordinary `Model`
234
+ sharing structure with a reference by identity or by `constrain_equal`.
235
+ `derive_contrast` copies touched assemblies, shares the rest and ties
236
+ parameters; `plan_link` / `apply_link` / `unlink` manage equalities
237
+ between two models of one layout with full state snapshots.
238
+
239
+ **`CalculatorBase` / `StatelessWrapperBase` / `CalculatorFactory`:**
240
+ Template + Adapter + Factory. Explicit model membership
241
+ (`register_model` / `discard_model` / `reset_storage`), per-model
242
+ resolution, a magnetism flag reconciled with the served models,
243
+ channel-bound fit functions.
244
+
245
+ **`references` (sample/references.py):** Context-managed shared-object
246
+ serialization (`writing()`, `reading(report)`, `suspended()`), with
247
+ `prune`, `rebuild` and `register` for load-time reconciliation.
248
+
249
+ **`paths.walk` (sample/paths.py):** Structural addressing of every
250
+ parameter; the stable identity used by constraints and persistence
251
+ instead of EasyScience unique names.
252
+
253
+ ## Entry Points
254
+
255
+ - `Project.prepare_fit` / `prepare_sample` / `record_fit` - GUI fit and
256
+ sampling
257
+ - `Project.load_experiment_from_files`, `load_polarized_experiment*`,
258
+ `load_orso_file`, `save_as_json`, `load_from_json`
259
+ - `Project.add_contrast`, `plan_link`, `apply_link`, `detach`
260
+ - `MultiFitter.fit`, `fit_polarized`, `mcmc_sample`
261
+ - `Summary(project).save_html_summary` / `save_pdf_summary`
262
+ - `python -m easyreflectometry.main` prints available calculators (no
263
+ console script registered)
264
+
265
+ ## Error Handling
266
+
267
+ **Strategy:** Fail loudly at preparation time; warn for recoverable data
268
+ issues; refuse silent degradation.
269
+
270
+ **Patterns:**
271
+
272
+ - `FitScopeError` (nothing to fit / experiment without model),
273
+ `FitPreconditionError` (unusable bounds for the engine), `ValueError`
274
+ for invalid settings or empty datasets after masking
275
+ - `RuntimeError` when sampling without a BUMPS minimizer
276
+ - `ValueError(NON_BUMPS_ERROR)` when inequality constraints meet a
277
+ non-BUMPS engine
278
+ - `UnitError` (subclass of `ValueError`) for inequality unit mismatches
279
+ - `FileExistsError` / `FileNotFoundError` / `OSError` / `ValueError`
280
+ from project persistence (no printing)
281
+ - `warnings.warn` for zero-variance handling, ORSO column problems,
282
+ negative volume fractions, unreadable dependencies on load; load
283
+ issues also collected in `Project.load_report`
284
+ - `try/finally` restores parameter state around posterior predictive;
285
+ `Project.save_as_json` writes atomically
286
+
287
+ ## Cross-Cutting Concerns
288
+
289
+ **Logging:** `logging.getLogger(__name__)` in a handful of modules; no
290
+ handler configured. `warnings` is the main user-facing channel.
291
+
292
+ **Validation:** `FitSettings.validate`, `MinimizerOption.validate`,
293
+ `InequalitySpec.validate_syntax` + `check_units`, ORSO column
294
+ validation, `_check_fit_preconditions`.
295
+
296
+ **Threading:** The constraints factory lives in a `contextvars`
297
+ variable, resolved in the calling thread; `PreparedFit` owns array
298
+ copies so a worker thread is isolated from later edits. Resolution for
299
+ one-off curves is bound to the evaluator rather than swapped on the
300
+ model to avoid racing a running fit.
301
+
302
+ **Serialization:** EasyScience serializer plus library extensions:
303
+ `references` for shared objects, structural paths for constraints, per
304
+ dataset resolution entries, a `file_format` check. Posterior chains have
305
+ their own sidecar format.
306
+
307
+ **Authentication:** Not applicable.
308
+
309
+ ---
310
+
311
+ _Architecture analysis: 2026-10-15 (develop @ 66adaf54)_
@@ -0,0 +1,280 @@
1
+ # Codebase Concerns
2
+
3
+ **Analysis Date:** 2026-10-15 **Branch analysed:** `develop` @
4
+ `66adaf54` **Revised:** 2026-11-08 for the easyscience 3 port
5
+ (`core-3.0`)
6
+
7
+ ## Summary
8
+
9
+ `develop` is 51 commits past `v1.7.0` and carries the unreleased 1.9
10
+ work (contrasts, shared-object persistence, per-dataset resolution,
11
+ prepare/record fit split, volume fractions, inequality constraints on
12
+ BUMPS). The earlier concerns about the `bayesian_mp` branch
13
+ (multiprocessing, chains/seed, git-branch core pin) are gone: the core
14
+ dependency is a PyPI range, `n_workers`/`chains`/`seed` and
15
+ `CalculatorFactory.__reduce__` no longer exist in the library, and MCMC
16
+ goes through the core `Sampler`. The concerns below are what remains.
17
+
18
+ ---
19
+
20
+ ## 1. Coupling to EasyScience internals
21
+
22
+ ### Monkeypatch of `minimizer_bumps.FitDriver`
23
+
24
+ **Issue:** Inequality constraints work only because
25
+ `_bumps_constraints.install()` rebinds the module-level `FitDriver` name
26
+ inside the core's `minimizer_bumps` namespace and inspects
27
+ `problem.models[0].pars`. Any core refactor (importing `FitDriver`
28
+ differently, building the problem elsewhere, renaming `.pars`) silently
29
+ disables every inequality constraint; the shim passes untouched problems
30
+ through without complaint.
31
+
32
+ **Files:** `src/easyreflectometry/_bumps_constraints.py`,
33
+ `src/easyreflectometry/fitting.py` (`_constraints`,
34
+ `_keep_constraints_on_extend`),
35
+ `tests/unit/test_bumps_constraints_shim.py`
36
+
37
+ **Impact:** High. Physics silently dropped on a core upgrade.
38
+
39
+ **Fix approach:** Keep the shim test that asserts penalties reach the
40
+ problem on both `fit` and `mcmc_sample`; add a start-up assertion that
41
+ the patched attribute exists; pursue an upstream hook
42
+ (`constraints=`/callback) in corelib and keep the `<4` pin until it
43
+ lands. Since core 3 the shim patches both `minimizer_bumps` and
44
+ `samplers.sampler_bumps`, which imports its own `FitDriver`.
45
+
46
+ ### Core API surface (`easyscience>=3.0.2,<4`)
47
+
48
+ **Issue:** Besides the public `Parameter`, `DescriptorNumber`,
49
+ `global_object`, `Minimizers`, `ModelBase`/`EasyList`/`EasyBase`,
50
+ `SerializerBase`, `MultiFitter`, `Sampler` and `FitResults`, the library
51
+ depends on module paths a core refactor could move:
52
+ `fitting.engine_base.PARAMETER_PREFIX`, the module-level `FitDriver` in
53
+ `fitting.minimizers.minimizer_bumps` and
54
+ `fitting.samplers.sampler_bumps`, and
55
+ `variable.descriptor_base.DescriptorBase`.
56
+
57
+ **Files:** `analysis/bayesian.py`, `inequality_constraints.py`,
58
+ `_bumps_constraints.py`, `fitting.py`, `sample/base_core.py`
59
+
60
+ **Impact:** Medium. A core minor release can break them.
61
+
62
+ **Fix approach:** Keep the shim tests; check this list on every core
63
+ upgrade.
64
+
65
+ ### Private core imports
66
+
67
+ **Issue:** `PARAMETER_PREFIX` from `fitting.engine_base` and
68
+ `DescriptorBase` from `variable.descriptor_base` are not exported at a
69
+ package level.
70
+
71
+ **Files:** `analysis/bayesian.py`, `inequality_constraints.py`,
72
+ `sample/base_core.py`
73
+
74
+ **Impact:** Medium.
75
+
76
+ ---
77
+
78
+ ## 2. Size and complexity hotspots
79
+
80
+ ### `project.py` is a 3211-line god object
81
+
82
+ **Issue:** `Project` mixes session state, persistence (two file formats,
83
+ reference resolution, constraint records), fit orchestration, contrasts
84
+ and links, ORSO import, polarized loading, spin asymmetry, derived data
85
+ for plots, and GUI index bookkeeping. Most behaviours are tested through
86
+ it, so refactors are expensive.
87
+
88
+ **Files:** `src/easyreflectometry/project.py`
89
+
90
+ **Impact:** Medium-High for maintainability; the radon dashboard
91
+ publishes the complexity.
92
+
93
+ **Fix approach:** Extract cohesive modules that `Project` delegates to:
94
+ `project_io.py` (as_dict/from_dict helpers, `_needs_new_format`),
95
+ `project_experiments.py` (loading, polarized, merge),
96
+ `spin_asymmetry.py` (`_spin_asymmetry`, `_unwrapped_angle`, channel
97
+ helpers). Keep `Project` as the facade.
98
+
99
+ ### `fitting.py` (1428 lines) and `analysis/bayesian.py` (1412 lines)
100
+
101
+ **Issue:** `bayesian.py` is mostly plotly styling constants and
102
+ hand-rolled contour/density code for corner plots; `fitting.py` combines
103
+ data preparation, settings, constraints wiring and five public fit
104
+ paths.
105
+
106
+ **Impact:** Low-Medium.
107
+
108
+ **Fix approach:** Split `bayesian.py` into `posterior.py` (results,
109
+ persistence, predictive) and `posterior_plots.py`; consider moving
110
+ `PreparedFit`/`FitRun` to `fit_run.py`.
111
+
112
+ ### `Project.__init__` and `from_dict` ordering
113
+
114
+ **Issue:** Loading depends on a specific order (materials → models →
115
+ experiments → constraints → contrasts → links) and on `references`
116
+ contexts being active. Several `_from_dict_*` helpers append to a shared
117
+ `report` list. Easy to break when adding a persisted field.
118
+
119
+ **Files:** `project.py` lines ~2845-3000
120
+
121
+ **Impact:** Medium.
122
+
123
+ ---
124
+
125
+ ## 3. Persistence format risks
126
+
127
+ ### Project files from 1.x no longer load
128
+
129
+ **Issue:** By design: 2.0 writes and reads `file_format` 4 only, and
130
+ refuses files from 1.x (format 2, 3 or none) with a message; there is no
131
+ converter. 1.x refuses format 4 in turn.
132
+
133
+ **Files:** `project.py` (`FILE_FORMAT`, `from_dict`)
134
+
135
+ **Impact:** Medium. Users re-create such projects or stay on 1.9; in the
136
+ CHANGELOG.
137
+
138
+ ### Constraint records keyed by structural path
139
+
140
+ **Issue:** `_user_constraints` refuses (raises `ValueError`) when a
141
+ constrained parameter is not reachable from the models (e.g. a
142
+ constraint on a palette material not used by any model). Saving a
143
+ project then fails late, at save time.
144
+
145
+ **Files:** `project.py` (`_constraint_candidates`, `_constraint_record`)
146
+
147
+ **Impact:** Medium. Surfaced as an exception, which is the intended
148
+ behaviour, but the GUI must handle it.
149
+
150
+ ### `derived_parameter` is session-only
151
+
152
+ **Issue:** Documented, but a user who builds a derived parameter and
153
+ saves loses it silently.
154
+
155
+ **Files:** `constraints.py`
156
+
157
+ **Impact:** Low.
158
+
159
+ ---
160
+
161
+ ## 4. Testing gaps
162
+
163
+ ### Thin integration/functional layers
164
+
165
+ **Issue:** `tests/functional/test_dummy.py` asserts `2 + 2 == 4` and is
166
+ the whole functional suite; `tests/integration/fitting/test_dummy.py`
167
+ and `tests/integration/scipp-analysis/test_dummy.py` are `slow`-marked
168
+ placeholders. Real integration coverage is `integration/convergence/`,
169
+ `test_cross_engine_resolution.py`, `test_ort_file_fitting.py`.
170
+
171
+ **Impact:** Medium. CI jobs named "functional tests" pass vacuously.
172
+
173
+ **Fix approach:** Either delete the placeholders or move the end-to-end
174
+ project tests (`test_project.py` fit paths, `test_polarized_fitting.py`
175
+ slow cases) under `integration/`.
176
+
177
+ ### Sampler continuation and persistence
178
+
179
+ **Issue:** `fitter.sampler.extend()` after constraints, and
180
+ `save_posterior`/`load_posterior` fingerprint mismatch handling, have
181
+ limited coverage (`TestMCMCSample*` in `test_fitting.py`,
182
+ `test_bayesian.py`); all real DREAM runs are `slow` and only run on
183
+ PRs/develop.
184
+
185
+ **Impact:** Medium.
186
+
187
+ ### Default `pytest` skips `slow`
188
+
189
+ **Issue:** `addopts = -m "not slow"` means local runs and some CI pushes
190
+ never execute a real minimizer or sampler. Regressions in engine wiring
191
+ surface only on PR CI.
192
+
193
+ **Impact:** Medium; deliberate trade-off (see `pyproject.toml` comment).
194
+ 34 of 1410 tests are `slow`.
195
+
196
+ ### refnx-only assumptions
197
+
198
+ **Issue:** Several fits and convergence references are refnx-based;
199
+ refl1d is exercised mainly through polarized/magnetism tests and
200
+ `test_cross_engine_resolution.py`.
201
+
202
+ **Impact:** Low-Medium.
203
+
204
+ ---
205
+
206
+ ## 5. Repository hygiene
207
+
208
+ ### Duplicate CI matrices
209
+
210
+ **Issue:** `python-ci.yml` and `python-package.yml` (pip-based, run on
211
+ every push/PR) duplicate `test.yml`; `python-publish.yml` duplicates
212
+ `pypi-publish.yml`.
213
+
214
+ **Impact:** Low-Medium (CI minutes, confusing failures).
215
+
216
+ ### Untracked planning/notes directories
217
+
218
+ **Issue:** `.planning/`, `.claude/`, `MD/`, `examples/`, `integration/`
219
+ are untracked and not in `.gitignore`, so `git status` is always noisy
220
+ and an accidental `git add .` would commit them.
221
+
222
+ **Impact:** Low.
223
+
224
+ **Fix approach:** Add `.planning/`, `.claude/`, `MD/` to `.gitignore`
225
+ (or a global excludes file); decide whether `examples/` and
226
+ `integration/` belong in the repo.
227
+
228
+ ### Docstring tooling disabled
229
+
230
+ **Issue:** `pydoclint` and `format-docstring` are configured with
231
+ `exclude = '\.'` (everything), `interrogate --fail-under=0`. The
232
+ `docstring-lint-check` CI step therefore cannot fail.
233
+
234
+ **Impact:** Low.
235
+
236
+ ### `main.py` has no console entry
237
+
238
+ **Issue:** `main()` prints available calculators but `pyproject.toml`
239
+ declares no `[project.scripts]`.
240
+
241
+ **Impact:** Low; remove or register.
242
+
243
+ ---
244
+
245
+ ## 6. Numerical and physics conventions to keep in view
246
+
247
+ - **SLD unit convention:** values are stored in units of 1e-6 Å^-2 while
248
+ the declared unit is `1/angstrom^2`; EasyScience strips numeric unit
249
+ factors so this cannot be fixed without a breaking migration (issue
250
+ #377 left open on purpose).
251
+ - **Resolution convention:** `SIGMA_TO_FWHM` conversions in
252
+ `resolution_functions.py`, `orso_utils.py` and the refnx wrapper must
253
+ stay consistent; `tests/calculators/test_resolution_conventions.py`
254
+ and `tests/integration/test_cross_engine_resolution.py` pin them.
255
+ - **Weights convention:** core weights are 1/σ (issue #370); keep in
256
+ mind when touching `_prepare_fit_arrays`.
257
+ - **refl1d magnetism angle:** `DEFAULT_THETA_M = 270.0` and
258
+ `GUIDE_FIELD_ANGLE = 270.0`; thetaM=90 is parallel to the guide field
259
+ in refl1d. Past reviews got this backwards.
260
+ - **Mighell objective:** `hybrid` default substitutes Mighell for zero
261
+ variance points; sampling with `mighell` is a bias correction, not a
262
+ likelihood (warned in `mcmc_sample`).
263
+ - **Spin asymmetry heuristics:** `SPIN_ASYMMETRY_SIGNIFICANCE = 3.0`,
264
+ `SPIN_ASYMMETRY_CANCELLATION_FRACTION = 1e-3`,
265
+ `MAGNETIC_MOMENT_FLOOR_FRACTION = 0.01` in `project.py` are tuned
266
+ constants without a documented derivation.
267
+
268
+ ---
269
+
270
+ ## 7. Work in flight (not on develop)
271
+
272
+ - The easyscience 3 port is on `core-3.0`, in PRs into that branch; it
273
+ is merged into `develop` only once the app no longer installs the
274
+ library from `develop` (the app needs its own port).
275
+ - Uncommitted app-side work for per-dataset resolution (A1-A5) and the
276
+ magnetism/fitting gaps lives in the GUI repository, not here.
277
+
278
+ ---
279
+
280
+ _Concerns audit: 2026-10-15 (develop @ 66adaf54)_