From f68a7a0bf38ce08628b8022db05097c94d2a5414 Mon Sep 17 00:00:00 2001 From: Joaquim Date: Thu, 12 Jun 2025 18:52:04 -0300 Subject: [PATCH 1/5] [Utilities] optionally disable warning in `PenaltyRelaxation` (#2774) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Oscar Dowson Co-authored-by: Benoît Legat --- src/Utilities/penalty_relaxation.jl | 14 ++++++++++++-- test/Utilities/penalty_relaxation.jl | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/Utilities/penalty_relaxation.jl b/src/Utilities/penalty_relaxation.jl index 3521cc3cce..ed414225fb 100644 --- a/src/Utilities/penalty_relaxation.jl +++ b/src/Utilities/penalty_relaxation.jl @@ -142,6 +142,7 @@ end PenaltyRelaxation( penalties = Dict{MOI.ConstraintIndex,Float64}(); default::Union{Nothing,T} = 1.0, + warn::Bool = true, ) A problem modifier that, when passed to [`MOI.modify`](@ref), destructively @@ -187,6 +188,9 @@ cannot be modified in-place. To modify variable bounds, rewrite them as linear constraints. +If a constraint cannot be modified, a warning is logged and the +constraint is skipped. The warning can be disabled by setting `warn = false`. + ## Example ```jldoctest @@ -242,12 +246,14 @@ true mutable struct PenaltyRelaxation{T} default::Union{Nothing,T} penalties::Dict{MOI.ConstraintIndex,T} + warn::Bool function PenaltyRelaxation( p::Dict{MOI.ConstraintIndex,T}; default::Union{Nothing,T} = one(T), + warn::Bool = true, ) where {T} - return new{T}(default, p) + return new{T}(default, p, warn) end end @@ -286,7 +292,11 @@ function _modify_penalty_relaxation( map[ci] = MOI.modify(model, ci, ScalarPenaltyRelaxation(penalty)) catch err if err isa MethodError && err.f == MOI.modify - @warn("Skipping PenaltyRelaxation for ConstraintIndex{$F,$S}") + if relax.warn + @warn( + "Skipping PenaltyRelaxation for ConstraintIndex{$F,$S}" + ) + end return end rethrow(err) diff --git a/test/Utilities/penalty_relaxation.jl b/test/Utilities/penalty_relaxation.jl index 226bf8a623..ae6c839f4b 100644 --- a/test/Utilities/penalty_relaxation.jl +++ b/test/Utilities/penalty_relaxation.jl @@ -65,6 +65,25 @@ function test_relax_bounds() return end +function test_relax_no_warn() + input = """ + variables: x, y + minobjective: x + y + x >= 0.0 + y <= 0.0 + x in ZeroOne() + y in Integer() + """ + model = MOI.Utilities.Model{Float64}() + MOI.Utilities.loadfromstring!(model, input) + relaxation = MOI.Utilities.PenaltyRelaxation(; warn = false) + @test_logs MOI.modify(model, relaxation) + dest = MOI.Utilities.Model{Float64}() + MOI.Utilities.loadfromstring!(dest, input) + MOI.Bridges._test_structural_identical(model, dest) + return +end + function test_relax_affine_lessthan() _test_roundtrip( """ From cc6437ec6ee9af512cf91489d74ccbb896137205 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 3 Jul 2025 10:04:53 +1200 Subject: [PATCH 2/5] [FileFormats.MPS] fix writing objective constant in MAX_SENSE (#2778) --- src/FileFormats/MPS/MPS.jl | 2 +- test/FileFormats/MPS/MPS.jl | 51 +++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/FileFormats/MPS/MPS.jl b/src/FileFormats/MPS/MPS.jl index 36f3e94d89..9d6de0173a 100644 --- a/src/FileFormats/MPS/MPS.jl +++ b/src/FileFormats/MPS/MPS.jl @@ -451,7 +451,7 @@ end function _extract_terms_objective(model, var_to_column, coefficients, flip_obj) obj_func = _get_objective(model) _extract_terms(var_to_column, coefficients, "OBJ", obj_func, flip_obj) - return obj_func.constant + return flip_obj ? -obj_func.constant : obj_func.constant end function _var_name( diff --git a/test/FileFormats/MPS/MPS.jl b/test/FileFormats/MPS/MPS.jl index 8a642d3269..e9f770dba2 100644 --- a/test/FileFormats/MPS/MPS.jl +++ b/test/FileFormats/MPS/MPS.jl @@ -1610,6 +1610,57 @@ function test_int_round_trip() return end +function test_obj_constant_min() + model = MOI.FileFormats.MPS.Model() + x = MOI.add_variable(model) + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + f = 1.0 * x + 2.0 + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + io = IOBuffer() + write(io, model) + dest = MOI.FileFormats.MPS.Model() + seekstart(io) + read!(io, dest) + g = MOI.get(dest, MOI.ObjectiveFunction{typeof(f)}()) + @test g.constant == 2.0 + @test MOI.get(dest, MOI.ObjectiveSense()) == MOI.MIN_SENSE + return +end + +function test_obj_constant_max_to_min() + model = MOI.FileFormats.MPS.Model() + x = MOI.add_variable(model) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + f = 1.0 * x + 2.0 + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + io = IOBuffer() + write(io, model) + dest = MOI.FileFormats.MPS.Model() + seekstart(io) + read!(io, dest) + g = MOI.get(dest, MOI.ObjectiveFunction{typeof(f)}()) + @test g.constant == -2.0 + @test MOI.get(dest, MOI.ObjectiveSense()) == MOI.MIN_SENSE + return +end + +function test_obj_constant_max_to_max() + model = MOI.FileFormats.MPS.Model(; print_objsense = true) + x = MOI.add_variable(model) + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + f = 1.0 * x + 2.0 + MOI.set(model, MOI.ObjectiveFunction{typeof(f)}(), f) + io = IOBuffer() + write(io, model) + dest = MOI.FileFormats.MPS.Model() + seekstart(io) + read!(io, dest) + g = MOI.get(dest, MOI.ObjectiveFunction{typeof(f)}()) + @test g.constant == 2.0 + @test MOI.get(dest, MOI.ObjectiveSense()) == MOI.MAX_SENSE + return +end + end # TestMPS TestMPS.runtests() From 575c09d04894fb6fb142bb6b256039ba298a45b1 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Fri, 4 Jul 2025 15:15:50 +1200 Subject: [PATCH 3/5] Fix change in Expr == for Julia nightly (#2780) --- src/Test/test_nonlinear.jl | 4 ++-- test/FileFormats/MOF/MOF.jl | 10 ++++++---- test/FileFormats/NL/read.jl | 5 +++-- test/Nonlinear/Nonlinear.jl | 28 ++++++++++++++-------------- 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/Test/test_nonlinear.jl b/src/Test/test_nonlinear.jl index 1d746a3665..79829fbaf7 100644 --- a/src/Test/test_nonlinear.jl +++ b/src/Test/test_nonlinear.jl @@ -323,7 +323,7 @@ MOI.objective_expr(::FeasibilitySenseEvaluator) = :() function MOI.constraint_expr(::FeasibilitySenseEvaluator, i::Int) @assert i == 1 - return :(x[$(MOI.VariableIndex(1))]^2 == 1) + return :(x[$(MOI.VariableIndex(1))]^2.0 == 1.0) end MOI.eval_objective(d::FeasibilitySenseEvaluator, x) = 0.0 @@ -1072,7 +1072,7 @@ written. External solvers can exclude this test without consequence. function test_nonlinear_Feasibility_internal(::MOI.ModelLike, ::Config) d = FeasibilitySenseEvaluator(true) @test MOI.objective_expr(d) == :() - @test MOI.constraint_expr(d, 1) == :(x[$(MOI.VariableIndex(1))]^2 == 1.0) + @test MOI.constraint_expr(d, 1) == :(x[$(MOI.VariableIndex(1))]^2.0 == 1.0) @test_throws AssertionError MOI.constraint_expr(d, 2) MOI.initialize(d, [:Grad, :Jac, :ExprGraph, :Hess]) @test :Hess in MOI.features_available(d) diff --git a/test/FileFormats/MOF/MOF.jl b/test/FileFormats/MOF/MOF.jl index 2e4297503b..5a58acb719 100644 --- a/test/FileFormats/MOF/MOF.jl +++ b/test/FileFormats/MOF/MOF.jl @@ -96,8 +96,8 @@ function HS071(x::Vector{MOI.VariableIndex}) ExprEvaluator( :(x[$x1] * x[$x4] * (x[$x1] + x[$x2] + x[$x3]) + x[$x3]), [ - :(x[$x1] * x[$x2] * x[$x3] * x[$x4] >= 25), - :(x[$x1]^2 + x[$x2]^2 + x[$x3]^2 + x[$x4]^2 == 40), + :(x[$x1] * x[$x2] * x[$x3] * x[$x4] >= 25.0), + :(x[$x1]^2.0 + x[$x2]^2.0 + x[$x3]^2.0 + x[$x4]^2.0 == 40.0), ], ), true, @@ -117,7 +117,9 @@ function test_HS071() target = read(joinpath(@__DIR__, "nlp.mof.json"), String) target = replace(target, r"\s" => "") target = replace(target, "MathOptFormatModel" => "MathOptFormat Model") - @test read(TEST_MOF_FILE, String) == target + # Normalize .0 floats and integer representations in JSON + normalize(x) = replace(x, ".0" => "") + @test normalize(read(TEST_MOF_FILE, String)) == normalize(target) _validate(TEST_MOF_FILE) return end @@ -308,7 +310,7 @@ function test_nonlinear_readingwriting() block = MOI.get(model2, MOI.NLPBlock()) MOI.initialize(block.evaluator, [:ExprGraph]) @test MOI.constraint_expr(block.evaluator, 1) == - :(2 * x[$x] + sin(x[$x])^2 - x[$y] == 1.0) + :(2.0 * x[$x] + sin(x[$x])^2.0 - x[$y] == 1.0) _validate(TEST_MOF_FILE) return end diff --git a/test/FileFormats/NL/read.jl b/test/FileFormats/NL/read.jl index 9c2b0a97a6..fcc0574b44 100644 --- a/test/FileFormats/NL/read.jl +++ b/test/FileFormats/NL/read.jl @@ -64,7 +64,8 @@ function test_parse_expr() # (* x1 (* 2 (* x4 x2))) seekstart(io) x = MOI.VariableIndex.(1:4) - @test NL._parse_expr(io, model) == :(*($(x[1]), *(2, *($(x[4]), $(x[2]))))) + @test NL._parse_expr(io, model) == + :(*($(x[1]), *(2.0, *($(x[4]), $(x[2]))))) @test eof(io) return end @@ -76,7 +77,7 @@ function test_parse_expr_nary() seekstart(io) x = MOI.VariableIndex.(1:4) @test NL._parse_expr(io, model) == - :(+($(x[1])^2, $(x[3])^2, $(x[4])^2, $(x[2])^2)) + :(+($(x[1])^2.0, $(x[3])^2.0, $(x[4])^2.0, $(x[2])^2.0)) @test eof(io) return end diff --git a/test/Nonlinear/Nonlinear.jl b/test/Nonlinear/Nonlinear.jl index 4904585963..5c85b4b0b6 100644 --- a/test/Nonlinear/Nonlinear.jl +++ b/test/Nonlinear/Nonlinear.jl @@ -62,7 +62,7 @@ function test_parse_sin_squared() Nonlinear.set_objective(model, :(sin($x)^2)) evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) - @test MOI.objective_expr(evaluator) == :(sin(x[$x])^2) + @test MOI.objective_expr(evaluator) == :(sin(x[$x])^2.0) return end @@ -72,7 +72,7 @@ function test_parse_ifelse() Nonlinear.set_objective(model, :(ifelse($x, 1, 2))) evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) - @test MOI.objective_expr(evaluator) == :(ifelse(x[$x], 1, 2)) + @test MOI.objective_expr(evaluator) == :(ifelse(x[$x], 1.0, 2.0)) return end @@ -83,7 +83,7 @@ function test_parse_ifelse_inequality_less() evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) @test MOI.objective_expr(evaluator) == - :(ifelse(x[$x] < 1, x[$x] - 1, x[$x] + 1)) + :(ifelse(x[$x] < 1.0, x[$x] - 1.0, x[$x] + 1.0)) return end @@ -94,7 +94,7 @@ function test_parse_ifelse_inequality_greater() evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) @test MOI.objective_expr(evaluator) == - :(ifelse(x[$x] > 1, x[$x] - 1, x[$x] + 1)) + :(ifelse(x[$x] > 1.0, x[$x] - 1.0, x[$x] + 1.0)) return end @@ -105,7 +105,7 @@ function test_parse_ifelse_comparison() evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) @test MOI.objective_expr(evaluator) == - :(ifelse(0 <= x[$x] <= 1, x[$x] - 1, x[$x] + 1)) + :(ifelse(0.0 <= x[$x] <= 1.0, x[$x] - 1.0, x[$x] + 1.0)) return end @@ -251,7 +251,7 @@ function test_set_objective() @test model.objective == Nonlinear.parse_expression(model, input) evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) - @test MOI.objective_expr(evaluator) == :(x[$x]^2 + 1) + @test MOI.objective_expr(evaluator) == :(x[$x]^2.0 + 1.0) return end @@ -263,7 +263,7 @@ function test_set_objective_subexpression() Nonlinear.set_objective(model, :($expr^2)) evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) - @test MOI.objective_expr(evaluator) == :((x[$x]^2 + 1)^2) + @test MOI.objective_expr(evaluator) == :((x[$x]^2.0 + 1.0)^2.0) return end @@ -276,7 +276,7 @@ function test_set_objective_nested_subexpression() Nonlinear.set_objective(model, :($expr_2^2)) evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) - @test MOI.objective_expr(evaluator) == :(((x[$x]^2 + 1)^2)^2) + @test MOI.objective_expr(evaluator) == :(((x[$x]^2.0 + 1.0)^2.0)^2.0) return end @@ -287,7 +287,7 @@ function test_set_objective_parameter() Nonlinear.set_objective(model, :($x^2 + $p)) evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) - @test MOI.objective_expr(evaluator) == :(x[$x]^2 + 1.2) + @test MOI.objective_expr(evaluator) == :(x[$x]^2.0 + 1.2) return end @@ -300,7 +300,7 @@ function test_add_constraint_less_than() @test model[c].set == set evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) - @test MOI.constraint_expr(evaluator, 1) == :(x[$x]^2 + 1 <= 1.0) + @test MOI.constraint_expr(evaluator, 1) == :(x[$x]^2.0 + 1.0 <= 1.0) return end @@ -311,7 +311,7 @@ function test_add_constraint_delete() _ = Nonlinear.add_constraint(model, :(sqrt($x)), MOI.LessThan(1.0)) evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) - @test MOI.constraint_expr(evaluator, 1) == :(x[$x]^2 + 1 <= 1.0) + @test MOI.constraint_expr(evaluator, 1) == :(x[$x]^2.0 + 1.0 <= 1.0) @test MOI.constraint_expr(evaluator, 2) == :(sqrt(x[$x]) <= 1.0) Nonlinear.delete(model, c1) evaluator = Nonlinear.Evaluator(model) @@ -330,7 +330,7 @@ function test_add_constraint_greater_than() @test model[c].set == set evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) - @test MOI.constraint_expr(evaluator, 1) == :(x[$x]^2 + 1 >= 1.0) + @test MOI.constraint_expr(evaluator, 1) == :(x[$x]^2.0 + 1.0 >= 1.0) return end @@ -342,7 +342,7 @@ function test_add_constraint_equal_to() @test model[c].set == set evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) - @test MOI.constraint_expr(evaluator, 1) == :(x[$x]^2 + 1 == 1.0) + @test MOI.constraint_expr(evaluator, 1) == :(x[$x]^2.0 + 1.0 == 1.0) return end @@ -354,7 +354,7 @@ function test_add_constraint_interval() @test model[c].set == set evaluator = Nonlinear.Evaluator(model) MOI.initialize(evaluator, [:ExprGraph]) - @test MOI.constraint_expr(evaluator, 1) == :(-1.0 <= x[$x]^2 + 1 <= 1.0) + @test MOI.constraint_expr(evaluator, 1) == :(-1.0 <= x[$x]^2.0 + 1.0 <= 1.0) return end From 90bcaaed231e48c7ec21ece2461bdfde9dd77b38 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 10 Jul 2025 13:21:25 +1200 Subject: [PATCH 4/5] [Nonlinear.ReverseAD] fix performance bug in Hessian computation (#2783) --- src/Nonlinear/ReverseAD/forward_over_reverse.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Nonlinear/ReverseAD/forward_over_reverse.jl b/src/Nonlinear/ReverseAD/forward_over_reverse.jl index 03ea1d89fc..c531fb9878 100644 --- a/src/Nonlinear/ReverseAD/forward_over_reverse.jl +++ b/src/Nonlinear/ReverseAD/forward_over_reverse.jl @@ -92,16 +92,19 @@ function _eval_hessian_chunk( for s in 1:chunk # If `chunk < chunk_size`, leaves junk in the unused components d.input_ϵ[(idx-1)*chunk_size+s] = ex.seed_matrix[r, offset+s-1] + # Ensure the output is clear in preparation for the chunk + d.output_ϵ[(idx-1)*chunk_size+s] = 0.0 end end _hessian_slice_inner(d, ex, chunk_size) - fill!(d.input_ϵ, 0.0) # collect directional derivatives for r in eachindex(ex.rinfo.local_indices) @inbounds idx = ex.rinfo.local_indices[r] # load output_ϵ into ex.seed_matrix[r,k,k+1,...,k+remaining-1] for s in 1:chunk ex.seed_matrix[r, offset+s-1] = d.output_ϵ[(idx-1)*chunk_size+s] + # Reset the input in preparation for the next chunk + d.input_ϵ[(idx-1)*chunk_size+s] = 0.0 end end return @@ -122,7 +125,6 @@ end end function _hessian_slice_inner(d, ex, ::Type{T}) where {T} - fill!(d.output_ϵ, 0.0) output_ϵ = _reinterpret_unsafe(T, d.output_ϵ) subexpr_forward_values_ϵ = _reinterpret_unsafe(T, d.subexpression_forward_values_ϵ) From d66c13dab9cbd76c3451faffafd7d828a4e04ee1 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 10 Jul 2025 15:07:52 +1200 Subject: [PATCH 5/5] Prep for v1.42.0 (#2784) --- Project.toml | 2 +- docs/src/changelog.md | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index b8a10506a2..7ac2d9ad9a 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "MathOptInterface" uuid = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" -version = "1.41.0" +version = "1.42.0" [deps] BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" diff --git a/docs/src/changelog.md b/docs/src/changelog.md index b6c76c7c70..f3fa1a828d 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -7,6 +7,21 @@ CurrentModule = MathOptInterface The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## v1.42.0 (July 10, 2025) + +### Added + + - Added an option to disable warnings in [`Utilities.PenaltyRelaxation`](@ref) + (#2774) + +### Fixed + + - Fixed a bug writing objective constant in `MAX_SENSE` with `FileFormats.MPS` + (#2778) + - Fixed a change in how `==(::Expr, ::Expr)` works on for Julia nightly (#2780) + - Fixed a performance bug in the Hessian computation of `Nonlinear.ReverseAD` + (#2783) + ## v1.41.0 (June 9, 2025) ### Added pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy