diff --git a/src/SinusoidalRegressions.jl b/src/SinusoidalRegressions.jl index 2d23fea..fd9ba56 100644 --- a/src/SinusoidalRegressions.jl +++ b/src/SinusoidalRegressions.jl @@ -28,6 +28,43 @@ include("lsqfit.jl") include("liang.jl") include("plotrecipes.jl") +""" + sinfit(problem::Problem, algorithm::Algorithm) + +Calculate a sinosoidal regression on `problem` using `algorithm`. + +Currently supported problem types are: +* `Sin3Problem` -- three-parameter sinusoidal regression +* `Sin4Problem` -- four-parameter sinusoidal regression +* `MixedLinSin4Problem` -- four-parameter mixed linear and sinusoidal regression +* `MixedLinSin5Problem` -- five-parameter mixed linear and sinusoidal regression + +Currently supported algorithms are: +* `IEEE1057` +* `IntegralEquations` +* `LevMar` +* `Liang` + +Example +======= + +``` +julia> using SinusoidalRegressions +julia> t = range(0, 1, length = 100) # time instants +julia> s = sin.(2*pi*15*t .+ pi/4) .+ 0.1*randn(100) # noisy samples +julia> p = Sin3Problem(t, s, 15) # define regression problem +julia> sinfit(p, IEEE1057()) # calculate fit with IEEE 1057 +Sinusoidal parameters SinusoidP{Float64}: + Frequency (Hz) : 15.0 + DC : -0.01067218324878172 + Sine amplitude (Q) : 0.7299806464221965 + Cosine amplitude (I): 0.6822068658523716 +``` + +See the documentation for more details. +""" +sinfit(p::Problem, a::Algorithm) = sinfit(p, a) + function sinfit(p::Sin3Problem, ::IEEE1057) ieee1057(p) end diff --git a/src/ieee1057.jl b/src/ieee1057.jl index 9df572d..867bc81 100644 --- a/src/ieee1057.jl +++ b/src/ieee1057.jl @@ -1,15 +1,4 @@ -""" - ieee1057(X, Y, f::Real) :: SinusoidP - -Calculate a three-parameter sinusoidal fit of the independent variables `X` and dependent -variables `Y`, using the algorithm described by the IEEE 1057 Standard. Argument `f` -is the exact frequency of the sinusoid, in hertz. - -The data is fit to the model ``f(x; DC, Q, I) = DC + Q\\sin(2πfx) + I\\cos(2πfx)``. - -See also [`SinusoidP`](@ref). -""" function ieee1057(p::Sin3Problem) (; X, Y, f) = p if length(X) != length(Y) @@ -21,19 +10,6 @@ function ieee1057(p::Sin3Problem) return _ieee1057_3P(X, Y, f) end -""" - ieee1057(X, Y ; f = nothing, iterations = 6) :: SinusoidP - -Calculate a four-parameter sinusoidal fit of the independent variables `X` and -dependent variables `Y`, using the algorithm described by the IEEE 1057 -Standard. - -Argument `f` is an estimate of the frequency, in hertz. If not provided, then -it is calculated using the integral equations method (see [`sinfit_j`](@ref)). - -Argument `iterations` specifies how many iterations to run. The default value is 6, -which is the value recommended by the standard. -""" function ieee1057(p::Sin4Problem, a::IEEE1057) (; X, Y, f) = p if length(X) != length(Y) diff --git a/src/jacquelin_mlsr.jl b/src/jacquelin_mlsr.jl index 033753b..1038f78 100644 --- a/src/jacquelin_mlsr.jl +++ b/src/jacquelin_mlsr.jl @@ -1,15 +1,4 @@ -""" - mixlinsinfit_j(X, Y) :: MixedLinearSinusoidP -Perform a mixed linear-sinusoidal fit of the independent variables `X` and dependent -variables `Y`, using the method of integral equations. - -The data is fit to the model ``s(x; f, DC, Q, I, m) = DC + mx + Q\\sin(2πfx) + I\\sin(2πfx)``. -No initial guess as to the values of the parameters `f`, `DC`, `Q`, `I`, or `m` is required. -The values in `X` must be sorted in ascending order. - -See also [`sinfit_j`](@ref) -""" function jacquelin(prob::MixedLinSin5Problem) (; X, Y) = prob # verify elements of X are ordered diff --git a/src/jacquelin_sr.jl b/src/jacquelin_sr.jl index 1658c71..6bb5565 100644 --- a/src/jacquelin_sr.jl +++ b/src/jacquelin_sr.jl @@ -1,17 +1,4 @@ -""" - jacquelin(X, Y) :: SinusoidP -Perform a four-parameter sinusoidal fit of the independent variables `X` and -dependent variables `Y`, using the method of integral equations described in J. -Jacquelin, "Régressions et équations intégrales", 2014 (available at -https://fr.scribd.com/doc/14674814/Regressions-et-equations-integrales) - -The data is fit to the model ``s(x; f, DC, Q, I) = DC + Q\\sin(2πfx) + -I\\cos(2πfx)``. No initial guess as to the values of the parameters `f`, `DC`, -`Q`, or `I` is required. The values in `X` must be sorted in ascending order. - -See also [`SinusoidP`](@ref). -""" function jacquelin(p::Sin4Problem) (; X, Y) = p n = length(X) diff --git a/src/lsqfit.jl b/src/lsqfit.jl index e483486..92f6574 100644 --- a/src/lsqfit.jl +++ b/src/lsqfit.jl @@ -1,22 +1,4 @@ -""" - sinfit(X, Y, f ; kwargs...) -Perform a three-parameter least-squares sinusoidal fit of the independent variables -`X` and dependent variables `Y`, assuming a known frequency `f`, using the non-linear -optimization solver from `LsqFit.jl`. - -The data is fit to the model ``s(x; DC, Q, I) = DC + Q\\sin(2πfx) + I\\cos(2πfx)``. The -values in `X` must be sorted in ascending order. - -The Levenberg-Marquardt algorithm used by `LsqFit.jl` requires an initial guess -of all three parameters, `DC`, `Q` and `I`. If no initial guess is provided, then one is -calculated using the linear regression algorithm from IEEE 1057 (see [`ieee1057`](@ref)). - -All keyword arguments provided are directly passed to `LsqFit.curve_fit`. - -See also [`SinusoidP`](@ref), [`ieee1057`](@ref), -[`curve_fit`](https://github.com/JuliaNLSolvers/LsqFit.jl). -""" function levmar(prob::Sin3Problem, a::LevMar ; kwargs...) (; X, Y, f, DC, Q, I, lb, ub) = prob # Check if all parameters are given initial estimates @@ -50,26 +32,6 @@ function levmar(prob::Sin3Problem, a::LevMar ; kwargs...) return SinusoidP(f, coef(fit)...) end -""" - sinfit(X, Y, guess::SinusoidP = sinfit_j(X, Y) ; kwargs...) - -Perform a four-parameter least-squares sinusoidal fit of the independent variables -`X` and dependent variables `Y`, using the non-linear optimization solver from -`LsqFit.jl`. - -The data is fit to the model ``s(x; f, DC, Q, I) = DC + Q\\sin(2πfx) + I\\cos(2πfx)``. The -values in `X` must be sorted in ascending order. - -The Levenberg-Marquardt algorithm used by `LsqFit.jl` requires an initial guess -of all four model parameters. If no initial guess is provided, then one is calculated -using [`sinfit_j`](@ref). - -All keyword arguments provided are directly passed to `LsqFit.curve_fit`. - -See also [`SinusoidP`](@ref), [`sinfit_j`](@ref), -[LsqFit.jl](https://github.com/JuliaNLSolvers/LsqFit.jl), -[`curve_fit`](https://github.com/JuliaNLSolvers/LsqFit.jl) -""" function levmar(prob::Sin4Problem, a::LevMar ; kwargs...) (; X, Y, f, DC, Q, I, lb, ub) = prob # Check if all parameters are given initial estimates @@ -104,24 +66,6 @@ function levmar(prob::Sin4Problem, a::LevMar ; kwargs...) return SinusoidP(coef(fit)...) end -""" - mixlinsinfit(X, Y, guess::MixedLinearSinusoidP = mixlinsinfit_j(X, Y) ; kwargs...) - -Perform a least-squares mixed linear-sinusoid fit of the independent variables `X` and dependent -variables `Y`, using the non-linear optimization solver from `LsqFit.jl`. - -The data is fit to the model ``s(x, f, DC, Q, I, m) = DC + Q\\sin(2πfx) + I\\cos(2πfx) + mx``. The -values in `X` must be sorted in axcending order. - -The Levenberg-Marquardt algorithm used by `LsqFit.jl` requires an initial guess -of the parameters, `DC`, `Q` `I`, and `m`. If no initial guess is provided, then one is -calculated then one is calculated using [`mixlinsinfit_j`](@ref). - -All keyword arguments provided are directly passed to `LsqFit.curve_fit`. - -See also [`MixedLinearSinusoidP`](@ref), [`mixlinsinfit_j`](@ref), -[`curve_fit`](https://github.com/JuliaNLSolvers/LsqFit.jl). -""" function levmar(prob::MixedLinSin4Problem, a::LevMar ; kwargs...) (; X, Y, f, DC, Q, I, m, lb, ub) = prob # Check if all parameters are given initial estimates diff --git a/src/typedefs.jl b/src/typedefs.jl index 6276a4d..ccd7726 100644 --- a/src/typedefs.jl +++ b/src/typedefs.jl @@ -5,16 +5,55 @@ abstract type Algorithm end +""" + IEEE1057([iterations = 6]) <: Algorithm + +Define an instance of the IEEE 1057 sinusoidal fitting algorithm. + +Optional argument `iterations` specifies how many iterations to run before the algorithm +stops. The default value is 6, which is the value recommended by the standard. This value +is only used when calculating a 4-parameter fit. +""" Base.@kwdef struct IEEE1057 <: Algorithm iterations::Int = 6 end +""" + IntegralEquations() <: Algorithm + +Define an instance of the integral-equations sinusoidal fitting algorithm described by +J. Jacquelin in "Régressions et équations intégrales", 2014 (available at +https://fr.scribd.com/doc/14674814/Regressions-et-equations-integrales). + +This algorithm does not accept any configuration parameters. +""" struct IntegralEquations <: Algorithm end +""" + LevMar([use_ga = false]) <: Algorithm + +Define an instance of the Levenberg-Marquardt sinusoidal fitting algorithm. + +If the optional argument `use_ga` is set to `true`, the algorithm will use geodesic +acceleration to potentially improve its performance and accuracy. See +[`curve_fit`](https://github.com/JuliaNLSolvers/LsqFit.jl) for more details. +""" Base.@kwdef struct LevMar <: Algorithm use_ga :: Bool = false end +""" + Liang([threshold = 0.15, iterations = 100, q = 1e-5]) <: Algorithm + +Define an instance of the sinusoidal fitting algorithm described in Liang et al, +"Fitting Algorithm of Sine Wave with Partial Period Waveforms and Non-Uniform Sampling +Based on Least-Square Method." Journal of Physics: Conference Series 1149.1 +(2018)ProQuest. Web. 17 Apr. 2023. + +This algorithm is designed for scenarios where only a fraction of a period of the sinusoid +has been sampled. Its optional parameters `threshold` and `q` are described in the paper. +Additionally, a maximum number of iterations may be specified in `iterations`. +""" Base.@kwdef struct Liang <: Algorithm threshold :: Float64 = 0.15 iterations :: Int = 100 @@ -27,6 +66,21 @@ end abstract type Problem end +""" + Sin3Problem(X, Y, f , [DC, Q, I, lb, ub]) <: Problem + +Define a three-parameter sinusoidal regression problem. + +The data is fit to the model ``f(x; DC, Q, I) = DC + Q\\sin(2πfx) + I\\cos(2πfx)``. The sampling +instants are given by `X`, and the samples by `Y`. The frequency `f` (in Hz) is assumed to +be known exactly. + +When using a fitting algorithm that accepts initial parameter estimates, these may be given +by the optional keyword arguments `DC`, `Q` and `I` (default `missing`). Lower and upper bounds +may be specified in `lb` and `ub`, which must be vectors of length 3. + +See also: [`Sin4Problem`](@ref) +""" Base.@kwdef struct Sin3Problem{T1, T2, T3} <: Problem where {T1 <: AbstractVector, T2 <: AbstractVector, T3 <: Real} X :: T1 # sampling times @@ -41,6 +95,20 @@ end Sin3Problem(X, Y, f ; kwargs...) = Sin3Problem(; X, Y, f, kwargs...) +""" + Sin4Problem(X, Y ; [f, DC, Q, I, lb, ub]) <: Problem + +Define a four-parameter sinusoidal regression problem. + +The data is fit to the model ``f(x; f, DC, Q, I) = DC + Q\\sin(2πfx) + I\\cos(2πfx)``. +The sampling instants are given by `X`, and the samples by `Y`. + +When using a fitting algorithm that accepts initial parameter estimates, these may be given +by the optional keyword arguments `f`, `DC`, `Q` and `I` (default `missing`). Lower and upper +bounds may be specified in `lb` and `ub`, which must be vectors of length 4. + +See also: [`Sin3Problem`](@ref) +""" Base.@kwdef struct Sin4Problem{T1, T2} <: Problem where {T1 <: AbstractVector, T2 <: AbstractVector} X :: T1 # sampling times @@ -55,6 +123,21 @@ end Sin4Problem(X, Y ; kwargs...) = Sin4Problem(; X, Y, kwargs...) +""" + MixedLinSin4Problem(X, Y, f ; [DC, Q, I, m, lb, ub]) + +Define a four-parameter mixed linear-sinusoidal regression problem. + +The data is fit to the model ``f(x; DC, Q, I, m) = DC + Q\\sin(2πfx) + I\\cos(2πfx) + mx``. +The sampling instants are given by `X`, and the samples by `Y`. The frequency `f` (in Hz) is +assumed to be known exactly. + +When using a fitting algorithm that accepts initial parameter estimates, these may be given +by the optional keyword arguments `DC`, `Q` `I` and `m` (default `missing`). Lower and upper +bounds may be specified in `lb` and `ub`, which must be vectors of length 4. + +See also: [`MixedLinSin5Problem`](@ref) +""" Base.@kwdef struct MixedLinSin4Problem{T1, T2, T3} <: Problem where {T1 <: AbstractVector, T2 <: AbstractVector, T3 <: Real} X :: T1 @@ -70,6 +153,20 @@ end MixedLinSin4Problem(X, Y, f ; kwargs...) = MixedLinSin4Problem(; X, Y, f, kwargs...) +""" + MixedLinSin5Problem(X, Y ; [f, DC, Q, I, m, lb, ub]) + +Define a five-parameter mixed linear-sinusoidal regression problem. + +The data is fit to the model ``f(x ; f, DC, Q, I, m) = DC + Q\\sin(2πfx) + I\\cos(2πfx) + mx``. +The sampling instants are given by `X`, and the samples by `Y`. + +When using a fitting algorithm that accepts initial parameter estimates, these may be given +by the optional keyword arguments `f`, `DC`, `Q` `I` and `m` (default `missing`). Lower and upper +bounds may be specified in `lb` and `ub`, which must be vectors of length 5. + +See also: [`MixedLinSin4Problem`](@ref) +""" Base.@kwdef struct MixedLinSin5Problem{T1, T2} <: Problem where {T1 <: AbstractVector, T2 <: AbstractVector, T3 <: Real} X :: T1