specmod 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. {specmod-0.2.0 → specmod-0.2.2}/.github/workflows/docs.yml +6 -1
  2. {specmod-0.2.0 → specmod-0.2.2}/.github/workflows/release.yml +33 -9
  3. {specmod-0.2.0 → specmod-0.2.2}/.gitignore +11 -3
  4. {specmod-0.2.0 → specmod-0.2.2}/.readthedocs.yaml +7 -1
  5. specmod-0.2.2/.release-please-manifest.json +3 -0
  6. {specmod-0.2.0 → specmod-0.2.2}/CHANGELOG.md +33 -1
  7. {specmod-0.2.0 → specmod-0.2.2}/CITATION.cff +7 -0
  8. {specmod-0.2.0 → specmod-0.2.2}/PKG-INFO +34 -5
  9. {specmod-0.2.0 → specmod-0.2.2}/README.md +33 -4
  10. {specmod-0.2.0 → specmod-0.2.2}/docs/conf.py +61 -7
  11. {specmod-0.2.0 → specmod-0.2.2}/docs/documentation.md +39 -2
  12. {specmod-0.2.0 → specmod-0.2.2}/docs/index.md +10 -4
  13. {specmod-0.2.0 → specmod-0.2.2}/docs/releasing.md +80 -23
  14. specmod-0.2.2/docs/roadmap.md +157 -0
  15. specmod-0.2.2/tutorial/SpecModTutorial.ipynb +1032 -0
  16. specmod-0.2.0/.release-please-manifest.json +0 -3
  17. specmod-0.2.0/docs/roadmap.md +0 -109
  18. specmod-0.2.0/tutorial/SpecModTutorial.ipynb +0 -2000
  19. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/spectra/2019-08-26T07:30:47.000000Z.h5 +0 -0
  20. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/spectra/flatfiles/2019-08-26T07:30:47.000000Z.csv +0 -29
  21. specmod-0.2.0/tutorial/data/events/2019-08-26T07:30:47.000000Z/spectra/flatfiles/2019-08-26T07:30:47.000000Z.parquet +0 -0
  22. {specmod-0.2.0 → specmod-0.2.2}/.git-blame-ignore-revs +0 -0
  23. {specmod-0.2.0 → specmod-0.2.2}/.github/workflows/build.yml +0 -0
  24. {specmod-0.2.0 → specmod-0.2.2}/.github/workflows/test.yml +0 -0
  25. {specmod-0.2.0 → specmod-0.2.2}/.pre-commit-config.yaml +0 -0
  26. {specmod-0.2.0 → specmod-0.2.2}/AGENTS.md +0 -0
  27. {specmod-0.2.0 → specmod-0.2.2}/CLAUDE.md +0 -0
  28. {specmod-0.2.0 → specmod-0.2.2}/CONTRIBUTING.md +0 -0
  29. {specmod-0.2.0 → specmod-0.2.2}/LICENSE +0 -0
  30. {specmod-0.2.0 → specmod-0.2.2}/datasets/magna_2020.toml +0 -0
  31. {specmod-0.2.0 → specmod-0.2.2}/datasets/pnr_2019.toml +0 -0
  32. {specmod-0.2.0 → specmod-0.2.2}/docs/REFACTOR_PLAN.md +0 -0
  33. {specmod-0.2.0 → specmod-0.2.2}/docs/api.md +0 -0
  34. {specmod-0.2.0 → specmod-0.2.2}/docs/choosing-a-transform.md +0 -0
  35. {specmod-0.2.0 → specmod-0.2.2}/docs/development.md +0 -0
  36. {specmod-0.2.0 → specmod-0.2.2}/docs/notebooks/_build_notebook.py +0 -0
  37. {specmod-0.2.0 → specmod-0.2.2}/docs/notebooks/choosing-a-transform.ipynb +0 -0
  38. {specmod-0.2.0 → specmod-0.2.2}/docs/notes/api-audit.md +0 -0
  39. {specmod-0.2.0 → specmod-0.2.2}/docs/notes/window-position.md +0 -0
  40. {specmod-0.2.0 → specmod-0.2.2}/docs/pick-formats.md +0 -0
  41. {specmod-0.2.0 → specmod-0.2.2}/docs/processing.md +0 -0
  42. {specmod-0.2.0 → specmod-0.2.2}/docs/releasing-data.md +0 -0
  43. {specmod-0.2.0 → specmod-0.2.2}/pyproject.toml +0 -0
  44. {specmod-0.2.0 → specmod-0.2.2}/release-please-config.json +0 -0
  45. {specmod-0.2.0 → specmod-0.2.2}/requirements.txt +0 -0
  46. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/__init__.py +0 -0
  47. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/_vendor/__init__.py +0 -0
  48. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/_vendor/qiinv.py +0 -0
  49. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/acquire.py +0 -0
  50. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/api.py +0 -0
  51. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/cli.py +0 -0
  52. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/config/__init__.py +0 -0
  53. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/config/layers.py +0 -0
  54. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/config/provenance.py +0 -0
  55. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/config/sections.py +0 -0
  56. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/config/serialize.py +0 -0
  57. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/core/__init__.py +0 -0
  58. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/core/bandwidth.py +0 -0
  59. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/core/collection.py +0 -0
  60. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/core/noise.py +0 -0
  61. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/core/scalogram.py +0 -0
  62. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/core/spectrum.py +0 -0
  63. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/core/units.py +0 -0
  64. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/datasets.py +0 -0
  65. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/distance.py +0 -0
  66. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/exceptions.py +0 -0
  67. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/fitting/__init__.py +0 -0
  68. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/fitting/base.py +0 -0
  69. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/fitting/event.py +0 -0
  70. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/fitting/guess.py +0 -0
  71. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/fitting/spectrum.py +0 -0
  72. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/io.py +0 -0
  73. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/magnitude.py +0 -0
  74. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/picks/__init__.py +0 -0
  75. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/picks/base.py +0 -0
  76. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/picks/delimited.py +0 -0
  77. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/picks/events.py +0 -0
  78. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/picks/resolution.py +0 -0
  79. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/picks/snuffler.py +0 -0
  80. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/pipeline.py +0 -0
  81. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/plotting.py +0 -0
  82. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/preprocess.py +0 -0
  83. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/smoothing/__init__.py +0 -0
  84. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/smoothing/base.py +0 -0
  85. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/smoothing/konno_ohmachi.py +0 -0
  86. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/smoothing/log_bins.py +0 -0
  87. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/sources/__init__.py +0 -0
  88. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/sources/attenuation.py +0 -0
  89. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/sources/composite.py +0 -0
  90. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/sources/motion.py +0 -0
  91. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/sources/source.py +0 -0
  92. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/spreading.py +0 -0
  93. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/staged.py +0 -0
  94. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/tables.py +0 -0
  95. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/transforms/__init__.py +0 -0
  96. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/transforms/base.py +0 -0
  97. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/transforms/cwt.py +0 -0
  98. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/transforms/fft.py +0 -0
  99. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/transforms/multitaper.py +0 -0
  100. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/transforms/prieto.py +0 -0
  101. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/transforms/quadratic.py +0 -0
  102. {specmod-0.2.0 → specmod-0.2.2}/src/specmod/utils.py +0 -0
  103. {specmod-0.2.0 → specmod-0.2.2}/stubs/README.md +0 -0
  104. {specmod-0.2.0 → specmod-0.2.2}/stubs/lmfit/__init__.pyi +0 -0
  105. {specmod-0.2.0 → specmod-0.2.2}/stubs/lmfit/model.pyi +0 -0
  106. {specmod-0.2.0 → specmod-0.2.2}/stubs/lmfit/parameter.pyi +0 -0
  107. {specmod-0.2.0 → specmod-0.2.2}/stubs/obspy/__init__.pyi +0 -0
  108. {specmod-0.2.0 → specmod-0.2.2}/stubs/obspy/clients/__init__.pyi +0 -0
  109. {specmod-0.2.0 → specmod-0.2.2}/stubs/obspy/clients/fdsn/__init__.pyi +0 -0
  110. {specmod-0.2.0 → specmod-0.2.2}/stubs/obspy/core/__init__.pyi +0 -0
  111. {specmod-0.2.0 → specmod-0.2.2}/stubs/obspy/core/event.pyi +0 -0
  112. {specmod-0.2.0 → specmod-0.2.2}/stubs/obspy/core/inventory.pyi +0 -0
  113. {specmod-0.2.0 → specmod-0.2.2}/stubs/obspy/core/stream.pyi +0 -0
  114. {specmod-0.2.0 → specmod-0.2.2}/stubs/obspy/core/trace.pyi +0 -0
  115. {specmod-0.2.0 → specmod-0.2.2}/stubs/obspy/core/utcdatetime.pyi +0 -0
  116. {specmod-0.2.0 → specmod-0.2.2}/stubs/obspy/core/util/__init__.pyi +0 -0
  117. {specmod-0.2.0 → specmod-0.2.2}/stubs/obspy/core/util/base.pyi +0 -0
  118. {specmod-0.2.0 → specmod-0.2.2}/stubs/obspy/geodetics/__init__.pyi +0 -0
  119. {specmod-0.2.0 → specmod-0.2.2}/stubs/obspy/signal/__init__.pyi +0 -0
  120. {specmod-0.2.0 → specmod-0.2.2}/stubs/obspy/signal/konnoohmachismoothing.pyi +0 -0
  121. {specmod-0.2.0 → specmod-0.2.2}/studies/magna_2020_paper.toml +0 -0
  122. {specmod-0.2.0 → specmod-0.2.2}/tests/__init__.py +0 -0
  123. {specmod-0.2.0 → specmod-0.2.2}/tests/conftest.py +0 -0
  124. {specmod-0.2.0 → specmod-0.2.2}/tests/golden/motion_reference.json +0 -0
  125. {specmod-0.2.0 → specmod-0.2.2}/tests/golden/pipeline_reference.json +0 -0
  126. {specmod-0.2.0 → specmod-0.2.2}/tests/golden/window_reference.json +0 -0
  127. {specmod-0.2.0 → specmod-0.2.2}/tests/test_acquire.py +0 -0
  128. {specmod-0.2.0 → specmod-0.2.2}/tests/test_ambient_state.py +0 -0
  129. {specmod-0.2.0 → specmod-0.2.2}/tests/test_api_surface.py +0 -0
  130. {specmod-0.2.0 → specmod-0.2.2}/tests/test_collection.py +0 -0
  131. {specmod-0.2.0 → specmod-0.2.2}/tests/test_config.py +0 -0
  132. {specmod-0.2.0 → specmod-0.2.2}/tests/test_cwt.py +0 -0
  133. {specmod-0.2.0 → specmod-0.2.2}/tests/test_datasets.py +0 -0
  134. {specmod-0.2.0 → specmod-0.2.2}/tests/test_docs_are_current.py +0 -0
  135. {specmod-0.2.0 → specmod-0.2.2}/tests/test_end_to_end.py +0 -0
  136. {specmod-0.2.0 → specmod-0.2.2}/tests/test_fitting_defaults.py +0 -0
  137. {specmod-0.2.0 → specmod-0.2.2}/tests/test_golden_reference.py +0 -0
  138. {specmod-0.2.0 → specmod-0.2.2}/tests/test_import.py +0 -0
  139. {specmod-0.2.0 → specmod-0.2.2}/tests/test_io_and_plotting.py +0 -0
  140. {specmod-0.2.0 → specmod-0.2.2}/tests/test_legacy_fixes.py +0 -0
  141. {specmod-0.2.0 → specmod-0.2.2}/tests/test_magnitude.py +0 -0
  142. {specmod-0.2.0 → specmod-0.2.2}/tests/test_make_golden.py +0 -0
  143. {specmod-0.2.0 → specmod-0.2.2}/tests/test_pick_plugins.py +0 -0
  144. {specmod-0.2.0 → specmod-0.2.2}/tests/test_pick_readers.py +0 -0
  145. {specmod-0.2.0 → specmod-0.2.2}/tests/test_picks.py +0 -0
  146. {specmod-0.2.0 → specmod-0.2.2}/tests/test_pipeline.py +0 -0
  147. {specmod-0.2.0 → specmod-0.2.2}/tests/test_pipeline_smoke.py +0 -0
  148. {specmod-0.2.0 → specmod-0.2.2}/tests/test_preprocess.py +0 -0
  149. {specmod-0.2.0 → specmod-0.2.2}/tests/test_prieto.py +0 -0
  150. {specmod-0.2.0 → specmod-0.2.2}/tests/test_quadratic.py +0 -0
  151. {specmod-0.2.0 → specmod-0.2.2}/tests/test_release_config.py +0 -0
  152. {specmod-0.2.0 → specmod-0.2.2}/tests/test_smoothing.py +0 -0
  153. {specmod-0.2.0 → specmod-0.2.2}/tests/test_sources.py +0 -0
  154. {specmod-0.2.0 → specmod-0.2.2}/tests/test_spectral_wiring.py +0 -0
  155. {specmod-0.2.0 → specmod-0.2.2}/tests/test_staged.py +0 -0
  156. {specmod-0.2.0 → specmod-0.2.2}/tests/test_stubs.py +0 -0
  157. {specmod-0.2.0 → specmod-0.2.2}/tests/test_transforms.py +0 -0
  158. {specmod-0.2.0 → specmod-0.2.2}/tests/test_tutorial.py +0 -0
  159. {specmod-0.2.0 → specmod-0.2.2}/tests/test_typing_backlog.py +0 -0
  160. {specmod-0.2.0 → specmod-0.2.2}/tests/test_utils.py +0 -0
  161. {specmod-0.2.0 → specmod-0.2.2}/tests/test_versioning.py +0 -0
  162. {specmod-0.2.0 → specmod-0.2.2}/tools/check_built_version.py +0 -0
  163. {specmod-0.2.0 → specmod-0.2.2}/tools/check_floors.py +0 -0
  164. {specmod-0.2.0 → specmod-0.2.2}/tools/make_golden.py +0 -0
  165. {specmod-0.2.0 → specmod-0.2.2}/tools/measure_docs.py +0 -0
  166. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/picks/2019-08-26T07:30:47.000000Z.picks +0 -0
  167. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/picks/2019-08-26T07:30:47.000000Z.xml +0 -0
  168. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/stations/inventory.xml +0 -0
  169. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L001..HHE_2019-08-26T07:30:47.000000Z +0 -0
  170. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L001..HHN_2019-08-26T07:30:47.000000Z +0 -0
  171. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L001..HHZ_2019-08-26T07:30:47.000000Z +0 -0
  172. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L002..HHE_2019-08-26T07:30:47.000000Z +0 -0
  173. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L002..HHN_2019-08-26T07:30:47.000000Z +0 -0
  174. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L002..HHZ_2019-08-26T07:30:47.000000Z +0 -0
  175. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L006..HHE_2019-08-26T07:30:47.000000Z +0 -0
  176. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L006..HHN_2019-08-26T07:30:47.000000Z +0 -0
  177. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L006..HHZ_2019-08-26T07:30:47.000000Z +0 -0
  178. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L007..HHE_2019-08-26T07:30:47.000000Z +0 -0
  179. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L007..HHN_2019-08-26T07:30:47.000000Z +0 -0
  180. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L007..HHZ_2019-08-26T07:30:47.000000Z +0 -0
  181. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L008..HHE_2019-08-26T07:30:47.000000Z +0 -0
  182. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L008..HHN_2019-08-26T07:30:47.000000Z +0 -0
  183. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L008..HHZ_2019-08-26T07:30:47.000000Z +0 -0
  184. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L009..HHE_2019-08-26T07:30:47.000000Z +0 -0
  185. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L009..HHN_2019-08-26T07:30:47.000000Z +0 -0
  186. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L009..HHZ_2019-08-26T07:30:47.000000Z +0 -0
  187. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.LD06..HH1_2019-08-26T07:30:47.000000Z +0 -0
  188. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.LD06..HH2_2019-08-26T07:30:47.000000Z +0 -0
  189. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.LD06..HH3_2019-08-26T07:30:47.000000Z +0 -0
  190. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ01.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
  191. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ01.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
  192. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ01.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
  193. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ02.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
  194. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ03.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
  195. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ03.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
  196. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ03.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
  197. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ04.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
  198. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ04.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
  199. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ04.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
  200. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ05.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
  201. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ05.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
  202. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ05.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
  203. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ06.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
  204. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ06.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
  205. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ06.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
  206. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ07.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
  207. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ07.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
  208. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ07.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
  209. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ09.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
  210. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ09.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
  211. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ09.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
  212. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ10.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
  213. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ10.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
  214. {specmod-0.2.0 → specmod-0.2.2}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ10.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
