Upgrading

Four moves are documented here. Most readers porting code want 0.2 to 0.3, which renamed ten functions in specmod.preprocess and changed what they do with the stream they are given. The two moves above it rename nothing: they are settings that began doing what they always said, so the break is in the numbers rather than in the call.

0.x ships breaking changes in minor bumps without a deprecation cycle, because shimming an API still being worked out costs more than it protects. See Releasing the software.

From 0.4 to 0.5

max_radius_km and min_radius_km now mean kilometres. They were converted to degrees at a fixed 111.195 km per degree and handed to the station query. A degree of arc is 110.574 km at the equator and 111.691 km at the poles, so the boundary moved with latitude — up to 0.3 km on a 50 km radius and 2 km on 400 km. The query is now sent deliberately wide and the cut made locally against the true WGS84 epicentral distance. A config whose radius falls near a station’s distance can select a different set of stations than it did before, and verify on an existing artefact is unaffected: nothing on disk changes.

Three more changes to the same block:

Before

From 0.5

min_radius_km was only sent alongside max_radius_km, so on its own it did nothing

It applies on its own

max_radius_km = 0, a negative radius, or min_radius_km >= max_radius_km selected nothing, silently

Each raises ValueError when the config is read

Waveforms were requested with the config’s wildcards, so the radius reached the station query and never the download

Waveforms are requested by name, from the inventory the station query returned

New: channel_priorities and location_priorities rank co-sited instruments, first match wins per station. Leaving them unset fetches every channel as before and warns, naming the stations that carry more than one instrument. See Publishing a dataset.

From 0.3 to 0.4

[smoothing] method is applied. It selected nothing: every spectrum was log-binned whatever the setting said, and log_bins, konno_ohmachi and none produced bit-identical output. A config that set konno_ohmachi or none was getting log-binned spectra and now gets what it asked for, so results move for those two values — none most of all, since it now means no smoothing rather than the default binning.

log_window and savitzky_golay are new, and nothing needs changing to keep the previous behaviour: log_bins is still the default.

From 0.2 to 0.3

specmod.preprocess no longer modifies the stream it is given. Every function returns a new one and leaves the caller’s untouched — the rule specmod.core has always followed, and the last item 1.0 was waiting on.

Every rename below exists because of that. A function that started returning instead of mutating while keeping its name would leave every existing call compiling, running, and silently doing nothing: un-cut records read as cut windows, and a wrong magnitude with no error anywhere. Renaming makes the break an AttributeError on the first line that uses one, which is the same reason nothing was aliased in 0.2.

0.2.x

0.3.x

set_stream_distance(st, ...)

st = with_distance(st, ...)

set_picks(st, ...)

st = with_picks(st, ...)

set_origin_time(tr, ot)

tr = with_origin_time(tr, ot)

basic_set_theoreticals(st, ...)

st = with_theoretical_picks(st, ...)

link_window_to_trace(tr, s, e)

tr = with_window(tr, s, e)

cut_p(st, ...)

sig = p_window(st, ...)

cut_s(st, ...)

sig = s_window(st, ...)

cut_c(st, ...)

coda = coda_window(st, ...)

pad_traces(st, ...)

st = padded(st, ...)

get_signal(st, cut_s, **kw)

sig = s_window(st, **kw)

get_signal is removed rather than renamed. It existed only to copy a stream before handing it to a mutating function; with the cutters returning new streams there is nothing left for it to do.

get_noise_p and get_noise_s are unchanged. They already returned new streams, so neither their names nor their call sites move.

What a script looks like after

import specmod.preprocess as pre

# 0.2.x — every call wrote into `st`, and `get_signal` copied it first
# so the cut did not consume the stream the noise still needed.
pre.set_stream_distance(st, olat, olon, odep, otime, inventory=inv, dtype="mseed")
pre.set_picks(st, "event.xml")
sig = pre.get_signal(st, pre.cut_s, rafp=0.8, tafs=20)
noise = pre.get_noise_p(st, sig)
import specmod.preprocess as pre

# 0.3.x — each step returns the next stream, and `st` is still the
# untouched record the noise is measured from.
st = pre.with_distance(st, olat, olon, odep, otime, inventory=inv, dtype="mseed")
st = pre.with_picks(st, "event.xml")
sig = pre.s_window(st, rafp=0.8, tafs=20)
noise = pre.get_noise_p(st, sig)

The mechanical rule: bind the result. A call whose result is discarded now does nothing at all, and that is the one mistake this rename cannot make loud for you.

Two deprecation shims are gone

Removed in 0.3.0

Use instead

preprocess.set_picks_from_pyrocko

preprocess.with_picks

with_distance(dtype="none")

with_distance(dtype="list")

dtype="none" now raises rather than warning, and names the accepted values. fitting.PLOT_COLUMNS is untouched and still dated 0.4.0.

The tutorial moved

