Skip to content

Commit

Permalink
add sdft_ prefix to internal API
Browse files Browse the repository at this point in the history
The following names are also slightly changed:
backdft > sdft_backindices
iterationcount > sdft_iteration
updatedft! > sdft_update!
  • Loading branch information
heliosdrm committed Jan 7, 2025
1 parent 0ff62cf commit 183dd95
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 78 deletions.
42 changes: 21 additions & 21 deletions docs/src/slidingdft.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,42 +67,42 @@ The internals of this package take care of the design of the iterator and state

Before explaining how to define such a struct, it is convenient to know the functions that can be used to extract the information that is stored in the state of SDFT iterators. There is a function for each one of the three kinds of variables presented in the general recursive equation above.

* [`FourierTools.previousdft`](@ref) for the values of the DFT in previous iterations.
* [`FourierTools.previousdata`](@ref) for the values of the data series used in the most recent iteration.
* [`FourierTools.nextdata`](@ref) for the next value of the data series after the fragment used in the most recent iteration.
* [`FourierTools.sdft_previousdft`](@ref) for the values of the DFT in previous iterations.
* [`FourierTools.sdft_previousdata`](@ref) for the values of the data series used in the most recent iteration.
* [`FourierTools.sdft_nextdata`](@ref) for the next value of the data series after the fragment used in the most recent iteration.

For instance, the values used in the formula of the basic SDFT may be obtained from a `state` object as:
* `FourierTools.previousdft(state, 0)` for $X_{i}$.
* `FourierTools.previousdata(state, 0)` for $x[i]$.
* `FourierTools.nextdata(state)` for $x[i+n+1]$.
* `FourierTools.sdft_previousdft(state, 0)` for $X_{i}$.
* `FourierTools.sdft_previousdata(state, 0)` for $x[i]$.
* `FourierTools.sdft_nextdata(state)` for $x[i+n+1]$.

Notice that the second arguments of `previousdft` and `previousdata` might have been ommited in this case, since they are zero by default.
Notice that the second arguments of `sdft_previousdft` and `sdft_previousdata` might have been ommited in this case, since they are zero by default.

For methods that need to know how many steps of the SDFT have been done, this can also be extracted with the function [`FourierTools.iterationcount`](@ref).
For methods that need to know how many steps of the SDFT have been done, this can also be extracted with the function [`FourierTools.sdft_iteration`](@ref).

The design of the `struct` representing a new SDFT type is free, but it is required to be a subtype of [`AbstractSDFT`](@ref), and implement the following methods dispatching on that type:

* [`FourierTools.windowlength`](@ref) to return the length of the DFT window.
* [`FourierTools.updatepdf!`](@ref) with the implementation of the recursive equation, extracting the information stored in the state with the functions commented above (`previousdft`, etc.) .
* [`FourierTools.sdft_windowlength`](@ref) to return the length of the DFT window.
* [`FourierTools.sdft_update!`](@ref) with the implementation of the recursive equation, extracting the information stored in the state with the functions commented above (`sdft_previousdft`, etc.) .

Depending on the functions that are used in the particular implementation of `updatepdf!` for a given type, the following methods should be defined too:
Depending on the functions that are used in the particular implementation of `sdft_update!` for a given type, the following methods should be defined too:

* [`FourierTools.dftback`](@ref) if `FourierTools.previousdft` is used.
* [`FourierTools.dataoffsets`](@ref) if `FourierTools.previousdata` is used.
* [`FourierTools.sdft_backindices`](@ref) if `FourierTools.sdft_previousdft` is used.
* [`FourierTools.sdft_dataoffsets`](@ref) if `FourierTools.sdft_previousdata` is used.

### Example

The formula of the basic SDFT formula could be implemented for a type `MyBasicSDFT` as follows:

```julia
import FourierTools: updatedft!, windowlength, nextdata, previousdata
import FourierTools: sdft_update!, sdft_windowlength, sdft_nextdata, sdft_previousdata

function udpatedft!(dft, x, method::MyBasicSDFT, state)
n = windowlength(method)
function sdft_udpatedft!(dft, x, method::MyBasicSDFT, state)
n = sdft_windowlength(method)
for k in eachindex(dft)
X_i = dft[k]
x_iplusn = nextdata(state)
x_i = previousdata(state)
x_iplusn = sdft_nextdata(state)
x_i = sdft_previousdata(state)
Wk = exp(2π*im*k/n)
dft[k] = Wk * (X_i + x_iplusn - x_i)
end
Expand All @@ -111,13 +111,13 @@ end

(The type [`SDFT`](@ref) actually has as a similar, but not identical definition.)

The implementation of `updatepdf!` given in the previous example does use `previousdata` - with the default offset value, equal to zero - so the following is required in this case:
The implementation of `sdft_update!` given in the previous example does use `sdft_previousdata` - with the default offset value, equal to zero - so the following is required in this case:

```julia
FourierTools.dataoffsets(::MyBasicSDFT) = 0
FourierTools.sdft_dataoffsets(::MyBasicSDFT) = 0
```

On the other hand there is no need to define `FourierTools.dftback` in this case, since the interface of of `updatedft!` assumes that the most recent DFT is already contained in its first argument `dft`, so it is not necessary to use the function `previousdft` to get it.
On the other hand there is no need to define `FourierTools.sdft_backindices` in this case, since the interface of of `sdft_update!` assumes that the most recent DFT is already contained in its first argument `dft`, so it is not necessary to use the function `sdft_previousdft` to get it.

### Alternative to subtyping `AbstractSDFT`

Expand Down
8 changes: 4 additions & 4 deletions src/sdft_implementations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ SDFT(n) = SDFT(ComplexF64, n)

# Required functions

windowlength(method::SDFT) = method.n
sdft_windowlength(method::SDFT) = method.n

function updatedft!(dft, x, method::SDFT{T,C}, state) where {T,C}
function sdft_update!(dft, x, method::SDFT{T,C}, state) where {T,C}
twiddle = one(C)
for k in eachindex(dft)
dft[k] = twiddle * (dft[k] + nextdata(state) - previousdata(state))
dft[k] = twiddle * (dft[k] + sdft_nextdata(state) - sdft_previousdata(state))
twiddle *= method.factor
end
end

dataoffsets(::SDFT) = 0
sdft_dataoffsets(::SDFT) = 0

Check warning on line 54 in src/sdft_implementations.jl

View check run for this annotation

Codecov / codecov/patch

src/sdft_implementations.jl#L54

Added line #L54 was not covered by tests
106 changes: 53 additions & 53 deletions src/sdft_interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,23 @@ export AbstractSDFT
## Required functions

"""
windowlength(method)
sdft_windowlength(method)
Return the length of the window used by `method`.
"""
function windowlength end
function sdft_windowlength end

"""
updatedft!(dft, x, method, state)
sdft_update!(dft, x, method, state)
Update the values of a sliding Discrete Fourier Transform (DFT) of a data series,
according to the algorithm of the provided method, for a given state of the sliding DFT.
`dft` is a mutable collection of complex values with length equal to `windowlength(method)`,
`dft` is a mutable collection of complex values with length equal to `sdft_windowlength(method)`,
containing the value returned by the last iteration of the sliding DFT.
`x` is the data series for which the sliding DFT is computed, at least as long as
`windowlength(method)`.
`sdft_windowlength(method)`.
`method` is the object that defines the method to compute the sliding DFT.
Expand All @@ -30,59 +30,59 @@ of an SDFT iterator made from `method` and `x`,
The information that is needed to update the sliding DFT can be extracted from `state`
with the following functions:
* [`FourierTools.previousdft`](@ref) to get the DFTs of previous iterations.
* [`FourierTools.previousdata`](@ref) to get a previous value of the data series.
* [`FourierTools.nextdata`](@ref) to get the next value of the data series.
* [`FourierTools.sdft_previousdft`](@ref) to get the DFTs of previous iterations.
* [`FourierTools.sdft_previousdata`](@ref) to get a previous value of the data series.
* [`FourierTools.sdft_nextdata`](@ref) to get the next value of the data series.
"""
function updatedft! end
function sdft_update! end

## Conditionally required functions

"""
dftback(method)
sdft_backindices(method)
Return an integer or a vector of positive integers with the indices of the previous iterations
that are needed by the given method to compute a sliding DFT.
If the code of `FourierTools.updatepdf!` for the type of `method` uses the function `FourierTools.previousdft`,
If the code of `FourierTools.updatepdf!` for the type of `method` uses the function `FourierTools.sdft_previousdft`,
this function must return the integers that are used as the third argument (`back`) of that function.
If that function is not needed, this one may return `nothing` to reduce memory allocations.
"""
dftback(::Any) = nothing
sdft_backindices(::Any) = nothing

"""
dataoffsets(method)
sdft_dataoffsets(method)
Return an integer or a vector of integers with the offsets of data samples
that are needed by the given method to compute a sliding DFT.
If the code of `FourierTools.updatepdf!` that dispatches on the type of `method` uses the function `FourierTools.previousdata`,
If the code of `FourierTools.updatepdf!` that dispatches on the type of `method` uses the function `FourierTools.sdft_previousdata`,
this function must return the integers that are used as the third argument (`offset`) of that function.
If that function is not needed (no past samples are used), this one may return `nothing` to reduce memory allocations.
"""
dataoffsets(::Any) = nothing
sdft_dataoffsets(::Any) = nothing

Check warning on line 65 in src/sdft_interface.jl

View check run for this annotation

Codecov / codecov/patch

src/sdft_interface.jl#L65

Added line #L65 was not covered by tests


## States

# State data used by `updatedft!`
struct StateData{H, F, N}
# State data used by `sdft_update!`
struct SDFTStateData{H, F, N}
dfthistory::H # record of back dfts if needed, otherwise nothing
fragment::F # previous data fragment if needed, otherwise nothing
nextdatapoint::N # next data point after the fragment
windowlength::Int # window length
iteration::Int # iteration count
end

hasdfthistory(::StateData{Nothing}) = false
hasdfthistory(::StateData) = true
haspreviousdata(::StateData{<:Any, Nothing}) = false
haspreviousdata(::StateData) = true
hasdfthistory(::SDFTStateData{Nothing}) = false
hasdfthistory(::SDFTStateData) = true
haspreviousdata(::SDFTStateData{<:Any, Nothing}) = false
haspreviousdata(::SDFTStateData) = true

Check warning on line 82 in src/sdft_interface.jl

View check run for this annotation

Codecov / codecov/patch

src/sdft_interface.jl#L79-L82

Added lines #L79 - L82 were not covered by tests

"""
previousdft(state[, back=0])
sdft_previousdft(state[, back=0])
Return the DFT computed in the most recent iteration
of the sliding DFT represented by `state`, or in a number of previous
Expand All @@ -92,9 +92,9 @@ If the DFT computed in the most recent iteration corresponds to the
fragment of the data series between its positions `i` and `i+n`,
then this function returns its DFT for the fragment between `i-back` and `i+n-back`.
"""
function previousdft(state::StateData, back=0)
function sdft_previousdft(state::SDFTStateData, back=0)
!hasdfthistory(state) && throw(ErrorException(

Check warning on line 96 in src/sdft_interface.jl

View check run for this annotation

Codecov / codecov/patch

src/sdft_interface.jl#L95-L96

Added lines #L95 - L96 were not covered by tests
"previous DFT results not available; the SDFT method has no valid definition of `FourierTools.dftback`"
"previous DFT results not available; the SDFT method has no valid definition of `FourierTools.sdft_backindices`"
))
dfthistory = state.dfthistory
n = state.windowlength
Expand All @@ -105,7 +105,7 @@ function previousdft(state::StateData, back=0)
end

"""
previousdata(state[, offset=0])
sdft_previousdata(state[, offset=0])
Return the first value of the fragment of the data series that was used
in the most recent iteration of the sliding DFT represented by `state`,
Expand All @@ -115,9 +115,9 @@ If the DFT computed in the most recent iteration corresponds to the
fragment of the data series between its positions `i` and `i+n`,
then this function returns the `i+offset`-th value.
"""
function previousdata(state::StateData, offset=0)
function sdft_previousdata(state::SDFTStateData, offset=0)
!haspreviousdata(state) && throw(ErrorException(
"previous data values not available; the SDFT method has no valid definition of `FourierTools.dataoffsets`"
"previous data values not available; the SDFT method has no valid definition of `FourierTools.sdft_dataoffsets`"
))
fragment = state.fragment
adjustedoffset = rem(offset + state.iteration - 1, length(fragment))
Expand All @@ -126,7 +126,7 @@ end


"""
nextdata(state)
sdft_nextdata(state)
Return the next value after the fragment of the data series that was used
in the most recent iteration of the sliding DFT represented by `state`.
Expand All @@ -138,18 +138,18 @@ then this function returns the `i+n+1`-th value.
There is no defined behavior if such value does not exist
(i.e. if the end of a finite data series was reached).
"""
nextdata(state::StateData) = state.nextdatapoint
sdft_nextdata(state::SDFTStateData) = state.nextdatapoint

"""
iterationcount(state)
sdft_iteration(state)
Return the number of iterations done for the sliding DFT represented by `state`.
If the DFT computed in the most recent iteration corresponds to the
fragment of the data series between its positions `i` and `i+n`,
then this function returns the number `i`.
"""
iterationcount(state) = state.iteration
sdft_iteration(state) = state.iteration

Check warning on line 152 in src/sdft_interface.jl

View check run for this annotation

Codecov / codecov/patch

src/sdft_interface.jl#L152

Added line #L152 was not covered by tests

# State of the iterator
struct SDFTState{T, H, F, S}
Expand All @@ -161,26 +161,26 @@ struct SDFTState{T, H, F, S}
end

# Return the updated state of the iterator, or `nothing` if the data series is consumed.
function updatestate(state::SDFTState, method, x)
function sdft_updatestate(state::SDFTState, method, x)
nextiter = iterate(x, state.nextdatastate)
if isnothing(nextiter)
return nothing
end
nextdatapoint, nextdatastate = nextiter
dfthistory = state.dfthistory
fragment = state.fragment
n = windowlength(method)
n = sdft_windowlength(method)
# explicit fft of shifted fragment until dfthistory is complete
if !isnothing(dfthistory) && state.iteration <= maximum(dftback(method))
updatefragment!(fragment, nextdatapoint, state.iteration)
if !isnothing(dfthistory) && state.iteration <= maximum(sdft_backindices(method))
sdft_updatefragment!(fragment, nextdatapoint, state.iteration)
dft = shiftedfft(fragment, state.iteration)

Check warning on line 176 in src/sdft_interface.jl

View check run for this annotation

Codecov / codecov/patch

src/sdft_interface.jl#L175-L176

Added lines #L175 - L176 were not covered by tests
else
dft = state.dft
statedata = StateData(dfthistory, fragment, nextdatapoint, n, state.iteration)
updatedft!(dft, x, method, statedata)
updatefragment!(fragment, nextdatapoint, state.iteration)
statedata = SDFTStateData(dfthistory, fragment, nextdatapoint, n, state.iteration)
sdft_update!(dft, x, method, statedata)
sdft_updatefragment!(fragment, nextdatapoint, state.iteration)
end
updatedfthistory!(dfthistory, dft, n, state.iteration + 1)
sdft_updatedfthistory!(dfthistory, dft, n, state.iteration + 1)
return SDFTState(dft, dfthistory, fragment, nextdatastate, state.iteration + 1)
end

Expand All @@ -196,20 +196,20 @@ function shiftedfft(x, delay)
return y

Check warning on line 196 in src/sdft_interface.jl

View check run for this annotation

Codecov / codecov/patch

src/sdft_interface.jl#L187-L196

Added lines #L187 - L196 were not covered by tests
end

function updatedfthistory!(dfthistory, dft, n, iteration)
function sdft_updatedfthistory!(dfthistory, dft, n, iteration)
offset = rem((iteration - 1) * n, length(dfthistory))
dfthistory[(1:n) .+ offset] .= dft

Check warning on line 201 in src/sdft_interface.jl

View check run for this annotation

Codecov / codecov/patch

src/sdft_interface.jl#L199-L201

Added lines #L199 - L201 were not covered by tests
end

function updatedfthistory!(::Nothing, args...) end
function sdft_updatedfthistory!(::Nothing, args...) end

Check warning on line 204 in src/sdft_interface.jl

View check run for this annotation

Codecov / codecov/patch

src/sdft_interface.jl#L204

Added line #L204 was not covered by tests

function updatefragment!(fragment, nextdatapoint, iteration)
function sdft_updatefragment!(fragment, nextdatapoint, iteration)
n = length(fragment)
offset = rem(iteration - 1, n)
fragment[begin + offset] = nextdatapoint
end

function updatefragment!(::Nothing, ::Any, ::Any) end
function sdft_updatefragment!(::Nothing, ::Any, ::Any) end

Check warning on line 212 in src/sdft_interface.jl

View check run for this annotation

Codecov / codecov/patch

src/sdft_interface.jl#L212

Added line #L212 was not covered by tests

## Iterator

Expand Down Expand Up @@ -239,7 +239,7 @@ IteratorSizeWrapper(iteratorsizetrait) = iteratorsizetrait # inherit everything
function Base.length(iterator::SDFTIterator)
method = getmethod(iterator)
data = getdata(iterator)
return length(data) - windowlength(method) + 1
return length(data) - sdft_windowlength(method) + 1
end

Base.eltype(::SDFTIterator{M,T}) where {M,T} = Vector{Complex{eltype(T)}}
Expand All @@ -248,11 +248,11 @@ Base.isdone(iterator::SDFTIterator) = Base.isdone(getdata(iterator))
Base.isdone(iterator::SDFTIterator, state::SDFTState) = Base.isdone(getdata(iterator), state.nextdatastate)

Check warning on line 248 in src/sdft_interface.jl

View check run for this annotation

Codecov / codecov/patch

src/sdft_interface.jl#L247-L248

Added lines #L247 - L248 were not covered by tests

function Base.iterate(itr::SDFTIterator)
windowed_data, datastate = initialize(itr)
windowed_data, datastate = initializesdft(itr)
dft = fft(windowed_data)
method = getmethod(itr)
backindices = dftback(method)
dfthistory = create_dfthistory(dft, backindices)
backindices = sdft_backindices(method)
dfthistory = create_sdfthistory(dft, backindices)
state = SDFTState(dft, dfthistory, windowed_data, datastate, 1)
returned_dft = itr.safe ? copy(dft) : dft
return returned_dft, state
Expand All @@ -261,7 +261,7 @@ end
function Base.iterate(itr::SDFTIterator, state)
method = getmethod(itr)
x = getdata(itr)
newstate = updatestate(state, method, x)
newstate = sdft_updatestate(state, method, x)
if isnothing(newstate)
return nothing
end
Expand All @@ -271,9 +271,9 @@ function Base.iterate(itr::SDFTIterator, state)
end

# Get the window of the first chunk of data and the state of the data iterator at the end
function initialize(itr)
function initializesdft(itr)
x = getdata(itr)
n = windowlength(getmethod(itr))
n = sdft_windowlength(getmethod(itr))
firstiteration = iterate(x)
if isnothing(firstiteration)
throw(ErrorException("insufficient data to compute a sliding DFT of window length = $n"))

Check warning on line 279 in src/sdft_interface.jl

View check run for this annotation

Codecov / codecov/patch

src/sdft_interface.jl#L279

Added line #L279 was not covered by tests
Expand All @@ -293,9 +293,9 @@ function initialize(itr)
return windowed_data, datastate
end

create_dfthistory(::Any, ::Nothing) = nothing
create_dfthistory(dft, n::Integer) = repeat(dft, n+1)
create_dfthistory(dft, indices) = create_dfthistory(dft, maximum(indices))
create_sdfthistory(::Any, ::Nothing) = nothing
create_sdfthistory(dft, n::Integer) = repeat(dft, n+1)
create_sdfthistory(dft, indices) = create_sdfthistory(dft, maximum(indices))

Check warning on line 298 in src/sdft_interface.jl

View check run for this annotation

Codecov / codecov/patch

src/sdft_interface.jl#L297-L298

Added lines #L297 - L298 were not covered by tests

"""
(m::AbstractSDFT)(x[, safe=true])
Expand Down

0 comments on commit 183dd95

Please sign in to comment.