Skip to content

Add waveform arithmetic and statistical reductions - #45

Open
theHenks wants to merge 28 commits into
JuliaPhysics:mainfrom
theHenks:waveform-arithmetic
Open

Add waveform arithmetic and statistical reductions#45
theHenks wants to merge 28 commits into
JuliaPhysics:mainfrom
theHenks:waveform-arithmetic

Conversation

@theHenks

@theHenks theHenks commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

Disclaimer: I applied my newly gained claude code knowledge from the juliacon to this PR!

This resurrects and extends the stashed waveform-math branch, which came up in the discussion on JuliaPhysics/RadiationDetectorDSP.jl#71 (sum_waveforms) as the more natural home for this kind of operation.

What's added

  • RDWaveform: +, -, unary - between waveforms (matching time axes required); scalar *, /, \; scalar +/- shifts in both argument orders, with either side free to carry a unit
  • ArrayOfRDWaveforms: sum, mean, var, std sample-wise across waveforms, including a flatview-based fast path for contiguous storage
  • Base.Broadcast.broadcasted specializations so broadcasting the above over an ArrayOfRDWaveforms preserves the underlying signal storage layout (contiguous storage stays contiguous), following the same pattern RadiationDetectorDSP.jl uses for shift_waveform/multiply_waveform

Testing

  • Pkg.test() passes 153/153, including the Documenter doctests
  • Reductions and broadcasting are additionally tested against JLArrays.jl (a non-Array AbstractArray backend used for GPU-generic testing without requiring GPU hardware), confirming the storage-preserving fast paths dispatch and produce correct results on a backend other than Array
  • Validated against real LEGEND calibration waveforms (baseline subtraction + superpulse construction via mean/sum)

Compatibility

Statistics is added as a new direct dependency (stdlib, available on the 1.10 LTS floor) for the mean/var/std extensions. No breaking changes.

CC @oschulz — saw your note on #71 about possibly tackling this yourself; just wanted to have a look and test my claude code setup. let me know if you intended something else here