@@ -33,7 +33,12 @@ jobs:
33
33
 
34
34
  # The package itself, not just the docs extra: autodoc imports every
35
35
  # module it documents, so a docs build is also an import check.
36
- - run: uv pip install -e ".[docs,io]"
36
+ #
37
+ # `tutorial` supplies the Jupyter kernel. `docs/conf.py` executes the
38
+ # tutorial notebook on every build instead of trusting committed outputs,
39
+ # so without ipykernel the build fails on a missing kernel. Keep this list
40
+ # in step with `.readthedocs.yaml`, which installs the same three.
41
+ - run: uv pip install -e ".[docs,io,tutorial]"
37
42
 
38
43
  - name: Build
39
44
  # No -W. Intersphinx resolves seven inventories over the network and
@@ -28,20 +28,44 @@ jobs:
28
28
  - uses: googleapis/release-please-action@v4
29
29
  id: release
30
30
  with:
31
+ # A pull request opened with the default GITHUB_TOKEN starts no
32
+ # workflow runs at all — "events triggered by the GITHUB_TOKEN will
33
+ # not create a new workflow run". The release PR therefore arrived
34
+ # with *zero* checks, and since `ci`, `docs` and `build` are required
35
+ # on main, it could never satisfy them: permanently blocked, with no
36
+ # failure to point at. v0.2.0 shipped only because the PR was closed
37
+ # and reopened by hand, which re-fires the event from a real account.
38
+ #
39
+ # RELEASE_PLEASE_TOKEN_SPECMOD is a fine-grained PAT (contents + pull
40
+ # requests, write) so the PR is opened by a user and CI runs on it
41
+ # normally. It is *not* a PyPI credential — the upload below still
42
+ # authenticates by OIDC, so the number of publishing secrets is
43
+ # still zero. See docs/releasing.md for how to mint and renew it.
44
+ #
45
+ # The fallback keeps the workflow working in a fork or before the
46
+ # secret exists; it degrades to the blocked-PR behaviour, not to a
47
+ # broken run.
48
+ token: ${{ secrets.RELEASE_PLEASE_TOKEN_SPECMOD || secrets.GITHUB_TOKEN }}
31
49
  config-file: release-please-config.json
