From 2c9570b61423bc43f80430f182343d46df881517 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Fri, 27 Sep 2024 17:34:09 +0200 Subject: [PATCH] Add divexact!, lcm!, submul! (#1812) --- docs/src/ring.md | 53 ++++++--- docs/src/ring_interface.md | 54 ++-------- src/Groups.jl | 8 +- src/exports.jl | 3 + src/fundamental_interface.jl | 201 +++++++++++------------------------ 5 files changed, 113 insertions(+), 206 deletions(-) diff --git a/docs/src/ring.md b/docs/src/ring.md index ba7c3c2b9..1273d910a 100644 --- a/docs/src/ring.md +++ b/docs/src/ring.md @@ -167,22 +167,49 @@ right, respectively, of `a`. ## Unsafe ring operators -To speed up polynomial arithmetic, various unsafe operators are provided, which may -mutate the output rather than create a new object. +To speed up polynomial and matrix arithmetic, it sometimes makes sense to mutate values +in place rather than replace them with a newly created object every time they are +modified. -```julia -zero!(a::NCRingElement) -mul!(a::T, b::T, c::T) where T <: NCRingElement -add!(a::T, b::T, c::T) where T <: NCRingElement -addmul!(a::T, b::T, c::T, t::T) where T <: NCRingElement -``` +For this purpose, certain mutating operators are required. In order to support immutable +types (struct in Julia) and systems that don't have in-place operators, all unsafe +operators must return the (ostensibly) mutated value. Only the returned value is used +in computations, so this lifts the requirement that the unsafe operators actually +mutate the value. + +Note the exclamation point is a convention, which indicates that the object may be +mutated in-place. + +To make use of these functions, one must be certain that no other references are held +to the object being mutated, otherwise those values will also be changed! + +The results of `deepcopy` and all arithmetic operations, including powering and division +can be assumed to be new objects without other references being held, as can objects +returned from constructors. -In each case the mutated object is the leftmost parameter. +!!! note + + It is important to recognise that `R(a)` where `R` is the ring `a` belongs + to, does not create a new value. For this case, use `deepcopy(a)`. + +```@docs +zero! +one! +add! +sub! +mul! +neg! +inv! +addmul! +submul! +divexact! +div! +rem! +mod! +gcd! +lcm! +``` -The `add!(a, b)` operation does the same thing as `add!(a, a, b)`. The -optional `addmul!(a, b, c, t)` operation does the same thing as -`mul!(t, b, c); add!(a, t)` where `t` is a temporary which can be mutated so -that an addition allocation is not needed. ## Random generation diff --git a/docs/src/ring_interface.md b/docs/src/ring_interface.md index 9a229bff1..da5bdd32c 100644 --- a/docs/src/ring_interface.md +++ b/docs/src/ring_interface.md @@ -517,51 +517,6 @@ point division. Here we mean exact division in the ring. A fallback for this function is provided in terms of `divexact` so an implementation can be omitted if preferred. -### Unsafe operators - -To speed up polynomial and matrix arithmetic, it sometimes makes sense to mutate values -in place rather than replace them with a newly created object every time they are -modified. - -For this purpose, certain mutating operators are required. In order to support immutable -types (struct in Julia) and systems that don't have in-place operators, all unsafe -operators must return the (ostensibly) mutated value. Only the returned value is used -in computations, so this lifts the requirement that the unsafe operators actually -mutate the value. - -Note the exclamation point is a convention, which indicates that the object may be -mutated in-place. - -To make use of these functions, one must be certain that no other references are held -to the object being mutated, otherwise those values will also be changed! - -The results of `deepcopy` and all arithmetic operations, including powering and division -can be assumed to be new objects without other references being held, as can objects -returned from constructors. - -!!! note - - It is important to recognise that `R(a)` where `R` is the ring `a` belongs - to, does not create a new value. For this case, use `deepcopy(a)`. - -```julia -zero!(f::MyElem) -``` - -Set the value $f$ to zero in place. Return the mutated value. - -```julia -mul!(c::MyElem, a::MyElem, b::MyElem) -``` - -Set $c$ to the value $ab$ in place. Return the mutated value. Aliasing is permitted. - -```julia -add!(c::MyElem, a::MyElem, b::MyElem) -``` - -Set $c$ to the value $a + b$ in place. Return the mutated value. Aliasing is permitted. - ### Random generation The random functions are only used for test code to generate test data. They therefore @@ -679,6 +634,15 @@ functions. As these functions are optional, they do not need to exist. Julia wil already inform the user that the function has not been implemented if it is called but doesn't exist. +### Optional unsafe operators + +The various operators described in [Unsafe ring operators](@ref) such as +`add!` and `mul!` have default implementations which are not faster than their +regular safe counterparts. Implementors may wish to implement some or all of +them for their rings. Note that in general only the variants with the most +arguments needs to be implemented. E.g. for `add!` only `add(z,a,b)` has to be +implemented for any new ring type, as `add!(a,b)` delegates to `add!(a,a,b)`. + ### Optional basic manipulation functionality ```julia diff --git a/src/Groups.jl b/src/Groups.jl index f903aa702..c077af82f 100644 --- a/src/Groups.jl +++ b/src/Groups.jl @@ -237,16 +237,10 @@ end ################################################################################ # further mutable functions in fundamental_interface.jl: +# one!(g::GroupElem) # mul!(out::T, g::T, h::T) where {T<:GroupElem} = g * h # inv!(out::T, g::T) where {T<:GroupElem} = inv(g) -""" - one!(g::GroupElem) - -Return `one(g)`, possibly modifying `g`. -""" -one!(g::GroupElem) = one(parent(g)) - """ div_right!(out::T, g::T, h::T) where {GEl <: GroupElem} diff --git a/src/exports.jl b/src/exports.jl index 39cd644a4..72ee1162d 100644 --- a/src/exports.jl +++ b/src/exports.jl @@ -189,6 +189,7 @@ export div_left! export div_right export div_right! export divexact +export divexact! export divexact_left export divexact_low export divexact_right @@ -349,6 +350,7 @@ export laurent_series export laurent_series_field export laurent_series_ring export lcm +export lcm! export leading_coefficient export leading_exponent_vector export leading_exponent_word @@ -530,6 +532,7 @@ export strictly_upper_triangular_matrix export sturm_sequence export sub export sub! +export submul! export subst export summands export supermodule diff --git a/src/fundamental_interface.jl b/src/fundamental_interface.jl index 27fdd43ba..45124af68 100644 --- a/src/fundamental_interface.jl +++ b/src/fundamental_interface.jl @@ -302,177 +302,96 @@ const VarName = Union{Symbol, AbstractString, Char} @doc raw""" zero!(a) -Return the zero of `parent(a)`, possibly modifying the object `a` in the process. +Return `zero(parent(a))`, possibly modifying the object `a` in the process. """ -function zero!(a) - return zero(parent(a)) -end - -@doc raw""" - add!(a, b, c) - -Return `b + c`, possibly modifying the object `a` in the process. -""" -function add!(a, b, c) - return b + c -end - -@doc raw""" - add!(a, b) - -Return `a + b`, possibly modifying the object `a` in the process. -This is a shorthand for `add!(a, a, b)`. -""" -add!(a, b) = add!(a, a, b) - -@doc raw""" - sub!(a, b, c) - -Return `b - c`, possibly modifying the object `a` in the process. -""" -function sub!(a, b, c) - return b - c -end - -@doc raw""" - sub!(a, b) - -Return `a - b`, possibly modifying the object `a` in the process. -This is a shorthand for `sub!(a, a, b)`. -""" -sub!(a, b) = sub!(a, a, b) +zero!(a) = zero(parent(a)) @doc raw""" - neg!(a, b) + one!(a) -Return `-b`, possibly modifying the object `a` in the process. +Return `one(parent(a))`, possibly modifying the object `a` in the process. """ -function neg!(a, b) - return -b -end +one!(a) = one(parent(a)) @doc raw""" + neg!(z, a) neg!(a) -Return `-a`, possibly modifying the object `a` in the process. -This is a shorthand for `neg!(a, a)`. +Return `-a`, possibly modifying the object `z` in the process. +Aliasing is permitted. +The unary version is a shorthand for `neg!(a, a)`. """ +neg!(z, a) = -a neg!(a) = neg!(a, a) @doc raw""" - mul!(a, b, c) - -Return `b * c`, possibly modifying the object `a` in the process. -""" -function mul!(a, b, c) - return b * c -end - -@doc raw""" - mul!(a, b) - -Return `a * b`, possibly modifying the object `a` in the process. -This is a shorthand for `mul!(a, a, b)`. -""" -mul!(a, b) = mul!(a, a, b) - -@doc raw""" - addmul!(a, b, c, t) - -Return `a + b * c`, possibly modifying the objects `a` and `t` in the process. -""" -function addmul!(a, b, c, t) - t = mul!(t, b, c) - return add!(a, t) -end - -@doc raw""" - addmul!(a, b, c) - -Return `a + b * c`, possibly modifying the object `a` in the process. - -This is usually a shorthand for `addmul!(a, b, c, parent(a)())`, but -in some cases may be more efficient. For multiple operations in a row -that use temporary storage, it is still best to use the four argument -version. -""" -addmul!(a, b, c) = addmul!(a, b, c, parent(a)()) - -@doc raw""" - div!(a, b, c) - -Return `div(b, c)`, possibly modifying the object `a` in the process. -""" -function div!(a, b, c) - return div(b, c) -end - -@doc raw""" - div!(a, b) + inv!(z, a) + inv!(a) -Return `div(a, b)`, possibly modifying the object `a` in the process. -This is a shorthand for `div!(a, a, b)`. +Return `inv(a)`, possibly modifying the object `z` in the process. +Aliasing is permitted. +The unary version is a shorthand for `inv!(a, a)`. """ -div!(a, b) = div!(a, a, b) - -@doc raw""" - rem!(a, b, c) +inv!(z, a) = inv(a) +inv!(a) = inv!(a, a) -Return `rem(b, c)`, possibly modifying the object `a` in the process. -""" -function rem!(a, b, c) - return rem(b, c) +for (name, op) in ((:add!, :+), (:sub!, :-), (:mul!, :*)) + @eval begin + @doc """ + $($name)(z, a, b) + $($name)(a, b) + + Return `a $($op) b`, possibly modifying the object `z` in the process. + Aliasing is permitted. + The two argument version is a shorthand for `$($name)(a, a, b)`. + """ + $name(z, a, b) = $op(a, b) + $name(a, b) = $name(a, a, b) + end end @doc raw""" - rem!(a, b) - -Return `rem(a, b)`, possibly modifying the object `a` in the process. -This is a shorthand for `rem!(a, a, b)`. -""" -rem!(a, b) = rem!(a, a, b) + addmul!(z, a, b, t) + addmul!(z, a, b) -@doc raw""" - mod!(a, b, c) +Return `z + a * b`, possibly modifying the objects `z` and `t` in the process. -Return `mod(b, c)`, possibly modifying the object `a` in the process. +The second version is usually a shorthand for `addmul!(z, a, b, parent(z)())`, +but in some cases may be more efficient. For multiple operations in a row that +use temporary storage, it is still best to use the four argument version. """ -function mod!(a, b, c) - return mod(b, c) -end +addmul!(z, a, b, t) = add!(z, mul!(t, a, b)) +addmul!(z, a, b) = addmul!(z, a, b, parent(z)()) @doc raw""" - mod!(a, b) - -Return `mod(a, b)`, possibly modifying the object `a` in the process. -This is a shorthand for `mod!(a, a, b)`. -""" -mod!(a, b) = mod!(a, a, b) + submul!(z, a, b, t) + submul!(z, a, b) -@doc raw""" - inv!(a, b) +Return `z - a * b`, possibly modifying the objects `z` and `t` in the process. -Return `inv(b)`, possibly modifying the object `a` in the process. +The second version is usually a shorthand for `submul!(z, a, b, parent(z)())`, +but in some cases may be more efficient. For multiple operations in a row that +use temporary storage, it is still best to use the four argument version. """ -function inv!(a, b) - return inv(b) -end - -@doc raw""" - inv!(a) +submul!(z, a, b, t) = sub!(z, mul!(t, a, b)) +submul!(z, a, b) = submul!(z, a, b, parent(z)()) -Return `inv(a)`, possibly modifying the object `a` in the process. -This is a shorthand for `inv!(a, a)`. -""" -inv!(a) = inv!(a, a) +function divexact end -@doc raw""" - gcd!(a, b, c) +for name in (:divexact, :div, :rem, :mod, :gcd, :lcm) + name_bang = Symbol(name, "!") + @eval begin + @doc """ + $($name_bang)(z, a, b) + $($name_bang)(a, b) -Return `gcd(b, c)`, possibly modifying the object `a` in the process. -""" -function gcd!(a, b, c) - return gcd(b, c) + Return `$($name)(a, b)`, possibly modifying the object `z` in the process. + Aliasing is permitted. + The two argument version is a shorthand for `$($name)(a, a, b)`. + """ + $name_bang(z, a, b) = $name(a, b) + $name_bang(a, b) = $name_bang(a, a, b) + end end @doc raw"""