Skip to content

Commit 37d9cf3

Browse files
authored
Trigonometric basis (#49)
* Trigonometric basis * Fix format * Add tests
1 parent e4737e2 commit 37d9cf3

File tree

5 files changed

+143
-14
lines changed

5 files changed

+143
-14
lines changed

docs/src/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,5 @@ AbstractGegenbauer
3838
Legendre
3939
ChebyshevFirstKind
4040
ChebyshevSecondKind
41+
Trigonometric
4142
```

src/MultivariateBases.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ include("scaled.jl")
4747
export AbstractMultipleOrthogonal,
4848
ProbabilistsHermite, PhysicistsHermite, Laguerre
4949
export AbstractGegenbauer,
50-
Legendre, Chebyshev, ChebyshevFirstKind, ChebyshevSecondKind
50+
Legendre, Chebyshev, ChebyshevFirstKind, ChebyshevSecondKind, Trigonometric
5151
export algebra_element,
5252
sparse_coefficients,
5353
univariate_orthogonal_basis,
@@ -62,6 +62,7 @@ include("hermite.jl")
6262
include("laguerre.jl")
6363
include("legendre.jl")
6464
include("chebyshev.jl")
65+
include("trigonometric.jl")
6566
include("lagrange.jl")
6667
include("quotient.jl")
6768

src/chebyshev.jl

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,26 @@ const Chebyshev = ChebyshevFirstKind
2424

2525
# https://en.wikipedia.org/wiki/Chebyshev_polynomials#Properties
2626
# `T_n * T_m = T_{n + m} / 2 + T_{|n - m|} / 2`
27-
function (::Mul{Chebyshev})(a::MP.AbstractMonomial, b::MP.AbstractMonomial)
27+
function univariate_mul!(::Mul{Chebyshev}, terms, var, a, b)
28+
I = eachindex(terms)
29+
for i in I
30+
mono = MP.monomial(terms[i]) * var^(a + b)
31+
terms[i] = MA.mul!!(terms[i], var^abs(a - b))
32+
terms[i] = MA.operate!!(/, terms[i], 2)
33+
α = MA.copy_if_mutable(MP.coefficient(terms[i]))
34+
push!(terms, MP.term(α, mono))
35+
end
36+
return
37+
end
38+
39+
function (mul::Mul{B})(
40+
a::MP.AbstractMonomial,
41+
b::MP.AbstractMonomial,
42+
) where {B<:AbstractMonomialIndexed}
2843
terms = [MP.term(1 // 1, MP.constant_monomial(a * b))]
29-
vars_a = MP.variables(a)
44+
vars_a = MP.effective_variables(a)
3045
var_state_a = iterate(vars_a)
31-
vars_b = MP.variables(b)
46+
vars_b = MP.effective_variables(b)
3247
var_state_b = iterate(vars_b)
3348
while !isnothing(var_state_a) || !isnothing(var_state_b)
3449
if isnothing(var_state_a) ||
@@ -48,16 +63,13 @@ function (::Mul{Chebyshev})(a::MP.AbstractMonomial, b::MP.AbstractMonomial)
4863
else
4964
var_a, state_a = var_state_a
5065
var_b, state_b = var_state_b
51-
d_a = MP.degree(a, var_a)
52-
d_b = MP.degree(b, var_b)
53-
I = eachindex(terms)
54-
for i in I
55-
mono = MP.monomial(terms[i]) * var_a^(d_a + d_b)
56-
terms[i] = MA.mul!!(terms[i], var_a^abs(d_a - d_b))
57-
terms[i] = MA.operate!!(/, terms[i], 2)
58-
α = MA.copy_if_mutable(MP.coefficient(terms[i]))
59-
push!(terms, MP.term(α, mono))
60-
end
66+
univariate_mul!(
67+
mul,
68+
terms,
69+
var_a,
70+
MP.degree(a, var_a),
71+
MP.degree(b, var_b),
72+
)
6173
var_state_a = iterate(vars_a, state_a)
6274
var_state_b = iterate(vars_b, state_b)
6375
end

src/trigonometric.jl

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"""
2+
struct Trigonometric <: AbstractMonomialIndexed end
3+
4+
Univariate trigonometric basis is
5+
```
6+
a0 + a1 cos(ωt) + a2 sin(ωt) + a3 cos(2ωt) + a4 sin(2ωt)
7+
```
8+
"""
9+
struct Trigonometric <: AbstractMultipleOrthogonal end
10+
11+
_cos_id(d) = iszero(d) ? 0 : 2d - 1
12+
_sin_id(d) = 2d
13+
_is_cos(d) = isodd(d)
14+
_is_sin(d) = d > 0 && iseven(d)
15+
_id(d) = div(d + 1, 2)
16+
17+
# https://en.wikipedia.org/wiki/Chebyshev_polynomials#Properties
18+
# Using
19+
# sin(a + b) = sin(a) cos(b) + cos(a) sin(b)
20+
# sin(a - b) = sin(a) cos(b) - cos(a) sin(b)
21+
# If a > b
22+
# sin(a) cos(b) = sin(a + b) + sin(a - b)
23+
# sin(a) cos(b) = sin(a + b) + sin(a - b)
24+
function univariate_mul!(::Mul{Trigonometric}, terms, var, a, b)
25+
@assert !iszero(a)
26+
@assert !iszero(b)
27+
I = eachindex(terms)
28+
da = _id(a)
29+
db = _id(b)
30+
for i in I
31+
if _is_cos(a) == _is_cos(b)
32+
# Chebyshev first kind
33+
mono = MP.monomial(terms[i]) * var^(_cos_id(da + db))
34+
terms[i] = MA.mul!!(terms[i], var^_cos_id(abs(da - db)))
35+
terms[i] = MA.operate!!(/, terms[i], 2)
36+
α = MA.copy_if_mutable(MP.coefficient(terms[i]))
37+
push!(terms, MP.term(α, mono))
38+
# cos(a + b) = cos(a) cos(b) - sin(a) sin(b)
39+
# cos(a - b) = cos(a) cos(b) + sin(a) sin(b)
40+
# cos(a - b) - cos(a + b)
41+
if _is_sin(a)
42+
terms[end] = MA.operate!!(*, terms[end], -1)
43+
end
44+
else
45+
if _is_cos(a)
46+
da, db = db, da
47+
end
48+
# sin(da) * cos(db)
49+
if da == db
50+
# sin(da) * cos(da) = sin(2da) / 2
51+
terms[i] = MA.mul!!(terms[i], var^_cos_id(da + db))
52+
terms[i] = MA.operate!!(/, terms[i], 2)
53+
else
54+
# Using
55+
# sin(a + b) = sin(a) cos(b) + cos(a) sin(b)
56+
# sin(a - b) = sin(a) cos(b) - cos(a) sin(b)
57+
# If a > b
58+
# sin(a) cos(b) = (sin(a + b) + sin(a - b)) / 2
59+
# If a < b
60+
# sin(a) cos(b) = (sin(b + a) - sin(b - a)) / 2
61+
mono = MP.monomial(terms[i]) * var^(_sin_id(da + db))
62+
terms[i] = MA.mul!!(terms[i], var^_sin_id(abs(da - db)))
63+
terms[i] = MA.operate!!(/, terms[i], 2)
64+
α = MA.copy_if_mutable(MP.coefficient(terms[i]))
65+
push!(terms, MP.term(α, mono))
66+
if da < db
67+
terms[i] = MA.operate!!(*, terms[i], -1)
68+
end
69+
end
70+
end
71+
end
72+
return
73+
end
74+
75+
function degree_one_univariate_polynomial(::Type{Trigonometric}, variable)
76+
MA.@rewrite(variable + 0)
77+
end
78+
79+
function recurrence_eval(
80+
::Type{Trigonometric},
81+
previous::AbstractVector,
82+
value,
83+
degree,
84+
)
85+
d = _id(degree)
86+
if _is_cos(degree)
87+
# Chebyshev first order
88+
return 2 * value * previous[_cos_id(d - 1)+1] -
89+
previous[_cos_id(d - 2)+1]
90+
else
91+
return sqrt(1 - previous[degree]^2)
92+
end
93+
end
94+
95+
function _promote_coef(::Type{T}, ::Type{Trigonometric}) where {T}
96+
return _promote_div(T)
97+
end
98+
99+
# FIXME The cos part is, like Chebysev, maybe the sin part too ? We should do better here, this is just a stopgap
100+
even_odd_separated(::Type{Trigonometric}) = false

test/trigonometric.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using Test
2+
using MultivariateBases
3+
using DynamicPolynomials
4+
5+
@testset "StarAlgebras" begin
6+
@polyvar x
7+
a = MB.Polynomial{MB.Trigonometric}(x)
8+
b = a * a
9+
@test b.coeffs == MB.sparse_coefficients(1 // 2 + 1 // 2 * x^3)
10+
c = b * b
11+
@test c.coeffs ==
12+
MB.sparse_coefficients(3 // 8 + 1 // 2 * x^3 + 1 // 8 * x^7)
13+
@test a * MB.Polynomial{MB.Trigonometric}(constant_monomial(typeof(x))) ==
14+
a * MB.Polynomial{MB.Trigonometric}(x^0)
15+
end

0 commit comments

Comments
 (0)