Mutable arithmetic
The high level interface can be combined with the low level wrapper to allow for efficient computations using mutable arithmetic.
In the future it would be nice to have an interface to MutableArithmetics.jl, see #118.
The following methods are useful for mutating part of a value
Arblib.radref
— Functionradref(x::ArbLike, prec = precision(x))
Return a MagRef
referencing the radius of x
.
Arblib.midref
— Functionmidref(x::ArbLike, prec = precision(x))
Return an ArfRef
referencing the midpoint of x
.
Arblib.realref
— Functionrealref(z::AcbLike, prec = precision(z))
Return an ArbRef
referencing the real part of x
.
Arblib.imagref
— Functionimagref(z::AcbLike, prec = precision(z))
Return an ArbRef
referencing the imaginary part of x
.
Arblib.ref
— Functionref(v::Union{ArbVectorOrRef,AcbVectorOrRef}, i)
Similar to v[i]
but instead of an Arb
or Acb
returns an ArbRef
or AcbRef
which still shares the memory with the i
-th entry of v
.
ref(A::Union{ArbMatrixOrRef,AcbMatrixOrRef}, i, j)
Similar to A[i,j]
but instead of an Arb
or Acb
returns an ArbRef
or AcbRef
which still shares the memory with the (i,j)
-th entry of A
.
ref(p::Union{ArbPoly,ArbSeries,AcbPoly,AcbSeries}, i)
Similar to p[i]
but instead of an Arb
or Acb
returns an ArbRef
or AcbRef
which shares the memory with the i
-th coefficient of p
.
Using ref
for reading coefficients is always safe, but if the coefficient is mutated then care has to be taken. See the comment further down for how to handle mutation.
It only allows accessing coefficients that are allocated. For ArbPoly
and AcbPoly
this is typically all coefficients up to the degree of the polynomial, but can be higher if for example Arblib.fit_length!
is used. For ArbSeries
and AcbSeries
all coefficients up to the degree of the series are guaranteed to be allocated, even if the underlying polynomial has a lower degree.
If the coefficient is mutated in a way that the degree of the polynomial changes then one needs to also update the internally stored length of the polynomial.
- If the new degree is the same or lower this can be achieved with
Arblib.normalise!(p)
- If the new degree is higher you need to manually set the length. This can be achieved with
whereArblib.set_length!(p, len) Arblib.normalise!(p)
len
is one higher than (an upper bound of) the new degree.
See the extended help for more details.
Extended help
Here is an example were the leading coefficient is mutated so that the degree is lowered.
julia> p = ArbPoly([1, 2], prec = 64) # Polynomial of degree 1
1.0 + 2.0⋅x
julia> Arblib.zero!(Arblib.ref(p, 1)) # Set leading coefficient to 0
0
julia> Arblib.degree(p) # The degree is still reported as 1
1
julia> length(p) # And the length as 2
2
julia> p # Printing gives weird results
1.0 +
julia> Arblib.normalise!(p) # Normalising the polynomial updates the degree
1.0
julia> Arblib.degree(p) # This is now correct
0
julia> p # And so is printing
1.0
Here is an example when a coefficient above the degree is mutated.
julia> q = ArbSeries([1, 2, 0], prec = 64) # Series of degree 3 with leading coefficient zero
1.0 + 2.0⋅x + 𝒪(x^3)
julia> Arblib.one!(Arblib.ref(q, 2)) # Set the leading coefficient to 1
1.0
julia> q # The leading coefficient is not picked up
1.0 + 2.0⋅x + 𝒪(x^3)
julia> Arblib.degree(q.poly) # The degree of the underlying polynomial is still 1
1
julia> Arblib.set_length!(q, 3) # After explicitly setting the length to 3 it is ok
1.0 + 2.0⋅x + 1.0⋅x^2 + 𝒪(x^3)
Examples
Compare computing $\sqrt{x^2 + y^2}$ using mutable arithmetic with the default.
julia> using Arblib, BenchmarkTools
julia> x = Arb(1 // 3)
[0.33333333333333333333333333333333333333333333333333333333333333333333333333333 +/- 4.78e-78]
julia> y = Arb(1 // 5)
[0.20000000000000000000000000000000000000000000000000000000000000000000000000000 +/- 3.89e-78]
julia> res = zero(x)
0
julia> f(x, y) = sqrt(x^2 + y^2)
f (generic function with 1 method)
julia> f!(res, x, y) = begin Arblib.sqr!(res, x) Arblib.fma!(res, res, y, y) return Arblib.sqrt!(res, res) end
f! (generic function with 1 method)
julia> @benchmark f($x, $y) samples=10000 evals=500
BenchmarkTools.Trial: 8714 samples with 500 evaluations. Range (min … max): 608.042 ns … 394.946 μs ┊ GC (min … max): 0.00% … 82.40% Time (median): 625.255 ns ┊ GC (median): 0.00% Time (mean ± σ): 1.149 μs ± 9.007 μs ┊ GC (mean ± σ): 21.81% ± 2.84% █▅▅▅▄▄▄▄▃▃▃▃▃▃▃▂▂▁▁ ▂ ████████████████████████▇▇▇▆▇▆▅▆▆▅▅▅▆▃▅▅▆▄▃▄▄▁▃▅▃▄▃▁▃▃▄▁▃▃▁▃▃ █ 608 ns Histogram: log(frequency) by time 1.09 μs < Memory estimate: 448 bytes, allocs estimate: 8.
julia> @benchmark f!($res, $x, $y) samples=10000 evals=500
BenchmarkTools.Trial: 10000 samples with 500 evaluations. Range (min … max): 367.680 ns … 1.072 μs ┊ GC (min … max): 0.00% … 0.00% Time (median): 369.692 ns ┊ GC (median): 0.00% Time (mean ± σ): 389.196 ns ± 63.030 ns ┊ GC (mean ± σ): 0.00% ± 0.00% █ ▂▂ ▁▁ ▁ █▄██▇█████▇▇▆▇▇▅▅▄▅▄▅▆▅▆▆▆▆▇▇▇▇██▇▇▇▇▆▆▆▆▅▆▄▅▅▄▄▃▃▄▄▃▂▃▄▆▆▄▄ █ 368 ns Histogram: log(frequency) by time 640 ns < Memory estimate: 0 bytes, allocs estimate: 0.
Set the radius of the real part of an Acb
.
julia> using Arblib
julia> z = Acb(1, 2)
1.0 + 2.0im
julia> Arblib.set!(Arblib.radref(Arblib.realref(z)), 1e-10)
1.0e-10
julia> z
[1.000000000 +/- 1.01e-10] + 2.0im
Compare a naive implementation of polynomial evaluation using mutable arithmetic with one not using using it.
julia> using Arblib, BenchmarkTools
julia> p = ArbPoly(1:10)
1.0 + 2.0⋅x + 3.0⋅x^2 + 4.0⋅x^3 + 5.0⋅x^4 + 6.0⋅x^5 + 7.0⋅x^6 + 8.0⋅x^7 + 9.0⋅x^8 + 10.0⋅x^9
julia> x = Arb(1 // 3)
[0.33333333333333333333333333333333333333333333333333333333333333333333333333333 +/- 4.78e-78]
julia> res = zero(x)
0
julia> function eval(p, x) res = zero(x) xi = one(x) for i in 0:Arblib.degree(p) res += p[i] * xi xi *= x end return res end
eval (generic function with 2 methods)
julia> function eval!(res, p, x) Arblib.zero!(res) xi = one(x) for i in 0:Arblib.degree(p) Arblib.addmul!(res, Arblib.ref(p, i), xi) Arblib.mul!(xi, xi, x) end return res end
eval! (generic function with 1 method)
julia> @benchmark eval($p, $x) samples = 10000 evals = 30
BenchmarkTools.Trial: 10000 samples with 30 evaluations. Range (min … max): 2.806 μs … 6.760 ms ┊ GC (min … max): 0.00% … 81.85% Time (median): 2.881 μs ┊ GC (median): 0.00% Time (mean ± σ): 8.280 μs ± 131.701 μs ┊ GC (mean ± σ): 33.79% ± 2.18% ██▆▅▄▄▄▃▃▃▂▂▁▁▁▂▁▁ ▁▂▃▃▂▂▂▂▁▁▁ ▂ ██████████████████████████████████▇▇██▇▆▇▆▆▆▆▆▅▆▆▅▄▆▆▅▅▁▁▄▃ █ 2.81 μs Histogram: log(frequency) by time 5.19 μs < Memory estimate: 3.94 KiB, allocs estimate: 70.
julia> @benchmark eval!($res, $p, $x) samples = 10000 evals = 30
BenchmarkTools.Trial: 10000 samples with 30 evaluations. Range (min … max): 1.134 μs … 994.812 μs ┊ GC (min … max): 0.00% … 46.78% Time (median): 1.156 μs ┊ GC (median): 0.00% Time (mean ± σ): 1.278 μs ± 9.937 μs ┊ GC (mean ± σ): 3.64% ± 0.47% █▁ ▅██▆▄▃▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▁▂▂ ▂ 1.13 μs Histogram: frequency by time 1.78 μs < Memory estimate: 160 bytes, allocs estimate: 3.
julia> @benchmark $p($x) samples = 10000 evals = 30 # Arb implementation for reference
BenchmarkTools.Trial: 10000 samples with 30 evaluations. Range (min … max): 786.300 ns … 987.704 μs ┊ GC (min … max): 0.00% … 45.16% Time (median): 800.167 ns ┊ GC (median): 0.00% Time (mean ± σ): 921.684 ns ± 9.869 μs ┊ GC (mean ± σ): 4.84% ± 0.45% ▆█▇▆▅▅▄▄▃▃▂▁▁ ▁ ▂ ███████████████▆▆▆▅▅▆▅▆▅▄▆▅▆▆▆▆▇▇▆▆▅▆▅▆▆▆▅▆▆▆▆▇▄▇██▇█▇▆▆▅▁▅▄▃ █ 786 ns Histogram: log(frequency) by time 1.16 μs < Memory estimate: 160 bytes, allocs estimate: 3.