Changelog#
Published on 2026-09-05 UTC: PyPI 0.2.3, conda-forge, GitHub Release, and Zenodo DOI 10.5281/zenodo.22344992.
Weekly development activity#
Weekly activity through the immutable v0.2.3 release tag. The stacked bars group non-merge commits and edited source lines by category; release markers identify reachable stable tags, including v0.2.3.#
The snapshot ends at commit 75d3d1a89ebc8942af1f3228152fea99d2d3420e. Both language versions use the same weekly data. Bar heights show the logarithm of each weekly total, while the coloured segments show category proportions; edited lines are additions plus deletions, excluding generated files and binary changes.
The pre-publication candidate snapshot at 3ade51de26c9adf21c4a7ad076837967e61c8038 remains available as an SVG and weekly CSV.
The earlier v0.2.2 snapshot remains available as an SVG and weekly CSV.
The v0.2.2 release is available from GitHub Releases and archived under Zenodo DOI 10.5281/zenodo.22228340.
[0.2.3] - 2026-09-05#
Published on 2026-09-05 UTC: PyPI 0.2.3, conda-forge, GitHub Release, and Zenodo DOI 10.5281/zenodo.22344992.
This maintenance release implements GWpy 4.0.1/4.0.2 compatibility
fixes across the audited GWpy-derived API surface. Historical automated
differential evidence is recorded for runtime candidate
d55717e9aed9ef5c22bb5d8ed0df95e19a313545, with review and evidence source
ff47d66ce985c295193a8d8cd1acef3ddd61add1. That runtime’s human
scientific/data-model sign-off is approved: the release owner reapproved the
four unchanged parent-parity risks, signal methods, and signal-related internal
reconstruction authority for non-second irregular axes on 2026-09-04. The
approval is strictly limited to the six approved risk groups and excludes other
contracts. The historical approval remains bound only to candidate
c7b79db7fee2e646069679a0efe3d65c7ed4e562 and exactly five disclosed
parent-parity risks; see the
aggregate sign-off report.
This approval does not cover later source revisions.
This release adds no public API or dependency.
The release owner separately approved the scalar reconstruction delta on
2026-09-05 for runtime tree 88c9de982f4b284afbb5845c13cecb2d90d938dc,
including the inherited ScalarField reductions. This limited approval
preserves the earlier exception scopes and does not authorize publication.
The current follow-up fixes scalar reduction allocation on NumPy 2 and
updates CI provisioning and audit provenance. Each new source requires
same-candidate scientific/data-model review, same-candidate release-security
review, candidate-wide QA, and fresh 19-cell qualification. The
release owner authorized publication on 2026-09-05 after the final release gate
passed for source 75d3d1a89ebc8942af1f3228152fea99d2d3420e. Any later
runtime/data-model semantic change invalidates an aggregate sign-off and
requires reapproval. Documentation-only recording commits do not alter the
runtime candidate.
The current inventory evidence covers 575 logical members with 62 selectors and 396 executed cases per oracle. The historical c7 approval covered the same 575 logical members with 59 selectors and 384 executed cases per oracle; the generated inventory’s 1,150 case rows are a separate logical version-row count.
Implemented with automated evidence#
Scalar statistics on NumPy 2: reductions of
Array3D,Array4D, andScalarFieldcan allocate their zero-dimensionalQuantityresult instead of failing under strictcopy=Falsesemantics. Values, units, and reduction axis rules are preserved.HDF5 collections: appending a
TimeSeriesDictno longer opens an unspecified target in truncating mode. Nativenames=,group=, append, duplicate-name, legacy-manifest, and link-safety outcomes now follow the installed GWpy oracle while private exact-time sidecars remain additive.Native HDF5 auto-identification:
TimeSeriesandTimeSeriesDictnow match GWpy for native.h5/.hdf5auto-read and auto-write. Structural NDScope detection retains precedence over the lower-priority generic HDF5 route; other ambiguous and GWexpy-only class families remain explicit.HDF5 window safety (#611): GWpy 4.0.x can return samples outside the requested interval for a completely non-intersecting HDF5 collection window because negative stop indices wrap. GWexpy now returns a zero-length series only for that fully disjoint entry after the parent reader succeeds. Mixed collections retain their keys and order, and only disjoint entries become empty. Partial overlap,
pad=, parent reader errors, and caller-owned source handling retain GWpy semantics. A safety-created empty retains the series class, dtype, unit, name/channel, and cadence; its publict0/spancollapse to the stored series start boundary. It does not inherit private exact-time authority from the source.Constructors and time axes (historically approved for c7): GWpy-supported
t0andepochinputs now take the parent route without exception-driven retries. Positional and keyword forms bind consistently, while the existing exactt0_nsauthority remains separate. Copy, slice, crop, append, and supported time-axis mutations preserve or invalidate private exact state according to the resulting cadence.Time and frequency conversion (historically approved for c7): scalar and date-component
to_gps()andtconvert()calls,from_gps()inputs, andFrequencySeries.ifft()timing metadata now match the active GWpy oracle. Invalid date-shaped values fail instead of falling back to vector interpretation.Spectral, signal, and statistics routes (historically approved for c7): default CSD, Rayleigh, heterodyne, demodulate, RMS, resampling, reductions, and axis-result types now follow GWpy values, units, shapes, metadata, and exception classes.
Plotting, CSV, and collections: plot methods accept the complete GWpy positional layouts and preserve Python-compatible duplicate-argument failures. Plain CSV uses the native GWpy route; existing enhanced-only arguments remain explicit opt-ins. Collection crop, prepend, and the historical
filterba()shim preserve their established call contracts.Specialized arrays and fields (historically approved for c7): inherited constructor binding, transposition, BifrequencyMap plotting, ScalarField finite differences, unit conversion, grid validation, and percentage comparisons now have explicit GWpy or fail-closed data-model contracts.
Compatibility notes#
non_intersecting_window_safetyis the sole approved default divergence from GWpy in this release. It prevents an upstream negative-index wrap from selecting data outside a completely disjoint requested HDF5 window. See the GWpy behavioral compatibility policy.The #611 safety exception is
approved-separately-unchanged; its independent human approval and release-note gates remain satisfied, and the current reapproval does not reapprove it. Its scope remains limited to the fully disjoint read-window subcase.The recorded human scientific/data-model approval is approved for runtime candidate
d55717e9aed9ef5c22bb5d8ed0df95e19a313545and strictly limited to the six approved parent-parity and signal risk groups below. It does not approve other contracts. The historical c7 approval of exactly five risks remains recorded separately:the mixed-unit CSD
V²/Hzlabel;public Rayleigh parent segment selection with a private corrected route and known finite-Monte-Carlo limitations;
stale Array2D/Plane2D
min/maxindices; andstale numeric
swapaxes/transposemetadata.
The current signal group covers dimensionless signal outputs, raw-magnitude frequency
Quantityhandling, float32 RMS underflow, and signal-related internal reconstruction authority for non-second irregular axes.The final two groups retain the previously disclosed stale axis metadata on specific Array2D/Plane2D reductions and numeric array permutations. Corrected or metadata-aware GWexpy-only routes remain explicit where already available.
[0.2.2] - 2026-09-01#
This maintenance release restores default behavioural compatibility with GWpy for time-series selection while retaining exact HDF5 epoch metadata as GWexpy-private state.
Fixed#
TimeSeries crop compatibility:
TimeSeries.crop()now delegates GWpy-supported arguments to GWpy, so default sample selection,t0,dt, and success/failure behaviour match GWpy at ordinary and high sample rates.Exact epoch propagation: slicing and cropping finish the base operation before propagating private exact state. A cadence that is not an integral number of nanoseconds now drops derived exact authority instead of causing a successful GWpy operation to fail.
Lazy I/O registration: collection-first time-series and frequency-series I/O now performs the same idempotent registry bootstrap as scalar I/O. Repeated operations retain reader and writer identity and registration order.
[0.2.1] - 2026-08-31#
This maintenance release corrects the HDF5 exact-epoch regression and restores lazy bootstrap I/O registration without changing the public API.
[0.2.0] - 2026-08-26#
This minor release establishes the v0.2.0 semantic-contract baseline for exact timing, interoperable persistence, deterministic provenance, and public GWpy compatibility.
Added#
time (exact GPS state):
TimeSeriesand related supported paths retain keyword-only GPS-nanosecond origins through copies, slices, pickles, MNE, and HDF5 sidecars. Exact state is kept separately from binary64 time coordinates where required.HDF5 and provenance: native HDF5 sidecars preserve exact epoch state, metadata, and structured provenance without changing the GWpy-readable core payload. Provenance-aware pathname transactions now coordinate local POSIX processes with bounded advisory locking; append and caller-owned open containers preserve distinct datasets, while pathname replacement remains serialized last-writer-wins.
GWF reads:
TimeSeries,TimeSeriesDict,StateVector, andStateVectorDictsupport spawn-safeparallel=reads.nproc=remains a compatibility alias. Multi-worker reads accept only single local frame paths and fail before I/O for caches, URI/composite spellings, or unsupported nested execution.coupling: the public v1 coupling-segment schema, pandas/Astropy adapters, JSON envelopes, canonical coordinate units, and exact finite-precision grid checks are available.
significanceremains outside the v1 schema.spectral estimation:
TimeSeries.psd()andTimeSeries.asd()acceptmethod="median-mean"; publicmedian_bias(n)exposes the FINDCHIRP-compatible finite-sample correction. PSD/ASD now guarantee GWexpy preservation ofname,channel, andepochacross supported backends, while the backend remains authoritative for numerical values, units, and the frequency axis.
Changed#
bootstrap and I/O: a plain
import gwexpyno longer eagerly registers constructors or I/O. Callgwexpy.register_all(include_io=False)for constructors orgwexpy.register_all()for constructors plus I/O; supported public I/O entry points register handlers on demand.SeriesMatrix B0: the 480-cell Phase A container contract is frozen. Dimensional raw-
ndarrayaddition and subtraction withSpectrogramMatrixfail atomically withTypeError; the broader B1 composition runtime remains deferred (adopted: false) pending explicit human D21/data-model sign-off.
Removed (breaking)#
The obsolete developer proxy imports
gwexpy.utils.shell,gwexpy.utils.sphinx,gwexpy.utils.sphinx.ex2rst, andgwexpy.utils.sphinx.zenodohave been removed. Usesubprocessandshutil.whichfor shell helpers, maintained documentation tooling directly, and a maintained Zenodo client or project release tooling as appropriate.
Compatibility fixes#
GWpy 4 runtime proxies now expose curated public table, TimeSeries, LAL, and misc utility surfaces. The optional FrameL compatibility proxy is lazy: it imports without
python-frameland reports the original dependency error only when a FrameL-backed symbol is requested.
Update history#
flowchart LR
baseline["v0.1.14 baseline"] --> integration["v0.2 contract integration"]
integration --> median_mean["#686 median-mean spectral dispatch"]
median_mean --> source["v0.2.0 release-source metadata"]
0.1.14 - 2026-08-15#
Removed (breaking)#
io (SDB): the undocumented
sqliteandsqlite3format aliases and.sqlite/.sqlite3GUI fallbacks have been removed. Use the canonicalformat="sdb"name and.sdbextension instead (#635).Before
After
format="sqlite"orformat="sqlite3"selected the SDB reader.Use
format="sdb"; the removed aliases raiseIORegistryError..sqlite/.sqlite3paths were routed to SDB by the GUI fallback.Rename archives to
.sdbor selectformat="sdb"in direct I/O; unsupported GUI paths raiseRuntimeError.
Behaviour-visible bug fixes#
io (ATS.MTH5):
TimeSeries.read(..., format="ats.mth5")now uses the supportedmth5.read_file(..., file_type="metronix")API frommth5>=0.6.8. The reader preserves raw ATSS sample values, maps Ex/Ey to mV/km and Hx/Hy/Hz to nT, and fails closed when data, start time, sample rate, component, or unit metadata is missing or inconsistent. The published path remains single-series only;TimeSeriesDict.read(..., format="ats.mth5")now raises an explicitTypeErrorbefore importing the optional dependency instead of entering an incompatible dict route. Source timing and units are authoritative, soepoch=,timezone=,unit=, and unknown reader overrides now fail explicitly instead of being ignored (#619).Before
After
The reader called an obsolete nested
mth5.io.metronix...read_atssAPI and could fail before returning a series.The reader calls the supported top-level API and validates the returned channel contract.
TimeSeriesDict.read(..., format="ats.mth5")could dispatch into a single-channel implementation.The unsupported collection route fails immediately and directs callers to
TimeSeries.read.Reader overrides could be accepted but ignored, leaving source timing and units unchanged without notice.
Unsupported timing, unit, and unknown overrides raise before dependency lookup.
io (CSV/SDB cadence): numeric and configured component-column CSV timestamps are validated before float conversion or resampling, and SDB validates integer Unix-second timestamps in database storage order before constructing its time axis. Malformed CSV rows report their physical line; duplicate, backward, missing, or overlarge timestamp gaps now raise
ValueErrorinstead of being silently accepted or repaired. A finite, positive CSVsample_ratedeclares source cadence and is honoured for a single row; without it, the legacy one-second fallback remains.resample=remains a separate finite, positive target cadence, and interpolated values now stay aligned with the returned target-rate time axis. UTC component cadence uses continuous GPS instants so leap-second gaps fail closed. Absolute float64 axes must have rounding error and spacing strictly below half a cadence. Resampling is capped at 10,000,000 requested-channel output values across the complete top-level single- or multi-file read before allocation (#648, #649).io (WIN): WIN reads now require a consecutive one-second global packet cadence while preserving legitimate channel late starts, early ends, and channel-local
t0. They fail closed on internal channel gaps and reappearance, duplicate channel blocks, sample-rate changes, duplicate or backward packet times, global gaps, and malformed, truncated, or overlong payloads. Bounded length and sample-count checks prevent oversized reads, cumulative deltas avoid integer wraparound, BCD year99to00advances the century, and UTC interpretation still warns once per top-level read (#647).io (SDB): archives that provide a
usUnitscolumn now validate every row as the supported US customary unit-system code1and fail closed on NULL, text, non-integral, or other values. Archives without that column retain the legacy US customary unit assumption;usUnitsis metadata and is not returned as a data channel.io (timezones): readers now distinguish source-defined absolute times, naive civil times, and relative sample indices. A caller-supplied
timezone=can localize only a naive explicitepoch=or configured CSV component columns; it can no longer reinterpret an absolute source time. Absolute/numeric/aware epoch overrides preserve their instant and warn thattimezone=is ignored, while formats that do not accept timezone input fail closed. Malformed offsets, non-finite offsets, and boolean timezone/epoch values are rejected instead of being coerced. Local civil times that fall in a daylight-saving fold or gap now raiseValueErrorinstead of silently selecting or normalizing an instant (#633, #651).Before
After
A seismic source at
2024-01-01 12:00:00 UTCcould be reinterpreted as Tokyo civil time, changing GPS1388145618to1388113218(−9 hours).Source timestamps remain absolute at GPS
1388145618; only a naive explicitepoch=may be localized.timezone=could be silently dropped by SDB, ATS, TDMS, DTT XML, audio, WAV, NDScope HDF5, and direct HDF5/TXT collection routes.Unsupported timezone input raises a contextual
ValueErrorbefore optional backends or source traversal.An ambiguous
2024-11-03 01:30or nonexistent2024-03-10 02:30inAmerica/New_Yorkwas assigned an offset byreplace(tzinfo=...).Naive local times are checked by UTC round-trip; DST folds and gaps fail closed.
time (GPS/UTC conversion): NumPy
datetime64[s/ms/us/ns]scalars and vectors now convert through a time-aware Astropy representation, retaining the instant represented by the input dtype through integer nanoseconds. Default datetime64 vectors therefore return object arrays of exactLIGOTimeGPSelements;dtype=floatremains the explicit binary64 output mode. Scalartconvert(datetime64)uses the same exact route.from_gps()now returns timezone-aware UTCdatetimevalues with one rounding rule for scalar, vector, and AstropyTimeinputs. A GPS instant inside a UTC leap second raisesValueErroratomically for vector input because Pythondatetimecannot represent23:59:60(#646, #650).Before
After
A
datetime64[ns]scalar could become a Unix-nanosecond integer through.item(), while a vector was immediately rounded to binary64 GPS seconds.All supported datetime64 resolutions preserve datetime semantics; scalar and default vector outputs retain exact integer nanoseconds in
LIGOTimeGPS.Vector and Astropy routes could return timezone-naive datetimes with semantics different from the scalar GWpy route.
Every
from_gps()route returns the same UTC-aware instant and is independent of the host timezone.io (WIN/CSV warnings): WIN header times are explicitly interpreted as UTC and emit one top-level warning even for multi-file reads (#632 partial). CSV numeric-time and sample-index routes likewise emit one warning when
timezone=is ignored, while component columns continue to localize the configured civil time (#634 partial).
Known limitations#
io (CSV time scale): numeric CSV timestamps retain the legacy GPS-second interpretation. v0.1.14 does not add
time_scale=ortime_unit=; convert non-GPS timestamps before reading. The broader time-scale ambiguity remains tracked by #634 for v0.2.0.
Development and CI#
CI: I/O conformance generator smoke checks now enforce a 60-second production timeout and terminate the complete process group with bounded SIGTERM/SIGKILL cleanup, reaping, and diagnostic output tails. The PR-fast gate no longer repeats the dedicated I/O conformance suite, and workflow checkouts fetch full history only where merge-base or ancestry checks need it (#629, #630).
0.1.13 - 2026-08-08#
Behaviour-visible bug fixes#
timeseries:
TimeSeries.rms(stride=1, *, ignore_nan=True)again accepts a positional numeric stride in seconds and returns a dimensionless trend series. Quantity strides and generic reduction keywords are rejected, so the public GWpy-compatible API has one unambiguous meaning (#451).types: arithmetic between an
astropyQuantityand aSeriesMatrix(TimeSeriesMatrix,FrequencySeriesMatrix,SpectrogramMatrix) now preserves the matrix type, its per-cell units and all axis metadata when theQuantityis on the left. Previously(2 * u.s) * matrixreturned a bareQuantitywhose value was the raw matrix data with every per-cell unit, row and column key, and time/frequency axis silently discarded: for a matrix of cells inm,(2 * u.s) * matrixreturned aQuantityinsrather thanm s, so every cell was off by the matrix’s own unit with no error or warning. The same expression with the matrix on the left (matrix * (2 * u.s)) was already correct, so the two orderings disagreed (#575).types: comparison operators on
SeriesMatrixare elementwise and return a boolean matrix container that preserves shape, rows, columns and axes, with dimensionless cell units. Operands are unit-converted before comparison, somatrix_m < quantity_cmcompares physical values rather than raw numbers, and comparing incompatible units raisesUnitConversionErrorinstead of returning a silently wrong answer (#576).types: the guard defect on the fast arithmetic path reported in #577 has been removed, and in-place operators are now atomic — a failure part-way through leaves the operand unmodified instead of half-updated.
types:
SpectrogramMatrix.clip()/.round()(and.copy(), which they build on) previously discarded the frequency axis (frequencies,f0,df) silently whenever the operation changed dtype. They now preserve it, along with the time axis, row/column labels and cell metadata (#623).types:
SeriesMatrix.clip(min, max)unit handling was inverted — a plain number or dimensionless bound against a unit-bearing matrix was silently accepted, while an equivalent-but-differently-scaledQuantitybound was mishandled.clip()now accepts aQuantitybound equivalent to the matrix’s unit (converting it, e.g.clip(1*u.cm, 2*u.cm)against a metre matrix), and raisesUnitConversionErrorfor a bare number, dimensionless bound, or dimensionally incompatible bound against a unit-bearing matrix.clip()further requires every cell in the matrix to share the identical unit (not merely a dimensionally equivalent one, e.g.mandcmmixed in one matrix) before applying a unit-bearing bound, and raisesUnitConversionErrorotherwise — clipmatrix.valuedirectly for a heterogeneous-unit matrix. This also closes a related hazard wherenp.clip(matrix, ...)(as opposed tomatrix.clip(...)) silently fell back to a wrong, aliased-metadata result under NumPy’s_wrapfunc, because a plainTypeErrorfrom.clip()/.round()— including from theirout=guard — is swallowed by that fallback path; both now raiseNotImplementedError, which is not swallowed (#623).types:
matrix ** True/matrix ** Falsenow match Python/NumPy integer semantics (True == 1,False == 0):matrix ** Truepreserves the original value and unit,matrix ** Falsegives value1with a dimensionless result (#623).types:
SeriesMatrix.astype()(used internally byclip()/round()whenever an operation changes dtype, e.g. clipping an integer matrix against float/Quantitybounds) passedxindex,meta,rowsandcolsthrough unchanged instead of copying them, unlikecopy(). A clippedSpectrogramMatrix’stimesaxis could alias the source’s, so mutating one silently corrupted the other.astype()now mirrorscopy()’s independence guarantees for all four (#623).types: a bare NumPy integer scalar operand (e.g.
np.int64(2), as opposed to a Pythonintornp.float64, which happens to subclass Pythonfloat) is now accepted everywhere a plain number is. Previouslyspectrogram_matrix * np.int64(2)andspectrogram_matrix ** np.int64(2)raisedTypeError: operand 'SpectrogramMatrix' does not support ufuncs, becauseSpectrogramMatrix’s own operand-acceptance check listedint/float/complexbut notnp.number. Separately,matrix ** np.int64(n)onTimeSeriesMatrix/FrequencySeriesMatrixalways computed the correct result but took a guaranteed exception-and-fallback path on every call — logging a full traceback and raising aPerformanceWarning— because the new (#577e) scalar-exponent normalization passed a barenp.numberthrough toMetaDataMatrix’s per-cell unit computation, which only acceptsint/float/complex. Both are now normalized before use (#623).io (WIN): the WIN reader decoded the per-channel sampling rate from byte 3 alone, but the rate is a 12-bit field whose top 4 bits live in the low nibble of byte 2. Every rate at or above 256 Hz was therefore truncated modulo 256 — 1000 Hz was read as 232 Hz. Because the reader also derives the packet payload length from that rate (
xlen = (srate - 1) * datawide), the byte stream was misaligned and the decoded samples themselves were garbage, not merely the time axis. No exception was raised: the reader returned a plausible-lookingTimeSeriesDict. The full 12-bit rate is now decoded, matching ObsPy’s reader (obspy/io/win/core.py, upstream obspy#3641). Rates at or below 255 Hz are unaffected. An encoded rate of zero, which cannot describe a channel block carrying a leading absolute sample, now raisesValueErrorinstead of mis-slicing the packet; no spec consulted here documents zero as a sentinel for 4096 Hz (#610).io (zarr): reading a store returned a channel chosen by dictionary iteration order rather than by any stated rule, so two reads of the same unchanged multi-channel store could return different data with no exception and no warning. Channel selection is now resolved before any payload is read: the available array names are sorted, and a
channels=selector that names a missing array or repeats a name raisesValueErrorinstead of being silently skipped. The single-series reader now requires the selection to resolve to exactly one channel and raisesValueErrorotherwise, rather than picking one on your behalf (#614).io (zarr):
TimeSeries.read(source, format="zarr")raisedIsADirectoryErrorbefore it ever reached the zarr reader, because the explicit format never intercepted the generic registry path and a zarr directory store was then opened as a file. The documented entry point had therefore never worked.TimeSeries.readandTimeSeriesDict.readnow dispatchformat="zarr"(andformat="nc"/"netcdf4") directly to their readers, mirroring the interception thatTimeSeriesDict.readalready performed for a.zarrsuffix (#620).io (NetCDF4): a write→read round trip did not return the time axis it was given.
t0was carried through a datetime-mediated conversion anddtthrough integer-nanosecond quantization, so at a realistic GPS epoch — wheret0is around 1e9 s and binary64 has no spare precision — both the epoch and the sample spacing came back perturbed, silently shifting every timestamp in the file. Files are now written with a versioned timing schema that storest0as an exact binary64 hex literal plus integer GPS seconds/nanoseconds, anddtas an exact integer ratio, so the axis round-trips bit-for-bit. Files written by earlier versions carry no schema marker; they are still readable, and reading one now emits aRuntimeWarningstating that their timing precision is limited instead of presenting the degraded axis as exact (#615).timeseries:
crop()selected samples through a materialized timestamp index, so at large GPS epochs floating-point cancellation could move the boundary by one sample and perturbdtby a few ulp. A perturbeddtchangessample_rate, and the truncatingnfftderivation amplifies that into an O(1/nfft) frequency-axis error, so a spectrum computed after a crop could be shifted without anything raising. Crop on a regular axis is now a positional slice computed fromt0,dt, and a cancellation-aware tolerance, and the sourcedtis retained rather than re-derived from the cropped coordinates. Irregular axes keep GWpy’s existing behaviour. The same correction applies toTimeSeriesMatrix.crop(#617).timeseries:
TimeSeriesDict.readdiscarded the_gwexpy_ioread provenance that its readers attach, because re-wrapping the reader’s result in the collection class did not carry the attribute across. Provenance now survives the re-wrap, so the record of how a dictionary was read is available on the object you get back (#618).interop (ROOT):
from_rootread 2-D histogram contents by reinterpreting the raw bin buffer asfloat64. ATH2Fstoresfloat32contents, so the buffer was decoded at the wrong width and returned plausible-looking but meaningless values with no exception. Contents are now read through the ROOT accessor at the class’s native scalar dtype (TH1C/S/I/Las the corresponding integer width,TH1F/TH2Fasfloat32,TH1D/TH2Dasfloat64), andHistogramno longer promotes an integer input array tofloat64, so an integer-typed ROOT histogram keeps its integer contents. Bin errors are likewise read throughGetBinErrorrather than reconstructed from aSumw2buffer (#593).fields: arithmetic between a
Quantity(or a bare scalar, or aUnit) and a field collection —FieldList,FieldDict, and thereforeVectorFieldandTensorField— lost the per-component physical units and axis metadata, because the collection had no operator contract of its own and Astropy’sndarraydispatch consumed it. The collections now implement the binary, reflected, and in-place operators explicitly: each component is operated on individually and its axis indices, axis names, domains, offsets, name, epoch, and channel are copied onto the result rather than shared with the source. Dimensional errors are raised before any component is replaced, so a failed in-place operation leaves the collection untouched (#578).plot:
VectorField.plot(stride=...)raisedTypeError: got an unexpected keyword argument 'stride'becausestridewas forwarded to the magnitudepcolormeshas well as to the quiver layer. It is now consumed before the scalar layer is drawn and applied only to the quiver decimation it was always meant for (#559).docs (segments): the
SegmentTablereference documented methods that do not exist in the implementation. The reference now matches the shipped API (#605).docs (interop): the
gwinc_docstring pointed at a classmethod that does not exist, and the module’s stated test coverage did not match the tests that actually run. Both now describe the implementation (#608).ci: the interop gates aggregated JUnit output without checking that any test had been collected, so a run that collected zero tests reported success. A gate with no collected tests now fails (#511).
Compatibility#
types (API narrowing, patch release): applying a NumPy ufunc directly to a
SeriesMatrix—np.sqrt(matrix),np.negative(matrix),np.add(a, b),np.add(a, b, out=target),np.add.reduce(matrix),np.add.accumulate(matrix),np.multiply.outer(a, b)— now raisesTypeError: operand 'TimeSeriesMatrix' does not support ufuncs (__array_ufunc__=None)
Previously these calls either executed silently with undefined results or discarded metadata without warning. This narrows the public surface in a patch release, which the project’s release policy permits when it corrects a contract violation: the same mechanism that stops a left-hand
Quantityfrom swallowing per-cell units (__array_ufunc__ = None, which routesquantity * matrixback through the reflected operator) also makes NumPy refuse direct ufunc dispatch. Silently returning a wrong-unit result is treated as worse than an explicit failure.Every operation remains available through the explicit operator suite —
+ - * / // % divmod ** @, their reflected and in-place forms, the six comparisons and unary+ - abs()— which preserve type, per-cell units and metadata. For a raw NumPy result, operate onmatrix.value; note that this drops units and metadata by design.Full NumPy/GWpy ufunc compatibility is deferred to v0.2.0 (#637), where it is a release blocker for the
SeriesMatrixsemantic-contract redesign. Restoring it under the currentndarray-subclass data model was measured to require changing either.unitor.valuepublic semantics, which is out of scope for a patch release (#575, #576, #577, #623).types:
matrix_m + matrix_cmnow converts the right operand to the left operand’s per-cell units and succeeds, where it previously raisedUnitConversionError. Results that used to fail now return a value; results that used to succeed are unchanged (#575).types (API narrowing, patch release):
divmod(matrix, operand)is explicitly unsupported and raisesTypeError. An earlier draft of this fix had added a working__divmod__/__rdivmod__that silently ignored units on non-zero divisors; rather than ship a unit-awaredivmod, this keeps the explicit failuredivmodalready had onmainbefore this change — not a functional regression (#623).types (API narrowing, patch release):
%and//between a unit-bearingSeriesMatrixand any non-dimensionless operand now raiseTypeErrorexplicitly, instead of silently copying the left operand’s unit onto a value computed without unit conversion (the same defect class as the #576 fix above, found separately in%///). The dimensionless check requires an exact — not merely dimensionally equivalent — unit on every cell, so a matrix mixingu.dimensionless_unscaledandu.percentcells is rejected regardless of which cell comes first (previously the check compared only against the first cell, so500%could be silently treated as500depending on cell order). Dimensionless%and//are unaffected. Addition, subtraction, and comparison are unit-safe. Modulo, floor division, anddivmodare not supported for unit-bearingSeriesMatrixobjects in v0.1.x — they raise explicitly rather than performing value-only arithmetic. A correct unit-aware floor-division/remainder implementation is deferred to the v0.2.0 semantic redesign (#637) (#623).types: for finiteness checks on a matrix, use
mask = np.isfinite(matrix.value); this is the documented v0.1.13 alternative now that direct ufunc application (includingnp.isfinite) raisesTypeError(see the ufunc-narrowing entry above). No new API is added in this patch release (#623).io (API narrowing, patch release): readers that have no windowed read path accepted
start=andend=and then dropped them, returning the whole file as if it were the requested span. The arguments are now rejected instead. This affects theats.mth5andxml.diaggui/dttxmlreaders.Call
Before
After
TimeSeriesDict.read(src, format="ats.mth5", start=t0, end=t1)returns the full file, silently ignoring the span
raises
IoNotImplementedError(aNotImplementedErrorsubclass)TimeSeriesDict.read(src, format="dttxml", start=t0, end=t1)as above
as above
The remedy is in the exception message: read the source in full and crop the result,
TimeSeries.read(source, format=...).crop(start, end). Calls that pass neither selector are unaffected (#611).io (GWF) (API narrowing, patch release):
parallel=andnproc=were accepted by the GWF read path and then discarded, so a caller asking for parallel reads got a serial read and no indication of it. Parallel GWF reads are still not implemented; the arguments now say so.Call
Before
After
read(..., parallel=4)/read(..., nproc=4)serial read, request silently dropped
raises
NotImplementedErrorread(..., parallel=True)serial read
raises
NotImplementedErrorread(..., parallel=1)/nproc=1/ either set toNoneserial read
unchanged — serial read
read(..., nproc=0)or a non-integersilently dropped
raises
ValueErrorread(..., parallel=2, nproc=2)one of the two silently won
raises
TypeErrorImplementing parallel GWF reads is deferred to v0.2.0 (#588).
io (ndscope HDF5) (API narrowing, patch release): the ndscope HDF5 writer accepted arbitrary keyword arguments — including dataset creation options such as compression — and ignored them, so a file requested with compression was written without it and nothing said so. Unknown writer keywords now raise
TypeErrorbefore the target is opened, so no partial file is produced. No dataset creation option is supported in v0.1.13; the supported set is defined in v0.2.0 (#590).io (zarr, GBD, NetCDF4) (API narrowing, patch release): the single-series readers for these formats returned the first channel of a multi-channel source, and a
channels=selector naming an array that was not present was silently skipped rather than reported. Both now fail explicitly.Call
Before
After
single-series read of a multi-channel source
returns an arbitrary channel
raises
ValueError; passchannels=to select onechannels=["missing"]silently returns fewer channels, or none
raises
ValueErrornaming the missing channelschannels=["a", "a"]duplicate silently collapsed
raises
ValueErrorSelectors are validated against the source’s channel list before any payload is read, so an invalid selection costs nothing (#614, #615).
histogram (API narrowing, patch release):
Histogramhad no arithmetic contract, and whether an expression such asquantity * histogramfailed or silently produced a value depended on the incidental interaction between the histogram’sunit/valueattributes and Astropy’sQuantitydispatch. Uncertainty propagation for histogram arithmetic is not defined, so rather than leave that balance to chance,+ - * /and their reflected and in-place forms now raiseTypeErroronHistogram, and__array_ufunc__ = Nonemakes NumPy dispatch fail the same way instead of routing around the operators. Transform the values explicitly (for example via.value) until the propagation rules are defined (#579).interop (ROOT) (API narrowing, patch release):
from_rootacceptedTProfile,TProfile2D, andTH2Polyobjects and decoded them as if their bins hadTH1/TH2semantics, which they do not. These classes now raiseTypeErrornaming the class rather than returning a misinterpreted result (#593).
Known limitations#
#577c(in-placeout=update) is not achievable under this approach and remains an explicitTypeErrorrather than a working in-place update.The originally reported symptoms of
#577a/#577bdid not reproduce against this change’s base (dead code path); this release does not claim to have fixed them as originally described.Quantity == SeriesMatrixreturns a scalarFalsewhileSeriesMatrix == Quantityreturns a boolean matrix. This asymmetry originates inastropyand is not corrected here.
0.1.12 - 2026-07-31#
This is a metadata-integrity, statistics-correctness, and release-tooling patch release. It closes a silent NDScope HDF5 channel-drop bug (#541), corrects the Rayleigh-statistic null model and several related edge cases (#506), and hardens the release-publication workflow with fail-closed validation, exact-SHA pinning, and a documented trust boundary (#536).
Behaviour-visible bug fixes#
io: reading an ndscope HDF5 file now raises
ValueErrornaming the offending group when a data-bearing group carries neither arate_hznor asample_rateattribute, instead of skipping that group. Previously such a group was silently dropped, so a multi-channel file with one metadata-incomplete channel returned aTimeSeriesDictmissing that channel with no error, warning, or other indication that data had been lost. A group is data-bearing when it carriesgps_startand at least one of theraw/mean/min/maxdatasets; groups withoutgps_startand groups holding no ndscope dataset are not data-bearing and continue to be skipped, as before. Channels excluded by an explicitchannels=argument are never read and so are unaffected. Format auto-detection no longer requires a sampling-rate attribute either, so this error is raised throughTimeSeriesDict.read()/TimeSeriesMatrix.read()without an explicitformat=; previously a file whose groups all lacked the attribute failed identification and fell through to another reader, hiding the loss behind an unrelated error. This completes the external-metadata compatibility work in #534/#535 (#541).
Release tooling#
ci: the release publication workflow is now a single fail-closed
publish-release.yml, replacingrelease.yml. It resolves the release ref to an exact 40-character SHA beforebuild,smoke, orpublishconsume it, checks the validator code out separately from the revision it validates, pins every action to a full commit SHA, gates PyPI publication behind a tag push and thepypienvironment, and restricts OIDCid-token: writeto the publish job alone. Manual dispatches are dry-runs and must be launched with--ref main.scripts/validate_release.pyandRELEASING.mdare added alongside it. The validator rejects duplicate release metadata – two## [version]CHANGELOG headings, or a repeated top-levelversion/date-releasedinCITATION.cff– rather than reading the first occurrence, matching the release-note generator’s fail-closed behaviour.RELEASING.mddocuments where the trust boundary actually is, separating the controls enforced by repository and PyPI configuration from the operational rules that are not mechanically enforced, records the readback fields an auditor must check before a tag push (enforcement,target,conditions,rules[].type,bypass_actors, and the Trusted Publisher binding, which has no GitHub API readback), and states explicitly that the workflow’s dual checkout is not itself protection against a modified tag revision. The requiredbypass_actorsstate is given per ruleset rather than once for both, because acreation/update/deletionrule restricts its operation to the listed actors instead of forbidding it: the tag-creation ruleset must enumerate the permitted creators, while the tag-integrity ruleset must list none (#536).ci: the
pypa/gh-action-pypi-publishpin inpublish-release.ymlused the annotated tag object SHA forv1.14.2instead of the commit SHA it points to, so the pinned ref did not resolve to any container image and thepublishjob failed before uploading anything. Repinned to the peeled commit SHA (dc37677b2e1c63e2034f94d8a5b11f265b73ba33). No package was published to PyPI by the failed run.
Reproducibility note: the rayleigh_test entries below change numeric
output. The affected statistical model was introduced in 70bc11f55
(2026-03-28) and shipped unchanged in every release from v0.1.1 through
v0.1.11 – eleven releases, all still on PyPI. p-values produced by those
versions are not comparable with these and should not be pooled. There is no
flag to restore the old output; analyses that need to reproduce earlier
numbers must pin to the version that produced them.
statistics:
rayleigh_pvalue()/TimeSeries.rayleigh_test()now simulate the null distribution from exponentially-distributed power samples, matching the power-based statistic that GWpy’srayleigh_spectrogram()actually reports. Previously the null was drawn from Rayleigh-distributed amplitude samples and rescaled by the Rayleigh distribution’s coefficient of variation; matching the mean did not make the distribution shapes agree, so the reported p-values were systematically miscalibrated by an amount that depended on thestride/fftlengthratio. The corrected null is verified against the exact momentE[R^2] = (n-1)/(n+1), which follows from the sample coefficient of variation of exponential samples reducing to Greenwood’s statistic (#506).statistics:
TimeSeries.rayleigh_test()now derivesn_samplesfromfftlength,strideandoverlapinstead of defaulting to the constant39, which bore no relation to the data. The count is the number of periodogram segments GWpy averages per column, which is notdt * dfwhenever the segments overlap: GWpy chunks the series intonstride + noverlapsamples, sorayleigh_test()’s default path – which resolvesoverlap=Noneto the recommended 50% for the default hann window – produces twicedt * df. Fixing only the null distribution above would have left the default path worse calibrated than before, because the stale39happened to compensate for the wrong distribution shape at some configurations.n_samplesmay still be passed explicitly for backward compatibility; a value that disagrees with the derived one now emits aUserWarning. Note that the default value changed, soinspect.signature()reports a different default (#506).statistics:
TimeSeries.rayleigh_test()now raisesValueErrorwhenoverlapresolves to anything other than0or GWpy’s recommended overlap for the window (50% for the default hann), allowing one sample of slack for the truncating seconds-to-samples conversion. At 75% overlap GWpy previously returned without error or warning while using about 36% of the data, and the per-segment powers stop being even approximately i.i.d. exponential there, so no segment-count correction can make the null distribution apply (#506).statistics:
rayleigh_pvalue()now reports the DC bin and, when the FFT length in samples is even, the Nyquist bin asNaN, and accepts annfft=keyword so the Nyquist bin can be identified. A real-valued input’s DC and Nyquist FFT coefficients are purely real, so their power follows chi2_1 rather than an exponential; scoring them against the exponential null fired on pure Gaussian noise about 77% of the time at nominalalpha=0.05, which alone pinnedto_segments()near a 100% veto rate. An odd FFT length has no exact Nyquist bin and its last one-sided bin is left scored. This mirrors the DC/Nyquist handling added tocompute_student_t_nu()in v0.1.11 (#465). Recovering detection power at these bins with a chi2_1 null is left for a future release (#506).statistics:
TimeSeries.rayleigh_spectrogram()now derives its own per-segment averaging instead of delegating to GWpy’srayleigh(), which advances segment starts byfftlength - overlapbut counts segments usingoverlap. Those agree only at exactly 50% overlap, so for an odd FFT length – where the recommended Hann overlap is notnfft // 2– or for any other explicit overlap, GWpy omits valid segments or requests short ones. The count and the slice starts now come from the same hop. This changes the reported statistic values themselves, not just the p-values derived from them, relative to bothgwpyand GWexpy<=v0.1.11, in exactly those configurations. Therayleigh_test()recommended-overlap path is unchanged; directrayleigh_spectrogram()calls continue to default tooverlap=0, which is also unchanged.TimeSeries.rayleigh_test()rejects the divergent overlaps outright, so this affects directrayleigh_spectrogram()callers passing an explicit overlap only (#506).statistics:
rayleigh_pvalue(),_get_rayleigh_stat_null_distribution(), and_simulate_rayleigh_null()now raiseValueErrorwhenn_samples < 2. The statistic is a sample coefficient of variation overnsegments, son == 1produced an all-zero null against which every observed value scoredp == 0, andn <= 0produced an all-NaN null with the same effect – both silently. Then <= 0case previously raised only at the distribution layer; the bound is now>= 2and is enforced at all three entry points, since the distribution layer memoises into a shared cache (#506).
Known Limitations#
statistics:
to_segments()still applies no multiple-comparison correction – it flags a time when any frequency bin hasp < alpha. With correctly calibrated p-values and 63 scored bins this vetoes about 96% of times at the defaultalpha=0.05, so the above is a fix to per-bin calibration and not a fix to spurious vetoes. Choosealphaaccordingly, or restrict the frequency range.statistics:
rayleigh_pvalue()can still return exactlyp == 0, whereascompute_gauch()floors its p-values at1/n_monte_carlo. This asymmetry is unchanged here and tracked in #507.docs: the Japanese translation catalogue for the Rayleigh/GauCh tutorial does not yet contain the new v0.1.12 note; the notebook carries an explicit Japanese cell in the meantime.
0.1.11 - 2026-07-25#
This is a time/metadata-integrity and statistics-robustness patch release.
It fixes MNE interop epoch/metadata handling (#493), restores a
GPS-absolute time axis and DC/Nyquist fit correctness for the Student-t
non-Gaussianity indicator (#465), and adds Monte Carlo rng=/seed=
reproducibility for the Rayleigh and GauCh statistical tests (#464).
Behaviour-visible bug fixes#
Reproducibility note: the two statistics entries below (GPS time axis
and DC/Nyquist nu fit) change numeric output relative to <=v0.1.10.
Both are correctness fixes for previously wrong values, not new opt-in
behavior – there is no flag to restore the pre-v0.1.11 output. Analyses
that depend on exact reproduction of results computed with
gwexpy<=0.1.10 should pin to that version range.
interop:
to_mne_rawarray()now setsinfo["meas_date"]from the input epoch (t0) when not already present, and validates it against an existinginfo["meas_date"](within ~1us) rather than silently ignoring it; a mismatch now raisesValueErrorinstead of producing anmne.io.Rawwith an incorrect or missing epoch. Multi-channel conversion now requires all stacked channels to share an exactly matching epoch (previously unchecked), raisingValueErroron mismatch rather than silently interleaving samples from different acquisition times.t0values that fall on a leap second continue to raiseLeapSecondConversionError.from_mne_raw()now accounts forraw.first_sampwhen reconstructing the GPS epoch (previously ignored, undercounting the epoch for cropped/resumedRawobjects) and appliesunit_mapto set channel units, including fixing anAttributeErrorwhenunit_mapwas omitted (#493).statistics:
compute_student_t_nu()/TimeSeries.student_t_spectrogram()now return a GPS time axis (the inputTimeSeries’st0plus the STFT relative time) instead of relative-to-start seconds.fftlength,stride(andoverlap),sample_rate,window, andfrangeare now validated: non-finite/non-positive values raiseValueErrorinstead of an opaque downstream failure, andstride > fftlengthnow raisesValueErrorexplicitly rather than silently running a gapped analysis that skips samples between segments (scipy.signal.stftaccepts a negativenoverlapwithout erroring). The underlyingscipy.signal.stftcall now passeswindow="hann", detrend=False, boundary="zeros", padded=Trueexplicitly instead of relying on scipy’s defaults. Partially addresses #465 (the DC/Nyquist bin bias half of #465 is fixed by the following entry).Migration note (#465, #502): callers that previously added
ts.t0(or an equivalent GPS offset) to the returnedtimesthemselves should remove that step, since the returned times are now already GPS-absolute and double-adding the epoch would shift results byt0seconds. Code that indexes or slices these times assuming a relative-seconds axis starting at0should be updated for the new GPS-absolute axis.statistics:
compute_student_t_nu()now fits the DC (index 0) and, when the segment length is even, Nyquist (the last one-sided bin) frequency bins using only their real FFT coefficient, instead of concatenating real and imaginary parts as if they were independent for those bins too. A real-valued signal’s DC/Nyquist FFT coefficients are purely real, so the previous re+im concatenation fed a constant-zero imaginary half into the fit, collapsing the estimated Student-tnutoward 0 and producing a systematic false non-Gaussianity detection at DC regardless of the actual input. This is a GWexpy-specific correctness fix, not a GWpy compatibility change (GWpy has no equivalent Student-t fit API); DC/Nyquist are identified structurally (index 0 / the last one-sided bin), not via a floating-point frequency comparison, computed from the effective segment length (scipy.signal.stftsilently shrinksnpersegtolen(ts)when the requested value exceeds it, which can flip its parity relative to the requestedfftlength * sample_rate).compute_student_t_nu()now also rejects complex-valued input withValueError(previously silent, andscipy.signal.stftignoresreturn_onesided=Truefor complex input, which would have broken this bin classification). Completes #465.statistics:
compute_gauch()/TimeSeries.gauch()andrayleigh_pvalue()/TimeSeries.rayleigh_test()now acceptrng=(anumpy.random.Generator) orseed=for a reproducible Monte Carlo null distribution. The default (norng/seed) path is unchanged: it still draws from the legacy globalnumpy.randomstate, so an existingnumpy.random.seed(...)call continues to control it exactly as before. Passingrng=/seed=uses a dedicatednumpy.random.Generatorinstead and bypasses the shared, process-global null-distribution cache (previously keyed only by(n, n_trials), with no way to request a specific draw). The default path’s cache population is now serialized with a lock, fixing a pre-existing race where concurrent callers on the same(n, n_trials)key could each redundantly recompute and overwrite the cached distribution.compute_gauch()’s result now recordsn_monte_carloand, when given,seed(orrng_provided/seed_unused) in its.metadata; theSpectrogramreturned byrayleigh_pvalue()records the same as instance attributes that do not survive.copy()/slicing/serialization. Passing bothrngandseednow emits aUserWarningnoting thatseedis ignored (#464).
Dependencies#
Added
inspiral-rangeto thegwandalloptional-dependency extras (no change to core/required dependencies) (#487).
Known Limitations#
Added 2026-07-26, after the original release. The Zenodo record published on 2026-07-25 is an immutable snapshot and does not contain this section (see #506).
statistics:
rayleigh_pvalue()/TimeSeries.rayleigh_test()simulates its null distribution from amplitude (Rayleigh-distributed) samples, but the statistic it is compared against (TimeSeries.rayleigh_spectrogram(), from GWpy) is computed from power (exponentially-distributed) PSD segments. The two distributions have different shape even after matching their mean, which makes the reported p-values systematically miscalibrated. The effect depends on the stride/fftlength ratio: false-positive rates can be close to nominal for some configurations (e.g. ~5% when stride/fftlength ~= 2) and elevated for others (e.g. ~11% when stride/fftlength ~= 20), causing inconsistent over- or under-detection of non-Gaussianity/spectral lines. This is a pre-existing issue, not introduced by therng=/seed=reproducibility work in this release; a fix is expected in v0.1.12 and will changerayleigh_pvalue()’s numeric output (see #506).
[0.1.10] - 2026-07-18#
This is a bugfix release covering numerical regularization, axis regularity, histogram weights, and TimeSeries resampling dtype and boundary contracts.
Behaviour-visible bug fixes#
numerics:
TimeSeriesMatrix.partial_correlation_matrix()now defaults toeps="auto"instead of1e-8, andWhitenTransformnow defaults toeps="auto"instead of1e-12.LaplaceGram.normalize_per_sigma()now defaults toeps="auto"instead of1e-30. These defaults scale their regularization to the input, so strain-scale partial correlations and whitening no longer collapse toward zero. Pass the former explicit value to retain prior behavior.partial_correlation_matrix(eps=None)continues to disable its added ridge for compatibility. All three APIs now reject non-finite input and invalid epsilon values withValueError(#482).types:
AxisDescriptor.regularnow compares represented adjacent intervals with a narrowly bounded interval-scale tolerance. It accepts ordinary floating-point regular grids while rejecting materially unequal large-offset float32 intervals that could yield an incorrectdelta(#492).histogram:
Histogram.fill()now preserves fractional scalar weights when histogram coordinates use an integer dtype, including flow-bin values and variance accumulation (#489).timeseries: time-bin
resample()now implementsclosed="right"as an include-lowest first bin followed by right-closed bins. It also validates enum options, widths, and offsets before constructing the bin grid (#491).timeseries:
TimeSeries.asfreq()preserves integer source dtype and exact values when an integralfill_valueis representable. Out-of-range integral fills now raiseValueErrorrather than causing a lossy dtype promotion; fractional, complex, NaN, and infinite fills still promote to a dtype that can represent them (#490).
[0.1.9] - 2026-07-11#
This is a bugfix release. It completes the GWF read-path NaN-padding
harmonization deferred from 0.1.8 (#481) and fixes two fitting bugs
surfaced by the #461 follow-up audit: an off-by-one in fit_series’s
sigma cropping at exact x_range boundaries, and an unvalidated
run_mcmc(n_walkers=...) that let emcee’s internal error leak through
(#466).
Behaviour-visible bug fixes#
GWF-specific reads (
TimeSeries.read/TimeSeriesDict.read/TimeSeriesMatrix.readwithformat="gwf") now defaultgap="pad"toNaNpadding instead of0.0, completing the harmonization withSeriesMatrix.append’s NaN default shipped in 0.1.8. Code that relied on zero-filled GWF gaps should pass an explicitpad=value. Padding a missing region (an inter-file gap or a requested interval that extends beyond the available data) withNaNon a non-floating-point GWF channel dtype now raises a clearValueErrorinstead of silently corrupting the data with an out-of-range integer fill value or leaking NumPy’s opaquecannot convert float NaN to integer(#481).
Bug fixes#
fitting:
fit_series(..., sigma=..., x_range=...)no longer crashes with a spuriousSigma length mismatchValueErrorwhenx_range’s upper bound exactly matches a data bin edge. The sigma array is now cropped using the same index range thatSeries.crop()actually used for the fitted data (#466).fitting:
FitResult.run_mcmc()now validatesn_walkers >= 2 * ndimbefore invokingemcee.EnsembleSampler, raising a clearValueErrorwith the required minimum instead of letting emcee’s internalRuntimeErrorsurface with no gwexpy-level context (#466).
Development#
Move maintainer-only
.harness/AI workflow files out of the public repository and skip private harness sync tests when the harness is absent (#483).
[0.1.8] - 2026-07-04#
This is a bugfix and I/O-hardening release. It fixes the from_obspy
crash on obspy.Stream input (#452) and hardens the SeriesMatrix / I/O
layer: gap handling, metadata aliasing, key round-trips, and reader
failure modes surfaced by the #443 follow-up audit are fixed with explicit
contracts and regression tests (#450).
Behaviour-visible bug fixes#
SeriesMatrix.append(gap="pad")(and the matrix multi-source merge paths built on it) now pads gaps withNaNinstead of0.0by default, matching the missing-data convention already used by the multi-source readers. Code that relied on zero-filled gaps should pass an explicitpad=value or handle NaNs before reductions/FFT/filtering. Padding a gap withNaNon a non-floating matrix dtype now raisesValueErrorinstead of silently inserting an invalid fill value.io/netcdf4: A failed per-channel series construction no longer degrades to a plain dict (which deferred an opaque
AttributeErrordownstream); the reader now raises a clearValueErrorat the source.io/netcdf4: Time-coordinate detection now prefers an explicitly named coordinate (
time/Time/TIME/t) and warns when it has to fall back to a datetime64 coordinate — more loudly when several candidates make the choice ambiguous. Passtime_coord=to select the axis explicitly.io/zarr: Reading a store without a stored epoch now emits a
UserWarning(the silent default was GPS epoch 1980); a newt0_override=argument sets the epoch explicitly.io/sdb: Non-numeric values dropped by numeric coercion are no longer silently lost; the reader warns with the affected column and count.
Bug fixes#
interop/obspy:
TimeSeries.from_obspyno longer crashes with an opaqueAttributeErrorwhen given anobspy.Stream: a single-trace Stream is converted directly, and a multi-trace Stream raises a clearTypeErrorpointing to the newTimeSeriesDict.from_obspy/to_obspymethods. Thefrom_neo(Block/Segment),from_torch(non-1D tensors) andfrom_root(container inputs) converters gained equivalent clear-error guards (#452).SeriesMatrixmetadata aliasing is now fully closed out:rows/cols/meta/attrsare deep-copied in_get_meta_for_constructor(used bycrop/append/diff/pad/interpolate), in the matrix math ops (trace,matmul,schur,abs, …) and in__array_ufunc__, so mutating a derived object’s metadata no longer corrupts the source. This extends theastype/real/imagfix shipped in 0.1.6 (#442) to the remaining construction paths.io/hdf5:
SeriesMatrixrow/column keys are now JSON-encoded on write (key_format="json", mirroring the NetCDF4/Zarr encoders), so tuple and numeric keys survive HDF5 round-trips instead of collapsing to strings. Legacy string-keyed files remain readable.io/dttxml: JSON key decoding now recursively converts nested lists to tuples (matching the Zarr decoder) so round-trips preserve tuple keys and hashability.
interop:
to_xarray()(andto_xarray_frequencyseries()) no longer persist an unsetchannel/nameas the literal string"None", which previously round-tripped back into a bogusChannel("None"). Thechannelmetadata now survivesto_dict/from_dict,to_xarray/from_xarrayandto_hdf5/from_hdf5round-trips.
Behaviour changes#
interop:
TimeSeries.from_dict/from_json/from_xarray/from_pandas/from_hdf5_dataset/from_netcdf4/from_polars(and the underlying interop helpers) now accept explicitchannel/unit/name/t0/dtkeyword arguments to supply metadata the source object cannot carry; an explicit argument always takes priority over a stored value (user > source).interop: when
t0/dtcannot be recovered or inferred, the converters (from_dict,from_pandas,from_hdf5,from_netcdf4,from_polars) now fall back tot0=0/dt=1with aUserWarninginstead of silently, matching the Zarr reader. The fallback values are unchanged, so this is a backward- compatible (SemVer MINOR) addition; passt0=/dt=explicitly to silence it.
Dependencies#
Added upper version bounds to I/O backends to guard against major-version API breaks:
h5py>=3.0,<4(core),obspy<2,nptdms<2,netCDF4<2,zarr>=2,<4(extras). These caps stay within the currently tested major series but can affect dependency resolution in environments that already pin newer majors (#450, D1).
Tests#
interop: Added regression tests for the
resolve_timing/resolve_metahelpers,channelround-trips across dict/json/xarray/hdf5/netcdf4, the"None"-string guard, falsy-zerot0=0.0handling, user-supplied metadata overrides, and the new missing-timingUserWarningfor each converter.Added matrix key round-trip contracts across HDF5/NetCDF4/Zarr, multi-source reader and gap-padding coverage, SDB/Zarr reader warning tests, and io-conformance generators/validators for the SDB and Zarr backends.
Known limitations#
GWF-specific read padding still follows its existing zero-padding path (
gwexpy/timeseries/_gwf_io.py); harmonizing GWF padding with the matrix/core NaN convention is deferred to a follow-up issue.
[0.1.7] - 2026-06-27#
This is a numerical-robustness hardening release. A Phase 1 audit across the statistics, fitting, and spectral modules surfaced a set of silent-failure and degenerate-input bugs; this release adds explicit input-contract guards and a matching suite of regression tests so that invalid inputs raise clear errors instead of producing Inf/NaN or silently wrong results.
Bug fixes#
fitting/core: LSQ cost classes now validate
dy— zero, negative, non-finite, or complex elements raiseValueErrorinstead of causing silent fit failures or Inf/NaN results (#469).fitting/gls: GLS classes gained covariance / inverse-covariance conditioning guards and PSD checks (#457 via #472).
fitting/models: Added degenerate-parameter guards to model shape functions (#455 via #471).
statistics: Guarded degenerate and non-finite inputs across
rayleigh_test,gauch,dq_flag, andstudent_t_indicator— all-NaN inputs,p=0false vetoes, Inf-corrupted distributions, anIndexError, and mis-sized segments are now handled explicitly (#459 via #470).statistics/roc:
calculate_rocandevaluate_detection_performanceenforce their input contract — empty classes, shape mismatch, non-finite scores, and tied-FPR bias now raiseValueError, and sklearn-style{-1, +1}labels are handled correctly (#468).spectral: Guarded
bootstrap_spectrogramedge cases — float truncation, NaN/Inf energy,rebin_widthvalidation, covariance mean-imputation, and zero-width confidence-interval warnings (#460 via #473).
Tests#
Added input-contract regression suites covering the fixes above:
tests/fitting/test_lsq_cost_dy_contract.py,tests/fitting/test_gls_contracts.py,tests/fitting/test_models_domain_contract.py,tests/statistics/test_degenerate_input_contract.py,tests/statistics/test_roc_input_contract.py,tests/spectral/test_bootstrap_spectrogram_contract.py.
Maintenance#
Centralized gwexpy provisioning across CI workflows and fixed nightly drift via a shared
setup-gwexpycomposite action (#454).Added release-note tooling (
tools/gen_release_notes.py,tools/publish_releases.sh) that generates standardized GitHub Release notes fromCHANGELOG.md.
Documentation#
Added the Phase 1 numerical-robustness sweep and supplement reports under
tech_notes/(#462).
[0.1.6] - 2026-06-11#
This is a bugfix and maintenance release: plotting/I/O follow-up fixes (#440, #441, #442 via #443), a development dependency sweep (#431), and FrequencySeries collection registry audit tests (#438).
Bug fixes#
Fixed subplot geometry calculation in
Plotso the expansion count always matches_expand_args: all arguments are now counted regardless of order (a leading Spectrogram or matrix no longer hides later containers), the duplicated counting loops were unified into a single helper, and a leading matrix keeps its grid geometry only when it is the sole argument (#440).All TimeSeries readers now handle a list or tuple of paths: formats with well-defined merge semantics (tdms, ats, csv, netcdf4, gbd, ndscope HDF5, zarr, sdb, win, dttxml) concatenate channels along time with NaN gap padding via a shared multi-source helper, while self-contained formats (wav, audio) raise a clear
ValueErrorinstead of an opaque backendTypeError(#441).GWF alias registration no longer swallows unexpected errors silently: expected missing-backend lookups return
Noneas before, anything else emits a warning (#442).Pickling a
SeriesMatrixnow emits a warning listing any attrs entries that had to be dropped because they cannot be pickled, instead of dropping them silently (#442).SeriesMatrix.astype(),.real,.imag,.conj(),.transpose()/.Tand.reshape()now deep-copyattrslike.copy()does, so mutating the result’s attrs no longer leaks into the source matrix (#442).Multi-file NetCDF4/Zarr reads into
TimeSeriesMatrixnow preserve the matrix row/column keys instead of collapsing them through the dict-reader shortcut, and NetCDF4 gained a dedicated matrix writer.Multi-file matrix segment merging now passes
pad=np.nan, so gaps between files are NaN-padded instead of raising.
Maintenance#
Updated the development dependency group (24 packages) in
requirements-dev.txt(#431).
Tests#
Added
tests/timeseries/test_rms_compat.pypinning gwpy-compatibleTimeSeries.rms(stride)behaviour (gwpy reference parity, positional-int regression for #451, trailing-window drop, NaN-per-window propagation) and the gwexpy enhancements (time/dimensionlessQuantitystride, unit preservation, andValueErrorfor sub-sample/zero/negative/irregular strides). Re-pointedtest_stats_mixin.py::test_rms_with_unitontoSeries.Added plot geometry tests for mixed-container argument orders, single 2D/3D/4D matrices, and parity with
_expand_argsexpansion counts.Added multi-source reader tests covering merge, gap padding, overlap errors, empty-list rejection, and clear single-file-only errors.
Added regression tests for pickle attrs warnings and attrs independence of derived matrices.
frequencyseries/io: Added registry-backend audit tests and a developer note for FrequencySeries collection read/write fallback (#438). No behaviour change.
[0.1.5] - 2026-06-10#
This is a patch release focused on plotting and I/O hotfixes.
Bug fixes#
Fixed
TimeSeriesDict.plot()so multi-channel dictionaries are expanded into separate subplots instead of producing a blank or invalid figure (#432).Fixed ObsPy-backed seismic readers so
TimeSeriesDictkeys are stable string trace names (e.g."IU.ANMO.00.BHZ"), enabling reliable string-based lookup (#435).Added support for passing a list or tuple of miniSEED paths to
TimeSeriesDict.read(..., format="mseed")(#433).Fixed
gwexpy.frequencyseriesimport-time I/O registration so FrequencySeries read formats are visible through the GWpy default I/O registry (#437).
Tests#
Added regression tests for
TimeSeriesDict.plot().Added seismic I/O tests for string keys, list-of-path miniSEED input, and empty-list rejection.
Added subprocess-isolated FrequencySeries I/O registration tests.
Documentation#
Clarified that GWexpy is an independent package built on top of GWpy and is not an official component of the GWpy project.
Updated the README installation notes to reflect that the conda-forge feedstock is available, while conda-forge packages may lag the latest PyPI release.
Deferred#
Broad FrequencySeries collection read/write registry-backend migration is deferred to #438.
Dependency sweep (#431) is deferred to the v0.2.0-prep lane.
0.1.4 - 2026-05-20#
Added#
io/conformance: Added the first contract-driven I/O conformance baseline for
gwf,hdf.ndscope,hdf5,csv,txt, andwav.io/contracts: Added v3 public I/O contract policy fields for fixture generation, coverage status, CI jobs, and missing optional dependency behavior.
ci/io: Added the
io-conformancegate and expanded I/O gate documentation.time: Added opt-in
dtype=output modes forto_gps(). The default remains GWpy-compatible, whiledtype=float/dtype="float"return plain float values anddtype="quantity"returns seconds quantities for direct.timescomparisons.
Fixed#
io/dttxml: Fixed
load_dttxml_products()so DTTXMLTSentries remain raw dict payloads and do not collide withTimeSeries.get().io/gwf: Provisioned the GWF backend for the PR fast gate and tolerated backend-specific GWF channel metadata variance.
Documentation#
feedback: Updated the README, docs hub pages, footer links, roadmap, and troubleshooting pages to point lightweight bug reports and feature requests to the public feedback form. Security reports remain directed to the repository security policy.
Tests#
io/conformance: Added deterministic fixture generators and read/write round-trip coverage for the v0.1.4 blocking format baseline.
io/dttxml: Added regression coverage for DTTXML
TSdict parsing throughread_timeseriesdict_dttxml().netcdf: Added a fixture-generation contract that requires generated NetCDF fixtures to expose an explicit time coordinate (#393).
timeseries/gwf: Added regression coverage for multi-channel GWF list-source reads and padded gap reads with
parallel > 1.
Known Issues#
io/zarr: The optional
io-zarrgate can hang in environments where Zarr 3.1.5 stalls during basiccreate_array()fixture generation. Zarr remains outside the v0.1.4 base blocking gate and is tracked for optional-backend hardening.
0.1.3 - 2026-05-12#
Fixed#
timeseries/gwf: Fixed multi-file GWF reads for
TimeSeriesandTimeSeriesDictinputs.timeseries/matrix: Fixed ndscope HDF5 auto-detection for
TimeSeriesMatrix.read().io/contracts: Aligned public I/O docs and contract metadata with current autodetection behavior.
frequencyseries/csv: Added a dedicated CSV fast path that preserves the original frequency column values.
timeseries/zarr: Fixed matrix round-trip coverage under zarr 3 and removed timeout-prone fixture behavior.
plot/geomap: Treat PyGMT installations without a loadable GMT shared library as an unavailable optional backend instead of failing at import time.
Known Issues#
netcdf: The bundled NetCDF fixture can fail the TimeSeries reader time-coordinate contract in some cases (#393). Generated NetCDF round-trip coverage still passes; users relying on NetCDF fixtures should verify that their files expose an explicit time coordinate.
0.1.2 - 2026-05-08#
Narrow v0.1.2 hotfix scope#
io/gwpy4: Narrow compatibility hotfixes for public I/O proxy imports and GWF list/dict read paths.
io/formats: Targeted reader auto-identify and compatibility fixes for histogram HDF5, ATS/MTH5, audio, seismic, SegmentTable span CSV, and FrequencySeries DTT XML flows.
integration: Narrow landing updates include only the minimal #369 landing/demo import hunk required for this track.
release status: Version metadata and release notes are finalized for
v0.1.2, but tag creation, PyPI publication, Zenodo publication, fresh release smoke reruns, and conda-forge refresh are still pending.
Packaging & Optional Dependencies (issue #251)#
packaging: Added
netcdf4extra (netCDF4,xarray) andzarrextra (zarr) topyproject.toml; both are now included in theallconvenience extra.packaging: Removed the experimental
gwexpy.guipackage, console script, andguiextra from the published PyPI distribution; GUI work remains source/development-only until the post-release stabilization track is complete.packaging: Tightened first-release artifact hygiene by excluding top-level tests, docs sample data, and package-internal Sphinx helper shims from built distributions.
packaging: Removed hand-edited tail from
requirements-dev.txt;analysisextras are now managed exclusively throughpyproject.toml.interop: Fixed
_optional.py_EXTRA_MAP— phantom extras (interop,bio,stats,eda) replaced withNoneentries that fall back to barepip install <package>;netCDF4/xarraynow point tonetcdf4extra;zarrpoints tozarrextra.io:
ensure_dependency()error hint corrected topip install 'gwexpy[<extra>]'instead ofpip install <pkg>[<extra>].io:
_import_pydub,_import_obspy,_import_nptdms,_import_zarr,_import_xarrayerror messages now includepip install 'gwexpy[<extra>]'hints.fitting:
gwexpy.fitting.__getattr__error messages now suggestpip install 'gwexpy[fitting]'.ci:
io-optionalgate extended withtest_seismic_public_io.py;test_optional_deps.pyaugmented withgwexpy[extra]hint assertions andTestSeismicImportGuard.docs: Installation guide updated with
netcdf4,zarrextras and clarifiedguiis not inall.
Infrastructure & CI#
ci: Comprehensive stabilization of the CI pipeline, resolving all
ModuleNotFoundErrorandSyntaxErrorregressions.ci: Added mandatory Notebook syntax validation to the primary test workflow to proactively catch corrupted
.ipynbfiles.ci: Restored and standardized scientific dependencies (
control,statsmodels,scikit-learn, etc.) across all GitHub Actions environments.docs: Performed a global “reset-and-rewrap” of tutorial notebooks to fix indentation errors in
warnings.catch_warnings()blocks.
Added#
fields:
VectorFieldandTensorFieldnow support initialization directly from NumPy ndarrays (5D for VectorField, 6D for TensorField), automatically creating the componentScalarFields without breaking backward-compatible dictionary initialization.
Changed#
fields:
ScalarFieldbinary arithmetic now fails fast withValueErrorwhen operands have mismatched time/frequency domains, spatial domains, or coordinate grids. Align fields explicitly before arithmetic; future regridding/interpolation APIs will track explicit grid-alignment workflows.plot:
FieldPlotlabels now avoid empty unit brackets for unitless metadata and expose the latest scalar colorbar via the publiclast_field_colorbarattribute. Explicitlabel=""colorbar labels remain supported.
Documentation#
docs: Unified the Class Index into five major categories (Core, Field, Signal, Analysis, Utilities) with standardized Japanese translations (e.g., “時系列行列” for
TimeSeriesMatrix).docs: Redesigned major guidance pages (
io_formats,numerical_stability,time_utilities,architecture) using judgment tables and decision-driven structures.docs: Refined visual aesthetics with custom CSS for modern typography, responsive tables, and card-based navigation in the Sphinx RTD theme.
docs: Integrated SEO/OGP metadata, sitemaps, and automated “Last updated” timestamps.
Infrastructure#
ci: Implemented a weekly documentation health check (
docs-weekly-health.yml) to monitor broken links, terminology consistency, and JA/EN synchronization.ci: Standardized notebook testing pipeline with
papermillfor full execution (Light) andnbvalfor syntax validation (Heavy).ci: Integrated
nbstripoutinto pre-commit hooks to manage repository size and diff clarity.pre-commit: Added a GitHub Actions PR template with automated quality gate checklist.
0.1.1 - 2026-04-28#
Added#
SegmentTable: New factory methods
read()andread_csv()for initializing from external files.SegmentTable: Support for the iterable protocol (
__iter__) andRowProxyfor direct row-wise processing.Tutorials: Comprehensive new notebooks for
SegmentTable,Noise Generation, andSpectral Fitting.Infrastructure: Automated tutorial execution testing via
pytest --nbmakeand GitHub Actions.analysis/coupling:
CouplingFunctionAnalysis—from_time_windows(),from_time_windows_batch(),bkg_windowパラメータ追加 (Phase 1).analysis/coupling_result:
CouplingResult—to_csv(),from_csv(),to_txt(),from_txt(),to_summary_csv()によるファイルエクスポート (Phase 2).analysis/coupling_result:
CouplingResult—plot_significance(),plot_asdgram(),plot_snrgram()可視化メソッド追加 (Phase 3).analysis/coupling_result:
CouplingResultCollection— 複数結果の集約コンテナ (Phase 2).analysis/stats:
SpectralStats— スペクトル統計コンテナ(spectral_stats()より取得) (Phase 2).analysis/response:
ResponseFunctionResult—plot_projection_summary(),plot_response_matrix()可視化メソッド追加 (Phase 3).analysis:
ResponseFunctionResult,ResponseFunctionAnalysis,estimate_response_function,detect_step_segmentsをgwexpy.analysisから公開 (Phase 4).docs: Sphinx API リファレンスに
coupling_result,response,threshold,statsモジュール追加 (Phase 4).tutorials:
case_coupling_analysis.ipynb/case_response_analysis.ipynbに Phase 1–3 の利用例を追補 (Phase 4).
Changed#
SegmentTable:
add_series_column()now accepts a simpleloader(segment)callable for intuitive lazy loading.noise/peaks: Renamed
lorentzian_line()parameterfwhmtogammafor consistency with implementation.
Fixed#
fitting/highlevel: Resolved frequency bin alignment between PSD and covariance matrix in
fit_bootstrap_spectrum.fitting/highlevel: Removed unsupported
strideparameter fromfit_bootstrap_spectrum.table/segment_plot: Fixed
TypeErrorwhen an existingAxesobject is provided tosegments().
Previously Unreleased (merged into 0.1.1)#
interop/multitaper:
from_mtspec/from_mtspec_arrayがclsパラメータを 無視して CI 付き入力でも常にFrequencySeriesDictを返していた問題を修正。interop/meshio:
cell_dataのみを持つmeshio.Meshをfrom_meshioに渡した場合の 誤った補間経路を廃止し、明確なValueErrorを送出。interop/pyroomacoustics:
room.rirのインデックス順序(マイク ↔ ソース)を修正。interop/openems: HDF5 データセットの
"Time"/"frequency"属性の優先使用を修正。
0.1.0 - 2026-03-15#
Release Summary#
Early stable GWexpy release focused on API stability, GWpy compatibility, and reproducible commissioning workflows. Publication status is not asserted here.
Changed#
Version: Updated from
0.1.0b2to0.1.0(stable release)GWpy API UX Compatibility: Aligned key spectral API call conventions with GWpy 4.x usage patterns.
TimeSeries.transfer_functionnow accepts GWpy-style positional calls:transfer_function(other, fftlength, overlap, window, average, ...)
TimeSeriesDict/TimeSeriesListnow accept positional spectral args for:csd,coherence,csd_matrix,coherence_matrixpositional
(fftlength, overlap)is supported in addition to keyword usage
Mixed positional+keyword specification of
fftlength/overlapnow raises clearTypeError.
Authors: Removed email from
pyproject.tomlto prevent spam (contact via GitHub Issues or paper)
Added#
Compatibility policy doc:
docs/developers/compatibility/gwpy/API_UX_POLICY_20260303.md
GWpy compatibility tests:
tests/timeseries/test_transfer_function_compat.pytests/timeseries/test_collections_spectral_compat.pytests/timeseries/test_fft_param_compat.pyIncludes edge-case checks for positional/keyword conflicts and invalid numeric
otherin collection APIs.
CI workflow for compatibility gate:
.github/workflows/test-compat-gwpy.ymlRuns focused GWpy-compat tests plus
tests/timeseries, with pinnednumpy<2.0andastropy<7.0.
Publication materials:
Paper source:
docs/gwexpy-paper/main.texPublication preparation plan:
docs/developers/plans/for_paper_publication.md
0.1.0b2 - 2026-02-23#
Changed#
API Unification: Standardized all spectral analysis function signatures to use time-based parameters (
fftlength/overlapin seconds) instead of sample-count-based parameters (nperseg/noverlap). This aligns gwexpy with GWpy conventions and improves user experience.Affected Functions:
gwexpy.spectral.bootstrap_spectrogram()- now acceptsfftlengthandoverlap(seconds)gwexpy.fitting.fit_bootstrap_spectrum()- now acceptsfftlengthandoverlap(seconds)gwexpy.spectrogram.Spectrogram.bootstrap()and.bootstrap_asd()- now acceptfftlengthandoverlap(seconds)gwexpy.fields.signal.*spectral functions (spectral_density, compute_psd, freq_space_map, coherence_map) - now acceptfftlengthandoverlap(seconds)gwexpy.timeseries.TimeSeriesMatrixspectral methods (_vectorized_psd, _vectorized_csd, _vectorized_coherence) - now acceptfftlengthandoverlap(seconds)
Migration Note: Using deprecated
npersegornoverlapparameters will raiseTypeErrorwith guidance to usefftlengthandoverlapinstead. No deprecation period - breaking change applies immediately.New Module:
gwexpy.utils.fft_argsprovides helper functions for parameter validation and conversion:parse_fftlength_or_overlap()- converts time values (float, int, Quantity) to seconds and samplescheck_deprecated_kwargs()- detects and rejects deprecated parametersget_default_overlap()- returns window-appropriate default overlap values (GWpy-compatible)
GWpy Compatibility: All functions now follow GWpy conventions for time-based FFT parameters, improving interoperability and reducing API confusion.
Improved#
Numerical Stability: Implemented a comprehensive numerical hardening strategy for low-amplitude gravitational-wave data (O(1e-21)).
Adaptive Whitening:
whiten()now uses an adaptiveepsrelative to input variance, preventing signal destruction in quiet channels.Robust ICA:
ica_fit()includes internal standardization and relative tolerances to handle high-dynamic-range data.Safe Logging: Visualization tools now use dynamic floor calculation to prevent
-infor clipped values in dB plots.Machine Precision: Numerical constants now adapt to float32/float64 machine precision.
Fixed#
GBD: Apply amplifier range scaling when reading Graphtec
.gbdso analog channels are correctly converted from raw counts to volts, and treatAlarm/AlarmOut/Pulse*/Logic*as digital status channels (0/1, dimensionless). Digital channel mapping can be overridden viadigital_channels=....
0.1.0b1 - 2026-02-01#
Initial Public Release#
This is the first public beta release of
gwexpy. All previous development history (up to internal version 0.4.0) is consolidated here.
Important Notes#
gwpy Compatibility: This release is compatible with
gwpy>=3.0.0,<4.0.0. gwpy 4.0.0 introduced breaking API changes that are not yet supported. Users should ensure they have gwpy 3.x installed.
Refactored#
Exception Handling: Eliminated broad
except Exceptionpatterns in NDS, GUI, and IO modules. Replaced with specific exception types (OSError,ValueError,KeyError, etc.) for more predictable error handling and better debugging.GUI Architecture: Improved separation of concerns between UI and core logic layers in GUI components.
Added#
Core Data Structures:
TimeSeries,FrequencySeries,Spectrogramclasses with metadata management.TimeSeriesMatrix,FrequencySeriesMatrix,SpectrogramMatrixfor multi-channel data handling.ScalarField,VectorField,TensorFieldfor 4D experimental domain semantics.
Numerical Semantics:
Strict unit propagation for calculus methods.
Fixed DC component handling in integration to prevent singularities.
Advanced Signal Processing:
functional Short-Time Laplace Transform (
stlt).High-performance resampling with various aggregation methods.
Whitening and standardization models.
Interoperability:
Support for various file formats: TDMS, GBD, WIN, ATS, SDB/SQLite, WAV.
Integration with ML frameworks (Torch/TensorFlow) and ROOT (CERN).
MTH5 support for magnetotelluric data.
Fitting & Statistics:
Comprehensive
fittingmodule withiminuitandemcee(MCMC) support.Statistical aggregation and interpolation for matrix structures.
GUI:
Interactive GUI for real-time streaming data visualization and analysis.
Improved#
Type Safety: Comprehensive type annotation expansion across the codebase:
Added strict type hints to GUI (UI layer, NDS modules, streaming, engine).
Enhanced
TimeSeriesMatrixmixin with Protocol-based type-safesuper()calls.Introduced
TypedDictdefinitions for structured data in IO and GUI modules.Expanded MyPy coverage to include
gui/nds/andgui/ui/directories.
CI Stability:
Replaced deprecated
qtbot.waitForWindowShown()withqtbot.waitExposed()in GUI tests.Added warning filters to suppress third-party deprecation warnings (NumPy, pandas) in test configuration.
Refined MyPy exclude patterns for better coverage-exclusion balance.
Optimized ROOT/NumPy vectorization.
Refactored
noisemodule for better maintenance.
Fixed#
GUI Tests: Resolved flaky test issues related to window visibility timing.
Type Errors: Fixed various MyPy errors including uninitialized attributes and missing return type annotations.
Fixed unit propagation in complex matrix operations.
Corrected IFFT amplitude scaling for one-sided spectra.