32
50
  manifest-file: .release-please-manifest.json
33
51
 
34
52
  # Deliberately in this workflow rather than a separate publish.yml keyed on
35
- # `release: published`, which is what §6.5 of the plan used to describe.
36
- # release-please creates the release with the default GITHUB_TOKEN, and
37
- # per GitHub's docs "events triggered by the GITHUB_TOKEN will not create a
38
- # new workflow run" — so that publish workflow would never fire. The
39
- # alternative is a personal access token in secrets, which is the one thing
40
- # Trusted Publishing exists to avoid. Gating on the action's own output
41
- # keeps the token count at zero.
53
+ # `release: published`, which is what §6.5 of the plan used to describe. That
54
+ # workflow could not fire at all while release-please ran on GITHUB_TOKEN,
55
+ # so this job gates on the action's own `release_created` output instead.
42
56
  #
43
- # Zenodo is unaffected: it listens to the release *webhook*, which is not
44
- # subject to that restriction, so the DOI is still minted from the release.
57
+ # With RELEASE_PLEASE_TOKEN_SPECMOD the release is created by a PAT, which
58
+ # *does* start workflow runs, so a separate publish.yml would now work. Still
59
+ # not worth splitting: one workflow is one concurrency group and one place to
60
+ # read, with no race between two runs when two merges land together. The
61
+ # `release_created` gate is also exact, where an event key would fire on any
62
+ # release — including one created by hand.
63
+ #
64
+ # Note the PAT is not a publishing credential: the upload below authenticates
65
+ # by OIDC, so the number of secrets that can push to PyPI is still zero.
66
+ #
67
+ # Zenodo is unaffected either way: it listens to the release *webhook*, which
68
+ # is not subject to the token restriction, so the DOI is minted regardless.
45
69
  publish:
