Skip to content

Commit

Permalink
fix: make compose do what it says
Browse files Browse the repository at this point in the history
- First step at resolving oscar-system/Oscar.jl#690
- Introduce a mandatory keyword `inner = :first/:second` argument for
  compose(f, g) and add a hint for f(g)/g(f) syntax.

[breaking]
  • Loading branch information
thofma committed Feb 13, 2024
1 parent a77cde7 commit 209dae9
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 56 deletions.
2 changes: 1 addition & 1 deletion docs/src/polynomial.md
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,7 @@ julia> k = evaluate(f, 3)
julia> m = evaluate(f, x^2 + 2x + 1)
x^5 + 4*x^4 + 7*x^3 + 7*x^2 + 4*x + 4
julia> n = compose(f, g)
julia> n = compose(f, g; inner = :second)
(x^3 + 2*x^2 + x)*y^2 + (2*x^5 + 2*x^4 + 4*x^3 + 9*x^2 + 6*x + 1)*y + x^7 + 4*x^5 + 5*x^4 + 5*x^3 + 10*x^2 + 8*x + 5
julia> p = subst(f, M)
Expand Down
26 changes: 21 additions & 5 deletions src/AbsSeries.jl
Original file line number Diff line number Diff line change
Expand Up @@ -764,13 +764,29 @@ end
###############################################################################

@doc raw"""
compose(a::AbsPowerSeriesRingElem, b::AbsPowerSeriesRingElem)
compose(f::AbsPowerSeriesRingElem, g::AbsPowerSeriesRingElem; inner)
Compose the series $a$ with the series $b$ and return the result,
i.e. return $a\circ b$. The two series do not need to be in the same ring,
however the series $b$ must have positive valuation or an exception is raised.
Compose the series $a$ with the series $b$ and return the result.
- If `inner = :second`, then `f(g)` is returned and `g` must have positive valuation.
- If `inner = :first`, then `g(f)` is returned and `f` must have positive valuation.
"""
function compose(a::AbsPowerSeriesRingElem, b::AbsPowerSeriesRingElem)
function compose(f::AbsPowerSeriesRingElem, g::AbsPowerSeriesRingElem; inner = nothing)
if inner === nothing
error("""compose(f, g) requires a keyword argument inner
- inner = :second yields f(g)
- inner = :first yields g(f)
Alternatively, use directly f(g) or g(f)
""")
end

if inner == :second
return _compose_right(f, g)
else
return _compose_right(g, f)
end
end

function _compose_right(a::AbsPowerSeriesRingElem, b::AbsPowerSeriesRingElem)
valuation(b) == 0 && error("Series being substituted must have positive valuation")
i = length(a)
R = base_ring(a)
Expand Down
29 changes: 24 additions & 5 deletions src/Poly.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2201,12 +2201,31 @@ function evaluate(a::PolyRingElem, b::T) where T <: RingElement
end

@doc raw"""
compose(a::PolyRingElem, b::PolyRingElem)
compose(f::PolyRingElem, g::PolyRingElem; inner)
Compose the polynomial $a$ with the polynomial $b$ and return the result.
- If `inner = :right`, then `f(g)` is returned.
- If `inner = :left`, then `g(f)` is returned.
"""
function compose(f::PolyRingElem, g::PolyRingElem; inner = nothing)
if inner === nothing
error("""compose(f, g) requires a keyword argument inner
- inner = :second yields f(g)
- inner = :first yields g(f)
Alternatively, use directly f(g) or g(f)
""")
end

if inner == :second
return _compose_right(f, g)
elseif inner == :first
return _compose_right(g, f)
else
error("Keyword inner must be :first or :second")
end
end

