Skip to content

Commit 1688368

Browse files
authored
Speed up with MutableArithmetics (#343)
* Speed up with MutableArithmetics * Fix tol * Unifty tol * Fixes * Add missing tol * Fixes * Bump Julia to v1.10 * Fix doctest output
1 parent 4026e18 commit 1688368

25 files changed

+296
-221
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
- version: '1'
2525
os: ubuntu-latest
2626
arch: x86
27-
- version: '1.6'
27+
- version: '1.10'
2828
os: ubuntu-latest
2929
arch: x64
3030
steps:

Project.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ PolyhedraRecipesBaseExt = "RecipesBase"
2727
[compat]
2828
GenericLinearAlgebra = "0.2, 0.3"
2929
GeometryBasics = "0.2, 0.3, 0.4, 0.5"
30-
JuMP = "0.23, 1"
30+
JuMP = "1"
3131
LinearAlgebra = "1.6"
3232
MathOptInterface = "1"
3333
MutableArithmetics = "1"
34-
RecipesBase = "0.7, 0.8, 1.0"
34+
RecipesBase = "1.0"
3535
SparseArrays = "1.6"
36-
StaticArrays = "0.12, 1.0"
37-
julia = "1.6"
36+
StaticArrays = "1.0"
37+
julia = "1.10"

docs/src/optimization.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ julia> using JuMP
4040
4141
julia> model = Model()
4242
A JuMP Model
43-
Feasibility problem with:
44-
Variables: 0
45-
Model mode: AUTOMATIC
46-
CachingOptimizer state: NO_OPTIMIZER
47-
Solver name: No optimizer attached.
43+
├ solver: none
44+
├ objective_sense: FEASIBILITY_SENSE
45+
├ num_variables: 0
46+
├ num_constraints: 0
47+
└ Names registered in the model: none
4848
4949
julia> @variable(model, λ[1:2])
5050
2-element Vector{VariableRef}:

ext/PolyhedraGeometryBasicsExt.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,15 +90,15 @@ function _isdup(zray, triangles)
9090
end
9191
_isdup(poly, hidx, triangles) = _isdup(get(poly, hidx).a, triangles)
9292

93-
function decompose_plane!(triangles::Vector, d::FullDim, zray, incident_points, incident_lines, incident_rays, exit_point::Function, counterclockwise::Function, rotate::Function)
93+
function decompose_plane!(triangles::Vector, d::FullDim, zray, incident_points, incident_lines, incident_rays, exit_point::Function, counterclockwise::Function, rotate::Function; tol)
9494
# xray should be the rightmost ray
9595
xray = nothing
9696
# xray should be the leftmost ray
9797
yray = nothing
9898
isapproxzero(zray) && return
9999

100100
# Checking rays
101-
hull, lines, rays = _planar_hull(d, incident_points, incident_lines, incident_rays, counterclockwise, rotate)
101+
hull, lines, rays = _planar_hull(d, incident_points, incident_lines, incident_rays, counterclockwise, rotate; tol)
102102
if isempty(lines)
103103
if length(hull) + length(rays) < 3
104104
return
@@ -190,24 +190,24 @@ function fulldecompose(triangles::Vector{_Tri{N,T}}) where {N,T}
190190
return pts, faces, ns
191191
end
192192

193-
function fulldecompose(poly_geom::Mesh, ::Type{T}) where T
193+
function fulldecompose(poly_geom::Mesh, ::Type{T}; tol = Polyhedra._default_tol(T)) where T
194194
poly = poly_geom.polyhedron
195195
exit_point = scene(poly_geom, T)
196196
triangles = _Tri{2,T}[]
197197
z = StaticArrays.SVector(zero(T), zero(T), one(T))
198-
decompose_plane!(triangles, FullDim(poly), z, collect(points(poly)), lines(poly), rays(poly), exit_point, counterclockwise, rotate)
198+
decompose_plane!(triangles, FullDim(poly), z, collect(points(poly)), lines(poly), rays(poly), exit_point, counterclockwise, rotate; tol)
199199
return fulldecompose(triangles)
200200
end
201201

202-
function fulldecompose(poly_geom::Mesh{3}, ::Type{T}) where T
202+
function fulldecompose(poly_geom::Mesh{3}, ::Type{T}; tol = Polyhedra._default_tol(T)) where T
203203
poly = poly_geom.polyhedron
204204
exit_point = scene(poly_geom, T)
205205
triangles = _Tri{3,T}[]
206206
function decompose_plane(hidx)
207207
zray = get(poly, hidx).a
208208
counterclockwise(a, b) = dot(cross(a, b), zray)
209209
rotate(r) = cross(zray, r)
210-
decompose_plane!(triangles, FullDim(poly), zray, incidentpoints(poly, hidx), incidentlines(poly, hidx), incidentrays(poly, hidx), exit_point, counterclockwise, rotate)
210+
decompose_plane!(triangles, FullDim(poly), zray, incidentpoints(poly, hidx), incidentlines(poly, hidx), incidentrays(poly, hidx), exit_point, counterclockwise, rotate; tol)
211211
end
212212
for hidx in eachindex(hyperplanes(poly))
213213
decompose_plane(hidx)

perf/runbenchmarks.jl

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,24 +39,33 @@ using StaticArrays
3939
# samples: 9809
4040
# evals/sample: 1
4141

42-
let
43-
h = hrep([HalfSpace([ 1., 1], 1),
44-
HalfSpace([ 1., -1], 0),
45-
HalfSpace([-1., 0], 0)])
42+
using BenchmarkTools
43+
using Polyhedra
44+
45+
function vector(T)
46+
h = hrep([HalfSpace(T[ 1, 1], T(1)),
47+
HalfSpace(T[ 1, -1], T(0)),
48+
HalfSpace(T[-1, 0], T(0))])
4649
display(@benchmark(doubledescription($h)))
4750
end
4851

49-
let
50-
h = hrep([HalfSpace((@SVector [ 1, 1]), 1),
51-
HalfSpace((@SVector [ 1, -1]), 0),
52-
HalfSpace((@SVector [-1, 0]), 0)])
52+
using StaticArrays
53+
function static(T)
54+
h = hrep([HalfSpace((@SVector T[ 1, 1]), T(1)),
55+
HalfSpace((@SVector T[ 1, -1]), T(0)),
56+
HalfSpace((@SVector T[-1, 0]), T(0))])
5357
display(@benchmark(doubledescription($h)))
58+
@profview_allocs for _ in 1:10000
59+
doubledescription(h)
60+
end
5461
end
5562

56-
let
57-
h = hrep([ 1 1
58-
1 -1
59-
-1 0],
60-
[1., 0, 0])
63+
function matrix(T)
64+
h = hrep(T[
65+
1 1
66+
1 -1
67+
-1 0],
68+
T[1, 0, 0],
69+
)
6170
display(@benchmark(doubledescription($h)))
6271
end

src/Polyhedra.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ using LinearAlgebra
99
# like RowEchelon.jl (slow) or LU (faster).
1010
import GenericLinearAlgebra
1111

12-
import MutableArithmetics
13-
const MA = MutableArithmetics
12+
import MutableArithmetics as MA
1413

1514
import MathOptInterface as MOI
1615
import MathOptInterface.Utilities as MOIU

src/center.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function maximum_radius_with_center(h::HRep{T}, center) where T
1919
return zero(T)
2020
end
2121
else
22-
new_radius = (hs.β - center hs.a) / n
22+
new_radius = (hs.β - _dot(center, hs.a)) / n
2323
if radius === nothing
2424
radius = new_radius
2525
else

src/comp.jl

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,40 @@
1+
# We use `Zero` so that it is allocation-free, while `zero(Rational{BigInt})` would be costly
2+
_default_tol(::Type{<:Union{Integer, Rational}}) = MA.Zero()
3+
# `rtoldefault(BigFloat)` is quite slow and allocates a lot so we hope that
4+
# `tol` will be called at some upper level function and passed in here instead
5+
# of created everytime
6+
_default_tol(::Type{T}) where {T<:AbstractFloat} = Base.rtoldefault(T)
7+
8+
_default_tol(::Type{T}, ::Type{U}) where {T,U} = _default_tol(promote_type(T, U))
9+
10+
# We should **not** define `isless(::Float64, ::MutableArithmetics.Zero)`.
11+
# When we get such `MethodError`, it a nice error that always indicate some underlying issue
12+
# so we should fix that underlying issue, not add this method that would just hide bugs.
13+
114
isapproxzero(x::T; kws...) where {T<:Real} = x == zero(T)
2-
isapproxzero(x::T; ztol=Base.rtoldefault(T)) where {T<:AbstractFloat} = abs(x) < ztol
3-
isapproxzero(x::AbstractVector{T}; kws...) where {T<:Real} = isapproxzero(maximum(abs.(x)); kws...)
15+
isapproxzero(x::T; tol=Base.rtoldefault(T)) where {T<:AbstractFloat} = abs(x) < tol
16+
isapproxzero(x::AbstractVector{T}; kws...) where {T<:Real} = isapproxzero(maximum(abs, x); kws...)
417
#isapproxzero(x::Union{SymPoint, Ray, Line}; kws...) = isapproxzero(coord(x); kws...)
518
isapproxzero(x::Union{Ray, Line}; kws...) = isapproxzero(coord(x); kws...)
619
isapproxzero(h::HRepElement; kws...) = isapproxzero(h.a; kws...) && isapproxzero(h.β; kws...)
720

8-
_isapprox(x::Union{T, AbstractArray{T}}, y::Union{T, AbstractArray{T}}) where {T<:Union{Integer, Rational}} = x == y
21+
_isapprox(x::Union{T, AbstractArray{T}}, y::Union{T, AbstractArray{T}}; tol) where {T<:Union{Integer, Rational}} = x == y
922
# I check with zero because isapprox(0, 1e-100) is false...
1023
# but isapprox(1e-100, 2e-100) should be false
11-
_isapprox(x, y) = (isapproxzero(x) ? isapproxzero(y) : (isapproxzero(y) ? isapproxzero(x) : isapprox(x, y)))
24+
_isapprox(x, y; tol) = (isapproxzero(x; tol) ? isapproxzero(y; tol) : (isapproxzero(y; tol) ? isapproxzero(x; tol) : isapprox(x, y; rtol = tol)))
1225

1326
#Base.isapprox(r::SymPoint, s::SymPoint) = _isapprox(coord(r), coord(s)) || _isapprox(coord(r), -coord(s))
1427
function _scaleray(r::Union{Line, Ray}, s::Union{Line, Ray})
1528
cr = coord(r)
1629
cs = coord(s)
1730
cr * sum(abs.(cs)), cs * sum(abs.(cr))
1831
end
19-
function Base.isapprox(r::Line, s::Line)
32+
function _isapprox(r::Line, s::Line; tol)
2033
rs, ss = _scaleray(r, s)
21-
_isapprox(rs, ss) || _isapprox(rs, -ss)
34+
_isapprox(rs, ss; tol) || _isapprox(rs, -ss; tol)
2235
end
23-
function Base.isapprox(r::Ray, s::Ray)
24-
_isapprox(_scaleray(r, s)...)
36+
function _isapprox(r::Ray, s::Ray; tol)
37+
_isapprox(_scaleray(r, s)...; tol)
2538
end
2639
# TODO check that Vec in GeometryTypes also does that
2740
function _scalehp(h1, h2)
@@ -33,28 +46,28 @@ function Base.:(==)(h1::HyperPlane, h2::HyperPlane)
3346
(a1, β1), (a2, β2) = _scalehp(h1, h2)
3447
(a1 == a2 && β1 == β2) || (a1 == -a2 && β1 == -β2)
3548
end
36-
function Base.isapprox(h1::HyperPlane, h2::HyperPlane)
49+
function _isapprox(h1::HyperPlane, h2::HyperPlane; tol)
3750
(a1, β1), (a2, β2) = _scalehp(h1, h2)
38-
(_isapprox(a1, a2) && _isapprox(β1, β2)) || (_isapprox(a1, -a2) && _isapprox(β1, -β2))
51+
(_isapprox(a1, a2; tol) && _isapprox(β1, β2; tol)) || (_isapprox(a1, -a2; tol) && _isapprox(β1, -β2; tol))
3952
end
4053
function Base.:(==)(h1::HalfSpace, h2::HalfSpace)
4154
(a1, β1), (a2, β2) = _scalehp(h1, h2)
4255
a1 == a2 && β1 == β2
4356
end
44-
function Base.isapprox(h1::HalfSpace, h2::HalfSpace)
57+
function _isapprox(h1::HalfSpace, h2::HalfSpace; tol)
4558
(a1, β1), (a2, β2) = _scalehp(h1, h2)
46-
_isapprox(a1, a2) && _isapprox(β1, β2)
59+
_isapprox(a1, a2; tol) && _isapprox(β1, β2; tol)
4760
end
4861

49-
_lt(x::T, y::T) where {T<:Real} = x < y
50-
_lt(x::T, y::T) where {T<:AbstractFloat} = x < y && !_isapprox(x, y)
51-
_lt(x::S, y::T) where {S<:Real,T<:Real} = _lt(promote(x, y)...)
52-
_gt(x::S, y::T) where {S<:Real, T<:Real} = _lt(y, x)
53-
_leq(x::T, y::T) where {T<:Real} = x <= y
54-
_leq(x::T, y::T) where {T<:AbstractFloat} = x <= y || _isapprox(x, y)
55-
_leq(x::S, y::T) where {S<:Real,T<:Real} = _leq(promote(x, y)...)
56-
_geq(x::T, y::T) where {T<:Real} = _leq(y, x)
57-
_pos(x::T) where {T<:Real} = _gt(x, zero(T))
58-
_neg(x::T) where {T<:Real} = _lt(x, zero(T))
59-
_nonneg(x::T) where {T<:Real} = _geq(x, zero(T))
60-
_nonpos(x::T) where {T<:Real} = _leq(x, zero(T))
62+
_lt(x::T, y::T; tol) where {T<:Real} = x < y
63+
_lt(x::T, y::T; tol) where {T<:AbstractFloat} = x < y && !_isapprox(x, y; tol)
64+
_lt(x::S, y::T; tol) where {S<:Real,T<:Real} = _lt(promote(x, y)...; tol)
65+
_gt(x::S, y::T; tol) where {S<:Real, T<:Real} = _lt(y, x; tol)
66+
_leq(x::T, y::T; tol) where {T<:Real} = x <= y
67+
_leq(x::T, y::T; tol) where {T<:AbstractFloat} = x <= y || _isapprox(x, y; tol)
68+
_leq(x::S, y::T; tol) where {S<:Real,T<:Real} = _leq(promote(x, y)...; tol)
69+
_geq(x::T, y::T; tol) where {T<:Real} = _leq(y, x; tol)
70+
_pos(x::T; tol) where {T<:Real} = _gt(x, zero(T); tol)
71+
_neg(x::T; tol) where {T<:Real} = _lt(x, zero(T); tol)
72+
_nonneg(x::T; tol) where {T<:Real} = _geq(x, zero(T); tol)
73+
_nonpos(x::T; tol) where {T<:Real} = _leq(x, zero(T); tol)

0 commit comments

Comments
 (0)