46
70
  needs: release-please
47
71
  if: needs.release-please.outputs.release_created == 'true'
@@ -26,6 +26,11 @@ htmlcov/
26
26
 
27
27
  # Docs
28
28
  docs/_build/
29
+ # `docs/conf.py` copies `tutorial/` here so Sphinx can build the notebook from
30
+ # inside its source directory, with the data it reads by relative path. The
31
+ # copy is also what the notebook's own output lands in, which is the point —
32
+ # executing it where it lives would write into the working tree.
33
+ docs/tutorial/
29
34
 
30
35
  # Editors / OS
31
36
  .ftpconfig
@@ -39,9 +44,12 @@ docs/_build/
39
44
 
40
45
  # Tutorial output. Regenerated by running the notebook; committing it means a
41
46
  # diff every time anyone executes it, and a stale copy the moment the pipeline
42
- # changes. The inputs (Tutorial/Data, Tutorial/MetaData) *are* committed.
43
- Tutorial/Spectra/*.h5
44
- Tutorial/Spectra/FlatFiles/
47
+ # changes. The inputs — waveforms, stations, picks — *are* committed.
48
+ #
49
+ # These patterns named `Tutorial/Spectra/` until now, the capitalised layout
50
+ # from before the `src/` move, so they had matched nothing for the whole
51
+ # refactor and the three files below were tracked in spite of the rule above.
52
+ tutorial/data/events/*/spectra/
45
53
 
46
54
  # Created by `uv run` without --no-sync; this project resolves fresh on
47
55
  # purpose so the floors job can test the declared minimums.
@@ -42,6 +42,12 @@ python:
42
42
  extra_requirements:
43
43
  # `io` alongside `docs` because autodoc imports every module it
44
44
  # documents, and `specmod.io` imports h5py and pyarrow. Without it the
45
- # API reference loses those pages to import errors.
45
+ # API reference loses those pages to import errors — and the tutorial
46
+ # saves an HDF5 file, so it needs them at execution time too.
46
47
  - docs
47
48
  - io