Support `+`, `-`, unary `-` between waveforms sharing a time axis,
scalar `*`, `/`, `\` on a single waveform, and sample-wise `sum`,
`mean`, `var`, `std` across an ArrayOfRDWaveforms. Reductions require
all waveforms to share a time axis and throw an ArgumentError
otherwise; the shared axis carries through to the result.

Makes superpulse construction a direct `mean(wfs)` rather than
requiring a dedicated function.

Assisted-by: Claude Opus 5 <noreply@anthropic.com>
Sample-wise reductions accumulate into a single preallocated buffer
instead of combining whole signal vectors pairwise, which makes their
cost independent of how signals are stored: unchanged for contiguous
storage, and roughly twelve times faster with a fraction of the memory
for signals held as separately allocated vectors. Integer samples
narrower than Int now accumulate in Int, since summing thousands of
Int32 detector samples overflows silently.

Broadcasting an operator over an ArrayOfRDWaveforms is evaluated
eagerly over the whole sample storage, so contiguously stored signals
stay contiguous rather than being rebuilt as a vector of separately
allocated vectors. This trades away broadcast fusion, and matches how
RadiationDetectorDSP treats the equivalent named operations.

Assisted-by: Claude Opus 5 <noreply@anthropic.com>
Shifting every sample by a constant is one of the most common waveform
operations, but was reachable only by constructing a second waveform.
Support `wf + a` and `wf - a` on a single waveform, and broadcast forms
on an ArrayOfRDWaveforms taking either one shift for all waveforms or a
vector holding one shift per waveform, which covers subtracting
per-waveform baselines.

The scalar is typed as a RealQuantity, so adding a bare number to a
waveform carrying units is rejected rather than silently accepted.

`wfs + a` on an ArrayOfRDWaveforms is intentionally left undefined: it
is an AbstractArray, and adding a scalar to an array without
broadcasting is not valid Julia.

Assisted-by: Claude Opus 5 <noreply@anthropic.com>
Shifting a waveform whose samples carry a unit by a plain number is
unambiguous: the number is the shift expressed in that same unit. It
now does that instead of being rejected as a dimension error, which
matters because sample units are often attached only partway through an
analysis while the shifts computed from the data stay plain numbers.

Scalars that carry their own unit keep converting as before, and
waveforms with unitless samples are unaffected.

Assisted-by: Claude Opus 5 <noreply@anthropic.com>
Shifting combines samples and a shift amount, and either may carry a
unit while the other does not. In that case the side without one is now
taken to be expressed in the unit of the other, in both directions, so
plain samples accept a unitful shift just as unitful samples accept a
plain one. Sample units are often attached only partway through an
analysis, and neither mixture is ambiguous.

Shifts that already agree in unit convert as before, and shifting
unitless samples by a plain number leaves them unitless.

Assisted-by: Claude Opus 5 <noreply@anthropic.com>
The sample-wise mean, var and std add methods to the generic functions
Statistics defines, so name Statistics as the dependency they come from
and declare it in Project.toml. StatsBase re-exports those same
generics, so the methods themselves are unchanged.

Assisted-by: Claude Opus 5 <noreply@anthropic.com>
The suite had grown to cover the same behaviour several times over:
scaling and division were retested verbatim with units even though they
do not touch units, every broadcast operator ran against both signal
storage layouts although the layout only selects one shared dispatch
path, and the unit-matching rules were spread over three testsets.

Group the broadcast forms into a table checked in one loop, exercise the
generic storage path with one operator of each kind, and state each
unit-matching rule once. Same behaviours covered, a third fewer checks.

Assisted-by: Claude Opus 5 <noreply@anthropic.com>
Sample-wise sum/mean/var/std now specialize on contiguous storage,
reducing over flatview(signals) in one block instead of one broadcast
per waveform. This is a modest win on a CPU and the deciding factor on
a device that dispatches each pass separately, where the per-waveform
loop runs tens of times slower.
JLArrays.jl provides an AbstractArray backend with no special casing
anywhere in Base or this package, the same property a real GPU array
type (CuArray, ROCArray, ...) has. Add it as a test dependency and
exercise the ArrayOfSimilarVectors/flatview-specialized reductions and
every broadcast form against it, confirming the fast path dispatches
correctly and preserves non-Array storage without requiring GPU
hardware.
@codecov

codecov Bot commented Aug 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 54.41%. Comparing base (d825937) to head (5d8cda3).

Additional details and impacted files
@@             Coverage Diff             @@
##             main      #45       +/-   ##
===========================================
+ Coverage   12.38%   54.41%   +42.02%     
===========================================
  Files           4        4               
  Lines         113      215      +102     
===========================================
+ Hits           14      117      +103     
+ Misses         99       98        -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Statistics only gained registry-tracked versions at 1.11.0, so a "1"
compat bound has no satisfiable floor and fails downgrade CI, which
resolves every direct dependency to its lowest compatible version.
Statistics is a stdlib bundled with every Julia version this package
supports (down to the 1.10 LTS floor); leaving it uncompatted lets it
resolve to whatever ships with the running Julia, same as the
package's other stdlib dependencies.
Comment thread Project.toml Outdated
Comment thread test/test_detector_waveforms.jl Outdated
Comment thread test/test_detector_waveforms.jl Outdated
Comment thread test/test_detector_waveforms.jl
Comment thread test/test_detector_waveforms.jl
Comment thread src/detector_waveforms.jl Outdated
Comment thread src/detector_waveforms.jl Outdated
Comment thread src/detector_waveforms.jl Outdated
Comment thread src/detector_waveforms.jl Outdated
@fhagemann fhagemann mentioned this pull request Aug 16, 2026
@fhagemann

Copy link
Copy Markdown
Collaborator

Failing doc build might be fixed by #46.
Failing downgrade might be achievable by running the tests on julia version 1.10 instead of 1 (and skipping the downgrade of Statistics if this should be an issue).

@oschulz

oschulz commented Aug 16, 2026

Copy link
Copy Markdown
Member

ChatGPT 5.6 Sol max review (@theHenks you can forward this to your coding AI):

Review verdict: request changes

The PR is generally well put together: the API additions are coherent, time-axis compatibility is checked, the reduction implementation pays attention to integer widening, contiguous storage, nonstandard axes, units, and non-Array backends. ([GitHub]1) I found one correctness issue I would fix before merging, plus two smaller design/documentation concerns.

  1. [P2] Ragged reductions choose the accumulator type solely from the first waveform. In _sample_sum, the output is allocated as

    first_signal = first(signals)
    out = similar(first_signal, _sample_sum_eltype(eltype(first_signal)))
    ...
    out .+= signal

    so every subsequent signal has to fit into the type dictated by the first one. ([GitHub]1) That is weaker than Julia's normal arithmetic/reduction promotion semantics, where mixed numeric arguments are promoted to a common type. ([docs.julialang.org]2)

    This matters because the ArrayOfRDWaveforms constructor is typed in terms of abstract vectors of RealQuantity; it does not intrinsically establish that every contained signal has the same concrete sample type. ([GitHub]1) If a ragged collection contains, say, an integer-valued first waveform and a floating-point later waveform, the accumulator can therefore be integer even though the mathematical result is floating-point. Non-integral later contributions cannot be represented correctly in that buffer.

    The existing “mixed sample types” test does not exercise this case: it tests RDWaveform{Int32} + RDWaveform{Float64}, but its reduction tests then construct A from two Int32 signals. ([GitHub]3) I would add a genuinely heterogeneous ragged reduction test and derive the accumulator type from all signal element types, something conceptually like:

    T = mapreduce(eltype, promote_type, signals)
    Tout = _sample_sum_eltype(T)
    out = similar(first(signals), Tout)

    with whatever refinement is needed for Unitful types/backend compatibility. mean, var, and std inherit the issue because _sample_mean starts with _sample_sum. ([GitHub]1)

  2. [P3] The eager Broadcast.broadcasted override is a fairly large semantic tradeoff and deserves a stronger contract. The implementation explicitly makes broadcast eager so that contiguous/device-backed signal storage remains intact, and explicitly gives up fusion: 2 .* wfs .+ wfs executes in two stages. ([GitHub]1) The commit message confirms this is deliberate rather than accidental. ([GitHub]4)

    I don't think this blocks the PR if maintaining the storage backend is the priority. But overriding Broadcast.broadcasted itself makes ArrayOfRDWaveforms behave unlike normal Julia broadcast operands, rather than merely supplying a specialized materialization strategy. I would at minimum add tests for larger mixed broadcast expressions—e.g. several waveform/scalar/per-waveform operations composed under @.—because the current backend tests predominantly exercise the supported operations one at a time. ([GitHub]3) Longer term, a BroadcastStyle/materialization implementation would be worth considering if it can preserve the desired storage without changing construction semantics.

  3. [P3] The unit-inference rule should be prominent user-facing documentation. The PR deliberately makes the unitless side acquire the unit of the unitful side: unitful samples plus 10.0 interpret 10.0 in the samples' units, while plain samples plus 10u"eV" effectively interpret the existing samples as eV. ([GitHub]4) The tests explicitly lock in both directions. ([GitHub]3) That's a defensible detector-analysis convention, but it is not ordinary dimensional-analysis behavior and can silently turn dimensionless data into dimensionful data. The docstrings I inspected explain the bare-number shift case, but the bidirectional “either operand can supply the unit” rule deserves to be stated explicitly in the public API docs. ([GitHub]1)

Aside from those points, I like the implementation direction. The two-pass variance is preferable to E[X²] - E[X]²; the Bessel correction is stated explicitly; mismatched time axes and mismatched signal axes are rejected; small integer accumulation is intentionally widened in line with Base.sum; and the JLArrays tests are a useful check that the contiguous fast paths don't accidentally assume CPU Array storage. ([GitHub]1)

So my merge call would be: fix/test heterogeneous ragged reductions, then approve. I would treat the eager-broadcast and unit semantics as follow-up/documentation items rather than merge blockers.

I reviewed the current PR branch/source and tests statically; I did not execute the Julia test suite locally because Julia isn't available in this environment.

docs/Project.toml pinned Documenter to "~0.24" (resolving to 0.24.11)
while test/Project.toml already used "~0.27" for the doctest step.
Documenter 0.24's docstring-coverage check cannot correctly attribute
docstrings on methods that extend an external function (Statistics.mean/
var/std here) with a parametric where-clause signature, so it flagged
them as missing and failed the strict docs build. Verified locally: with
Documenter bumped to 0.27.25, the full strict build (CheckDocument
through HTMLWriter) completes with no missing-docstring warning.

Also declares RadiationDetectorSignals itself as a docs dependency
(Pkg.develop path-installed), matching what the CI docs job already
does before running make.jl.
_sample_sum derived its accumulator's element type from only the first
signal, so a ragged collection mixing e.g. an Int32 waveform with a
Float64 one would size the buffer to Int32/Int and lose or fail to
represent later, wider-typed contributions. Derive it from the
promoted type across every signal instead, matching Julia's normal
mixed-argument promotion. sum/mean/var/std on ArrayOfRDWaveforms all
inherit the fix, since they build on _sample_sum. The flatview-
specialized path for contiguous storage is unaffected: a single
contiguous block can only ever hold one concrete element type.

Reported by @oschulz.
The +/- scalar-shift docstrings on RDWaveform only documented the
"plain number takes the samples' unit" direction. State both
directions explicitly: whichever of the samples and the shift amount
carries no unit takes on the other's.

Requested by @oschulz.
Statistics only gained registry-tracked versions at 1.11.0, so
downgrading its compat entry fails no matter which Julia version runs
the job: the action narrows "1" to "~1.0.0", and neither the registry
range (1.11.0-1.11.1, checked on Julia "1"/latest) nor the bundled
stdlib version (1.10.0 on Julia 1.10) falls inside that window.
Verified both independently by reproducing the action's exact
rewrite and resolving/building against it on each Julia version.

Running the job on 1.10 instead of "1" also tests against the
package's declared LTS floor (`julia = "1.10"`) rather than whatever
Julia currently calls latest. Skipping Statistics from the downgrade
leaves its compat at "1", which resolves fine on 1.10 (bundled
Statistics reports 1.10.0, well within [1.0.0, 2.0.0)).

Verified end-to-end: reproduced the action's actual compat rewrite
for every dependency, resolved and built against it on Julia 1.10,
and ran the full test suite — 159/159 passing.
Comment thread .github/workflows/Downgrade.yml Outdated
A unitful shift amount on a plain waveform (wf + 10u"ns") silently
gave the samples that unit rather than shifting them, which reads as
a time shift but actually reinterprets an ADC/energy signal as being
measured in nanoseconds. Drop that direction: a plain shift is still
interpreted in unitful samples' own unit, but a unitful shift on plain
samples is now a Unitful.DimensionError, the same as adding any two
incompatible Unitful quantities, rather than a silent unit promotion.

Flagged by @fhagemann.
The reduction docstrings only mentioned the ArgumentError thrown for
mismatched time axes, not that a DimensionMismatch is also possible
when the signal arrays themselves don't share axes (a different check,
inside the sample-wise accumulation).

Flagged by @fhagemann.
The offset-axes generic-indexing coverage rolled its own ~20-line
ShiftedVector/BroadcastStyle pair to get a vector with non-1-based
indices. OffsetArrays.jl is the standard package for exactly this and
is already well-tested; use its OffsetVector as a test-only dependency
instead of duplicating what it does.

Flagged by @fhagemann.
Comment thread Project.toml Outdated
Nothing in the package uses StatsBase: the only reference was a
Histogram method left commented out at the end of detector_waveforms.jl,
predating this branch. The commented sketch stays as a placeholder for
whoever picks that up; only the live import and the compat/deps entries
are removed.
@theHenks

Copy link
Copy Markdown
Collaborator Author

@oschulz @fhagemann This is ready from my side.

@fhagemann fhagemann left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

Comment thread src/detector_waveforms.jl Outdated
Comment thread src/detector_waveforms.jl Outdated
Comment thread src/detector_waveforms.jl Outdated
Comment thread src/detector_waveforms.jl
Comment thread src/detector_waveforms.jl
Comment thread src/detector_waveforms.jl Outdated
Comment thread src/detector_waveforms.jl
The arithmetic and shift operators carry no docstrings of their own; the
capabilities they provide are documented once, on the type.
Shifting a waveform by a scalar uses plain broadcasting: the amount must be
dimensionally compatible with the samples, and mixing a unitful amount with
plain samples (or the reverse) throws a Unitful.DimensionError.
`Base.promote_op(Base.add_sum, T, T)` is the element type `sum` itself would
produce, so narrow-integer widening no longer depends on an enumerated list of
sample types.
Equal-length signals held in a VectorOfVectors reshape into a matrix without
copying, so they reduce in one pass over their own storage like an
ArrayOfSimilarVectors already did. Only signals with no contiguous block
behind them fall back to accumulating waveform by waveform.

Variance on that fallback defers to Statistics.varm, given the sample-wise
mean so that narrow integer samples still accumulate without overflow.
Broadcasting and per-waveform shifts take the same matrix view of the samples
that the reductions do, so equal-length signals held in a VectorOfVectors stay
in one block instead of being rebuilt as separately allocated vectors.
Each operation in a composed expression materializes in turn, so expressions
combining waveform, scalar and per-waveform operands are checked against the
same computation applied waveform by waveform, across every signal storage
layout and on the JLArrays backend.
Int32 is narrower than Int only where Int is 64 bits, so the widening is
checked with Int16 on every platform and with Int32 where it applies.
Comment thread src/detector_waveforms.jl Outdated
`sum`, `mean`, `var` and `std` are heavily specialized across packages, so
their behavior for waveform arrays is described on the type rather than
attached to the functions themselves. The same docstring now also covers
broadcast arithmetic over an array of waveforms.

Assisted-by: Claude Opus 5 <noreply@anthropic.com>
Comment thread src/detector_waveforms.jl
Base.float(wf::RDWaveform) = RDWaveform(float(wf.time), float(wf.signal))

function Base.:(+)(a::RDWaveform, b::RDWaveform)
a.time == b.time || throw(ArgumentError("Can't add RDWaveform with different time axes"))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe an approximate comparison instead of == ?

Comment thread src/detector_waveforms.jl
end

function Base.:(-)(a::RDWaveform, b::RDWaveform)
a.time == b.time || throw(ArgumentError("Can't subtract RDWaveform with different time axes"))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe an approximate comparison instead of ==?

Comment thread src/detector_waveforms.jl

Base.:(-)(a::RDWaveform) = RDWaveform(a.time, -a.signal)

Base.:(+)(wf::RDWaveform, a::RealQuantity) = RDWaveform(wf.time, wf.signal .+ a)

@oschulz oschulz Sep 4, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically this should probably be a broadcasted plus. But making semantically clean and work robustly via broadcast specialization would be non-trivial (see comment on broadcasted specialization s below). S I think going for Base.:(+) could be tolerable - it's mathematically a bit ugly, though:

If we define algebra on waveforms, we should define what waveforms are, mathematically, with respect to algebra operations. We could see waveforms with equal time axis as elements of a vector space: then addition of waveforms and multiplication of waveforms with scalars would be well defined, and so we be operations like sum, mean, etc. But addition between waveforms and scalar values would not be well defined, at least not without additinal mathematical gymnastics/definitions.

There is a clean solution though: We introduce a special signal type RDBaseline(level) that represents an axis-independent constant signal with amplitude level. Then wf + RDBaseline(level) would be both mathematically well-defined and also very readable without digging into the docs of RadiationDetectorSignals.

We would define

"""
    RDBaseline(level)

An axis-independent constant signal with amplitude `level`.

When combined with an `RDWaveform`, it represents the constant waveform with
value `level` on that waveform's time axis.
"""
struct RDBaseline{T<:RealQuantity}
    level::T
end

Base.:(+)(bl::RDBaseline, a::RealQuantity) = RDBaseline(bl.level + a)
Base.:(+)(a::RealQuantity, bl::RDBaseline) = bl + a

Base.:(-)(bl::RDBaseline, a::RealQuantity) = RDBaseline(bl.level - a)
Base.:(-)(a::RealQuantity, bl::RDBaseline) = RDBaseline(a - bl.level)

Base.:(*)(a::Real, bl::RDBaseline) = RDBaseline(a * bl.level)
Base.:(*)(bl::RDBaseline, a::Real) = a * bl

Base.:(/)(bl::RDBaseline, a::Real) = bl * inv(a)
Base.:(\)(a::Real, bl::RDBaseline) = bl / a

Base.:(+)(wf::RDWaveform, bl::RDBaseline) = RDWaveform(wf.time, wf.signal .+ bl.level)
Base.:(+)(bl::RDBaseline, wf::RDWaveform) = wf + bl

Base.:(-)(wf::RDWaveform, bl::RDBaseline) = RDWaveform(wf.time, wf.signal .- bl.level)
Base.:(-)(bl::RDBaseline, wf::RDWaveform) = RDWaveform(wf.time, bl.level .- wf.signal)

(untested, may contain bugs).

A clear distinction between a generic scalar quantity and a constant waveform could be very useful in general.

Comment thread src/detector_waveforms.jl
ArrayOfRDWaveforms((a.time, _broadcast_signals(f, a.signal, b.signal)))
end

Base.Broadcast.broadcasted(::typeof(*), a::Real, wfs::ArrayOfRDWaveforms) =

@oschulz oschulz Sep 4, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this robust? What if wfs is a Broadcasted, due to broadcast-chaining - we'd have different semantics or an error then, right?

Maybe not do this for now? It would be weird to define a .* wf if a * wf[i] is not well defined. A robust solution would require an iteration scheme over waveforms as custom pairs or time and signal values, with scalar multiplication defined for the pair (action only on the signal value) and a specialized broadcast output scheme.

Comment thread src/detector_waveforms.jl
return out
end

function _sample_sum(signals::AbstractVector{<:AbstractVector})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add something like

_sample_sum(signals::ArrayOfSimilarVectors) = sum(signals)

since ArraysOfArrays provides optimized and GPU-compatible specializations for sum,mean, var, etc.

Comment thread src/detector_waveforms.jl
# storage; `nothing` when the signals are not one contiguous block of equal-length
# vectors. Operating on the whole block in a single pass keeps the work on whatever
# device holds the samples, instead of dispatching one pass per waveform.
_sample_matrix(signals::ArrayOfSimilarVectors) = flatview(signals)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we require ArraysOfArrays v1, we can just use stacked(signals) instead of _sample_matrix.

Comment thread src/detector_waveforms.jl
Base.Broadcast.broadcasted(::typeof(-), wfs::ArrayOfRDWaveforms) =
_scaled_waveforms(wfs, x -> .-x)

Base.Broadcast.broadcasted(::typeof(+), a::ArrayOfRDWaveforms, b::ArrayOfRDWaveforms) =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why a different implementation than Base.:(+)(a::RDWaveform, b::RDWaveform)?

Also, this may not be a good idea, see comment on Base.Broadcast.broadcasted(::typeof(*), ...).

Comment thread src/detector_waveforms.jl
function _nested_sample_sum(signals::AbstractVector{<:AbstractVector})
out = similar(first(signals), _sample_sum_eltype(eltype(eltype(signals))))
fill!(out, zero(eltype(out)))
for signal in signals

@oschulz oschulz Sep 4, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summation via an accumulator can be numerically unstable, especially when summing over a large number of entries. sum(signals) uses advanced summation schemes and is numerically stable, but generates a lot of memory allocations.

A good approach might be to use sum(nestedview(stack(signals))) (or better sum(sliced(stacked(signals))) which would just do (at most) one or two memory block allocations/copies and use numerically stable summation.

We could define a helper function

_aosa(vs::VectorOfSimilarVectors} = vs
_aosa(vs::AbstractVector{<:AbstractVector}} = sliced(stacked(signals))  # nestedview(stack(signals)) for ArraysOfArrays v0.6

and then use that in waveform sums, means, etc. and let ArraysOfArrays use its specializations.

Comment thread src/detector_waveforms.jl

`sum`, `mean`, `var` and `std` reduce sample-wise over all waveforms and
return a single [`RDWaveform`](@ref) carrying the shared time axis. `var` and
`std` use the Bessel-corrected denominator `length(wfs) - 1`. Integer samples

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`std` use the Bessel-corrected denominator `length(wfs) - 1`. Integer samples
`std` resprent the pointwise variance and standard error and use the Bessel-corrected denominator `length(wfs) - 1`. Integer samples

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants