Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Add lombscargle based surrogate for irregular ts #67

Merged
merged 8 commits into from
Jan 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
InplaceOps = "505f98c9-085e-5b2c-8e89-488be7bf1f34"
Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
LombScargle = "fc60dff9-86e7-5f2f-a8a0-edeadbb75bd9"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
Expand Down
2 changes: 2 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ PAGES = [
"Amplitude-adjusted FT" => "constrained/amplitude_adjusted.md",
"Truncated FT/AAFT" => "constrained/truncated_fourier_transform.md",
"Pseudo-periodic" => "constrained/pps.md",
"Wavelet-based" => "constrained/wls.md",
"Pseudo-periodic twin" => "constrained/ppts.md",
"Wavelet-based" => "constrained/wls.md",
"Multidimensional surrogates" => "constrained/multidim.md",
"Surrogates for irregular timeseries" => "constrained/irregular_surrogates.md",
],
"Utility systems" => "man/exampleprocesses.md"
]
Expand Down
25 changes: 25 additions & 0 deletions docs/src/constrained/irregular_surrogates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Surrogates for unevenly sampled time series


To derive a surrogate for unevenly sampled time series, we can use surrogate methods which which does not explicitly use the time axis like [`RandomShuffle`](@ref) or [`BlockShuffle`](@ref)
or we need to use algorithms, which take the irregularity of the time axis into account.

## Lomb-Scargle based surrogate

The LS surrogate is a form of a constrained surrogate which takes the Lomb-Scargle periodogram to derive surrogates with similar phase distribution as the original time series.
This function uses the simulated annealing algorithm to compute a minima of the difference between the original periodogram and the surrogate periodogram.

```@docs
LS
```

```@example
using TimeseriesSurrogates, Plots
N=1000
ts = AR1(n_steps=N) # create a realization of a random AR(1) process
t = (1:length(ts)) - rand(length(ts)) # generate a time axis with unevenly spaced time steps
ls = LS(t, tol=1, N_total=100000, N_acc = 50000)
s = surrogate(ts, ls)
plot(t,ts,label="original data")
plot!(t, s, label="Surrogate data")
```
4 changes: 4 additions & 0 deletions src/TimeseriesSurrogates.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module TimeseriesSurrogates

using Random
using Distributions
using Distances # Will be used by the LombScargle method
using StatsBase
using InplaceOps
using AbstractFFTs
Expand Down Expand Up @@ -30,6 +31,9 @@ include("methods/pseudoperiodic_twin.jl")
include("methods/multidimensional.jl")
include("methods/ar.jl")

# Methods for irregular time series
include("methods/lombscargle.jl")

# Visualization routine for time series + surrogate + periodogram/acf/histogram
using Requires
function __init__()
Expand Down
90 changes: 90 additions & 0 deletions src/methods/lombscargle.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using LombScargle

export LS
"""
LS(t; tol=1, N_total=10000, N_acc=2000,q=1)
Compute a surrogate of an unevenly sampled time series with supporting time steps `t` based on the simulated annealing algorithm described in [^SchreiberSchmitz1999].

LS surrogates preserve the periodogram and the amplitude distribution of the original signal.

This algorithm starts with a random permutation of the original data.
Then it iteratively approaches the power spectrum of the original data by swapping two randomly selected values in the surrogate data
if the minkowski distance of order `q` between the power spectrum of the surrogate data and the original data is less than before.
The iteration procedure ends when the relative deviation between the periodograms is less than `tol` or when `N_total` number of tries or `N_acc` number of actual swaps is reached.

For time series with equidistant time steps,
surrogates generated by this method result in surrogest similar to those produced by the [`IAAFT`](@ref) method.

[^SchmitzSchreiber1999]: A.Schmitz T.Schreiber (1999). "Testing for nonlinearity in unevenly sampled time series" [Phys. Rev E](https://journals.aps.org/pre/pdf/10.1103/PhysRevE.59.4044)
"""
struct LS{T<:AbstractVector,S<:Real} <: Surrogate
t::T
tol::S
N_total::Int
N_acc::Int
q::Int
end

LS(t;tol=1.0, N_total=100000, N_acc=50000, q=1) = LS(t, tol, N_total, N_acc,q)


function surrogenerator(x, method::LS)
lsplan = LombScargle.plan(method.t, x, fit_mean=false, flags=LombScargle.FFTW.WISDOM_ONLY)
x_ls = lombscargle(lsplan)
# We have to copy the power vector here, because we are reusing lsplan later on
xpower = copy(x_ls.power)
dist=Minkowski(method.q)
init = (lsplan=lsplan, xpower=xpower, n=length(x), dist=dist)
return SurrogateGenerator(method, x, init)
end


function (sg::SurrogateGenerator{<:LS})()
lsplan, xpower, n, dist = sg.init
t = sg.method.t
tol = sg.method.tol
s = surrogate(t, RandomShuffle())
#_perodogram! reuses the lsplan with a shuffled time vector.
# This is the same as shuffling the signal
spower = LombScargle._periodogram!(s, lsplan)
lossold = evaluate(dist,xpower, spower)
i = j = 0
newsurr = zero(s)
while i < sg.method.N_total && j<sg.method.N_acc
if mod(i,2000) ==0
@show i, j,lossold
end

k,l = sample(1:n,2, replace=false)
copy!(newsurr, s)
#@show s
newsurr[[k,l]] = s[[l,k]]
#lsplan = LombScargle.plan(t, newsurr, fit_mean=false)
#s_ls = lombscargle(lsplan)
spower = LombScargle._periodogram!(newsurr, lsplan)
lossnew = evaluate(dist,xpower, spower)
if lossnew < lossold
lossnew <= tol && break
s, lossold = copy(newsurr), lossnew
j += 1
#=else
## Implement drawing with a probability p
lossdiff = lossnew - lossold
@show lossdiff
T = 1
p = exp(-lossdiff/log(i)) # Where does T come from?
if rand() < p
@show p
surr, lossold = newsurr, lossnew
j += 1
end
=#
end
i+=1
end
@info i,j, lossold
#Use the permutation of the time vector to permute the signal vector
perm = sortperm(sortperm(s)) # This gives us the inverse permutation from t to perm
@assert t[perm] == s # Check, whether this worked as expected
return sg.x[perm]
end
2 changes: 1 addition & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ using Test
using TimeseriesSurrogates

include("all_method_tests.jl")
include("reproducibility.jl")
include("reproducibility.jl")