Skip to content

Commit 018dfcd

Browse files
authored
Merge pull request #643 from JuliaOpt/bl/iszero
✨ Add iszero
2 parents 06aa12f + 5763400 commit 018dfcd

File tree

2 files changed

+51
-26
lines changed

2 files changed

+51
-26
lines changed

src/Utilities/functions.jl

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -353,11 +353,22 @@ function test_constraintnames_equal(model, constraintnames)
353353
end
354354
end
355355

356-
isapprox_zero::AbstractFloat, tol) = -tol < α < tol
357-
isapprox_zero::Union{Integer, Rational}, tol) = iszero(α)
358-
function isapprox_zero(t::Union{MOI.ScalarAffineTerm,
359-
MOI.ScalarQuadraticTerm}, tol)
360-
isapprox_zero(MOI.coefficient(t), tol)
356+
"""
357+
all_coefficients(p::Function, f::MOI.AbstractFunction)
358+
359+
Determine whether predicate `p` returns `true` for all coefficients of `f`,
360+
returning `false` as soon as the first coefficient of `f` for which `p`
361+
returns `false` is encountered (short-circuiting). Similar to `all`.
362+
"""
363+
function all_coefficients end
364+
365+
function all_coefficients(p::Function, f::MOI.ScalarAffineFunction)
366+
return p(f.constant) && all(t -> p(MOI.coefficient(t)), f.terms)
367+
end
368+
function all_coefficients(p::Function, f::MOI.ScalarQuadraticFunction)
369+
return p(f.constant) &&
370+
all(t -> p(MOI.coefficient(t)), f.affine_terms) &&
371+
all(t -> p(MOI.coefficient(t)), f.quadratic_terms)
361372
end
362373

363374
"""
@@ -379,16 +390,15 @@ then `isapprox_zero(f)` is `false` but `isapprox_zero(MOIU.canonical(f))` is
379390
"""
380391
function isapprox_zero end
381392

382-
function isapprox_zero(f::MOI.ScalarAffineFunction, tol)
383-
isapprox_zero(f.constant, tol) && all(t -> isapprox_zero(t, tol),
384-
f.terms)
393+
isapprox_zero::AbstractFloat, tol) = -tol <= α <= tol
394+
isapprox_zero::Union{Integer, Rational}, tol) = iszero(α)
395+
function isapprox_zero(f::MOI.AbstractFunction, tol)
396+
return all_coefficients-> isapprox_zero(α, tol), f)
385397
end
386-
function isapprox_zero(f::MOI.ScalarQuadraticFunction, tol)
387-
isapprox_zero(f.constant, tol) &&
388-
all(t -> isapprox_zero(t, tol),
389-
f.affine_terms) &&
390-
all(t -> isapprox_zero(t, tol),
391-
f.quadratic_terms)
398+
399+
function Base.iszero(f::Union{MOI.ScalarAffineFunction{T},
400+
MOI.ScalarQuadraticFunction{T}}) where T
401+
return all_coefficients(iszero, canonical(f))
392402
end
393403

394404
"""

test/Utilities/functions.jl

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ const MOIU = MOI.Utilities
175175
@testset "Affine" begin
176176
@testset "zero" begin
177177
f = @inferred MOIU.zero(MOI.ScalarAffineFunction{Float64})
178+
@test iszero(f)
178179
@test MOIU.isapprox_zero(f, 1e-16)
179180
end
180181
@testset "promote_operation" begin
@@ -187,17 +188,24 @@ const MOIU = MOI.Utilities
187188
MOI.ScalarAffineFunction{Int},
188189
MOI.ScalarAffineFunction{Int}) == MOI.ScalarAffineFunction{Int}
189190
end
190-
@testset "Comparison tolerance" begin
191+
@testset "Comparison" begin
191192
@test MOIU.operate(+, Float64, MOI.SingleVariable(x),
192193
MOI.SingleVariable(z)) + 1.0
193194
MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([1, 1e-7, 1], [x, y, z]), 1.0) atol=1e-6
194195
f1 = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x), MOI.ScalarAffineTerm(1e-7, y)], 1.0)
195196
f2 = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 1.0)
196197
@test f1 f2 atol=1e-6
197198
fdiff = f1 - f2
198-
MOIU.canonicalize!(fdiff)
199-
@test !MOIU.isapprox_zero(fdiff, 1e-8)
200-
@test MOIU.isapprox_zero(fdiff, 1e-6)
199+
@testset "With iszero" begin
200+
@test !iszero(fdiff)
201+
@test iszero(f1 - f1)
202+
@test iszero(f2 - f2)
203+
end
204+
@testset "With tolerance" begin
205+
MOIU.canonicalize!(fdiff)
206+
@test !MOIU.isapprox_zero(fdiff, 1e-8)
207+
@test MOIU.isapprox_zero(fdiff, 1e-6)
208+
end
201209
end
202210
@testset "canonical" begin
203211
f = MOIU.canonical(MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([2, 1, 3, -2, -3], [y, x, z, x, z]), 5))
@@ -290,14 +298,21 @@ const MOIU = MOI.Utilities
290298
f = 7 + 3fx + 1fx * fx + 2fy * fy + 3fx * fy
291299
MOIU.canonicalize!(f)
292300
@test MOI.output_dimension(f) == 1
293-
@testset "isapprox_zero" begin
294-
@test !MOIU.isapprox_zero(f, 1e-8)
295-
# Test isapprox_zero with zero terms
296-
@test MOIU.isapprox_zero(0 * f, 1e-8)
297-
g = 1.0fx * fy - (1 + 1e-6) * fy * fx
298-
MOIU.canonicalize!(g)
299-
@test MOIU.isapprox_zero(g, 1e-5)
300-
@test !MOIU.isapprox_zero(g, 1e-7)
301+
@testset "Comparison" begin
302+
@testset "With iszero" begin
303+
@test !iszero(f)
304+
@test iszero(0 * f)
305+
@test iszero(f - f)
306+
end
307+
@testset "With tolerance" begin
308+
@test !MOIU.isapprox_zero(f, 1e-8)
309+
# Test isapprox_zero with zero terms
310+
@test MOIU.isapprox_zero(0 * f, 1e-8)
311+
g = 1.0fx * fy - (1 + 1e-6) * fy * fx
312+
MOIU.canonicalize!(g)
313+
@test MOIU.isapprox_zero(g, 1e-5)
314+
@test !MOIU.isapprox_zero(g, 1e-7)
315+
end
301316
end
302317
@testset "convert" begin
303318
@test_throws InexactError convert(MOI.SingleVariable, f)

0 commit comments

Comments
 (0)