pyRadMC 0.2.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 (257) hide show
  1. pyradmc-0.2.0/.github/PULL_REQUEST_TEMPLATE.md +20 -0
  2. pyradmc-0.2.0/.github/workflows/ci.yml +158 -0
  3. pyradmc-0.2.0/.github/workflows/release.yml +114 -0
  4. pyradmc-0.2.0/.github/workflows/validation.yml +55 -0
  5. pyradmc-0.2.0/.gitignore +51 -0
  6. pyradmc-0.2.0/.gitlab-ci.yml +93 -0
  7. pyradmc-0.2.0/.pre-commit-config.yaml +51 -0
  8. pyradmc-0.2.0/.readthedocs.yaml +27 -0
  9. pyradmc-0.2.0/AGENTS.md +408 -0
  10. pyradmc-0.2.0/CHANGELOG.md +573 -0
  11. pyradmc-0.2.0/CITATION.cff +27 -0
  12. pyradmc-0.2.0/CLAUDE.md +53 -0
  13. pyradmc-0.2.0/CONTRIBUTING.md +111 -0
  14. pyradmc-0.2.0/LICENSE +201 -0
  15. pyradmc-0.2.0/NOTICE +60 -0
  16. pyradmc-0.2.0/PKG-INFO +162 -0
  17. pyradmc-0.2.0/README.md +102 -0
  18. pyradmc-0.2.0/docs/api.md +136 -0
  19. pyradmc-0.2.0/docs/assets/commissioning_vsm_example.png +0 -0
  20. pyradmc-0.2.0/docs/assets/dij_demo.png +0 -0
  21. pyradmc-0.2.0/docs/assets/electron_transport_demo.png +0 -0
  22. pyradmc-0.2.0/docs/assets/pencil_kernel_demo.png +0 -0
  23. pyradmc-0.2.0/docs/assets/reference_engine_demo.png +0 -0
  24. pyradmc-0.2.0/docs/assets/warp_backend_demo.png +0 -0
  25. pyradmc-0.2.0/docs/contributing.md +1 -0
  26. pyradmc-0.2.0/docs/data.md +88 -0
  27. pyradmc-0.2.0/docs/decisions.md +185 -0
  28. pyradmc-0.2.0/docs/getting-started.md +250 -0
  29. pyradmc-0.2.0/docs/index.md +66 -0
  30. pyradmc-0.2.0/docs/validation.md +86 -0
  31. pyradmc-0.2.0/examples/collimation_demo.py +298 -0
  32. pyradmc-0.2.0/examples/commissioning_vsm_example.py +3141 -0
  33. pyradmc-0.2.0/examples/ct_demo.py +165 -0
  34. pyradmc-0.2.0/examples/custom_source_demo.py +151 -0
  35. pyradmc-0.2.0/examples/dij_demo.py +243 -0
  36. pyradmc-0.2.0/examples/dose_grid_demo.py +221 -0
  37. pyradmc-0.2.0/examples/electron_transport_demo.py +153 -0
  38. pyradmc-0.2.0/examples/materials_demo.py +162 -0
  39. pyradmc-0.2.0/examples/noise_bias_study.py +557 -0
  40. pyradmc-0.2.0/examples/pencil_kernel_database.py +306 -0
  41. pyradmc-0.2.0/examples/pencil_kernel_demo.py +225 -0
  42. pyradmc-0.2.0/examples/phasespace_demo.py +186 -0
  43. pyradmc-0.2.0/examples/reference_engine_demo.py +280 -0
  44. pyradmc-0.2.0/examples/spectral_source_demo.py +162 -0
  45. pyradmc-0.2.0/examples/tabulated_demo.py +151 -0
  46. pyradmc-0.2.0/examples/virtual_source_demo.py +346 -0
  47. pyradmc-0.2.0/examples/warp_backend_demo.py +179 -0
  48. pyradmc-0.2.0/mkdocs.yml +68 -0
  49. pyradmc-0.2.0/pyproject.toml +171 -0
  50. pyradmc-0.2.0/pyradmc/__init__.py +308 -0
  51. pyradmc-0.2.0/pyradmc/adapters/__init__.py +9 -0
  52. pyradmc-0.2.0/pyradmc/adapters/ct.py +244 -0
  53. pyradmc-0.2.0/pyradmc/backends/__init__.py +1 -0
  54. pyradmc-0.2.0/pyradmc/backends/ref/__init__.py +5 -0
  55. pyradmc-0.2.0/pyradmc/backends/ref/engine.py +463 -0
  56. pyradmc-0.2.0/pyradmc/backends/results.py +115 -0
  57. pyradmc-0.2.0/pyradmc/backends/warp/__init__.py +1 -0
  58. pyradmc-0.2.0/pyradmc/backends/warp/engine.py +3451 -0
  59. pyradmc-0.2.0/pyradmc/backends/warp/kernels.py +2401 -0
  60. pyradmc-0.2.0/pyradmc/backends/warp/physics.py +211 -0
  61. pyradmc-0.2.0/pyradmc/backends/warp/presolve.py +742 -0
  62. pyradmc-0.2.0/pyradmc/data/__init__.py +1 -0
  63. pyradmc-0.2.0/pyradmc/data/analytic.py +321 -0
  64. pyradmc-0.2.0/pyradmc/data/berger_seltzer.py +240 -0
  65. pyradmc-0.2.0/pyradmc/data/goudsmit_saunderson.py +1122 -0
  66. pyradmc-0.2.0/pyradmc/data/handles.py +34 -0
  67. pyradmc-0.2.0/pyradmc/data/interface.py +437 -0
  68. pyradmc-0.2.0/pyradmc/data/materials.py +345 -0
  69. pyradmc-0.2.0/pyradmc/data/tables.py +362 -0
  70. pyradmc-0.2.0/pyradmc/data/tabulated/__init__.py +10 -0
  71. pyradmc-0.2.0/pyradmc/data/tabulated/build.py +211 -0
  72. pyradmc-0.2.0/pyradmc/data/tabulated/eedl.py +315 -0
  73. pyradmc-0.2.0/pyradmc/data/tabulated/endf.py +258 -0
  74. pyradmc-0.2.0/pyradmc/data/tabulated/epdl.py +234 -0
  75. pyradmc-0.2.0/pyradmc/data/tabulated/format.py +102 -0
  76. pyradmc-0.2.0/pyradmc/data/tabulated/model.py +48 -0
  77. pyradmc-0.2.0/pyradmc/data/tabulated/precompile.py +267 -0
  78. pyradmc-0.2.0/pyradmc/data/tabulated/source.py +241 -0
  79. pyradmc-0.2.0/pyradmc/geometry/__init__.py +1 -0
  80. pyradmc-0.2.0/pyradmc/geometry/collimation.py +1284 -0
  81. pyradmc-0.2.0/pyradmc/geometry/cylinder.py +118 -0
  82. pyradmc-0.2.0/pyradmc/geometry/fluence.py +147 -0
  83. pyradmc-0.2.0/pyradmc/geometry/grid.py +337 -0
  84. pyradmc-0.2.0/pyradmc/geometry/head.py +599 -0
  85. pyradmc-0.2.0/pyradmc/geometry/phasespace.py +769 -0
  86. pyradmc-0.2.0/pyradmc/geometry/source.py +1195 -0
  87. pyradmc-0.2.0/pyradmc/geometry/spectrum.py +310 -0
  88. pyradmc-0.2.0/pyradmc/physics/__init__.py +1 -0
  89. pyradmc-0.2.0/pyradmc/physics/brems.py +83 -0
  90. pyradmc-0.2.0/pyradmc/physics/channel.py +55 -0
  91. pyradmc-0.2.0/pyradmc/physics/compton.py +118 -0
  92. pyradmc-0.2.0/pyradmc/physics/direction.py +81 -0
  93. pyradmc-0.2.0/pyradmc/physics/gs.py +166 -0
  94. pyradmc-0.2.0/pyradmc/physics/moller.py +87 -0
  95. pyradmc-0.2.0/pyradmc/physics/msc.py +54 -0
  96. pyradmc-0.2.0/pyradmc/physics/path.py +35 -0
  97. pyradmc-0.2.0/pyradmc/physics/rayleigh.py +118 -0
  98. pyradmc-0.2.0/pyradmc/physics/roulette.py +34 -0
  99. pyradmc-0.2.0/pyradmc/progress.py +96 -0
  100. pyradmc-0.2.0/pyradmc/py.typed +0 -0
  101. pyradmc-0.2.0/pyradmc/rng/__init__.py +12 -0
  102. pyradmc-0.2.0/pyradmc/rng/host.py +48 -0
  103. pyradmc-0.2.0/pyradmc/rng/interface.py +64 -0
  104. pyradmc-0.2.0/pyradmc/rng/warp_shim.py +81 -0
  105. pyradmc-0.2.0/pyradmc/scoring/__init__.py +1 -0
  106. pyradmc-0.2.0/pyradmc/scoring/cylinder.py +513 -0
  107. pyradmc-0.2.0/pyradmc/scoring/dij.py +434 -0
  108. pyradmc-0.2.0/pyradmc/scoring/dose.py +207 -0
  109. pyradmc-0.2.0/pyradmc/scoring/dose_to_water.py +84 -0
  110. pyradmc-0.2.0/pyradmc/scoring/grid.py +219 -0
  111. pyradmc-0.2.0/pyradmc/study.py +150 -0
  112. pyradmc-0.2.0/pyradmc/transport/__init__.py +1 -0
  113. pyradmc-0.2.0/pyradmc/transport/electron.py +481 -0
  114. pyradmc-0.2.0/pyradmc/transport/history.py +150 -0
  115. pyradmc-0.2.0/pyradmc/transport/particles.py +102 -0
  116. pyradmc-0.2.0/pyradmc/transport/photon.py +348 -0
  117. pyradmc-0.2.0/tests/conftest.py +196 -0
  118. pyradmc-0.2.0/tests/dij/test_collimated_dij.py +133 -0
  119. pyradmc-0.2.0/tests/dij/test_custom_beamlet_source.py +88 -0
  120. pyradmc-0.2.0/tests/dij/test_custom_beamlet_source_warp.py +111 -0
  121. pyradmc-0.2.0/tests/dij/test_custom_beamlet_source_warp_kernel.py +157 -0
  122. pyradmc-0.2.0/tests/dij/test_dij_concurrent_batches.py +142 -0
  123. pyradmc-0.2.0/tests/dij/test_dij_correlated.py +265 -0
  124. pyradmc-0.2.0/tests/dij/test_dij_multi_device.py +175 -0
  125. pyradmc-0.2.0/tests/dij/test_dij_progress.py +186 -0
  126. pyradmc-0.2.0/tests/dij/test_dij_ref.py +212 -0
  127. pyradmc-0.2.0/tests/dij/test_dij_scoring_grid.py +177 -0
  128. pyradmc-0.2.0/tests/dij/test_dij_warp.py +339 -0
  129. pyradmc-0.2.0/tests/dij/test_mask_dij.py +65 -0
  130. pyradmc-0.2.0/tests/dij/test_primary_fluence_dij.py +124 -0
  131. pyradmc-0.2.0/tests/dij/test_spectral_source_dij.py +63 -0
  132. pyradmc-0.2.0/tests/dij/test_spectral_source_warp.py +158 -0
  133. pyradmc-0.2.0/tests/dij/test_weighted_beamlet_ledger.py +168 -0
  134. pyradmc-0.2.0/tests/integration/test_attenuation.py +133 -0
  135. pyradmc-0.2.0/tests/integration/test_boundary_truncation.py +110 -0
  136. pyradmc-0.2.0/tests/integration/test_broad_beam.py +98 -0
  137. pyradmc-0.2.0/tests/integration/test_channel_complete.py +105 -0
  138. pyradmc-0.2.0/tests/integration/test_collimated_transport.py +131 -0
  139. pyradmc-0.2.0/tests/integration/test_composite_source_transport.py +95 -0
  140. pyradmc-0.2.0/tests/integration/test_custom_source_warp.py +127 -0
  141. pyradmc-0.2.0/tests/integration/test_custom_source_warp_kernel.py +115 -0
  142. pyradmc-0.2.0/tests/integration/test_deposit_resolution.py +208 -0
  143. pyradmc-0.2.0/tests/integration/test_device_presolve_transport.py +181 -0
  144. pyradmc-0.2.0/tests/integration/test_dose_to_water.py +140 -0
  145. pyradmc-0.2.0/tests/integration/test_electron_transport.py +158 -0
  146. pyradmc-0.2.0/tests/integration/test_engine_invariants.py +63 -0
  147. pyradmc-0.2.0/tests/integration/test_gaussian_spot_transport.py +80 -0
  148. pyradmc-0.2.0/tests/integration/test_gs_step_schedule.py +150 -0
  149. pyradmc-0.2.0/tests/integration/test_gs_transport_warp.py +243 -0
  150. pyradmc-0.2.0/tests/integration/test_head_presolve_transport.py +160 -0
  151. pyradmc-0.2.0/tests/integration/test_inmemory_phasespace_transport.py +104 -0
  152. pyradmc-0.2.0/tests/integration/test_multimaterial_transport.py +101 -0
  153. pyradmc-0.2.0/tests/integration/test_pencil_kernel_transport.py +324 -0
  154. pyradmc-0.2.0/tests/integration/test_phasespace_transport.py +97 -0
  155. pyradmc-0.2.0/tests/integration/test_primary_fluence_transport.py +125 -0
  156. pyradmc-0.2.0/tests/integration/test_result_provenance.py +150 -0
  157. pyradmc-0.2.0/tests/integration/test_roulette_transport.py +90 -0
  158. pyradmc-0.2.0/tests/integration/test_run_concurrent_batches.py +137 -0
  159. pyradmc-0.2.0/tests/integration/test_scoring_grid_transport.py +157 -0
  160. pyradmc-0.2.0/tests/integration/test_spectral_source_inkernel_warp.py +153 -0
  161. pyradmc-0.2.0/tests/integration/test_split_transport.py +202 -0
  162. pyradmc-0.2.0/tests/integration/test_step_energy_fraction_knob.py +148 -0
  163. pyradmc-0.2.0/tests/integration/test_transmission_mask_warp.py +126 -0
  164. pyradmc-0.2.0/tests/integration/test_warp_backend.py +198 -0
  165. pyradmc-0.2.0/tests/integration/test_warp_device_reduction.py +733 -0
  166. pyradmc-0.2.0/tests/integration/test_warp_dose_to_water.py +144 -0
  167. pyradmc-0.2.0/tests/integration/test_warp_material_narrowing.py +53 -0
  168. pyradmc-0.2.0/tests/integration/test_warp_phasespace.py +159 -0
  169. pyradmc-0.2.0/tests/integration/test_warp_physics_import_leak.py +62 -0
  170. pyradmc-0.2.0/tests/integration/test_warp_scoring_grid.py +166 -0
  171. pyradmc-0.2.0/tests/integration/test_warp_table_cache.py +77 -0
  172. pyradmc-0.2.0/tests/perf/test_ct_throughput.py +138 -0
  173. pyradmc-0.2.0/tests/perf/test_dij_throughput.py +97 -0
  174. pyradmc-0.2.0/tests/perf/test_warp_throughput.py +82 -0
  175. pyradmc-0.2.0/tests/phsp_fixtures.py +98 -0
  176. pyradmc-0.2.0/tests/physics/test_channel_selection.py +86 -0
  177. pyradmc-0.2.0/tests/physics/test_compton_sampling.py +152 -0
  178. pyradmc-0.2.0/tests/physics/test_direction_sampling.py +99 -0
  179. pyradmc-0.2.0/tests/physics/test_electron_sampling.py +192 -0
  180. pyradmc-0.2.0/tests/physics/test_path_sampling.py +63 -0
  181. pyradmc-0.2.0/tests/physics/test_presolve_compton.py +68 -0
  182. pyradmc-0.2.0/tests/physics/test_rayleigh_sampling.py +124 -0
  183. pyradmc-0.2.0/tests/physics/test_roulette_sampling.py +65 -0
  184. pyradmc-0.2.0/tests/physics/test_substep_deposit_spread.py +141 -0
  185. pyradmc-0.2.0/tests/physics/test_warp_boundary_distance.py +134 -0
  186. pyradmc-0.2.0/tests/physics/test_warp_collimation_geometry.py +217 -0
  187. pyradmc-0.2.0/tests/physics/test_warp_compiled_stack.py +125 -0
  188. pyradmc-0.2.0/tests/physics/test_warp_gs_sampler.py +207 -0
  189. pyradmc-0.2.0/tests/physics/test_warp_physics.py +313 -0
  190. pyradmc-0.2.0/tests/physics/test_warp_presolve.py +226 -0
  191. pyradmc-0.2.0/tests/unit/test_beamlet_source.py +121 -0
  192. pyradmc-0.2.0/tests/unit/test_berger_seltzer.py +156 -0
  193. pyradmc-0.2.0/tests/unit/test_build.py +166 -0
  194. pyradmc-0.2.0/tests/unit/test_collimated_source.py +206 -0
  195. pyradmc-0.2.0/tests/unit/test_collimation.py +675 -0
  196. pyradmc-0.2.0/tests/unit/test_composite_sources.py +115 -0
  197. pyradmc-0.2.0/tests/unit/test_cross_sections_water.py +82 -0
  198. pyradmc-0.2.0/tests/unit/test_ct_calibration.py +160 -0
  199. pyradmc-0.2.0/tests/unit/test_ct_reader.py +121 -0
  200. pyradmc-0.2.0/tests/unit/test_cylindrical_scoring.py +419 -0
  201. pyradmc-0.2.0/tests/unit/test_dij_group_autosize.py +58 -0
  202. pyradmc-0.2.0/tests/unit/test_dij_scoring.py +291 -0
  203. pyradmc-0.2.0/tests/unit/test_dose_to_water_spr.py +103 -0
  204. pyradmc-0.2.0/tests/unit/test_eedl.py +328 -0
  205. pyradmc-0.2.0/tests/unit/test_elastic_screening.py +172 -0
  206. pyradmc-0.2.0/tests/unit/test_electron_data.py +213 -0
  207. pyradmc-0.2.0/tests/unit/test_endf.py +79 -0
  208. pyradmc-0.2.0/tests/unit/test_epdl.py +155 -0
  209. pyradmc-0.2.0/tests/unit/test_gaussian_spot_source.py +192 -0
  210. pyradmc-0.2.0/tests/unit/test_geometry.py +200 -0
  211. pyradmc-0.2.0/tests/unit/test_gs_grid.py +340 -0
  212. pyradmc-0.2.0/tests/unit/test_gs_moments.py +319 -0
  213. pyradmc-0.2.0/tests/unit/test_gs_sampler.py +390 -0
  214. pyradmc-0.2.0/tests/unit/test_head_presolve.py +318 -0
  215. pyradmc-0.2.0/tests/unit/test_iaea_phsp.py +275 -0
  216. pyradmc-0.2.0/tests/unit/test_icrp_media.py +167 -0
  217. pyradmc-0.2.0/tests/unit/test_inmemory_phasespace.py +153 -0
  218. pyradmc-0.2.0/tests/unit/test_interface_contracts.py +281 -0
  219. pyradmc-0.2.0/tests/unit/test_materials.py +129 -0
  220. pyradmc-0.2.0/tests/unit/test_oracle_power.py +116 -0
  221. pyradmc-0.2.0/tests/unit/test_phasespace_source.py +161 -0
  222. pyradmc-0.2.0/tests/unit/test_precompile.py +37 -0
  223. pyradmc-0.2.0/tests/unit/test_primary_fluence_source.py +285 -0
  224. pyradmc-0.2.0/tests/unit/test_progress.py +94 -0
  225. pyradmc-0.2.0/tests/unit/test_public_api.py +135 -0
  226. pyradmc-0.2.0/tests/unit/test_radial_fluence.py +142 -0
  227. pyradmc-0.2.0/tests/unit/test_range_tables.py +74 -0
  228. pyradmc-0.2.0/tests/unit/test_restricted_range.py +73 -0
  229. pyradmc-0.2.0/tests/unit/test_scattering_power_anchor.py +147 -0
  230. pyradmc-0.2.0/tests/unit/test_scoring.py +164 -0
  231. pyradmc-0.2.0/tests/unit/test_scoring_grid.py +144 -0
  232. pyradmc-0.2.0/tests/unit/test_source_interface.py +118 -0
  233. pyradmc-0.2.0/tests/unit/test_spectral_source.py +274 -0
  234. pyradmc-0.2.0/tests/unit/test_spectrum.py +252 -0
  235. pyradmc-0.2.0/tests/unit/test_study.py +118 -0
  236. pyradmc-0.2.0/tests/unit/test_tables.py +336 -0
  237. pyradmc-0.2.0/tests/unit/test_tabulated_format.py +102 -0
  238. pyradmc-0.2.0/tests/unit/test_tabulated_source.py +188 -0
  239. pyradmc-0.2.0/tests/unit/test_transmission_mask.py +178 -0
  240. pyradmc-0.2.0/tests/unit/test_tungsten.py +125 -0
  241. pyradmc-0.2.0/tests/unit/test_tungsten_alloy.py +156 -0
  242. pyradmc-0.2.0/tests/unit/test_warp_module_options.py +45 -0
  243. pyradmc-0.2.0/tests/unit/test_warp_rng.py +147 -0
  244. pyradmc-0.2.0/tests/unit/test_warp_sizing.py +143 -0
  245. pyradmc-0.2.0/tests/validation/data/README.md +34 -0
  246. pyradmc-0.2.0/tests/validation/data/center_ray_dose_EGSNrc_intRadius6.0mm.txt +201 -0
  247. pyradmc-0.2.0/tests/validation/test_ali_rogers_mu_parameterization.py +121 -0
  248. pyradmc-0.2.0/tests/validation/test_eedl_water_estar.py +65 -0
  249. pyradmc-0.2.0/tests/validation/test_eedl_water_scattering.py +98 -0
  250. pyradmc-0.2.0/tests/validation/test_epdl_water_nist.py +113 -0
  251. pyradmc-0.2.0/tests/validation/test_epics_library_digests.py +74 -0
  252. pyradmc-0.2.0/tests/validation/test_gs_eta_ladder.py +160 -0
  253. pyradmc-0.2.0/tests/validation/test_gs_thorax_cross_backend.py +147 -0
  254. pyradmc-0.2.0/tests/validation/test_penumbra_width.py +169 -0
  255. pyradmc-0.2.0/tests/validation/test_ranges_and_pdd.py +367 -0
  256. pyradmc-0.2.0/tests/validation/test_tabulated_materials_compile.py +190 -0
  257. pyradmc-0.2.0/tests/validation/test_tabulated_water_compile.py +204 -0