49
+ # `tutorial` supplies the Jupyter kernel. `docs/conf.py` executes the
50
+ # notebook on every build rather than trusting committed outputs, which
51
+ # needs ipykernel, nbclient and nbformat present. Without it the build
52
+ # fails on a missing kernel rather than quietly publishing stale cells.
53
+ - tutorial
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.2.2"
3
+ }
@@ -1,6 +1,38 @@
1
1
  # Changelog
2
2
 
3
- ## [0.2.0](https://github.com/sgjholt/SpecMod/compare/v0.1.1...v0.2.0) (2026-08-23)
3
+ <!-- The 0.2.0 heading below is edited by hand, and is the only entry that is.
4
+ release-please generated `compare/v0.1.1...v0.2.0`, which 404s: 0.1.1 predates
5
+ this repository's tagging and was never tagged, so there is nothing at that
6
+ ref. It is preserved on the frozen `master` branch instead, and comparing
7
+ against that branch is the same diff under a name that resolves. Only the first
8
+ release could have this problem — every later one has a real predecessor tag —
9
+ so leave subsequent headings as generated. release-please only prepends to this
10
+ file, so this edit survives. -->
11
+
12
+ ## [0.2.2](https://github.com/sgjholt/SpecMod/compare/v0.2.1...v0.2.2) (2026-09-05)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * ignore the tutorial output at the path it is actually written to ([ad34cc7](https://github.com/sgjholt/SpecMod/commit/ad34cc7d44208e5bd49a2d1f1277516670848dc7))
18
+
19
+
20
+ ### Documentation
21
+
22
+ * publish the tutorial, executed on every build ([ccd1842](https://github.com/sgjholt/SpecMod/commit/ccd18429b9da5db875885d88269fb70686e6eaa3))
23
+
24
+ ## [0.2.1](https://github.com/sgjholt/SpecMod/compare/v0.2.0...v0.2.1) (2026-08-23)
25
+
26
+
27
+ ### Documentation
28
+
29
+ * add the Zenodo DOI to the README and the citation metadata ([8a89884](https://github.com/sgjholt/SpecMod/commit/8a89884f654b14bf5eb9ed8a32d10dc22d5e5fb4))
30
+ * PyPI and docs badges, and an install section for people who are not us ([d444397](https://github.com/sgjholt/SpecMod/commit/d44439762214b93fd8eef3f64745570b3b8be2b0))
31
+ * repoint the 0.2.0 changelog link, which 404s ([f1bbe31](https://github.com/sgjholt/SpecMod/commit/f1bbe3143ca3ca6dd58f584546e716d2170a790a))
32
+ * say where the code behind the publication lives ([ae7e906](https://github.com/sgjholt/SpecMod/commit/ae7e906209a87844cf2106a826be5111a6ef2036))
33
+ * turn the roadmap's stages into milestones against versions ([b933e69](https://github.com/sgjholt/SpecMod/commit/b933e69d219a54e4a7db894797a507a3bb24e31c))
34
+
35
+ ## [0.2.0](https://github.com/sgjholt/SpecMod/compare/master...v0.2.0) (2026-08-23)
4
36
 
5
37
 
6
38
  ### ⚠ BREAKING CHANGES
@@ -7,6 +7,13 @@ authors:
7
7
  given-names: James
8
8
  repository-code: "https://github.com/sgjholt/SpecMod"
9
9
  license: MIT
10
+ identifiers:
11
+ # The concept DOI, which always resolves to the newest release. Zenodo also
12
+ # mints a version DOI per release; cite that one instead when reproducibility
13
+ # matters and you need the reader to land on the exact version you ran.
14
+ - type: doi
15
+ value: "10.5281/zenodo.22071455"
16
+ description: "Concept DOI — resolves to the latest release."
10
17
  keywords:
11
18
  - seismology
12
19
  - spectral modelling
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: specmod
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: A Python toolbox for processing and modelling seismic spectra
5
5
  Project-URL: Homepage, https://github.com/sgjholt/SpecMod
6
6
  Project-URL: Repository, https://github.com/sgjholt/SpecMod
@@ -56,6 +56,10 @@ Description-Content-Type: text/markdown
56
56
 
57
57
  # SpecMod
58
58
 
59
+ [![PyPI](https://img.shields.io/pypi/v/specmod.svg)](https://pypi.org/project/specmod/)
60
+ [![Documentation](https://readthedocs.org/projects/specmod/badge/?version=stable)](https://specmod.readthedocs.io/en/stable/)
61
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22071455.svg)](https://doi.org/10.5281/zenodo.22071455)
62
+
59
63
  A Python toolbox for processing and modelling seismic spectra, following the
60
64
  method of Edwards *et al.* (2010).
61
65
 
@@ -71,26 +75,44 @@ source model to direct-phase spectra.
71
75
  > breaking changes at every `0.x` release until the API settles at 1.0 — they
72
76
  > land in minor bumps by design, with no deprecation cycle. Pin an exact
73
77
  > version for anything you intend to publish.
74
- > [`docs/roadmap.md`](docs/roadmap.md) says which stages are done and what 1.0
75
- > will mean; [`docs/REFACTOR_PLAN.md`](docs/REFACTOR_PLAN.md) is the working
76
- > document behind it.
78
+ > [`docs/roadmap.md`](docs/roadmap.md) says what has shipped, in which version,
79
+ > and what 1.0 will mean; [`docs/REFACTOR_PLAN.md`](docs/REFACTOR_PLAN.md) is
80
+ > the working document behind it.
77
81
 
78
82
  ## Installation
79
83
 
80
84
  Requires Python 3.11 or newer.
81
85
 
86
+ ```sh
87
+ pip install specmod
88
+ ```
89
+
90
+ While this is `0.x`, pin the exact version in anything you intend to publish:
91
+ `pip install specmod==<version>`, taking the current one from the badge above.
92
+ The reason is in the status note — `0.x` releases move names and numbers, and
93
+ the pin plus the configuration stamp each output carries are together what
94
+ make a run reproducible.
95
+
96
+ To work on SpecMod rather than with it, clone the repository and install it
97
+ editable with the test and lint tooling:
98
+
82
99
  ```sh
83
100
  pip install -e ".[dev]"
84
101
  ```
85
102
 
86
- Optional extras:
103
+ Optional extras, installable as `pip install "specmod[multitaper]"` or in
104
+ combination — `pip install "specmod[multitaper,wavelet]"`:
87
105
 
88
106
  | Extra | Adds |
89
107
  |---|---|
108
+ | `io` | `h5py` and `pyarrow` — needed to save or load spectra, as HDF5 for arrays and Parquet for tables |
90
109
  | `multitaper` | Prieto's `multitaper` package — jackknife confidence intervals, F-test for spectral lines |
91
110
  | `wavelet` | PyWavelets, for wavelet families beyond the built-in Morlet |
92
111
  | `mcmc` | `emcee`, for Markov-chain Monte Carlo parameter search |
93
112
 
113
+ `io` is the one most people want: without it SpecMod computes and plots
114
+ normally, but `specmod.io` raises on the first save telling you to install it.
115
+
94
116
  No Fortran compiler is needed. Multitaper estimation is implemented natively on
95
117
  SciPy's DPSS tapers, so the historical `mtspec` dependency — Fortran source with
96
118
  no wheels and no release since 2016 — is no longer required.
@@ -202,6 +224,13 @@ Every output records the configuration that produced it, a hash of it, and the
202
224
  SpecMod version, so a locally-overridden run is still reproducible from its
203
225
  outputs.
204
226
 
227
+ The published Magna results were produced with **0.1.1**, which predates this
228
+ refactor. That code is preserved unchanged on the
229
+ [`master`](https://github.com/sgjholt/SpecMod/tree/master) branch, which is
230
+ protected and frozen; `main` is the trunk now. `0.1.1` was never tagged or
231
+ published to PyPI, so the branch is the reference — there is no release to
232
+ install. Read it there when you need to see exactly what the paper ran.
233
+
205
234
  ## Documentation
206
235
 
207
236
  The full documentation — the pipeline with its equations, the estimator
@@ -1,5 +1,9 @@
1
1
  # SpecMod
2
2
 
3
+ [![PyPI](https://img.shields.io/pypi/v/specmod.svg)](https://pypi.org/project/specmod/)
4
+ [![Documentation](https://readthedocs.org/projects/specmod/badge/?version=stable)](https://specmod.readthedocs.io/en/stable/)
5
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22071455.svg)](https://doi.org/10.5281/zenodo.22071455)
6
+
3
7
  A Python toolbox for processing and modelling seismic spectra, following the
4
8
  method of Edwards *et al.* (2010).
5
9
 
@@ -15,26 +19,44 @@ source model to direct-phase spectra.
15
19
  > breaking changes at every `0.x` release until the API settles at 1.0 — they
16
20
  > land in minor bumps by design, with no deprecation cycle. Pin an exact
17
21
  > version for anything you intend to publish.
18
- > [`docs/roadmap.md`](docs/roadmap.md) says which stages are done and what 1.0
19
- > will mean; [`docs/REFACTOR_PLAN.md`](docs/REFACTOR_PLAN.md) is the working
20
- > document behind it.
22
+ > [`docs/roadmap.md`](docs/roadmap.md) says what has shipped, in which version,
23
+ > and what 1.0 will mean; [`docs/REFACTOR_PLAN.md`](docs/REFACTOR_PLAN.md) is
24
+ > the working document behind it.
21
25
 
22
26
  ## Installation
23
27
 
24
28
  Requires Python 3.11 or newer.
25
29
 
30
+ ```sh
31
+ pip install specmod
32
+ ```
33
+
34
+ While this is `0.x`, pin the exact version in anything you intend to publish:
35
+ `pip install specmod==<version>`, taking the current one from the badge above.
36
+ The reason is in the status note — `0.x` releases move names and numbers, and
37
+ the pin plus the configuration stamp each output carries are together what
38
+ make a run reproducible.
39
+
40
+ To work on SpecMod rather than with it, clone the repository and install it
41
+ editable with the test and lint tooling:
42
+
26
43
  ```sh
27
44
  pip install -e ".[dev]"
28
45
  ```
29
46
 
30
- Optional extras:
47
+ Optional extras, installable as `pip install "specmod[multitaper]"` or in
48
+ combination — `pip install "specmod[multitaper,wavelet]"`:
31
49
 
32
50
  | Extra | Adds |
33
51
  |---|---|
52
+ | `io` | `h5py` and `pyarrow` — needed to save or load spectra, as HDF5 for arrays and Parquet for tables |
34
53
  | `multitaper` | Prieto's `multitaper` package — jackknife confidence intervals, F-test for spectral lines |
35
54
  | `wavelet` | PyWavelets, for wavelet families beyond the built-in Morlet |
36
55
  | `mcmc` | `emcee`, for Markov-chain Monte Carlo parameter search |
37
56
 
57
+ `io` is the one most people want: without it SpecMod computes and plots
58
+ normally, but `specmod.io` raises on the first save telling you to install it.
59
+
38
60
  No Fortran compiler is needed. Multitaper estimation is implemented natively on
39
61
  SciPy's DPSS tapers, so the historical `mtspec` dependency — Fortran source with
40
62
  no wheels and no release since 2016 — is no longer required.
@@ -146,6 +168,13 @@ Every output records the configuration that produced it, a hash of it, and the
146
168
  SpecMod version, so a locally-overridden run is still reproducible from its
147
169
  outputs.
148
170
 
171
+ The published Magna results were produced with **0.1.1**, which predates this
172
+ refactor. That code is preserved unchanged on the
173
+ [`master`](https://github.com/sgjholt/SpecMod/tree/master) branch, which is
174
+ protected and frozen; `main` is the trunk now. `0.1.1` was never tagged or
175
+ published to PyPI, so the branch is the reference — there is no release to
176
+ install. Read it there when you need to see exactly what the paper ran.
177
+
149
178
  ## Documentation
150
179
 
151
180
  The full documentation — the pipeline with its equations, the estimator
@@ -14,7 +14,9 @@ gives.
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
+ import shutil
17
18
  from importlib.metadata import PackageNotFoundError, version
19
+ from pathlib import Path
18
20
 
19
21
  project = "SpecMod"
20
22
  author = "James Holt"
@@ -32,7 +34,9 @@ except PackageNotFoundError: # pragma: no cover - docs built without an install
32
34
  version = ".".join(release.split(".")[:2])
33
35
 
34
36
  extensions = [
35
- "myst_parser",
37
+ #: `myst_nb` supersedes `myst_parser` — it is a superset, and loading both
38
+ #: makes Sphinx complain that the `.md` parser is registered twice.
39
+ "myst_nb",
36
40
  "sphinx.ext.autodoc",
37
41
  "sphinx.ext.napoleon",
38
42
  "sphinx.ext.intersphinx",
@@ -45,9 +49,10 @@ extensions = [
45
49
 
46
50
  #: `REFACTOR_PLAN.md` is a working document, not documentation — it is written
47
51
  #: for whoever is doing the refactor and records decisions and their evidence.
48
- #: `notebooks/` is built by Phase 6 with myst-nb; until then the `.ipynb` files
49
- #: would be copied in without being executed, which is worse than leaving them
50
- #: out. `notes/` *is* included: `choosing-a-transform.md` links to it for a
52
+ #: `notebooks/` holds the source for the transform comparison, which
53
+ #: `tools/measure_docs.py` renders into `choosing-a-transform.md`; the page is
54
+ #: what is published, so building the notebook too would duplicate it.
55
+ #: `notes/` *is* included: `choosing-a-transform.md` links to it for a
51
56
  #: per-trace table, so excluding it broke that link.
52
57
  exclude_patterns = [
53
58
  "_build",
@@ -57,9 +62,58 @@ exclude_patterns = [
57
62
  ".DS_Store",
58
63
  ]
59
64
 
60
- #: Markdown only. Every page in `docs/` is already written that way, and
61
- #: allowing both means two syntaxes for the same job.
62
- source_suffix = {".md": "markdown"}
65
+ # ------------------------------------------------------- the tutorial notebook
66
+
67
+ #: The tutorial lives in `tutorial/`, outside this source directory, next to the
68
+ #: 1 MB of PNR waveforms it reads through paths relative to itself. Sphinx only
69
+ #: builds what is under `docs/`, so it is copied in here.
70
+ #:
71
+ #: A copy rather than a move, and rather than executing it where it sits,
72
+ #: because **the notebook writes**: cell 53 saves an HDF5 file under
73
+ #: `data/events/<event>/spectra/` and cell 54 writes flatfiles beside it. Run in
74
+ #: place, a docs build would leave those artefacts in the working tree — which
75
+ #: is why `tests/test_tutorial.py` executes it in a `tmp_path` copy too. This is
76
+ #: the same trick, and the copy is what the artefacts land in.
77
+ #:
78
+ #: Copying the data with it is what keeps the notebook's `Path("data/events")`
79
+ #: working, so the notebook is identical whether opened from `tutorial/`, run by
80
+ #: pytest, or built here. `tutorial/` stays canonical: eight other files name
81
+ #: `tutorial/data/events/`, and the CI job that executes it records that a
82
+ #: renamed data directory has already broken this once.
83
+ _HERE = Path(__file__).parent
84
+ _TUTORIAL_SRC = _HERE.parent / "tutorial"
85
+ _TUTORIAL_DST = _HERE / "tutorial"
86
+
87
+ if _TUTORIAL_SRC.is_dir():
88
+ shutil.rmtree(_TUTORIAL_DST, ignore_errors=True)
89
+ shutil.copytree(_TUTORIAL_SRC, _TUTORIAL_DST)
90
+
91
+ #: `force`, not `auto`. `auto` executes only notebooks that arrive without
92
+ #: outputs, and the tutorial is committed *with* 28 cells of them — so `auto`
93
+ #: would publish whatever was last saved by hand, which is the silent rot this
94
+ #: is meant to end. Forcing it means the page can only show output the code
95
+ #: actually produced against the code being documented.
96
+ nb_execution_mode = "force"
97
+ #: Matches `tests/test_tutorial.py`. The whole notebook runs in ~40s; the
98
+ #: default 30s is per cell, and the two-stage fit is the one that would trip it.
99
+ nb_execution_timeout = 600
100
+ #: A notebook that raises fails the build. That is the point: an executed
101
+ #: tutorial is only a guarantee if a broken one is loud.
102
+ nb_execution_raise_on_error = True
103
+ #: `False` keeps the kernel's working directory at the notebook's own, which is
104
+ #: what `Path("data/events")` resolves against.
105
+ nb_execution_in_temp = False
106
+
107
+ #: Markdown for the prose pages — every one in `docs/` is written that way, and
108
+ #: allowing reStructuredText too would mean two syntaxes for the same job — plus
109
+ #: `.ipynb` for the tutorial.
110
+ #:
111
+ #: Both map to `myst-nb`, which is the only parser name `myst_nb` 1.4 registers:
112
+ #: it does not re-register `myst_parser`'s `markdown`, so leaving `.md` pointing
113
+ #: at that fails the build outright with "Source parser for markdown not
114
+ #: registered". The `myst-nb` parser is a superset and reads the prose pages
115
+ #: identically.
116
+ source_suffix = {".md": "myst-nb", ".ipynb": "myst-nb"}
63
117
 
64
118
  #: `linkify` is deliberately absent: it needs `linkify-it-py` and every link in
65
119
  #: these pages is already explicit.
@@ -195,8 +195,11 @@ pages can link to each other's sections.
195
195
  those work; without it MyST does not read `$` at all.
196
196
  - `amsmath` is **not** enabled — nothing here uses a bare `\begin{align}`
197
197
  outside `$` delimiters. A `\begin{cases}` inside `$$` renders without it.
198
- - Markdown only. `source_suffix` maps `.md`; there is no reStructuredText in
199
- `docs/`, so there is one syntax rather than two.
198
+ - Markdown and notebooks. `source_suffix` maps `.md` and `.ipynb`, both to
199
+ `myst-nb` — there is no reStructuredText in `docs/`, so there is one prose
200
+ syntax rather than two. Both suffixes name `myst-nb` because that is the only
201
+ parser `myst_nb` registers; pointing `.md` at `myst_parser`'s `markdown`
202
+ fails the build with "Source parser for markdown not registered".
200
203
  - For a Sphinx directive that has no MyST spelling, drop into rST:
201
204
 
202
205
  ````markdown
@@ -225,6 +228,40 @@ Type hints come from the annotations via `autodoc_typehints = "description"`.
225
228
  `sphinx-autodoc-typehints` is deliberately **not** used: measured, it produced
226
229
  the same 367 documented objects while calling an API Sphinx 10 removes.
227
230
 
231
+ ### The tutorial notebook
232
+
233
+ `tutorial/SpecModTutorial.ipynb` is published as part of the site and
234
+ **executed on every build** (`nb_execution_mode = "force"`). Every figure and
235
+ number on the page came from running that code against the code being
236
+ documented, and a notebook that raises fails the build
237
+ (`nb_execution_raise_on_error = True`). That is the whole point: a tutorial
238
+ nobody runs is the first thing to rot, and this one broke three times before
239
+ anything executed it.
240
+
241
+ Three things about the arrangement are worth knowing before changing it.
242
+
243
+ **It is copied into `docs/tutorial/` by `conf.py`, not moved.** Sphinx builds
244
+ only what is under `docs/`, and the notebook reads its 1 MB of waveforms
245
+ through paths relative to itself — so the data has to travel with it. The copy
246
+ is gitignored. `tutorial/` stays canonical because eight other files name
247
+ `tutorial/data/events/`, and a renamed data directory has broken this before.
248
+
249
+ **The copy is also what the notebook writes into.** It saves an HDF5 file and
250
+ two flatfiles as part of the lesson. Executed where it lives, a docs build
251
+ would leave those in your working tree; `tests/test_tutorial.py` copies to
252
+ `tmp_path` for exactly the same reason.
253
+
254
+ **Outputs are stripped in git**, by the `nbstripout` pre-commit hook, and
255
+ regenerated at build time. Do not commit them back: `force` ignores them, so a
256
+ committed output is never what a reader sees — only a stale diff.
257
+
258
+ The kernel comes from the `tutorial` extra. Both `.readthedocs.yaml` and
259
+ `.github/workflows/docs.yml` install `[docs,io,tutorial]`; keep them in step or
260
+ one of the two builds fails on a missing kernel. This does mean the notebook
261
+ executes twice per pull request — once here and once in the `notebook` CI job,
262
+ which also checks imports and that deleted modules stay unmentioned. Roughly
263
+ 40 seconds, paid twice, for two genuinely different failures.
264
+
228
265
  ### Numbers in prose
229
266
 
230
267
  Any table that came from a measurement is generated, not typed. Edit
@@ -24,8 +24,8 @@ treat everything here as provisional until the API settles at 1.0:
24
24
 
25
25
  What will *not* change silently: the units conventions and the Parseval
26
26
  contract are pinned by tests, and the golden references fail loudly rather
27
- than drifting. The [roadmap](roadmap.md) says which stages are done and what
28
- 1.0 will mean.
27
+ than drifting. The [roadmap](roadmap.md) says what has shipped, in which
28
+ version, and what 1.0 will mean.
29
29
  :::
30
30
 
31
31
  ```python
@@ -45,6 +45,11 @@ print(fits.table[["id", "llpsp", "fc", "ts"]])
45
45
 
46
46
  ## Where to start
47
47
 
48
+ [Tutorial](tutorial/SpecModTutorial.ipynb)
49
+ : One event end to end — waveforms and picks in, source parameters out. Every
50
+ figure and number on the page is produced by running the notebook at build
51
+ time, so it cannot describe an API that no longer exists.
52
+
48
53
  [Processing](processing.md)
49
54
  : Every step of the pipeline with the equation it implements — what a window
50
55
  is, how the noise is compared against it, and what the bandwidth selector
@@ -67,8 +72,8 @@ print(fits.table[["id", "llpsp", "fc", "ts"]])
67
72
  settings that have to be turned on once.
68
73
 
69
74
  [Roadmap](roadmap.md)
70
- : What is built, what is being worked on, and what 1.0 will mean. Stages, not
71
- dates.
75
+ : What has shipped and in which version, what is being worked on, and what 1.0
76
+ will mean. Milestones, not dates.
72
77
 
73
78
  ## Working on SpecMod
74
79
 
@@ -97,6 +102,7 @@ one either way.
97
102
  :maxdepth: 2
98
103
  :hidden:
99
104
 
105
+ tutorial/SpecModTutorial
100
106
  processing
101
107
  choosing-a-transform
102
108
  pick-formats