Skip to content

Commit 972e991

Browse files
authored
Issue 469 (#472)
* take 1 * fit using constrained least squares
1 parent a6ba8fa commit 972e991

File tree

3 files changed

+107
-7
lines changed

3 files changed

+107
-7
lines changed

src/common.jl

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ the variance-covariance matrix.)
107107
108108
For fitting with a large degree, the Vandermonde matrix is exponentially ill-conditioned. The [`ArnoldiFit`](@ref) type introduces an Arnoldi orthogonalization that fixes this problem.
109109
110+
110111
"""
111112
function fit(P::Type{<:AbstractPolynomial},
112113
x::AbstractVector{T},
@@ -141,7 +142,7 @@ fit(x::AbstractVector,
141142
function _fit(P::Type{<:AbstractPolynomial},
142143
x::AbstractVector{T},
143144
y::AbstractVector{T},
144-
deg::Integer = length(x) - 1;
145+
deg = length(x) - 1;
145146
weights = nothing,
146147
var = :x,) where {T}
147148
x = mapdomain(P, x)
@@ -152,7 +153,17 @@ function _fit(P::Type{<:AbstractPolynomial},
152153
coeffs = vand \ y
153154
end
154155
R = float(T)
155-
return P(R.(coeffs), var)
156+
if isa(deg, Integer)
157+
return P{R, Symbol(var)}(R.(coeffs))
158+
else
159+
cs = zeros(T, 1 + maximum(deg))
160+
for (i,aᵢ) zip(deg, coeffs)
161+
cs[1 + i] = aᵢ
162+
end
163+
return P{R, Symbol(var)}(cs)
164+
end
165+
166+
156167
end
157168

158169

src/polynomials/standard-basis.jl

Lines changed: 84 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -488,14 +488,34 @@ function roots(p::P; kwargs...) where {T, P <: StandardBasisPolynomial{T}}
488488
end
489489

490490
function vander(P::Type{<:StandardBasisPolynomial}, x::AbstractVector{T}, n::Integer) where {T <: Number}
491-
A = Matrix{T}(undef, length(x), n + 1)
492-
A[:, 1] .= one(T)
493-
@inbounds for i in 1:n
494-
A[:, i + 1] = A[:, i] .* x
491+
vander(P, x, 0:n)
492+
# A = Matrix{T}(undef, length(x), n + 1)
493+
# A[:, 1] .= one(T)
494+
# @inbounds for i in 1:n
495+
# A[:, i + 1] = A[:, i] .* x
496+
# end
497+
# return A
498+
end
499+
500+
# skip some degrees
501+
function vander(P::Type{<:StandardBasisPolynomial}, x::AbstractVector{T}, degs) where {T <: Number}
502+
A = Matrix{T}(undef, length(x), length(degs))
503+
Aᵢ = one.(x)
504+
505+
i′ = 1
506+
for i 0:maximum(degs)
507+
if i degs
508+
A[:, i′] = Aᵢ
509+
i′ += 1
510+
end
511+
for (i, xᵢ) enumerate(x)
512+
Aᵢ[i] *= xᵢ
513+
end
495514
end
496-
return A
515+
A
497516
end
498517

518+
499519
## as noted at https://github.com/jishnub/PolyFit.jl, using method from SpecialMatrices is faster
500520
## https://github.com/JuliaMatrices/SpecialMatrices.jl/blob/master/src/vandermonde.jl
501521
## This is Algorithm 2 of https://www.maths.manchester.ac.uk/~higham/narep/narep108.pdf
@@ -531,6 +551,65 @@ function fit(P::Type{<:StandardBasisPolynomial},
531551
end
532552
end
533553

554+
"""
555+
fit(P::Type{<:StandardBasisPolynomial}, x, y, J, [cs::Dict{Int, T}]; weights, var)
556+
557+
Using constrained least squares, fit a polynomial of the type
558+
`p = ∑_{i ∈ J} aᵢ xⁱ + ∑ cⱼxʲ` where `cⱼ` are fixed non-zero constants
559+
560+
* `J`: a collection of degrees to find coefficients for
561+
* `cs`: If given, a `Dict` of key/values, `i => cᵢ`, which indicate the degree and value of the fixed non-zero constants.
562+
563+
The degrees in `cs` and those in `J` should not intersect.
564+
565+
Example
566+
```
567+
x = range(0, pi/2, 10)
568+
y = sin.(x)
569+
P = Polynomial
570+
p0 = fit(P, x, y, 5)
571+
p1 = fit(P, x, y, 1:2:5)
572+
p2 = fit(P, x, y, 3:2:5, Dict(1 => 1))
573+
[norm(p.(x) - y) for p ∈ (p0, p1, p2)] # 1.7e-5, 0.00016, 0.000248
574+
```
575+
576+
"""
577+
function fit(P::Type{<:StandardBasisPolynomial},
578+
x::AbstractVector{T},
579+
y::AbstractVector{T},
580+
J,
581+
cs=nothing;
582+
weights = nothing,
583+
var = :x,) where {T}
584+
_fit(P, x, y, J; weights=weights, var=var)
585+
end
586+
587+
588+
function fit(P::Type{<:StandardBasisPolynomial},
589+
x::AbstractVector{T},
590+
y::AbstractVector{T},
591+
J,
592+
cs::Dict{Int, S};
593+
weights = nothing,
594+
var = :x,) where {T,S}
595+
596+
for i J
597+
haskey(cs, i) && throw(ArgumentError("cs can't overlap with deg"))
598+
end
599+
600+
# we subtract off `∑cᵢ xⁱ`ⱼ from `y`;
601+
# fit as those all degrees not in J are 0,
602+
# then add back the constant coefficients
603+
604+
q = SparsePolynomial(cs)
605+
y′ = y - q.(x)
606+
607+
p = fit(P, x, y′, J; weights=weights, var=var)
608+
609+
return p + q
610+
end
611+
612+
534613
function _polynomial_fit(P::Type{<:StandardBasisPolynomial}, x::AbstractVector{T}, y; var=:x) where {T}
535614
R = float(T)
536615
coeffs = Vector{R}(undef, length(x))

test/StandardBasis.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,8 +642,18 @@ end
642642
@test fit(P, 1:4, 1:4, var=:x) variable(P{Float64}, :x)
643643
@test fit(P, 1:4, 1:4, 1, var=:x) variable(P{Float64}, :x)
644644

645+
# issue #467, fit specific degrees only
646+
p = fit(P, xs, ys, 1:2:9)
647+
@test norm(p.(xs) - ys) 1e-4
648+
649+
# issue 467: with constants
650+
p = fit(P, xs, ys, 3:2:9, Dict(1 => 1))
651+
@test norm(p.(xs) - ys) 1e-3
652+
645653
end
646654

655+
656+
647657
f(x) = 1/(1 + 25x^2)
648658
N = 250; xs = [cos(j*pi/N) for j in N:-1:0];
649659
q = fit(ArnoldiFit, xs, f.(xs));

0 commit comments

Comments
 (0)