specmod 0.3.0__tar.gz → 0.4.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.
- specmod-0.4.0/.release-please-manifest.json +3 -0
- {specmod-0.3.0 → specmod-0.4.0}/CHANGELOG.md +23 -0
- {specmod-0.3.0 → specmod-0.4.0}/PKG-INFO +1 -1
- {specmod-0.3.0 → specmod-0.4.0}/docs/REFACTOR_PLAN.md +99 -20
- {specmod-0.3.0 → specmod-0.4.0}/docs/choosing-a-transform.md +84 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/development.md +2 -1
- {specmod-0.3.0 → specmod-0.4.0}/docs/roadmap.md +27 -8
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/config/sections.py +32 -4
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/core/collection.py +103 -17
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/pipeline.py +41 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/smoothing/__init__.py +14 -2
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/smoothing/base.py +16 -1
- specmod-0.4.0/src/specmod/smoothing/log_window.py +215 -0
- specmod-0.4.0/src/specmod/smoothing/savitzky_golay.py +127 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/golden/motion_reference.json +140 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/golden/pipeline_reference.json +140 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/golden/window_reference.json +140 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_config.py +99 -1
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_golden_reference.py +70 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_pipeline.py +123 -1
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_release_config.py +88 -0
- specmod-0.4.0/tests/test_smoothing.py +431 -0
- specmod-0.4.0/tests/test_tools_track_the_api.py +125 -0
- {specmod-0.3.0 → specmod-0.4.0}/tools/make_golden.py +34 -4
- {specmod-0.3.0 → specmod-0.4.0}/tools/measure_docs.py +106 -6
- specmod-0.3.0/.release-please-manifest.json +0 -3
- specmod-0.3.0/tests/test_smoothing.py +0 -226
- {specmod-0.3.0 → specmod-0.4.0}/.git-blame-ignore-revs +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/.github/workflows/build.yml +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/.github/workflows/docs.yml +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/.github/workflows/release.yml +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/.github/workflows/test.yml +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/.gitignore +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/.pre-commit-config.yaml +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/.readthedocs.yaml +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/AGENTS.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/CITATION.cff +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/CLAUDE.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/CONTRIBUTING.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/LICENSE +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/README.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/datasets/magna_2020.toml +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/datasets/pnr_2019.toml +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_builders/_notebook.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_builders/choosing_a_transform.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_builders/specmod_tutorial.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/academic.css +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/fonts/OFL-fira-code.txt +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/fonts/OFL-merriweather.txt +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/fonts/OFL-open-sans.txt +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/fonts/README.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/fonts/fira-code-400-greek.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/fonts/fira-code-400-latin-ext.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/fonts/fira-code-400-latin.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/fonts/merriweather-latin-ext.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/fonts/merriweather-latin.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/fonts/open-sans-greek.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/fonts/open-sans-latin-ext.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/fonts/open-sans-latin.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/fonts.css +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/LICENSE +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/README.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/katex/katex.min.css +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/specmod-logo-academic-dark-mark.svg +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/specmod-logo-academic-dark.svg +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/specmod-logo-academic-mark.svg +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/_static/specmod-logo-academic.svg +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/api.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/branding.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/conf.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/contributing.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/documentation.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/getting-started.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/guides.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/index.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/notebooks/choosing-a-transform.ipynb +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/notes/api-audit.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/notes/window-position.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/pick-formats.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/processing.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/releasing-data.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/releasing.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/docs/upgrading.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/pyproject.toml +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/release-please-config.json +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/requirements.txt +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/__init__.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/_vendor/__init__.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/_vendor/qiinv.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/acquire.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/api.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/cli.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/config/__init__.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/config/layers.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/config/provenance.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/config/serialize.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/core/__init__.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/core/bandwidth.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/core/noise.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/core/scalogram.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/core/spectrum.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/core/units.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/datasets.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/distance.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/exceptions.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/fitting/__init__.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/fitting/base.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/fitting/event.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/fitting/guess.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/fitting/spectrum.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/io/__init__.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/io/backend.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/io/hdf5.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/io/layout.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/magnitude.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/picks/__init__.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/picks/base.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/picks/delimited.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/picks/events.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/picks/resolution.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/picks/snuffler.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/plotting/__init__.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/plotting/grid.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/plotting/pair.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/plotting/style.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/preprocess.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/smoothing/konno_ohmachi.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/smoothing/log_bins.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/sources/__init__.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/sources/attenuation.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/sources/composite.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/sources/motion.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/sources/source.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/spreading.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/staged.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/tables.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/transforms/__init__.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/transforms/base.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/transforms/cwt.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/transforms/fft.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/transforms/multitaper.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/transforms/prieto.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/transforms/quadratic.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/src/specmod/utils.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/stubs/README.md +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/stubs/lmfit/__init__.pyi +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/stubs/lmfit/model.pyi +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/stubs/lmfit/parameter.pyi +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/stubs/obspy/__init__.pyi +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/stubs/obspy/clients/__init__.pyi +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/stubs/obspy/clients/fdsn/__init__.pyi +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/stubs/obspy/core/__init__.pyi +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/stubs/obspy/core/event.pyi +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/stubs/obspy/core/inventory.pyi +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/stubs/obspy/core/stream.pyi +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/stubs/obspy/core/trace.pyi +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/stubs/obspy/core/utcdatetime.pyi +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/stubs/obspy/core/util/__init__.pyi +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/stubs/obspy/core/util/base.pyi +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/stubs/obspy/geodetics/__init__.pyi +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/stubs/obspy/signal/__init__.pyi +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/stubs/obspy/signal/konnoohmachismoothing.pyi +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/studies/magna_2020_paper.toml +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/__init__.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/conftest.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_acquire.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_ambient_state.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_api_surface.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_collection.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_cwt.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_datasets.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_docs_are_current.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_end_to_end.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_fitting_defaults.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_import.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_io_and_plotting.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_legacy_fixes.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_magnitude.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_make_golden.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_pick_plugins.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_pick_readers.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_picks.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_pipeline_smoke.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_preprocess.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_prieto.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_quadratic.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_sources.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_spectral_wiring.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_staged.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_stubs.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_transforms.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_tutorial.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_typing_backlog.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_utils.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tests/test_versioning.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tools/check_built_version.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tools/check_floors.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tools/make_logo.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tools/vendor_fonts.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tools/vendor_katex.py +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/picks/2019-08-26T07:30:47.000000Z.picks +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/picks/2019-08-26T07:30:47.000000Z.xml +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/stations/inventory.xml +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L001..HHE_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L001..HHN_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L001..HHZ_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L002..HHE_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L002..HHN_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L002..HHZ_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L006..HHE_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L006..HHN_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L006..HHZ_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L007..HHE_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L007..HHN_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L007..HHZ_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L008..HHE_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L008..HHN_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L008..HHZ_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L009..HHE_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L009..HHN_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.L009..HHZ_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.LD06..HH1_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.LD06..HH2_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/LV.LD06..HH3_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ01.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ01.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ01.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ02.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ03.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ03.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ03.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ04.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ04.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ04.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ05.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ05.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ05.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ06.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ06.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ06.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ07.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ07.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ07.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ09.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ09.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ09.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ10.00.HHE_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ10.00.HHN_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/data/events/2019-08-26T07:30:47.000000Z/waveforms/UR.AQ10.00.HHZ_2019-08-26T07:30:47.000000Z +0 -0
- {specmod-0.3.0 → specmod-0.4.0}/tutorial/specmod-tutorial.ipynb +0 -0
|
@@ -9,6 +9,29 @@ release could have this problem — every later one has a real predecessor tag
|
|
|
9
9
|
so leave subsequent headings as generated. release-please only prepends to this
|
|
10
10
|
file, so this edit survives. -->
|
|
11
11
|
|
|
12
|
+
## [0.4.0](https://github.com/sgjholt/SpecMod/compare/v0.3.0...v0.4.0) (2026-09-16)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **smoothing:** wire [smoothing] method up, and add three more methods ([fff3ab1](https://github.com/sgjholt/SpecMod/commit/fff3ab1ef01a7f4d942fd776821d0418a0642a55))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* **config:** refuse a [smoothing] method the pipeline does not apply ([1f18c68](https://github.com/sgjholt/SpecMod/commit/1f18c68a71794c2b1612a7cf0feedcfef39b625b))
|
|
23
|
+
* **test:** compare the roadmap against the published minor, not the proposed one ([1e34b4e](https://github.com/sgjholt/SpecMod/commit/1e34b4e5ab2bdef5d65f6fc0d620a42b9c3891eb))
|
|
24
|
+
* **tools:** call the preprocess API that 0.3.0 left behind ([8501c8e](https://github.com/sgjholt/SpecMod/commit/8501c8ed618e5d652b9eeffc36893115a58fd7d5))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Documentation
|
|
28
|
+
|
|
29
|
+
* keep the reasoning out of the docstrings, in comments where it belongs ([2684ec9](https://github.com/sgjholt/SpecMod/commit/2684ec923c8dcf59e882b2d11e9dc11ccbcf3766))
|
|
30
|
+
* say what tagging the frozen commit does and does not trigger ([2273219](https://github.com/sgjholt/SpecMod/commit/2273219e2a78ac1c1562118c2a1b3bce4df597dd))
|
|
31
|
+
* state what the smoothers are, not how they were arrived at ([4c97491](https://github.com/sgjholt/SpecMod/commit/4c97491d50aba5a1468304072ab3c6ed2904f2e0))
|
|
32
|
+
* the 0.3.0 work shipped a week ago, so stop calling it unreleased ([e2aaced](https://github.com/sgjholt/SpecMod/commit/e2aaced546a247b2c5967f01586c19928bcfe297))
|
|
33
|
+
* v0.1.1 is master's tip, which is what freezing it was for ([db50972](https://github.com/sgjholt/SpecMod/commit/db509729f3b7877a20b5691dddec8a6e139fb466))
|
|
34
|
+
|
|
12
35
|
## [0.3.0](https://github.com/sgjholt/SpecMod/compare/v0.2.3...v0.3.0) (2026-09-06)
|
|
13
36
|
|
|
14
37
|
|
|
@@ -1087,7 +1087,7 @@ and there are presumably more in your working directories.
|
|
|
1087
1087
|
Two ways out, and the first is much better:
|
|
1088
1088
|
|
|
1089
1089
|
1. **Convert in the old environment.** Phase 0 is already building a Docker image
|
|
1090
|
-
where the 0.1.
|
|
1090
|
+
where the 0.1.1 code runs (§6.7). Add a small
|
|
1091
1091
|
`scripts/convert_legacy_spec.py` to it that loads `.spec` files with the old
|
|
1092
1092
|
classes present and writes the new HDF5 format. This is a one-shot migration
|
|
1093
1093
|
with no lasting cost to the codebase.
|
|
@@ -2742,13 +2742,30 @@ Two further problems with reproducing the published run from this repository:
|
|
|
2742
2742
|
manuscript filename (`...SRL3`) suggests a third revision, which would also
|
|
2743
2743
|
explain a 2021 code state behind a 2020 access date.
|
|
2744
2744
|
|
|
2745
|
-
|
|
2745
|
+
The evidence narrows it: **the candidate is `ba3f7ec` or later, not the
|
|
2746
2746
|
pre-November-2020 commits**, because the earlier code cannot perform the
|
|
2747
2747
|
workflow the paper describes. Only `09f57b9` (removed cython) changes
|
|
2748
2748
|
`specmod/` after that, so the realistic candidates are `ba3f7ec` and
|
|
2749
|
-
`453c77c`.
|
|
2750
|
-
|
|
2751
|
-
|
|
2749
|
+
`453c77c`.
|
|
2750
|
+
|
|
2751
|
+
**Answered: `453c77c`** — the tip of `master`, 2021-08-12. On the author's
|
|
2752
|
+
word, and it is why `master` was frozen at that commit rather than at any
|
|
2753
|
+
other: the branch *is* the v0.1.1 record (§6.7). Which of the two the
|
|
2754
|
+
evidence above prefers was never the question it looked like.
|
|
2755
|
+
|
|
2756
|
+
What that settles and what it does not: the code is identified, so §5.2.6
|
|
2757
|
+
step 2 can be run against a known commit. The dating oddity is unchanged —
|
|
2758
|
+
the manuscript's access date still precedes the commit it cites, which the
|
|
2759
|
+
third revision (`...SRL3`) explains without needing the code to have moved.
|
|
2760
|
+
It is recorded rather than resolved, because nothing in the repository can
|
|
2761
|
+
resolve it.
|
|
2762
|
+
|
|
2763
|
+
This is still the strongest possible argument for tagging that commit (§6.7)
|
|
2764
|
+
and for `hatch-vcs` (§6.4): a version that takes a page of archaeology to
|
|
2765
|
+
identify must never happen again. **No `v0.1.1` tag exists yet.** Creating
|
|
2766
|
+
one is safe — `453c77c` is an ancestor of `main`, and every later release
|
|
2767
|
+
tag outranks it for `hatch-vcs`, so nothing derives a version from it — but
|
|
2768
|
+
it is repository state, not something a commit here can carry.
|
|
2752
2769
|
2. **The full published pipeline is not in this repository.** The two-stage
|
|
2753
2770
|
inversion (fit Ω/`f_c`/`t*` free, then fix event `f_c` to the
|
|
2754
2771
|
inverse-hypocentral-distance-weighted mean of station `f_c` and refit) and the
|
|
@@ -3414,6 +3431,31 @@ been audited once is worth more than one that reads confidently throughout.
|
|
|
3414
3431
|
| "`commitlint`-enforced" again, in §6.4 | The §7 instance was corrected and this one was left standing, in the same document. §6.4 corrected. |
|
|
3415
3432
|
| `publish.yml` "GitHub Release published → PyPI" | It would never have run. release-please creates the release with the default `GITHUB_TOKEN`, and events triggered by that token do not start a workflow. The publish job moved into `release.yml`, gated on `release_created`. §6.5 corrected. |
|
|
3416
3433
|
|
|
3434
|
+
**A fourth of the same shape, found later** — the three above were corrected in
|
|
3435
|
+
the prose; this one was a live setting:
|
|
3436
|
+
|
|
3437
|
+
> `[smoothing] method` — "Name -> smoother, for resolving `SmoothingConfig.method`"
|
|
3438
|
+
|
|
3439
|
+
`SMOOTHERS` existed, `method` validated against its `Literal`, and **no code
|
|
3440
|
+
anywhere read it**. The comparison binned with `log_bin` unconditionally.
|
|
3441
|
+
Measured on the 28 PNR windows, `log_bins`, `konno_ohmachi` and `none` produced
|
|
3442
|
+
bit-identical output — `none` included, which reads as "leave my spectra
|
|
3443
|
+
unsmoothed" while every spectrum went on being binned exactly as before.
|
|
3444
|
+
|
|
3445
|
+
**Fixed rather than documented.** `SpectrumPair.compare` now takes a `smoother`,
|
|
3446
|
+
`[smoothing] method` resolves to one, and the registry gained `log_window`
|
|
3447
|
+
(constant-relative-bandwidth smoothing with a selectable window shape, and a
|
|
3448
|
+
median option) and `savitzky_golay` beside the two that were there.
|
|
3449
|
+
`tests/test_config.py::TestEverySmoothingMethodIsWiredUp` and
|
|
3450
|
+
`tests/test_pipeline.py::TestTheConfiguredSmootherReachesTheComparison` are the
|
|
3451
|
+
mechanism the comment claimed: each accepted value resolves, carries its
|
|
3452
|
+
section's parameters, and survives the whole path on real windows.
|
|
3453
|
+
|
|
3454
|
+
`log_bins` stays `core.collection.log_bin` rather than `smoothing.LogBinner` —
|
|
3455
|
+
the two differ in clamping and in what `n_bins` counts, and the difference is
|
|
3456
|
+
every committed golden number. The default path is unchanged, which the golden
|
|
3457
|
+
suite asserts and which is why this could land without moving a number.
|
|
3458
|
+
|
|
3417
3459
|
**A claim whose mechanism is absent but whose property holds — for a different
|
|
3418
3460
|
reason, which matters:**
|
|
3419
3461
|
|
|
@@ -3460,7 +3502,8 @@ of §5.2, and the two-stage fit API of §5.2.5.
|
|
|
3460
3502
|
**`master` is frozen; `main` is the trunk.** `main` was branched from `master` at
|
|
3461
3503
|
`453c77c` and is where all refactor work lands. `master` is never committed to
|
|
3462
3504
|
again — it becomes the permanent, named record of the pre-refactor code, doing
|
|
3463
|
-
the job a `v0.1.
|
|
3505
|
+
the job a `v0.1.1` tag would have done. Its tip, `453c77c`, **is** the v0.1.1
|
|
3506
|
+
the Magna paper cites (§5.2.5, §8).
|
|
3464
3507
|
|
|
3465
3508
|
**Every pull request must target `sgjholt/SpecMod`, never `uofuseismo/SpecMod`.**
|
|
3466
3509
|
This is not a matter of care — it is a GitHub default working against you.
|
|
@@ -3498,7 +3541,7 @@ Two consequences worth acting on:
|
|
|
3498
3541
|
- [ ] Settings → Actions → confirm workflows are enabled
|
|
3499
3542
|
- [ ] Settings → Branches → protect `master` (no pushes, no deletion) so the
|
|
3500
3543
|
frozen record stays frozen
|
|
3501
|
-
- [ ] `git tag -a v0.1.
|
|
3544
|
+
- [ ] `git tag -a v0.1.1 453c77c -m "the version the Magna paper cites" && git push origin v0.1.1`
|
|
3502
3545
|
|
|
3503
3546
|
**No Claude session URLs in commit messages, PR bodies, or any other published
|
|
3504
3547
|
artifact.** The repository is public and those links are private session state. A
|
|
@@ -3516,12 +3559,20 @@ needed and neither can be made from a git client:
|
|
|
3516
3559
|
convention rather than a guarantee — one absent-minded `git push origin
|
|
3517
3560
|
master` and the record is gone.
|
|
3518
3561
|
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3562
|
+
Also tag `453c77c` as `v0.1.1` — no longer optional now that §8 records it as
|
|
3563
|
+
the commit the paper cites. A branch is discoverable and a tag is immutable,
|
|
3564
|
+
and a citation should resolve to the immutable one.
|
|
3565
|
+
|
|
3566
|
+
Tagging it publishes nothing. No workflow triggers on tags; the PyPI upload is
|
|
3567
|
+
gated on release-please's `release_created`, which only a merged release PR on
|
|
3568
|
+
`main` can set; and `git describe` takes the nearest tag, which is a release
|
|
3569
|
+
far ahead of this one. **Do not create a GitHub Release from the tag**, which
|
|
3570
|
+
is the one action that would publish: Zenodo listens to the release webhook and
|
|
3571
|
+
would mint a DOI for the pre-refactor code. The commit has no `pyproject.toml`
|
|
3572
|
+
and is not packageable.
|
|
3522
3573
|
|
|
3523
3574
|
> Note on tooling limits: tag pushes are blocked in the automation environment
|
|
3524
|
-
> this plan was written in (HTTP 403 on `push origin v0.1.
|
|
3575
|
+
> this plan was written in (HTTP 403 on `push origin v0.1.1`, while branch
|
|
3525
3576
|
> pushes to the same remote succeed), which is why `main` exists as a branch
|
|
3526
3577
|
> rather than the state being pinned by a tag. **This does not affect the release
|
|
3527
3578
|
> automation in §6.4** — release-please creates tags from inside GitHub Actions
|
|
@@ -3531,10 +3582,10 @@ The two are complementary — the tag is immutable, the branch is discoverable.
|
|
|
3531
3582
|
|
|
3532
3583
|
Worth being precise about what any of this does and does not preserve: it
|
|
3533
3584
|
preserves the **code**, but the code is not **runnable** — `mtspec` no longer
|
|
3534
|
-
builds on any current toolchain (§2.1). Reproducing a 0.1.
|
|
3585
|
+
builds on any current toolchain (§2.1). Reproducing a 0.1.1 result needs three
|
|
3535
3586
|
things, which is what Phase 0 delivers:
|
|
3536
3587
|
|
|
3537
|
-
1. `master` (and
|
|
3588
|
+
1. `master` (and the `v0.1.1` tag on its tip) — the source.
|
|
3538
3589
|
2. A `Dockerfile` pinning `gfortran`, `python 3.9`, `numpy<2` — the environment
|
|
3539
3590
|
that can still build `mtspec`. Also the only place existing `.spec` pickles
|
|
3540
3591
|
can be read (§4.6).
|
|
@@ -3565,7 +3616,7 @@ Each phase ends green on CI and is independently mergeable.
|
|
|
3565
3616
|
|
|
3566
3617
|
| Phase | Work | Depends on | Rough size |
|
|
3567
3618
|
|---|---|---|---|
|
|
3568
|
-
| **0. Safety net** | Freeze `master`, default branch → `main`, optional `v0.1.
|
|
3619
|
+
| **0. Safety net** | Freeze `master`, default branch → `main`, optional `v0.1.1` tag (§6.7); reproducible legacy env (`Dockerfile`: gfortran + ObsPy 1.2.0 / SciPy 1.4.1 / NumPy 1.18 / pandas 1.0.0 (§5.2.6)); write `datasets/magna_2020.toml` and a first cut of `specmod.acquire`, publish the artifact as a `data-v1` release asset (§5.2); capture golden outputs for PNR **and** Magna; reproduce Table S2 / Figure 2 with 0.1.1 (§5.2.6 step 2); convert any `.spec` files (§4.6) | — | 1.5–2 days |
|
|
3569
3620
|
| **1. Make it installable** | `pyproject.toml` + hatch-vcs, `src/` layout, `__init__.py`; ruff config, one-shot `ruff format` + `.git-blame-ignore-revs`, module renames to snake_case; mypy skeleton; pre-commit; `test`/`build` CI; `.gitignore`, `CITATION.cff`; fix the three hard breakages (§1) and the four `F821` bugs ruff finds (§2.5); delete `Tests/Tutorial/`, strip notebook outputs, subset the inventory (§5.1) | 0 | 3–4 days |
|
|
3570
3621
|
| **2. De-globalise** | `config/` package per §4.8 — semantic groups, layer resolution, `config show`/`freeze`, provenance stamping; remove all module-level config reads (tracked by `PLW0603`); `Motion`/`AmplitudeKind` enums; `Spectrum` as a frozen dataclass with `duration`; mutable class attrs (`RUF012`); `isinstance` checks; `logging`. **Tag `v0.2.0`** | 1 | 3–4 days |
|
|
3571
3622
|
| **2b. Release plumbing** ✅ | ~~Sphinx skeleton + `pydata-sphinx-theme` + autodoc/napoleon/intersphinx~~ ✅; ~~`docs.yml` → a published site~~ ✅ — Read the Docs rather than GH Pages, for versions (§6.3); ~~release-please + PyPI Trusted Publishing~~ ✅ — one `release.yml`, not two workflows (§6.5); ~~Zenodo webhook~~ ✅ documented. Five repository settings have to be turned on by hand: `docs/releasing.md` lists them. The `ci/` mirror that staged workflow files for hand-copying is gone — it existed because an agent token could not push `.github/workflows/`, and that permission is now granted. Parallel with 2 | 1 | 1–2 days |
|
|
@@ -3658,20 +3709,48 @@ changes anything shipped.
|
|
|
3658
3709
|
### Still open
|
|
3659
3710
|
|
|
3660
3711
|
1. **Default estimator.** Multitaper (matching current behaviour) or FFT +
|
|
3661
|
-
Konno–Ohmachi (
|
|
3712
|
+
Konno–Ohmachi (more conventional in engineering seismology)? **No longer
|
|
3713
|
+
blocked** — §6.6's dead `[smoothing] method` is wired up, so the second
|
|
3714
|
+
option is selectable — and now measured, on the 28 PNR windows with each
|
|
3715
|
+
station fitted freely:
|
|
3716
|
+
|
|
3717
|
+
| Against the shipped multitaper + `log_bins` | Median ΔMw | Per-station range | `fc` ratio |
|
|
3718
|
+
|---|---|---|---|
|
|
3719
|
+
| `fft` + `log_bins` | −0.039 | −0.85 to +1.90 | 0.003 to 5.0 |
|
|
3720
|
+
| `fft` + `konno_ohmachi` | +0.027 | −0.52 to +0.51 | 0.038 to 3.2 |
|
|
3721
|
+
|
|
3722
|
+
**The smoothing is what makes the switch defensible, not the estimator.**
|
|
3723
|
+
FFT with the shipped binner moves a station by up to 1.9 magnitude units,
|
|
3724
|
+
because `log_bins` reduces the axis without reducing the variance — 151 log
|
|
3725
|
+
bins over a few hundred Fourier samples is roughly one sample per bin.
|
|
3726
|
+
Pairing it with a real smoother halves the worst excursion and pulls the
|
|
3727
|
+
`fc` range in by a third. The median station barely moves either way.
|
|
3728
|
+
|
|
3729
|
+
What this event cannot settle: the *event* corner is unconstrained under all
|
|
3730
|
+
three (stage-one spread 895% to 1042% of the event value), so the ensemble
|
|
3731
|
+
number is not evidence. Deciding on the ensemble needs an event where it is
|
|
3732
|
+
constrained.
|
|
3733
|
+
|
|
3734
|
+
Speed, measured rather than assumed and far too small to decide on: the
|
|
3735
|
+
transform is 0.209 s against 0.323 s for the 28 windows, and the smoothing
|
|
3736
|
+
adds 0.14 s (`log_bins`) to 0.83 s (`konno_ohmachi`) on top.
|
|
3737
|
+
|
|
3662
3738
|
2. **Python floor.** 3.11 is proposed. Any users stuck on 3.9/3.10?
|
|
3663
3739
|
3. **History rewrite.** Deleting the 9.9 MB catalog and stripping the notebook
|
|
3664
3740
|
(§5.1) shrinks the *working tree* but leaves both in history, so a fresh clone
|
|
3665
3741
|
still pulls ~15 MiB. Rewriting history with `git-filter-repo` would recover it
|
|
3666
3742
|
— but it rewrites every SHA, which moves what `master` and `main` currently
|
|
3667
|
-
point at and invalidates any `v0.1.
|
|
3743
|
+
point at and invalidates any `v0.1.1` tag. Given the end state is a ~1 MB repo
|
|
3668
3744
|
either way, **the recommendation is to leave history alone**: a one-time 15 MiB
|
|
3669
3745
|
clone is a much smaller cost than an unrecoverable pre-refactor record. Only
|
|
3670
3746
|
worth revisiting if the history genuinely becomes a burden.
|
|
3671
|
-
4.
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3747
|
+
4. ~~**Which commit is "v0.1.1"?**~~ **Resolved: `453c77c`**, the tip of
|
|
3748
|
+
`master`, 2021-08-12 — which is what freezing `master` at that commit was
|
|
3749
|
+
for (§6.7). The archaeology in §5.2.5 narrowed it to two candidates and
|
|
3750
|
+
could not choose between them; the answer did not come from the repository
|
|
3751
|
+
and was never going to. §5.2.6 step 2 now has a known commit to run. No
|
|
3752
|
+
`v0.1.1` tag exists yet, and creating one is repository state rather than
|
|
3753
|
+
something a commit can carry.
|
|
3675
3754
|
5. ~~**Was noise rotation on for the published run?**~~ **Assumed on.** Not
|
|
3676
3755
|
recalled, so `studies/magna_2020_paper.toml` starts from the shipped values —
|
|
3677
3756
|
`ROTATE_NOISE = true`, `ROT_METHOD = 2`, `ROT_PARS = {inc = 0.05, space =
|
|
@@ -582,6 +582,90 @@ narrow at low frequency, wide at high. It preserves the frequency axis, which is
|
|
|
582
582
|
what you want before fitting on the original grid. Bandwidth `b` is inverse:
|
|
583
583
|
smaller smooths harder; 40 is conventional.
|
|
584
584
|
|
|
585
|
+
**`LogWindow`** is the same idea with the window shape exposed: a fixed width in
|
|
586
|
+
octaves, and `boxcar`, `bartlett` (triangular), `hann`, `hamming`, `blackman` or
|
|
587
|
+
`gaussian` across it. Use it when you want the constant-relative-bandwidth
|
|
588
|
+
behaviour of Konno–Ohmachi and a say in how hard the taper is — or simply
|
|
589
|
+
because it is faster, being a sliding window rather than a full weight matrix.
|
|
590
|
+
|
|
591
|
+
Which shape matters less than it looks. On an exact `f**-2` power law, which
|
|
592
|
+
every one of them has to return unchanged, the median residual runs from
|
|
593
|
+
`1.3e-8` dex (`blackman`) to `3.4e-4` dex (`boxcar`) — an ordering that tracks
|
|
594
|
+
the taper's smoothness and a spread far below anything a fit would notice.
|
|
595
|
+
**Pick the width first and the shape second.**
|
|
596
|
+
|
|
597
|
+
`statistic="median"` is the one option that is not an average: the window
|
|
598
|
+
selects the samples and the median decides, so the shape is ignored. It is
|
|
599
|
+
there for spectra carrying spikes — a power-line tone, a dropout, a telemetry
|
|
600
|
+
glitch — which an average spreads across the whole window. With a single 60×
|
|
601
|
+
spike dropped into that power law, the worst residual across the axis is
|
|
602
|
+
`1.7e-2` dex for the median against `3.8e-2` for the geometric mean and
|
|
603
|
+
`3.6e-1` for the arithmetic one. It will discard a genuine narrow peak just as
|
|
604
|
+
happily, which is the trade.
|
|
605
|
+
|
|
606
|
+
One detail in it is worth knowing about, because it is not what the textbook
|
|
607
|
+
description of fractional-octave smoothing gives you. A window symmetric in log
|
|
608
|
+
frequency is **not** a symmetric average over samples: a Fourier grid is uniform
|
|
609
|
+
in Hz, so every window holds more samples above its centre than below in log
|
|
610
|
+
terms, and a plain per-sample mean tilts. Weighting each sample by its share of
|
|
611
|
+
the log axis removes it. Smoothing an exact `f**-2` power law, which has to come
|
|
612
|
+
back unchanged, the median residual is `1.5e-3` dex per-sample against `3.2e-8`
|
|
613
|
+
dex weighted for `hann`. It is on by default; `log_measure=False` gives the
|
|
614
|
+
per-sample behaviour, which is what Konno–Ohmachi does.
|
|
615
|
+
|
|
616
|
+
**`SavitzkyGolay`** fits a sliding polynomial rather than averaging, so it
|
|
617
|
+
flattens a peak far less. It is fitted to `log10(amp)` against `log10(f)` on a
|
|
618
|
+
uniform log-frequency grid — a polynomial in raw amplitude against raw frequency
|
|
619
|
+
spends its degrees of freedom on the falloff and can return a negative
|
|
620
|
+
amplitude. Ringing near the corner and the Nyquist roll-off is what it costs.
|
|
621
|
+
|
|
622
|
+
### What the choice actually changes
|
|
623
|
+
|
|
624
|
+
Less than it looks, and not where you would expect. **`[fitting] fit_bins` is
|
|
625
|
+
`false` by default, so the fit reads the unsmoothed spectrum.** The smoother's
|
|
626
|
+
whole influence on `Omega` is therefore indirect: it changes the signal-to-noise
|
|
627
|
+
ratio, which changes the band that passes the gate, which changes where the
|
|
628
|
+
plateau is read. The scatter column below is what the choice buys when
|
|
629
|
+
`fit_bins` is on, or when the reduced arrays are what you plot.
|
|
630
|
+
|
|
631
|
+
Measured on the 28 PNR windows, everything relative to the shipped default:
|
|
632
|
+
|
|
633
|
+
<!-- measured: smoothing_table -->
|
|
634
|
+
| Method | Banded | Band low (Hz) | Band (decades) | Δ plateau (dex) | Scatter (dex) |
|
|
635
|
+
|---|---|---|---|---|---|
|
|
636
|
+
| `log_bins` (default) | 28/28 | 0.94 | 1.55 | +0.000 | 0.317 |
|
|
637
|
+
| `konno_ohmachi`, b=40 | 28/28 | 0.93 | 1.68 | +0.067 | 0.093 |
|
|
638
|
+
| `log_window`, hann | 28/28 | 0.93 | 1.66 | +0.067 | 0.111 |
|
|
639
|
+
| `log_window`, bartlett | 28/28 | 0.93 | 1.66 | +0.067 | 0.102 |
|
|
640
|
+
| `log_window`, boxcar | 28/28 | 0.93 | 1.68 | +0.067 | 0.086 |
|
|
641
|
+
| `log_window`, gaussian | 28/28 | 0.93 | 1.65 | +0.067 | 0.147 |
|
|
642
|
+
| `log_window`, median | 28/28 | 0.93 | 1.69 | +0.067 | 0.091 |
|
|
643
|
+
| `savitzky_golay` | 28/28 | 0.93 | 1.67 | +0.067 | 0.063 |
|
|
644
|
+
| `none` | 28/28 | 0.94 | 1.51 | +0.000 | 0.318 |
|
|
645
|
+
<!-- /measured -->
|
|
646
|
+
|
|
647
|
+
Three things in that table are worth reading twice.
|
|
648
|
+
|
|
649
|
+
**The default barely smooths.** `log_bins` leaves the same scatter as `none`,
|
|
650
|
+
because 151 log bins over a window holding a few hundred Fourier samples puts
|
|
651
|
+
roughly one sample in most bins. It reduces the *axis*, not the noise.
|
|
652
|
+
|
|
653
|
+
**Every real smoother widens the band** by about a tenth of a decade. A smoothed
|
|
654
|
+
ratio crosses the threshold later, so more of the high-frequency tail survives
|
|
655
|
+
the gate — which is a gain if the tail is signal and a risk if it is not.
|
|
656
|
+
|
|
657
|
+
**The plateau moves by the same 0.067 dex under every one of them**, which is
|
|
658
|
+
0.045 in magnitude. That is not seven methods agreeing about amplitudes; it is
|
|
659
|
+
the low band edge moving from 0.94 Hz to 0.93 Hz under all of them, and the
|
|
660
|
+
plateau being read from there. The amplitudes the fit sees never changed.
|
|
661
|
+
|
|
662
|
+
Cost, measured once at best of three for the whole comparison over the 28
|
|
663
|
+
windows: `none` 63 ms,
|
|
664
|
+
`savitzky_golay` 140 ms, `log_bins` 143 ms, `log_window` 313 ms,
|
|
665
|
+
`log_window` with `statistic="median"` 688 ms, `konno_ohmachi` 830 ms. The
|
|
666
|
+
spread is one machine's, and the shape of it — Konno–Ohmachi building a full
|
|
667
|
+
weight matrix where `LogWindow` slides a window — is what generalises.
|
|
668
|
+
|
|
585
669
|
---
|
|
586
670
|
|
|
587
671
|
## Reproducing the published Magna configuration
|
|
@@ -114,7 +114,8 @@ with `specmod[multitaper]` installed passes tests that CI, which installs only
|
|
|
114
114
|
|
|
115
115
|
- **`main`** is the trunk. Everything lands here, and it is the default branch.
|
|
116
116
|
- **`master`** is frozen: the permanent record of the pre-refactor code, doing
|
|
117
|
-
the job a `v0.1.
|
|
117
|
+
the job a `v0.1.1` tag would have done: its tip, `453c77c`, is the commit the
|
|
118
|
+
Magna paper cites. Never commit to it.
|
|
118
119
|
- Feature branches are short-lived and merge into `main` via pull request.
|
|
119
120
|
|
|
120
121
|
Every pull request must target **`sgjholt/SpecMod`**. See §6.7 of the plan for
|
|
@@ -35,7 +35,10 @@ The version before this one, `0.1.1`, is the pre-refactor code that produced
|
|
|
35
35
|
the published Magna results. It is preserved unchanged on the
|
|
36
36
|
[`master`](https://github.com/sgjholt/SpecMod/tree/master) branch, which is
|
|
37
37
|
protected and frozen — it was never tagged or published to PyPI, so that
|
|
38
|
-
branch is the only reference to it.
|
|
38
|
+
branch is the only reference to it. Its tip,
|
|
39
|
+
[`453c77c`](https://github.com/sgjholt/SpecMod/commit/453c77c), is the commit
|
|
40
|
+
the paper cites as v0.1.1; cite that rather than the branch name, which is a
|
|
41
|
+
moving target in principle even though this one will not move. Everything below is the delta between it
|
|
39
42
|
and v0.2.0, which is why the first changelog is enormous and correctly so.
|
|
40
43
|
|
|
41
44
|
### An installable package
|
|
@@ -106,11 +109,11 @@ build**, so every figure and number on the page came from running the code
|
|
|
106
109
|
being documented. A notebook that stops working fails the build rather than
|
|
107
110
|
going quietly stale, which is why it was a 1.0 requirement.
|
|
108
111
|
|
|
109
|
-
##
|
|
112
|
+
## Shipped in v0.3.0
|
|
110
113
|
|
|
111
|
-
###
|
|
114
|
+
### The decomposition, finished
|
|
112
115
|
|
|
113
|
-
|
|
116
|
+
This was the last item 1.0 was waiting on.
|
|
114
117
|
|
|
115
118
|
`io.py` and `plotting.py` are now packages, alongside the spectral core, the
|
|
116
119
|
fitting layer and the pick readers; the type-checking backlog is empty. The
|
|
@@ -140,9 +143,9 @@ signposted from [Guides](guides.md) rather than split onto a page of their own.
|
|
|
140
143
|
|
|
141
144
|
### 1.0 — the API stops moving
|
|
142
145
|
|
|
143
|
-
**Everything this was waiting on
|
|
144
|
-
decomposition above with the signature changes it implied. What remains is
|
|
145
|
-
release itself, which says that names and signatures stop moving without a
|
|
146
|
+
**Everything this was waiting on shipped in v0.3.0** — the documentation, and
|
|
147
|
+
the decomposition above with the signature changes it implied. What remains is
|
|
148
|
+
the release itself, which says that names and signatures stop moving without a
|
|
146
149
|
deprecation cycle.
|
|
147
150
|
|
|
148
151
|
That promise is the whole content of the number, which is why it has to be a
|
|
@@ -151,6 +154,13 @@ is made, breaking changes bump the minor. The honest test before making it is
|
|
|
151
154
|
whether a release goes by without one — the API is not settled because the
|
|
152
155
|
backlog is empty, it is settled when it stops moving.
|
|
153
156
|
|
|
157
|
+
**By that test, 1.0 is not ready.** v0.3.0 is the release that emptied the
|
|
158
|
+
backlog and it carried two breaking changes itself: the ten renamed
|
|
159
|
+
`preprocess` functions, and the tutorial's page moving. So what 1.0 now waits
|
|
160
|
+
on is the next release going out without one — which is a thing that has to be
|
|
161
|
+
observed rather than declared, and cannot be brought forward by finishing
|
|
162
|
+
anything.
|
|
163
|
+
|
|
154
164
|
## After 1.0
|
|
155
165
|
|
|
156
166
|
Designed but deliberately not on the path to 1.0, because each is blocked on
|
|
@@ -175,7 +185,16 @@ guess written from documentation cannot substitute for:
|
|
|
175
185
|
Each shipped entry names the version that carries it, so a reader can install
|
|
176
186
|
that version and check the claim. Work that is merged but unreleased sits under
|
|
177
187
|
*In progress* and gets its version when a release goes out — merged is not
|
|
178
|
-
shipped, and this page does not blur the two.
|
|
188
|
+
shipped, and this page does not blur the two. **There is no such section right
|
|
189
|
+
now**, because nothing is merged and unreleased; that is the state the page
|
|
190
|
+
should be in between releases, not a section that went missing.
|
|
191
|
+
|
|
192
|
+
The move at release time is the step this page got wrong once. The v0.3.0 work
|
|
193
|
+
sat under *In progress — not yet released* for a week after v0.3.0 was
|
|
194
|
+
released, because `release-please` writes the changelog and the manifest and
|
|
195
|
+
knows nothing about this file. `tests/test_release_config.py` now fails when
|
|
196
|
+
the released minor version is ahead of every version this page names, which is
|
|
197
|
+
the check that was missing rather than a rule that was.
|
|
179
198
|
|
|
180
199
|
Before v0.2.0 this was a list of stages, because there was no released version
|
|
181
200
|
to point at and "done" could only mean merged. The full changelog for every
|
|
@@ -140,12 +140,23 @@ class TransformConfig:
|
|
|
140
140
|
|
|
141
141
|
@dataclass(frozen=True, slots=True)
|
|
142
142
|
class SmoothingConfig:
|
|
143
|
-
"""
|
|
143
|
+
"""What the signal-to-noise comparison smooths with, and how.
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
``log_bins`` replaces the frequency axis with bin centres; every other
|
|
146
|
+
method keeps it. See :mod:`specmod.smoothing` for what each one does to a
|
|
147
|
+
seismic spectrum and ``docs/choosing-a-transform.md`` for the measured
|
|
148
|
+
comparison.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
#: Resolved through `specmod.smoothing.SMOOTHERS`. Only the parameters
|
|
152
|
+
#: belonging to the chosen method are read.
|
|
153
|
+
method: Literal[
|
|
154
|
+
"log_bins", "konno_ohmachi", "log_window", "savitzky_golay", "none"
|
|
155
|
+
] = "log_bins"
|
|
146
156
|
|
|
147
|
-
#: Log bin edges
|
|
148
|
-
#: fmax from Nyquist. The old code hardcoded 0.001-200 Hz
|
|
157
|
+
#: Log bin edges, for `log_bins`. ``None`` derives them from the record:
|
|
158
|
+
#: fmin from 1/T, fmax from Nyquist. The old code hardcoded 0.001-200 Hz
|
|
159
|
+
#: regardless.
|
|
149
160
|
f_min: float | None = 0.001
|
|
150
161
|
f_max: float | None = 200.0
|
|
151
162
|
n_bins: int = 151
|
|
@@ -153,6 +164,23 @@ class SmoothingConfig:
|
|
|
153
164
|
#: Konno-Ohmachi bandwidth ``b``. Smaller smooths harder.
|
|
154
165
|
konno_ohmachi_bandwidth: float = 40.0
|
|
155
166
|
|
|
167
|
+
#: `log_window`: window width in octaves, its shape, and how the samples
|
|
168
|
+
#: it covers are combined. The shapes are listed in
|
|
169
|
+
#: `specmod.smoothing.WINDOWS`; `bartlett` is the triangular one.
|
|
170
|
+
#: `statistic = "median"` ignores the shape and rejects spikes instead of
|
|
171
|
+
#: averaging them in.
|
|
172
|
+
octave_fraction: float = 1.0 / 3.0
|
|
173
|
+
window: Literal["boxcar", "bartlett", "hann", "hamming", "blackman", "gaussian"] = (
|
|
174
|
+
"hann"
|
|
175
|
+
)
|
|
176
|
+
statistic: Literal["geometric", "mean", "median"] = "geometric"
|
|
177
|
+
|
|
178
|
+
#: `savitzky_golay`: the fit is done on a uniform log-frequency grid, so
|
|
179
|
+
#: the window is a fraction of a decade rather than a count of Fourier bins.
|
|
180
|
+
savgol_window_length: int = 41
|
|
181
|
+
savgol_polyorder: int = 3
|
|
182
|
+
savgol_points_per_decade: int = 200
|
|
183
|
+
|
|
156
184
|
|
|
157
185
|
@dataclass(frozen=True, slots=True)
|
|
158
186
|
class SnrConfig:
|
|
@@ -317,19 +317,26 @@ class SpectrumPair:
|
|
|
317
317
|
max_nyquist_fraction: float | None = None,
|
|
318
318
|
rotation_inc: float = 0.05,
|
|
319
319
|
rotation_space: tuple[float, float] = (0.001, 1.001),
|
|
320
|
+
smoother: Any = None,
|
|
320
321
|
meta: Mapping[str, Any] | None = None,
|
|
321
322
|
) -> SpectrumPair:
|
|
322
323
|
"""Pair the two and select the band.
|
|
323
324
|
|
|
324
325
|
The order matters and is not arbitrary. The noise is rescaled and moved
|
|
325
|
-
onto the signal's frequency axis *before*
|
|
326
|
-
the two
|
|
327
|
-
only meaningful because of it, and it holds for every
|
|
328
|
-
including those whose native axes differ in length.
|
|
326
|
+
onto the signal's frequency axis *before* smoothing, which is what
|
|
327
|
+
makes the two reduced arrays share an axis — the element-wise ratio
|
|
328
|
+
below is only meaningful because of it, and it holds for every
|
|
329
|
+
estimator including those whose native axes differ in length.
|
|
329
330
|
|
|
330
331
|
The floor is captured from the two spectra before the interpolation,
|
|
331
332
|
because afterwards the noise carries the signal's axis and its own
|
|
332
333
|
lowest resolvable frequency is unrecoverable.
|
|
334
|
+
|
|
335
|
+
``smoother`` chooses what reduces the two spectra before the ratio.
|
|
336
|
+
``None`` is :func:`log_bin` with the ``f_min``/``f_max``/``n_bins``
|
|
337
|
+
above. Anything else is a :mod:`specmod.smoothing` smoother, which
|
|
338
|
+
keeps the frequency axis rather than replacing it with bin centres, so
|
|
339
|
+
``snr`` is then as long as the spectrum.
|
|
333
340
|
"""
|
|
334
341
|
floor = max(_resolution_floor(signal), _resolution_floor(noise))
|
|
335
342
|
|
|
@@ -338,16 +345,32 @@ class SpectrumPair:
|
|
|
338
345
|
noise_amp = noise_amp * parseval_scale(signal.amp.size, noise.amp.size)
|
|
339
346
|
noise_amp = interpolate_onto(signal.freq, noise.freq, noise_amp)
|
|
340
347
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
348
|
+
reducer = _resolve_smoother(smoother)
|
|
349
|
+
|
|
350
|
+
def reduce(amp: NDArray[np.float64], like: Spectrum) -> BinnedSpectrum:
|
|
351
|
+
if reducer is None:
|
|
352
|
+
return log_bin(
|
|
353
|
+
signal.freq, amp, f_min=f_min, f_max=f_max, n_bins=n_bins
|
|
354
|
+
)
|
|
355
|
+
smoothed = reducer.smooth(
|
|
356
|
+
Spectrum(
|
|
357
|
+
freq=signal.freq,
|
|
358
|
+
amp=amp,
|
|
359
|
+
motion=like.motion,
|
|
360
|
+
kind=like.kind,
|
|
361
|
+
duration=like.duration,
|
|
362
|
+
sampling_rate=like.sampling_rate,
|
|
363
|
+
meta=dict(like.meta),
|
|
364
|
+
)
|
|
365
|
+
)
|
|
366
|
+
return BinnedSpectrum(
|
|
367
|
+
freq=np.asarray(smoothed.freq, dtype=np.float64),
|
|
368
|
+
amp=np.asarray(smoothed.amp, dtype=np.float64),
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
binned_signal = reduce(np.asarray(signal.amp, dtype=np.float64), signal)
|
|
372
|
+
binned_noise = reduce(noise_amp, noise)
|
|
373
|
+
_require_shared_axis(binned_signal, binned_noise, reducer)
|
|
351
374
|
|
|
352
375
|
if rotate_noise:
|
|
353
376
|
# The factor is derived on the binned axis — that is where the
|
|
@@ -373,9 +396,7 @@ class SpectrumPair:
|
|
|
373
396
|
noise_amp = noise_amp * interpolate_onto(
|
|
374
397
|
signal.freq, binned_noise.freq, factor
|
|
375
398
|
)
|
|
376
|
-
binned_noise =
|
|
377
|
-
signal.freq, noise_amp, f_min=f_min, f_max=f_max, n_bins=n_bins
|
|
378
|
-
)
|
|
399
|
+
binned_noise = reduce(noise_amp, noise)
|
|
379
400
|
|
|
380
401
|
snr = binned_signal.amp / binned_noise.amp
|
|
381
402
|
selector = _resolve_bandwidth(bandwidth)
|
|
@@ -416,6 +437,7 @@ class SpectrumPair:
|
|
|
416
437
|
"max_nyquist_fraction": max_nyquist_fraction,
|
|
417
438
|
"rotation_inc": rotation_inc,
|
|
418
439
|
"rotation_space": rotation_space,
|
|
440
|
+
"smoother": _strategy_record(smoother),
|
|
419
441
|
}
|
|
420
442
|
# A band the ratio did not choose is marked as such, so a stored result
|
|
421
443
|
# — or one read back a year later — still distinguishes a measurement
|
|
@@ -495,6 +517,70 @@ def _split_record(spec: Mapping[str, Any]) -> tuple[str, dict[str, Any]]:
|
|
|
495
517
|
return str(spec["name"]), params
|
|
496
518
|
|
|
497
519
|
|
|
520
|
+
def _resolve_smoother(smoother: Any) -> Any:
|
|
521
|
+
"""Turn a name, a record or an instance into a smoother, or ``None``.
|
|
522
|
+
|
|
523
|
+
``None`` and ``"log_bins"`` both mean :func:`log_bin`, this module's own
|
|
524
|
+
binner. It is not :class:`specmod.smoothing.LogBinner`: ``log_bin`` clamps
|
|
525
|
+
the requested range to the record and counts ``n_bins`` as edges, where
|
|
526
|
+
``LogBinner`` honours explicit edges exactly and can keep empty bins.
|
|
527
|
+
"""
|
|
528
|
+
if smoother is None or smoother == "log_bins":
|
|
529
|
+
return None
|
|
530
|
+
# Imported here because `specmod.smoothing` imports `specmod.core`, and at
|
|
531
|
+
# module level that is a cycle.
|
|
532
|
+
from ..smoothing import SMOOTHERS # noqa: PLC0415
|
|
533
|
+
|
|
534
|
+
if isinstance(smoother, Mapping):
|
|
535
|
+
name, params = _split_record(smoother)
|
|
536
|
+
if name == "log_bins":
|
|
537
|
+
return None
|
|
538
|
+
return SMOOTHERS[name](**params)
|
|
539
|
+
if isinstance(smoother, str):
|
|
540
|
+
try:
|
|
541
|
+
return SMOOTHERS[smoother]()
|
|
542
|
+
except KeyError:
|
|
543
|
+
raise ValueError(
|
|
544
|
+
f"Unknown smoother {smoother!r}. Available: {sorted(SMOOTHERS)}."
|
|
545
|
+
) from None
|
|
546
|
+
return smoother
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def _require_shared_axis(
|
|
550
|
+
signal: BinnedSpectrum, noise: BinnedSpectrum, reducer: Any
|
|
551
|
+
) -> None:
|
|
552
|
+
"""The ratio is element-wise, so the two axes have to be the same one.
|
|
553
|
+
|
|
554
|
+
Only reachable with a smoother that re-grids, since every shipped one keeps
|
|
555
|
+
the axis it is given. ``LogBinner`` with derived edges is the case to know
|
|
556
|
+
about: it takes its range from each spectrum's own duration, and the noise
|
|
557
|
+
window is not the signal window.
|
|
558
|
+
|
|
559
|
+
The axes are compared rather than their lengths. Two log binnings with the
|
|
560
|
+
same ``n_bins`` and different edges give arrays of equal shape over
|
|
561
|
+
different frequencies, which divides cleanly and is wrong everywhere.
|
|
562
|
+
"""
|
|
563
|
+
name = getattr(reducer, "name", reducer)
|
|
564
|
+
if signal.freq.shape != noise.freq.shape:
|
|
565
|
+
raise ValueError(
|
|
566
|
+
f"The smoother {name!r} produced {len(signal)} points for the "
|
|
567
|
+
f"signal and {len(noise)} for the noise, so the signal-to-noise "
|
|
568
|
+
f"ratio has no element-wise meaning. A smoother used here must "
|
|
569
|
+
f"leave the frequency axis alone, or pin its own axis explicitly "
|
|
570
|
+
f"(for LogBinner, by setting f_min and f_max rather than deriving "
|
|
571
|
+
f"them)."
|
|
572
|
+
)
|
|
573
|
+
if not np.allclose(signal.freq, noise.freq, rtol=1e-12, atol=0.0):
|
|
574
|
+
worst = int(np.argmax(np.abs(signal.freq - noise.freq)))
|
|
575
|
+
raise ValueError(
|
|
576
|
+
f"The smoother {name!r} put the signal and the noise on different "
|
|
577
|
+
f"frequencies — {signal.freq[worst]:.6g} Hz against "
|
|
578
|
+
f"{noise.freq[worst]:.6g} Hz at index {worst} — so the ratio has "
|
|
579
|
+
f"no element-wise meaning even though the two are the same length. "
|
|
580
|
+
f"Pin the axis explicitly (for LogBinner, set f_min and f_max)."
|
|
581
|
+
)
|
|
582
|
+
|
|
583
|
+
|
|
498
584
|
def _resolve_noise_model(
|
|
499
585
|
noise_model: str | NoiseModel | Mapping[str, Any], space: tuple[float, float]
|
|
500
586
|
) -> NoiseModel:
|