tutorial/SpecModTutorial.ipynb is now tutorial/specmod-tutorial.ipynb, and its page is /tutorial/specmod-tutorial.html. A filename is a URL, and the rest of the site kebab-cased its own before the first release; this one was missed. Bookmarks to released versions still work — /en/v0.2.3/… is frozen and keeps serving the old name — and a redirect covers stable and latest.

From 0.1.1 to 0.2

The version the Magna paper used is 0.1.1, preserved on the frozen master branch. It was never tagged or published to PyPI, so “upgrading” here means moving code written against that branch onto a released 0.2.x — and then through the section above.

Nothing is aliased. Modules moved, were renamed to snake_case, and several were deleted outright — a 0.1 script will fail at its imports rather than run and give different numbers, which is the intended failure mode.

Modules

0.1.1

0.2.x

specmod.PreProcess

specmod.preprocess

renamed

specmod.Fitting

specmod.fitting

now a package

specmod.config

specmod.config

now a package, layered

specmod.utils

specmod.utils

reduced to what has callers

specmod.Spectral

specmod.core

deleted — see below

specmod.Models

specmod.sources

deleted

specmod.ModelGuess

specmod.fitting

deleted

specmod.Ratios

—

deleted, no replacement

The import that breaks first is usually import specmod.PreProcess as pre. It is import specmod.preprocess as pre.

Containers

Spectral.py held Spectrum, Signal, Noise, SNP and Spectra. Those are replaced by two typed containers in specmod.core.collection:

0.1.1

0.2.x

SNP

SpectrumPair — a signal spectrum and its noise

Spectra

SpectrumSet — a mapping of channel id to pair

Signal, Noise

core.Spectrum, distinguished by what holds it

The difference that matters is not the names. A core.Spectrum carries its ground-motion domain and amplitude convention as typed attributes, where the old Spectrum carried neither and the domain lived in Models.MOTION, a module global read at import time that you had to keep in sync by hand with however many times you had called .inte() or .diff(). Getting it wrong returned a wrong seismic moment with no error anywhere. It is now a type error.

See §4 for the conventions themselves.

Models and guesses

Models.py bound a source shape and a ground-motion domain together at import time — MODEL = which_model(...) and MOTION read from config — which is why a Brune and a Boatwright could not be fitted in one session. Attenuation was not import-bound; it was chosen per call site by reaching for simple_model or simple_model_fdep. All three are now separate and composed:

0.1.1

0.2.x

which_model

sources.get_source_model

scale_to_motion

sources.motion_scaling (note the argument order flipped)

source

SourceModel.log10_shape — the source term alone

simple_model, simple_model_fdep

sources.SpectralModel, built by build_model or from_config, then .evaluate()

t_star, t_star_freq

AttenuationModel.log10_decay

create_simple_guess, create_simple_guess_fdep

fitting.initial_guess

The composed model is a sum of three log-space terms — source, attenuation, motion — written out in §8.

Reach for SpectralModel, not log10_shape, when porting simple_model. log10_shape is only the source term of the three; calling it in place of the old simple_model silently drops attenuation and motion, which is a wrong number rather than an error.

Persistence

write_methods and read_methods pickled. Nothing in the package can write a pickle now:

0.1.1

0.2.x

write_methods / read_methods

specmod.io.save / specmod.io.load

.spec pickle

.h5 (HDF5) for arrays, .parquet for tables

Old .spec files cannot be read by 0.2.x. A pickle stores the import path of every class it holds, and those paths no longer exist. If you need the contents, read them with a 0.1.1 checkout of master and re-save, or re-run the pipeline — which is cheaper than it sounds and gives you a file that does not depend on the code that wrote it.

Reading and writing needs the io extra: pip install "specmod[io]".

Configuration

Settings were module-level constants, read at import. They are now a layered specmod.config with semantic sections, resolved per access.

The practical consequence: overriding a setting after import now works. Under 0.1.1 a constant read at import time had already been captured by whatever read it, so a late override silently did nothing. Every output now also records the configuration that produced it and a hash of it, so a run is reproducible from its own outputs.

specmod config show      # resolved values, and which layer set each one
specmod config freeze    # emit TOML to commit alongside a result

Results will not match 0.1.1 bit for bit

The pipeline is continuous in its input, where 0.1.1 had discontinuities that made a last-bit difference between two machines move the noise by up to 82% and a band edge by 13 bins. Removing them changes results by design, and they were not reproducible before. See Reproducibility.

Multitaper adaptive weighting is on by default, as it was in 0.1.1 and as mtspec had it, so nothing needs passing to keep it. Choosing a transform explains why.

The one remaining deprecation shim

One name still works and warns, rather than failing:

Name

Use instead

Removed

fitting.PLOT_COLUMNS

fitting.plot_columns()

0.4.0

The two preprocess shims that used to sit beside it were removed in 0.3.0 — see above.

The shortest path

If you are porting a script rather than a package, the tutorial is the same pipeline written against the current release, executed on every documentation build so it cannot describe an API that no longer exists. Reading it beside your own script is usually faster than working through the tables above.

For a stable, path-free surface that will move less than the internals, specmod.api is 21 names covering estimate, compare, fit and configure — see the API reference.