Compose the polynomial $a$ with the polynomial $b$ and return the result,
i.e. return $a\circ b$.
"""
function compose(a::PolyRingElem, b::PolyRingElem)
function _compose_right(a::PolyRingElem, b::PolyRingElem)
i = length(a)
R = base_ring(a)
S = parent(b)
Expand Down
26 changes: 21 additions & 5 deletions src/RelSeries.jl
Original file line number Diff line number Diff line change
Expand Up @@ -991,13 +991,29 @@ end
###############################################################################

@doc raw"""
compose(a::RelPowerSeriesRingElem, b::RelPowerSeriesRingElem)
compose(f::RelPowerSeriesRingElem, g::RelPowerSeriesRingElem; inner)
Compose the series $a$ with the series $b$ and return the result,
i.e. return $a\circ b$. The two series do not need to be in the same ring,
however the series $b$ must have positive valuation or an exception is raised.
Compose the series $a$ with the series $b$ and return the result.
- If `inner = :second`, then `f(g)` is returned and `g` must have positive valuation.
- If `inner = :first`, then `g(f)` is returned and `f` must have positive valuation.
"""
function compose(a::RelPowerSeriesRingElem, b::RelPowerSeriesRingElem)
function compose(f::RelPowerSeriesRingElem, g::RelPowerSeriesRingElem; inner = nothing)
if inner === nothing
error("""compose(f, g) requires a keyword argument inner
- inner = :second yields f(g)
- inner = :first yields g(f)
Alternatively, use directly f(g) or g(f)
""")
end

if inner == :second
return _compose_right(f, g)
else
return _compose_right(g, f)
end
end

function _compose_right(a::RelPowerSeriesRingElem, b::RelPowerSeriesRingElem)
valuation(b) == 0 && error("Series being substituted must have positive valuation")
i = pol_length(a)
R = base_ring(a)
Expand Down
2 changes: 1 addition & 1 deletion src/polysubst.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ for T in subtypes(PolyRingElem)
if parent(f) != parent(a)
return subst(f, a)
end
return compose(f, a)
return compose(f, a; inner = :second)
end

(f::T)(a::Integer) = evaluate(f, a)
Expand Down
36 changes: 18 additions & 18 deletions test/generic/AbsSeries-test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -783,10 +783,10 @@ end

g = rand(R, 1:10, -10:10)

@test compose(f1 + f2, g) == compose(f1, g) + compose(f2, g)
@test compose(x, g) == g
@test compose(x^2, g) == g^2
@test compose(R(), g) == R()
@test compose(f1 + f2, g; inner = :second) == compose(f1, g; inner = :second) + compose(f2, g; inner = :second)
@test compose(x, g; inner = :second) == g
@test compose(x^2, g; inner = :second) == g^2
@test compose(R(), g; inner = :second) == R()
end

S, y = power_series_ring(ZZ, 10, "y", model=:capped_absolute)
Expand All @@ -796,8 +796,8 @@ end

g = rand(S, 1:10, -10:10)

@test compose(f1 + f2, g) == compose(f1, g) + compose(f2, g)
@test compose(R(), g) == S()
@test compose(f1 + f2, g; inner = :second) == compose(f1, g; inner = :second) + compose(f2, g; inner = :second)
@test compose(R(), g; inner = :second) == S()
end

# Inexact field
Expand All @@ -808,10 +808,10 @@ end

g = rand(R, 1:10, -10:10)

@test isapprox(compose(f1 + f2, g), compose(f1, g) + compose(f2, g))
@test isapprox(compose(x, g), g)
@test isapprox(compose(x^2, g), g^2)
@test isapprox(compose(R(), g), R())
@test isapprox(compose(f1 + f2, g; inner = :second), compose(f1, g; inner = :second) + compose(f2, g; inner = :second))
@test isapprox(compose(x, g; inner = :second), g)
@test isapprox(compose(x^2, g; inner = :second), g^2)
@test isapprox(compose(R(), g; inner = :second), R())
end

S, y = power_series_ring(RealField, 10, "y", model=:capped_absolute)
Expand All @@ -820,8 +820,8 @@ end
f2 = rand(R, 0:10, -10:10)

g = rand(S, 1:10, -10:10)
@test isapprox(compose(f1 + f2, g), compose(f1, g) + compose(f2, g))
@test isapprox(compose(R(), g), S())
@test isapprox(compose(f1 + f2, g; inner = :second), compose(f1, g; inner = :second) + compose(f2, g; inner = :second))
@test isapprox(compose(R(), g; inner = :second), S())
end

# Non-integral domain
Expand All @@ -833,10 +833,10 @@ end

g = rand(R, 1:10, -10:10)

@test compose(f1 + f2, g) == compose(f1, g) + compose(f2, g)
@test compose(x, g) == g
@test compose(x^2, g) == g^2
@test compose(R(), g) == R()
@test compose(f1 + f2, g; inner = :second) == compose(f1, g; inner = :second) + compose(f2, g; inner = :second)
@test compose(x, g; inner = :second) == g
@test compose(x^2, g; inner = :second) == g^2
@test compose(R(), g; inner = :second) == R()
end

S, y = power_series_ring(T, 10, "y", model=:capped_absolute)
Expand All @@ -846,8 +846,8 @@ end

g = rand(S, 1:10, -10:10)

@test compose(f1 + f2, g) == compose(f1, g) + compose(f2, g)
@test compose(R(), g) == S()
@test compose(f1 + f2, g; inner = :second) == compose(f1, g; inner = :second) + compose(f2, g; inner = :second)
@test compose(R(), g; inner = :second) == S()
end
end

Expand Down
8 changes: 5 additions & 3 deletions test/generic/Poly-test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1847,7 +1847,9 @@ end
g = rand(R, -1:5, -10:10)
h = rand(R, -1:5, -10:10)

@test compose(f, compose(g, h)) == compose(compose(f, g), h)
@test compose(f, compose(g, h; inner = :second); inner = :second) == compose(compose(f, g; inner = :second), h; inner = :second)
@test compose(f, g; inner = :first) == g(f)
@test compose(f, g; inner = :second) == f(g)
end

# Inexact field
Expand All @@ -1858,7 +1860,7 @@ end
g = rand(R, -1:5, 0:1)
h = rand(R, -1:5, 0:1)

@test isapprox(compose(f, compose(g, h)), compose(compose(f, g), h))
@test isapprox(compose(f, compose(g, h; inner = :second); inner = :second), compose(compose(f, g; inner = :second), h; inner = :second))
end

# Non-integral domain
Expand All @@ -1870,7 +1872,7 @@ end
g = rand(R, -1:5, 0:5)
h = rand(R, -1:5, 0:5)

@test compose(f, compose(g, h)) == compose(compose(f, g), h)
@test compose(f, compose(g, h; inner = :second); inner = :second) == compose(compose(f, g; inner = :second), h; inner = :second)
end
end

Expand Down
36 changes: 18 additions & 18 deletions test/generic/RelSeries-test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -806,10 +806,10 @@ end

g = rand(R, 1:10, -10:10)

@test compose(f1 + f2, g) == compose(f1, g) + compose(f2, g)
@test compose(x, g) == g
@test compose(x^2, g) == g^2
@test compose(R(), g) == R()
@test compose(f1 + f2, g; inner = :second) == compose(f1, g; inner = :second) + compose(f2, g; inner = :second)
@test compose(x, g; inner = :second) == g
@test compose(x^2, g; inner = :second) == g^2
@test compose(R(), g; inner = :second) == R()
end

S, y = power_series_ring(ZZ, 10, "y")
Expand All @@ -819,8 +819,8 @@ end

g = rand(S, 1:10, -10:10)

@test compose(f1 + f2, g) == compose(f1, g) + compose(f2, g)
@test compose(R(), g) == S()
@test compose(f1 + f2, g; inner = :second) == compose(f1, g; inner = :second) + compose(f2, g; inner = :second)
@test compose(R(), g; inner = :second) == S()
end

# Inexact field
Expand All @@ -831,10 +831,10 @@ end

g = rand(R, 1:10, -10:10)

@test isapprox(compose(f1 + f2, g), compose(f1, g) + compose(f2, g))
@test isapprox(compose(x, g), g)
@test isapprox(compose(x^2, g), g^2)
@test isapprox(compose(R(), g), R())
@test isapprox(compose(f1 + f2, g; inner = :second), compose(f1, g; inner = :second) + compose(f2, g; inner = :second))
@test isapprox(compose(x, g; inner = :second), g)
@test isapprox(compose(x^2, g; inner = :second), g^2)
@test isapprox(compose(R(), g; inner = :second), R())
end

S, y = power_series_ring(RealField, 10, "y")
Expand All @@ -843,8 +843,8 @@ end
f2 = rand(R, 0:10, -10:10)

g = rand(S, 1:10, -10:10)
@test isapprox(compose(f1 + f2, g), compose(f1, g) + compose(f2, g))
@test isapprox(compose(R(), g), S())
@test isapprox(compose(f1 + f2, g; inner = :second), compose(f1, g; inner = :second) + compose(f2, g; inner = :second))
@test isapprox(compose(R(), g; inner = :second), S())
end

# Non-integral domain
Expand All @@ -856,10 +856,10 @@ end

g = rand(R, 1:10, -10:10)

@test compose(f1 + f2, g) == compose(f1, g) + compose(f2, g)
@test compose(x, g) == g
@test compose(x^2, g) == g^2
@test compose(R(), g) == R()
@test compose(f1 + f2, g; inner = :second) == compose(f1, g; inner = :second) + compose(f2, g; inner = :second)
@test compose(x, g; inner = :second) == g
@test compose(x^2, g; inner = :second) == g^2
@test compose(R(), g; inner = :second) == R()
end

S, y = power_series_ring(T, 10, "y")
Expand All @@ -869,8 +869,8 @@ end

g = rand(S, 1:10, -10:10)

@test compose(f1 + f2, g) == compose(f1, g) + compose(f2, g)
@test compose(R(), g) == S()
@test compose(f1 + f2, g; inner = :second) == compose(f1, g; inner = :second) + compose(f2, g; inner = :second)
@test compose(R(), g; inner = :second) == S()
end
end

Expand Down

0 comments on commit 209dae9

Please sign in to comment.