Skip to content

fix: make compose do what it says #1606

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

Merged
merged 2 commits into from
Feb 13, 2024
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
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "AbstractAlgebra"
uuid = "c3fe647b-3220-5bb0-a1ea-a7954cac585d"
version = "0.38.1"
version = "0.39.0"

[deps]
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
Expand Down
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