@@ -0,0 +1,20 @@
1
+ <!--
2
+ The PR title becomes the squash-commit subject on develop. It should follow
3
+ AGENTS.md 9.2: type(scope): imperative description
4
+ (description <= 50 chars; whole line <= 72)
5
+ The description below becomes the commit body: wrap lines at 72.
6
+ -->
7
+
8
+ ## What and why
9
+
10
+ <!-- The physics / the reasoning, not the diff. Measurements go here. -->
11
+
12
+ ## Checklist
13
+
14
+ - [ ] A failing test motivated this change (AGENTS.md 2.1)
15
+ - [ ] `pre-commit run --all-files` and `pytest` pass locally
16
+ - [ ] Docs, README and docstrings this change made stale are updated
17
+ - [ ] If a user-visible capability: a runnable example under `examples/`
18
+ - [ ] If it can move a computed dose: `CHANGELOG.md` entry + the measurement
19
+ - [ ] If it touches `ECUT`/`PCUT`/truncation/step defaults: the dosimetric-effect
20
+ test of AGENTS.md 2.8 is included
@@ -0,0 +1,158 @@
1
+ name: CI
2
+
3
+ # GitHub is the primary remote (AGENTS.md 9): task branches squash-merge into develop
4
+ # through pull requests, and releases merge rc/* into main. The DKFZ GitLab mirror runs
5
+ # its own validate-only pipeline (.gitlab-ci.yml).
6
+ on:
7
+ push:
8
+ # rc/** included so a release candidate builds its wheel even before the PR exists.
9
+ branches: [main, develop, "rc/**"]
10
+ pull_request:
11
+ branches: [main, develop]
12
+ workflow_dispatch:
13
+
14
+ concurrency:
15
+ group: ci-${{ github.ref }}
16
+ cancel-in-progress: true
17
+
18
+ permissions:
19
+ contents: read
20
+
21
+ env:
22
+ PIP_DISABLE_PIP_VERSION_CHECK: "1"
23
+ FORCE_COLOR: "1"
24
+
25
+ jobs:
26
+ quality:
27
+ name: lint, format, types
28
+ runs-on: ubuntu-latest
29
+ steps:
30
+ - uses: actions/checkout@v4
31
+ - uses: actions/setup-python@v5
32
+ with:
33
+ python-version: "3.13"
34
+ cache: pip
35
+ cache-dependency-path: pyproject.toml
36
+ # The warp extra is installed for mypy: it type-checks the engine modules that
37
+ # import warp, even though the kernels themselves are exempt (AGENTS.md 5).
38
+ - run: pip install -e ".[warp,dev]"
39
+ - uses: actions/cache@v4
40
+ with:
41
+ path: ~/.cache/pre-commit
42
+ key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
43
+ # Run the hook definitions rather than re-listing the tools here. The pinned revs
44
+ # in .pre-commit-config.yaml are then the single authority: CI and a developer's
45
+ # commit hook cannot disagree, and neither can drift onto a new linter release.
46
+ # (mypy is a `language: system` hook, so it uses the environment installed above.)
47
+ - run: pre-commit run --all-files --show-diff-on-failure
48
+
49
+ test:
50
+ name: tests (py${{ matrix.python }}, ${{ matrix.os }})
51
+ runs-on: ${{ matrix.os }}
52
+ strategy:
53
+ fail-fast: false
54
+ matrix:
55
+ os: [ubuntu-latest]
56
+ python: ["3.10", "3.11", "3.12", "3.13"]
57
+ include:
58
+ # Coverage wherever it is nearly free, which is every job on 3.12+: the
59
+ # sysmon core below costs 1.01x, so these keep measuring (Codecov merges
60
+ # the uploads for a commit). It is dropped only from 3.10 and 3.11, where
61
+ # sys.monitoring does not exist and coverage.py falls back to the C tracer
62
+ # -- measured at 2.09x on the full fast suite, which is what made running
63
+ # it on all five jobs expensive. These merge into the matrix combinations
64
+ # they name rather than adding jobs, since every key here matches one.
65
+ - os: ubuntu-latest
66
+ python: "3.12"
67
+ coverage: true
68
+ - os: ubuntu-latest
69
+ python: "3.13"
70
+ coverage: true
71
+ # The maintainer develops on Windows, and several past bugs were
72
+ # Windows-specific (path handling, subprocess behaviour). One job keeps that
73
+ # platform honest without quadrupling the matrix -- and it carries coverage
74
+ # so that a Windows-only branch still reaches the report.
75
+ - os: windows-latest
76
+ python: "3.13"
77
+ coverage: true
78
+ steps:
79
+ - uses: actions/checkout@v4
80
+ - uses: actions/setup-python@v5
81
+ with:
82
+ python-version: ${{ matrix.python }}
83
+ cache: pip
84
+ cache-dependency-path: pyproject.toml
85
+ - run: pip install -e ".[warp,dev]"
86
+ # Runners have no CUDA device: warp runs on CPU and the gpu-marked tests skip
87
+ # themselves via wp.is_cuda_available(). The validation and perf tiers are
88
+ # excluded by the default addopts; validation runs on its own schedule.
89
+ # COVERAGE_CORE=sysmon puts coverage.py on CPython's sys.monitoring (3.12+)
90
+ # instead of its C tracer, which is what makes coverage affordable at all here:
91
+ # same suite, 630 s under the tracer against 308 s under sysmon and 304 s with
92
+ # no coverage — 2.09x overhead down to 1.01x. Set at job level and inert on the
93
+ # jobs that pass no --cov, which are exactly the pre-3.12 ones, so no job ever
94
+ # asks for a core it lacks. (No branch coverage is configured; that is the one
95
+ # feature the sysmon core does not support.)
96
+ - env:
97
+ COVERAGE_CORE: sysmon
98
+ run: pytest -q ${{ matrix.coverage && '--cov' || '' }} --junitxml=junit.xml -o junit_family=legacy
99
+ - name: Upload coverage to Codecov
100
+ if: ${{ matrix.coverage && !cancelled() }}
101
+ uses: codecov/codecov-action@v5
102
+ with:
103
+ token: ${{ secrets.CODECOV_TOKEN }}
104
+ # Same action twice: codecov deprecated test-results-action@v1 in favor of
105
+ # codecov-action with report_type, run once per report kind.
106
+ - name: Upload test results to Codecov
107
+ if: ${{ !cancelled() }}
108
+ uses: codecov/codecov-action@v5
109
+ with:
110
+ report_type: test_results
111
+ token: ${{ secrets.CODECOV_TOKEN }}
112
+
113
+ docs:
114
+ name: docs build
115
+ runs-on: ubuntu-latest
116
+ steps:
117
+ - uses: actions/checkout@v4
118
+ - uses: actions/setup-python@v5
119
+ with:
120
+ python-version: "3.13"
121
+ cache: pip
122
+ cache-dependency-path: pyproject.toml
123
+ - run: pip install -e ".[docs]"
124
+ # --strict turns broken references and missing nav entries into failures. The API
125
+ # reference is generated from the source, so a renamed public symbol breaks the
126
+ # docs build here rather than silently emptying a page on ReadTheDocs.
127
+ - run: mkdocs build --strict
128
+
129
+ build:
130
+ name: build distributions
131
+ runs-on: ubuntu-latest
132
+ steps:
133
+ - uses: actions/checkout@v4
134
+ - uses: actions/setup-python@v5
135
+ with:
136
+ python-version: "3.13"
137
+ cache: pip
138
+ - run: pip install build twine
139
+ - run: python -m build
140
+ - run: twine check dist/*
141
+ # Installing the built wheel into a clean environment is the only check that
142
+ # catches a package that imports fine from the source tree but ships broken:
143
+ # a missing py.typed, a subpackage left out of the wheel, a bad entry point.
144
+ - name: verify the wheel installs and imports standalone
145
+ run: |
146
+ python -m venv /tmp/fresh
147
+ /tmp/fresh/bin/pip install dist/*.whl
148
+ /tmp/fresh/bin/python -c "
149
+ import pyradmc, pathlib
150
+ print('version', pyradmc.__version__)
151
+ assert (pathlib.Path(pyradmc.__file__).parent / 'py.typed').is_file(), 'py.typed missing from wheel'
152
+ assert pyradmc.VoxelGrid is not None
153
+ print('public names:', len(pyradmc.__all__))
154
+ "
155
+ - uses: actions/upload-artifact@v4
156
+ with:
157
+ name: distributions
158
+ path: dist/
@@ -0,0 +1,114 @@
1
+ name: Release
2
+
3
+ # Tagging is the release action: the tag lands on an rc/* merge commit on main, per the
4
+ # release flow of AGENTS.md 9.4. A manual dispatch builds and verifies only — the
5
+ # publish jobs are gated on a v* tag ref, so nothing can reach PyPI without one.
6
+ on:
7
+ push:
8
+ tags: ["v*"]
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ env:
15
+ PIP_DISABLE_PIP_VERSION_CHECK: "1"
16
+
17
+ jobs:
18
+ build:
19
+ name: build and verify
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ - uses: actions/setup-python@v5
24
+ with:
25
+ python-version: "3.13"
26
+ cache: pip
27
+
28
+ # The version is single-sourced from pyradmc/__init__.py, so a tag is a *claim*
29
+ # about it that nothing else checks. A mismatch here would publish a release whose
30
+ # artifacts report a different version than the tag they were cut from — and every
31
+ # RunProvenance record stamped by that build would carry the wrong number. Refuse.
32
+ - name: tag must match pyradmc.__version__
33
+ if: startsWith(github.ref, 'refs/tags/')
34
+ run: |
35
+ tag="${GITHUB_REF_NAME#v}"
36
+ version=$(python -c "import re,pathlib; print(re.search(r'__version__ = \"([^\"]+)\"', pathlib.Path('pyradmc/__init__.py').read_text()).group(1))")
37
+ echo "tag=$tag __version__=$version"
38
+ if [ "$tag" != "$version" ]; then
39
+ echo "::error::tag v$tag does not match pyradmc.__version__ ($version)"
40
+ exit 1
41
+ fi
42
+
43
+ # A stale CITATION.cff is how a release ends up being cited as the previous one.
44
+ - name: CITATION.cff version must match the release
45
+ if: startsWith(github.ref, 'refs/tags/')
46
+ run: |
47
+ tag="${GITHUB_REF_NAME#v}"
48
+ cited=$(grep -E '^version:' CITATION.cff | head -1 | sed 's/version:[[:space:]]*//' | tr -d '"')
49
+ echo "tag=$tag CITATION.cff=$cited"
50
+ if [ "$tag" != "$cited" ]; then
51
+ echo "::error::CITATION.cff says $cited but the tag is v$tag; update it before releasing"
52
+ exit 1
53
+ fi
54
+
55
+ - run: pip install build twine
56
+ - run: python -m build
57
+ - run: twine check dist/*
58
+
59
+ - name: verify the wheel installs and imports standalone
60
+ run: |
61
+ python -m venv /tmp/fresh
62
+ /tmp/fresh/bin/pip install dist/*.whl
63
+ /tmp/fresh/bin/python -c "
64
+ import pyradmc, pathlib
65
+ assert (pathlib.Path(pyradmc.__file__).parent / 'py.typed').is_file()
66
+ print('ok', pyradmc.__version__)
67
+ "
68
+
69
+ - uses: actions/upload-artifact@v4
70
+ with:
71
+ name: distributions
72
+ path: dist/
73
+
74
+ publish:
75
+ name: publish to PyPI
76
+ needs: build
77
+ # Only ever from a version tag: a workflow_dispatch run stops after build.
78
+ if: success() && startsWith(github.ref, 'refs/tags/v')
79
+ runs-on: ubuntu-latest
80
+ # Trusted publishing (OIDC): PyPI verifies this workflow's identity directly, so
81
+ # there is no long-lived API token to store or rotate. Configure the publisher once
82
+ # at https://pypi.org/manage/project/pyradmc/settings/publishing/ with
83
+ # owner e0404, repository pyRadMC, workflow release.yml, environment pypi.
84
+ environment:
85
+ name: pypi
86
+ url: https://pypi.org/p/pyRadMC
87
+ permissions:
88
+ id-token: write
89
+ steps:
90
+ - uses: actions/download-artifact@v4
91
+ with:
92
+ name: distributions
93
+ path: dist/
94
+ - uses: pypa/gh-action-pypi-publish@release/v1
95
+
96
+ github-release:
97
+ name: GitHub release
98
+ needs: publish
99
+ if: success() && startsWith(github.ref, 'refs/tags/v')
100
+ runs-on: ubuntu-latest
101
+ permissions:
102
+ contents: write
103
+ steps:
104
+ - uses: actions/download-artifact@v4
105
+ with:
106
+ name: distributions
107
+ path: dist/
108
+ - uses: softprops/action-gh-release@v2
109
+ with:
110
+ files: dist/*
111
+ generate_release_notes: true
112
+ body: |
113
+ See [CHANGELOG.md](https://github.com/e0404/pyRadMC/blob/main/CHANGELOG.md)
114
+ for what changed, including anything that can move a computed dose.
@@ -0,0 +1,55 @@
1
+ name: Validation tier
2
+
3
+ # Deliberately NOT on push: the validation tier is slow, needs the ~120 MB EPICS
4
+ # libraries, and gates physics rather than code. AGENTS.md section 7 requires it to pass
5
+ # before a release, so it runs on a schedule (to catch rot) and on demand (before
6
+ # tagging), not on every commit.
7
+ on:
8
+ schedule:
9
+ - cron: "0 3 * * 1" # Mondays, 03:00 UTC
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ validation:
17
+ name: validation tier (with EPICS libraries)
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 120
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: actions/setup-python@v5
23
+ with:
24
+ python-version: "3.13"
25
+ cache: pip
26
+ cache-dependency-path: pyproject.toml
27
+ - run: pip install -e ".[warp,dev]"
28
+
29
+ # Cached so a scheduled run does not re-fetch 120 MB from the IAEA every week.
30
+ # The cache key carries the pinned digests, so changing a pin necessarily misses
31
+ # the cache and re-downloads — which is the correct behaviour, since a changed pin
32
+ # means the libraries themselves are supposed to be different.
33
+ - name: cache EPICS libraries
34
+ id: epics-cache
35
+ uses: actions/cache@v4
36
+ with:
37
+ path: ~/.cache/pyradmc/epics
38
+ key: epics-2023-${{ hashFiles('pyradmc/data/tabulated/build.py') }}
39
+
40
+ # download_library verifies every file against its pinned SHA-256 and fails
41
+ # closed. This is also what exercises the pins themselves in CI: a silent upstream
42
+ # revision shows up here as a hard failure rather than as drift in a dose gate.
43
+ - name: fetch and verify EPICS libraries
44
+ run: |
45
+ python -c "
46
+ from pyradmc.data.tabulated import build
47
+ for which in ('epdl', 'eedl'):
48
+ print(which, '->', build.download_library(which))
49
+ "
50
+
51
+ - name: run the validation tier
52
+ env:
53
+ PYRADMC_EPDL_PATH: ~/.cache/pyradmc/epics/EPDL2023.ALL
54
+ PYRADMC_EEDL_PATH: ~/.cache/pyradmc/epics/EEDL2023.ALL
55
+ run: pytest -m validation -q
@@ -0,0 +1,51 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ .pytest_cache/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ .hypothesis/
8
+ build/
9
+ dist/
10
+ *.egg-info/
11
+ .benchmarks/
12
+
13
+ # Compiled tabulated cross-section tables: derived from the (uncommitted, user-side)
14
+ # EPICS libraries by pyradmc.data.tabulated.build, so not tracked.
15
+ *.npz
16
+
17
+ # Phase-space demo outputs derive from an external (proprietary, machine-local)
18
+ # phase-space file and are not reproducible in the repo, so they are not tracked.
19
+ examples/phasespace_demo.png
20
+ examples/phasespace_demo.csv
21
+ site/
22
+
23
+ # Vendor beam data and anything fitted from it. The workbook a head is commissioned
24
+ # against is licensed to its owner, not to this repository, and at ~500 KB it slips
25
+ # under the check-added-large-files gate rather than being caught by it. The fitted
26
+ # radial-fluence table is derived from those measured profiles, so it travels with
27
+ # them: the shipped default is the analytic cone, and PRIMARY_FLUENCE_FILE is the hook
28
+ # for the table you fit against your own data.
29
+ *.xlsx
30
+ examples/halcyon_primary_fluence.dat
31
+
32
+ # Everything the examples render. They are regenerable by definition -- that is what an
33
+ # example is -- and tracking them cost ~5 MB of binaries that no reader of the repository
34
+ # opens from here, churned on every re-run, and went stale against the scripts that write
35
+ # them. The figures the README embeds live in docs/assets/ instead, which is where a
36
+ # documentation asset belongs; an example's own output is not one.
37
+ examples/*.png
38
+ examples/*.csv
39
+
40
+ # Local scratch: comparison scripts, sweeps and figures that need the vendor workbook,
41
+ # plus the run logs they were driven from. Not reproducible here, so not tracked.
42
+ local_*
43
+ *.log
44
+
45
+ .vscode/
46
+
47
+ # Coverage and test-result artifacts (pytest --cov / --junitxml, uploaded to Codecov in CI)
48
+ .coverage
49
+ .coverage.*
50
+ coverage.xml
51
+ junit.xml
@@ -0,0 +1,93 @@
1
+ # GitLab CI for the DKFZ GitLab mirror. GitHub (github.com/e0404/pyRadMC) is the
2
+ # primary remote (AGENTS.md 9): pull requests, reviews and releases happen there.
3
+ #
4
+ # This pipeline VALIDATES ONLY — there is deliberately no deploy stage. Releases
5
+ # (PyPI, GitHub Releases) and the documentation build run from GitHub, where trusted
6
+ # publishing and ReadTheDocs are wired up; see .github/workflows/release.yml. Keeping
7
+ # publication in exactly one place is what prevents two pipelines racing to upload the
8
+ # same version.
9
+ #
10
+ # The validation tier is not here either: it needs the ~120 MB EPICS libraries and runs
11
+ # on a schedule in .github/workflows/validation.yml.
12
+
13
+ stages: [check, test, build]
14
+
15
+ default:
16
+ image: python:3.13-slim
17
+ # Warp and SciPy wheels are large; caching pip keeps repeat pipelines cheap.
18
+ cache:
19
+ key:
20
+ files: [pyproject.toml]
21
+ paths: [.cache/pip]
22
+ before_script:
23
+ - python -V
24
+ - pip install --quiet --upgrade pip
25
+
26
+ variables:
27
+ PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
28
+ PIP_DISABLE_PIP_VERSION_CHECK: "1"
29
+
30
+ workflow:
31
+ rules:
32
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
33
+ - if: $CI_COMMIT_BRANCH == "main"
34
+ - if: $CI_COMMIT_BRANCH == "develop"
35
+ - if: $CI_PIPELINE_SOURCE == "web"
36
+
37
+ quality:
38
+ stage: check
39
+ variables:
40
+ PRE_COMMIT_HOME: "$CI_PROJECT_DIR/.cache/pre-commit"
41
+ cache:
42
+ key:
43
+ files: [.pre-commit-config.yaml]
44
+ paths: [.cache/pre-commit, .cache/pip]
45
+ script:
46
+ - pip install --quiet -e ".[warp,dev]"
47
+ # Run the hook definitions rather than re-listing the tools. The pinned revs in
48
+ # .pre-commit-config.yaml are the single authority, so this pipeline, the GitHub
49
+ # one, and a developer's commit hook cannot disagree or drift onto a new release.
50
+ - pre-commit run --all-files --show-diff-on-failure
51
+ before_script:
52
+ - python -V
53
+ - pip install --quiet --upgrade pip
54
+ - git config --global --add safe.directory "$CI_PROJECT_DIR"
55
+
56
+ tests:
57
+ stage: test
58
+ parallel:
59
+ matrix:
60
+ - PYTHON_VERSION: ["3.10", "3.11", "3.12", "3.13"]
61
+ image: python:$PYTHON_VERSION-slim
62
+ script:
63
+ - pip install --quiet -e ".[warp,dev]"
64
+ # No CUDA device on a standard runner: warp runs on CPU and the gpu-marked tests
65
+ # skip themselves. The default addopts already exclude the validation and perf tiers.
66
+ - pytest -q --junitxml=report.xml
67
+ artifacts:
68
+ when: always
69
+ reports:
70
+ junit: report.xml
71
+ expire_in: 1 week
72
+
73
+ docs:
74
+ stage: check
75
+ script:
76
+ - pip install --quiet -e ".[docs]"
77
+ # --strict only; the built site is not published from here.
78
+ - mkdocs build --strict
79
+
80
+ build:
81
+ stage: build
82
+ script:
83
+ - pip install --quiet build twine
84
+ - python -m build
85
+ - twine check dist/*
86
+ # The wheel must import from a clean environment — this catches a missing py.typed
87
+ # or a subpackage left out of the wheel, which a source-tree import never would.
88
+ - python -m venv /tmp/fresh
89
+ - /tmp/fresh/bin/pip install --quiet dist/*.whl
90
+ - /tmp/fresh/bin/python -c "import pyradmc, pathlib; assert (pathlib.Path(pyradmc.__file__).parent / 'py.typed').is_file(); print(pyradmc.__version__)"
91
+ artifacts:
92
+ paths: [dist/]
93
+ expire_in: 1 week
@@ -0,0 +1,51 @@
1
+ # Pre-commit gates: fast, file-scoped checks only.
2
+ #
3
+ # pip install -e ".[dev]" && pre-commit install
4
+ #
5
+ # Deliberately NO test run. The suite takes minutes, and a gate slow enough to be
6
+ # resented is a gate that gets bypassed with --no-verify. Tests are CI's job — see
7
+ # .github/workflows/ci.yml and .gitlab-ci.yml, which run the fast tiers on every push to
8
+ # main and develop, and the validation tier on its own schedule.
9
+ #
10
+ # Run against everything (not just staged files) with: pre-commit run --all-files
11
+ default_language_version:
12
+ python: python3
13
+
14
+ repos:
15
+ - repo: https://github.com/pre-commit/pre-commit-hooks
16
+ rev: v6.0.0
17
+ hooks:
18
+ - id: trailing-whitespace
19
+ # The EGSnrc benchmark is stored verbatim; reformatting reference data would
20
+ # silently change what the validation tier compares against.
21
+ exclude: ^tests/validation/data/
22
+ - id: end-of-file-fixer
23
+ exclude: ^tests/validation/data/
24
+ - id: check-yaml
25
+ - id: check-toml
26
+ - id: check-merge-conflict
27
+ - id: check-case-conflict
28
+ - id: check-added-large-files
29
+ # The committed example figures run to ~230 KB; this catches an accidental
30
+ # cross-section table or phase-space file, which are the real risks.
31
+ args: [--maxkb=512]
32
+
33
+ - repo: https://github.com/astral-sh/ruff-pre-commit
34
+ rev: v0.15.1
35
+ hooks:
36
+ - id: ruff-check
37
+ args: [--fix]
38
+ - id: ruff-format
39
+
40
+ - repo: local
41
+ hooks:
42
+ # Run from the project environment, not an isolated one: mypy --strict needs the
43
+ # real NumPy, SciPy and Warp stubs to say anything true, and it is configured
44
+ # whole-package in pyproject.toml (kernels exempt), so it does not take filenames.
45
+ - id: mypy
46
+ name: mypy --strict (non-kernel code)
47
+ entry: mypy pyradmc
48
+ language: system
49
+ types: [python]
50
+ pass_filenames: false
51
+ require_serial: true
@@ -0,0 +1,27 @@
1
+ # ReadTheDocs build configuration.
2
+ #
3
+ # RTD builds from the primary remote, github.com/e0404/pyRadMC.
4
+ version: 2
5
+
6
+ build:
7
+ os: ubuntu-24.04
8
+ tools:
9
+ python: "3.13"
10
+
11
+ mkdocs:
12
+ configuration: mkdocs.yml
13
+ # Broken references and missing nav entries fail the build. The API reference is
14
+ # generated from the source, so a renamed or removed public symbol is caught here
15
+ # rather than silently rendering an empty page.
16
+ fail_on_warning: true
17
+
18
+ python:
19
+ install:
20
+ # The docs extra pulls mkdocs-material and mkdocstrings; the package itself is
21
+ # installed because mkdocstrings imports it to read signatures and docstrings.
22
+ # No warp extra: mkdocstrings resolves the lazily re-exported names through the
23
+ # TYPE_CHECKING block, so the API reference builds without a CUDA toolchain.
24
+ - method: pip
25
+ path: .
26
+ extra_requirements:
27
+ - docs