From ecdf73510d225a28881215af5379bc37e8166820 Mon Sep 17 00:00:00 2001 From: Morten Piibeleht Date: Thu, 8 May 2025 16:52:48 +1200 Subject: [PATCH 1/9] [docs] filter varying width Time column in doctest (#2748) --- docs/src/submodules/Bridges/implementation.md | 4 ++-- src/Bridges/Bridges.jl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/submodules/Bridges/implementation.md b/docs/src/submodules/Bridges/implementation.md index f0ef9d7141..ae821f0087 100644 --- a/docs/src/submodules/Bridges/implementation.md +++ b/docs/src/submodules/Bridges/implementation.md @@ -66,7 +66,7 @@ arguments: the type of the bridge, the input model as a string, and the output model as a string. Here is an example: -```jldoctest; filter=r"[0-9.]+s" +```jldoctest; filter=[r"[0-9.]+s", r"\s+Time"] julia> MOI.Bridges.runtests( MOI.Bridges.Constraint.GreaterToLessBridge, """ @@ -100,7 +100,7 @@ There are a number of other useful keyword arguments. Here is an example: -```jldoctest; filter=r"[0-9.]+s" +```jldoctest; filter=[r"[0-9.]+s", r"\s+Time"] julia> MOI.Bridges.runtests( MOI.Bridges.Constraint.GreaterToLessBridge, """ diff --git a/src/Bridges/Bridges.jl b/src/Bridges/Bridges.jl index b3b963a9f6..797428d02f 100644 --- a/src/Bridges/Bridges.jl +++ b/src/Bridges/Bridges.jl @@ -273,7 +273,7 @@ and [`MOI.ConstraintPrimalStart`](@ref) to throw [`MOI.GetAttributeNotAllowed`]( ## Example -```jldoctest; filter=r"[0-9.]+s" +```jldoctest; filter=[r"[0-9.]+s", r"\\s+Time"] julia> MOI.Bridges.runtests( MOI.Bridges.Constraint.ZeroOneBridge, model -> MOI.add_constrained_variable(model, MOI.ZeroOne()), @@ -423,7 +423,7 @@ Run a series of tests that check the correctness of `Bridge`. ## Example -```jldoctest; filter=r"[0-9.]+s" +```jldoctest; filter=[r"[0-9.]+s", r"\\s+Time"] julia> MOI.Bridges.runtests( MOI.Bridges.Constraint.ZeroOneBridge, \"\"\" From 8ba5a938933c637858ccee1456986fbb6b928b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 10 May 2025 00:12:47 +0200 Subject: [PATCH 2/9] Implement dual for Hermitian PSD cone (#2749) --- src/sets.jl | 6 ++++++ test/General/sets.jl | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/src/sets.jl b/src/sets.jl index dc95db7b14..b564a338b9 100644 --- a/src/sets.jl +++ b/src/sets.jl @@ -1352,6 +1352,12 @@ function dimension(set::HermitianPositiveSemidefiniteConeTriangle) return real_nnz + imag_nnz end +dual_set(set::HermitianPositiveSemidefiniteConeTriangle) = set + +function dual_set_type(::Type{HermitianPositiveSemidefiniteConeTriangle}) + return HermitianPositiveSemidefiniteConeTriangle +end + """ side_dimension( set::Union{ diff --git a/test/General/sets.jl b/test/General/sets.jl index f4c86417b3..fdc32648a4 100644 --- a/test/General/sets.jl +++ b/test/General/sets.jl @@ -275,6 +275,14 @@ function test_sets_dual_psdtriangle() MOI.ScaledPositiveSemidefiniteConeTriangle(2), MOI.ScaledPositiveSemidefiniteConeTriangle(3), ) + _test_sets_dual_psdtriangle( + MOI.HermitianPositiveSemidefiniteConeTriangle(2), + MOI.HermitianPositiveSemidefiniteConeTriangle(3), + ) + _test_sets_dual_psdtriangle( + MOI.Scaled(MOI.HermitianPositiveSemidefiniteConeTriangle(2)), + MOI.Scaled(MOI.HermitianPositiveSemidefiniteConeTriangle(3)), + ) return end From 8113edf65dbae536dfe817166517ed76ac9d446c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 10 May 2025 09:56:03 +0200 Subject: [PATCH 3/9] Use Base.only when appropriate (#2751) --- src/Bridges/Constraint/bridges/VectorizeBridge.jl | 14 +++++--------- src/Bridges/Variable/bridges/VectorizeBridge.jl | 7 ++----- src/FileFormats/MPS/MPS.jl | 3 +-- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/Bridges/Constraint/bridges/VectorizeBridge.jl b/src/Bridges/Constraint/bridges/VectorizeBridge.jl index 7d9f7a3601..e39b1ae8e9 100644 --- a/src/Bridges/Constraint/bridges/VectorizeBridge.jl +++ b/src/Bridges/Constraint/bridges/VectorizeBridge.jl @@ -123,8 +123,7 @@ function MOI.get( if x === nothing return nothing end - @assert length(x) == 1 - return x[1] + bridge.set_constant + return only(x) + bridge.set_constant end function MOI.get( @@ -133,16 +132,15 @@ function MOI.get( bridge::VectorizeBridge, ) x = MOI.get(model, attr, bridge.vector_constraint) - @assert length(x) == 1 if MOI.Utilities.is_ray(MOI.get(model, MOI.PrimalStatus(attr.result_index))) # If it is an infeasibility certificate, it is a ray and satisfies the # homogenized problem, see https://github.com/jump-dev/MathOptInterface.jl/issues/433 - return x[1] + return only(x) else # Otherwise, we need to add the set constant since the ConstraintPrimal # is defined as the value of the function and the set_constant was # removed from the original function - return x[1] + bridge.set_constant + return only(x) + bridge.set_constant end end @@ -180,8 +178,7 @@ function MOI.get( if x === nothing return nothing end - @assert length(x) == 1 - return x[1] + return only(x) end function MOI.set( @@ -235,8 +232,7 @@ function MOI.get( MOI.get(model, attr, bridge.vector_constraint), true, ) - @assert length(f) == 1 - return convert(G, f[1]) + return convert(G, only(f)) end function MOI.get( diff --git a/src/Bridges/Variable/bridges/VectorizeBridge.jl b/src/Bridges/Variable/bridges/VectorizeBridge.jl index efe8a5ab59..3d658733b9 100644 --- a/src/Bridges/Variable/bridges/VectorizeBridge.jl +++ b/src/Bridges/Variable/bridges/VectorizeBridge.jl @@ -141,8 +141,7 @@ function MOI.get( bridge::VectorizeBridge, ) x = MOI.get(model, attr, bridge.vector_constraint) - @assert length(x) == 1 - y = x[1] + y = only(x) status = MOI.get(model, MOI.PrimalStatus(attr.result_index)) if !MOI.Utilities.is_ray(status) # If it is an infeasibility certificate, it is a ray and satisfies the @@ -160,9 +159,7 @@ function MOI.get( attr::MOI.ConstraintDual, bridge::VectorizeBridge, ) - x = MOI.get(model, attr, bridge.vector_constraint) - @assert length(x) == 1 - return x[1] + return only(MOI.get(model, attr, bridge.vector_constraint)) end function MOI.get( diff --git a/src/FileFormats/MPS/MPS.jl b/src/FileFormats/MPS/MPS.jl index c9ea639fae..bcce30a0e7 100644 --- a/src/FileFormats/MPS/MPS.jl +++ b/src/FileFormats/MPS/MPS.jl @@ -1199,8 +1199,7 @@ function Base.read!(io::IO, model::Model) if header == HEADER_NAME parse_name_line(data, line) elseif header == HEADER_OBJSENSE - @assert length(items) == 1 - sense = uppercase(items[1]) + sense = uppercase(only(items)) @assert sense == "MAX" || sense == "MIN" data.is_minimization = sense == "MIN" elseif header == HEADER_ROWS From 36912e1c327d1f74fcda159a55e52f0b68840288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 10 May 2025 11:00:01 +0200 Subject: [PATCH 4/9] [docs] remove experimental warning from Nonlinear module docstring (#2752) * [docs] remove experimental warning from Nonlinear module docstring * Remove Nonlinear from doc --- docs/src/submodules/Nonlinear/reference.md | 1 - src/Nonlinear/Nonlinear.jl | 8 -------- 2 files changed, 9 deletions(-) diff --git a/docs/src/submodules/Nonlinear/reference.md b/docs/src/submodules/Nonlinear/reference.md index b4ed621d44..842db49713 100644 --- a/docs/src/submodules/Nonlinear/reference.md +++ b/docs/src/submodules/Nonlinear/reference.md @@ -12,7 +12,6 @@ More information can be found in the [Nonlinear](@ref nonlinear_developers) section of the manual. ```@docs -Nonlinear Nonlinear.Model ``` diff --git a/src/Nonlinear/Nonlinear.jl b/src/Nonlinear/Nonlinear.jl index 20e6a0807d..9a5e4bd8d9 100644 --- a/src/Nonlinear/Nonlinear.jl +++ b/src/Nonlinear/Nonlinear.jl @@ -4,14 +4,6 @@ # Use of this source code is governed by an MIT-style license that can be found # in the LICENSE.md file or at https://opensource.org/licenses/MIT. -""" - Nonlinear - -!!! warning - The Nonlinear submodule is experimental. Until this message is removed, - breaking changes may be introduced in any minor or patch release of - MathOptInterface. -""" module Nonlinear import ForwardDiff From 4e2630554afcde0b0b7c3e680e7fd3666e9e3825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sat, 10 May 2025 23:47:47 +0200 Subject: [PATCH 5/9] Use throw_if_scalar_and_constant_not_zero when appropriate (#2753) --- src/Bridges/Constraint/bridges/VectorizeBridge.jl | 5 +---- src/Utilities/mockoptimizer.jl | 8 ++------ 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/Bridges/Constraint/bridges/VectorizeBridge.jl b/src/Bridges/Constraint/bridges/VectorizeBridge.jl index e39b1ae8e9..fb61a94a72 100644 --- a/src/Bridges/Constraint/bridges/VectorizeBridge.jl +++ b/src/Bridges/Constraint/bridges/VectorizeBridge.jl @@ -45,10 +45,7 @@ function bridge_constraint( scalar_f::G, set::MOI.Utilities.ScalarLinearSet{T}, ) where {T,F,S,G} - scalar_const = MOI.constant(scalar_f, T) - if !iszero(scalar_const) - throw(MOI.ScalarFunctionConstantNotZero{T,G,typeof(set)}(scalar_const)) - end + MOI.throw_if_scalar_and_constant_not_zero(scalar_f, typeof(set)) set_const = MOI.constant(set) vector_f = MOI.Utilities.operate( vcat, diff --git a/src/Utilities/mockoptimizer.jl b/src/Utilities/mockoptimizer.jl index b0aa93c1d0..2fc46c8653 100644 --- a/src/Utilities/mockoptimizer.jl +++ b/src/Utilities/mockoptimizer.jl @@ -181,12 +181,8 @@ function MOI.add_constraint( ) where {T} if !mock.add_con_allowed throw(MOI.AddConstraintNotAllowed{typeof(func),typeof(set)}()) - elseif mock.scalar_function_constant_non_zero && !iszero(func.constant) - throw( - MOI.ScalarFunctionConstantNotZero{T,typeof(func),typeof(set)}( - func.constant, - ), - ) + elseif mock.scalar_function_constant_non_zero + MOI.throw_if_scalar_and_constant_not_zero(func, typeof(set)) end ci = MOI.add_constraint(mock.inner_model, xor_indices(func), set) return xor_index(ci) From 2ecfa90cb7a56cfba09759d70c2d57d1c173b361 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Sun, 11 May 2025 01:31:48 +0200 Subject: [PATCH 6/9] [Utilities] fix get_fallback of DualObjectiveValue with HyperRectangle (#2755) --- src/Utilities/results.jl | 40 ++++++++++++++++++++-------- test/Utilities/results.jl | 56 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 11 deletions(-) create mode 100644 test/Utilities/results.jl diff --git a/src/Utilities/results.jl b/src/Utilities/results.jl index 981135ab02..151debdce4 100644 --- a/src/Utilities/results.jl +++ b/src/Utilities/results.jl @@ -108,6 +108,29 @@ function _dual_objective_value( return set_dot(constant, dual, set) end +function _dual_objective_value( + model::MOI.ModelLike, + ci::MOI.ConstraintIndex{<:MOI.AbstractVectorFunction,<:MOI.HyperRectangle}, + ::Type{T}, + result_index::Integer, +) where {T} + func_constant = + MOI.constant(MOI.get(model, MOI.ConstraintFunction(), ci), T) + set = MOI.get(model, MOI.ConstraintSet(), ci) + dual = MOI.get(model, MOI.ConstraintDual(result_index), ci) + constant = map(eachindex(func_constant)) do i + return func_constant[i] - if dual[i] < zero(dual[i]) + # The dual is negative so it is in the dual of the MOI.LessThan cone + # hence the upper bound of the Interval set is tight + set.upper[i] + else + # the lower bound is tight + set.lower[i] + end + end + return set_dot(constant, dual, set) +end + function _dual_objective_value( model::MOI.ModelLike, ::Type{F}, @@ -116,23 +139,18 @@ function _dual_objective_value( result_index::Integer, ) where {T,F<:MOI.AbstractFunction,S<:MOI.AbstractSet} value = zero(T) + if F == variable_function_type(S) && !_has_constant(S) + return value # Shortcut + end for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}()) value += _dual_objective_value(model, ci, T, result_index) end return value end -function _dual_objective_value( - ::MOI.ModelLike, - ::Type{MOI.VectorOfVariables}, - ::Type{<:MOI.AbstractVectorSet}, - ::Type{T}, - ::Integer, -) where {T} - # No constant in the function nor set so no contribution to the dual - # objective value. - return zero(T) -end +_has_constant(::Type{<:MOI.AbstractScalarSet}) = true +_has_constant(::Type{<:MOI.AbstractVectorSet}) = false +_has_constant(::Type{<:MOI.HyperRectangle}) = true """ get_fallback( diff --git a/test/Utilities/results.jl b/test/Utilities/results.jl new file mode 100644 index 0000000000..5286604f7d --- /dev/null +++ b/test/Utilities/results.jl @@ -0,0 +1,56 @@ +# Copyright (c) 2017: Miles Lubin and contributors +# Copyright (c) 2017: Google Inc. +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + +module TestResults + +using Test + +import MathOptInterface as MOI + +function runtests() + for name in names(@__MODULE__; all = true) + if startswith("$(name)", "test_") + @testset "$(name)" begin + getfield(@__MODULE__, name)() + end + end + end + return +end + +test_hyperrectangle_Int() = _test_hyperrectangle(Int) + +test_hyperrectangle_Float64() = _test_hyperrectangle(Float64) + +function _test_hyperrectangle(T) + model = MOI.Utilities.MockOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{T}()), + T, + ) + x = MOI.add_variables(model, 2) + c1 = MOI.add_constraint( + model, + MOI.VectorOfVariables(x), + MOI.HyperRectangle(T[3, -7], T[5, -2]), + ) + c2 = MOI.add_constraint( + model, + MOI.Utilities.vectorize(x .+ T[11, 13]), + MOI.HyperRectangle(T[-T(6), -T(4)], [T(3), T(2)]), + ) + MOI.set(model, MOI.ConstraintDual(), c1, T[4, -3]) + MOI.set(model, MOI.ConstraintDual(), c2, T[-2, 5]) + @test -53 == @inferred MOI.Utilities.get_fallback( + model, + MOI.DualObjectiveValue(), + T, + ) + return +end + +end # module TestResults + +TestResults.runtests() From 1d5ee59fbfa642ebc070b9340b5207ff28a385b6 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 14 May 2025 11:55:38 +1200 Subject: [PATCH 7/9] Improve text of showerror for NotAllowedError (#2757) --- src/error.jl | 37 ++++++++--- test/General/attributes.jl | 3 +- test/General/errors.jl | 124 ++++++++++++++++++------------------- 3 files changed, 90 insertions(+), 74 deletions(-) diff --git a/src/error.jl b/src/error.jl index e4d376979c..d8f8876876 100644 --- a/src/error.jl +++ b/src/error.jl @@ -51,19 +51,40 @@ form). function operation_name end function Base.showerror(io::IO, err::NotAllowedError) - print(io, typeof(err), ": ", operation_name(err), " cannot be performed") + println(io, typeof(err), ":\n") + println(io, "## Cause\n") + print(io, operation_name(err), " cannot be performed") m = message(err) - if Base.isempty(m) - print(io, ".") + if isempty(m) + println(io) else - print(io, ": ", m) + println(io, " because:\n\n", m) end - return print( + println(io) + print( io, - " You may want to use a `CachingOptimizer` in `AUTOMATIC` mode", - " or you may need to call `reset_optimizer` before doing this", - " operation if the `CachingOptimizer` is in `MANUAL` mode.", + """ + ## Fixing this error + + An `MOI.NotAllowedError` error occurs when you have tried to do something that + is not implemented by the solver. + + The most common way to fix this error is to wrap the optimizer in a + `MOI.Utilities.CachingOptimizer`. + + For example, if you are using `JuMP.Model` or `JuMP.set_optimizer`, do: + ```julia + model = JuMP.Model(optimizer; with_cache_type = Float64) + model = JuMP.GenericModel{T}(optimizer; with_cache_type = T) + JuMP.set_optimizer(model, optimizer; with_cache_type = Float64) + ``` + Similarly, if you are using `MOI.instantiate`, do: + ```julia + model = MOI.instantiate(optimizer; with_cache_type = Float64) + ``` + """, ) + return end """ diff --git a/test/General/attributes.jl b/test/General/attributes.jl index 86220f7f04..bd0a53be7b 100644 --- a/test/General/attributes.jl +++ b/test/General/attributes.jl @@ -356,7 +356,8 @@ function test_submit_not_allowed() @test MOI.SubmitNotAllowed(submit) == MOI.SubmitNotAllowed(submit, "") err = MOI.SubmitNotAllowed(submit, "msg") contents = sprint(showerror, err) - @test occursin("Submitting $submit cannot be performed: msg", contents) + @test occursin("Submitting $submit cannot be performed", contents) + @test occursin("msg", contents) return end diff --git a/test/General/errors.jl b/test/General/errors.jl index 8ba7306cab..91c109530e 100644 --- a/test/General/errors.jl +++ b/test/General/errors.jl @@ -27,12 +27,10 @@ function test_errors_fallback_AddVariableNotAllowed() try MOI.add_variable(model) catch err - @test sprint(showerror, err) == - "MathOptInterface.AddVariableNotAllowed:" * - " Adding variables cannot be performed. You may want to use a" * - " `CachingOptimizer` in `AUTOMATIC` mode or you may need to call" * - " `reset_optimizer` before doing this operation if the" * - " `CachingOptimizer` is in `MANUAL` mode." + contents = sprint(showerror, err) + @test occursin("$(MOI.AddVariableNotAllowed)", contents) + @test occursin("Adding variables cannot be performed", contents) + @test occursin("## Fixing this error", contents) end @test_throws MOI.AddVariableNotAllowed MOI.add_variables(model, 2) return @@ -104,13 +102,14 @@ function test_errors_add_constraint() try MOI.add_constraint(model, vi, MOI.EqualTo(0.0)) catch err - @test sprint(showerror, err) == - "$(MOI.AddConstraintNotAllowed{MOI.VariableIndex,MOI.EqualTo{Float64}}):" * - " Adding `$MOI.VariableIndex`-in-`$MOI.EqualTo{Float64}`" * - " constraints cannot be performed. You may want to use a" * - " `CachingOptimizer` in `AUTOMATIC` mode or you may need to call" * - " `reset_optimizer` before doing this operation if the" * - " `CachingOptimizer` is in `MANUAL` mode." + contents = sprint(showerror, err) + F, S = MOI.VariableIndex, MOI.EqualTo{Float64} + @test occursin("$(MOI.AddConstraintNotAllowed{F,S})", contents) + @test occursin( + "Adding `$F`-in-`$S` constraints cannot be performed", + contents, + ) + @test occursin("## Fixing this error", contents) end @test_throws( MOI.AddConstraintNotAllowed, @@ -139,23 +138,19 @@ function test_errors_DeleteNotAllowed() try MOI.delete(model, vi) catch err - @test sprint(showerror, err) == - "$(MOI.DeleteNotAllowed{typeof(vi)}): Deleting the index $vi " * - "cannot be performed. You may want to use a `CachingOptimizer` " * - "in `AUTOMATIC` mode or you may need to call `reset_optimizer` " * - "before doing this operation if the `CachingOptimizer` is in " * - "`MANUAL` mode." + contents = sprint(showerror, err) + @test occursin("$(MOI.DeleteNotAllowed{typeof(vi)})", contents) + @test occursin("Deleting the index $vi cannot be performed", contents) + @test occursin("## Fixing this error", contents) end @test_throws MOI.DeleteNotAllowed{typeof(ci)} MOI.delete(model, ci) try MOI.delete(model, ci) catch err - @test sprint(showerror, err) == - "$(MOI.DeleteNotAllowed{typeof(ci)}): Deleting the index $ci " * - "cannot be performed. You may want to use a `CachingOptimizer` " * - "in `AUTOMATIC` mode or you may need to call `reset_optimizer` " * - "before doing this operation if the `CachingOptimizer` is in " * - "`MANUAL` mode." + contents = sprint(showerror, err) + @test occursin("$(MOI.DeleteNotAllowed{typeof(ci)})", contents) + @test occursin("Deleting the index $ci cannot be performed", contents) + @test occursin("## Fixing this error", contents) end return end @@ -244,14 +239,14 @@ function test_errors_ModifyNotAllowed_constraint() change = MOI.ScalarConstantChange(1.0) err = MOI.ModifyConstraintNotAllowed(ci, change) @test_throws err MOI.modify(model, ci, change) - @test sprint(showerror, err) == - "$(MOI.ModifyConstraintNotAllowed{MOI.VariableIndex,MOI.EqualTo{Float64},MOI.ScalarConstantChange{Float64}}):" * - " Modifying the constraints $(MOI.ConstraintIndex{MOI.VariableIndex,MOI.EqualTo{Float64}}(1))" * - " with MathOptInterface.ScalarConstantChange{Float64}(1.0) cannot" * - " be performed. You may want to use a `CachingOptimizer` in" * - " `AUTOMATIC` mode or you may need to call `reset_optimizer`" * - " before doing this operation if the `CachingOptimizer` is in" * - " `MANUAL` mode." + contents = sprint(showerror, err) + @test occursin("$(typeof(err)):", contents) + @test occursin( + "Modifying the constraints $ci with $change cannot be performed", + contents, + ) + @test occursin("## Fixing this error", contents) + return end function test_errors_ModifyNotAllowed_objective() @@ -260,13 +255,14 @@ function test_errors_ModifyNotAllowed_objective() attr = MOI.ObjectiveFunction{MOI.VariableIndex}() err = MOI.ModifyObjectiveNotAllowed(change) @test_throws err MOI.modify(model, attr, change) - @test sprint(showerror, err) == - "$(MOI.ModifyObjectiveNotAllowed{MOI.ScalarConstantChange{Float64}}):" * - " Modifying the objective function with $(MOI.ScalarConstantChange{Float64}(1.0))" * - " cannot be performed. You may want to use a `CachingOptimizer`" * - " in `AUTOMATIC` mode or you may need to call `reset_optimizer`" * - " before doing this operation if the `CachingOptimizer` is in" * - " `MANUAL` mode." + contents = sprint(showerror, err) + @test occursin("$(typeof(err)):", contents) + @test occursin( + "Modifying the objective function with $change cannot be performed", + contents, + ) + @test occursin("## Fixing this error", contents) + return end function test_errors_show_SetAttributeNotAllowed() @@ -276,26 +272,22 @@ function test_errors_show_SetAttributeNotAllowed() @test sprint(showerror, MOI.UnsupportedAttribute(MOI.Name(), "Message")) == "$MOI.UnsupportedAttribute{$MOI.Name}:" * " Attribute $MOI.Name() is not supported by the model: Message" - @test sprint(showerror, MOI.SetAttributeNotAllowed(MOI.Name())) == - "$MOI.SetAttributeNotAllowed{$MOI.Name}:" * - " Setting attribute $MOI.Name() cannot be performed. You may want to use" * - " a `CachingOptimizer` in `AUTOMATIC` mode or you may need to call" * - " `reset_optimizer` before doing this operation if the" * - " `CachingOptimizer` is in `MANUAL` mode." - @test sprint( - showerror, - MOI.SetAttributeNotAllowed(MOI.Name(), "Message"), - ) == - "$MOI.SetAttributeNotAllowed{$MOI.Name}:" * - " Setting attribute $MOI.Name() cannot be performed: Message You may want" * - " to use a `CachingOptimizer` in `AUTOMATIC` mode or you may need to call" * - " `reset_optimizer` before doing this operation if the `CachingOptimizer`" * - " is in `MANUAL` mode." == - "$MOI.SetAttributeNotAllowed{$MOI.Name}:" * - " Setting attribute $MOI.Name() cannot be performed: Message You may want" * - " to use a `CachingOptimizer` in `AUTOMATIC` mode or you may need to call" * - " `reset_optimizer` before doing this operation if the `CachingOptimizer`" * - " is in `MANUAL` mode." + contents = sprint(showerror, MOI.SetAttributeNotAllowed(MOI.Name())) + @test occursin("$MOI.SetAttributeNotAllowed{$MOI.Name}:", contents) + @test occursin( + "Setting attribute $(MOI.Name()) cannot be performed", + contents, + ) + @test occursin("## Fixing this error", contents) + err = MOI.SetAttributeNotAllowed(MOI.Name(), "Message") + contents = sprint(showerror, err) + @test occursin("$(typeof(err))", contents) + @test occursin("Message", contents) + @test occursin( + "Setting attribute $(MOI.Name()) cannot be performed", + contents, + ) + @test occursin("## Fixing this error", contents) return end @@ -352,11 +344,13 @@ function test_get_fallback_error() MOI.get(model, MOI.SolveTimeSec()), ) err = MOI.GetAttributeNotAllowed(MOI.SolveTimeSec(), "") - @test sprint(showerror, err) == - "$(typeof(err)): Getting attribute $(MOI.SolveTimeSec()) cannot be " * - "performed. You may want to use a `CachingOptimizer` in " * - "`AUTOMATIC` mode or you may need to call `reset_optimizer` before " * - "doing this operation if the `CachingOptimizer` is in `MANUAL` mode." + contents = sprint(showerror, err) + @test occursin("$(typeof(err)):", contents) + @test occursin( + "Getting attribute $(MOI.SolveTimeSec()) cannot be performed", + contents, + ) + @test occursin("## Fixing this error", contents) return end From bb9d8fc46269aea5262cc65409ad1b1d9f5777ec Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Mon, 19 May 2025 14:34:01 +1200 Subject: [PATCH 8/9] [FileFormats.SDPA] fix reading files with {} punctuation (#2759) --- src/FileFormats/SDPA/SDPA.jl | 32 +++++++++---------- test/FileFormats/SDPA/SDPA.jl | 25 ++++++--------- test/FileFormats/SDPA/models/issue_1541.dat-s | 2 +- 3 files changed, 25 insertions(+), 34 deletions(-) diff --git a/src/FileFormats/SDPA/SDPA.jl b/src/FileFormats/SDPA/SDPA.jl index 8c73e04fa8..2bf3c67456 100644 --- a/src/FileFormats/SDPA/SDPA.jl +++ b/src/FileFormats/SDPA/SDPA.jl @@ -302,18 +302,15 @@ function _dim_to_set(s::AbstractString) end end -function _parse_dimensions(dims::AbstractString) - isvalid(char) = isdigit(char) || char == '-' - is_delimiter(char) = isspace(char) || char == ',' - start = findfirst(isvalid, dims) - if start === nothing - return Union{MOI.PositiveSemidefiniteConeTriangle,MOI.Nonnegatives}[] - end - stop = findlast(isvalid, dims)::Int - s = split(dims[start:stop], is_delimiter) - return Union{MOI.PositiveSemidefiniteConeTriangle,MOI.Nonnegatives}[ - _dim_to_set(dim) for dim in filter!(!isempty, s) - ] +function _split(line) + # In some variations of SDPA, there is the comment: + # + # The special characters `,`, `(`, `)`, `{`, and `}` can be used as + # punctuation and are ignored. + # + # As one example, see https://github.com/vsdp/SDPLIB + line = replace(line, r"[,{}\(\)]"=>' ') + return split(line) end """ @@ -365,19 +362,20 @@ function Base.read!(io::IO, model::Model{T}) where {T<:Real} num_variables_read = true # According to http://plato.asu.edu/ftp/sdpa_format.txt, # additional text after the number of variables should be ignored. - scalar_vars = MOI.add_variables(model, parse(Int, split(line)[1])) + scalar_vars = + MOI.add_variables(model, parse(Int, first(_split(line)))) elseif num_blocks === nothing if isempty(line) continue end # According to http://plato.asu.edu/ftp/sdpa_format.txt, # additional text after the number of blocks should be ignored. - num_blocks = parse(Int, split(line)[1]) + num_blocks = parse(Int, first(_split(line))) elseif !block_sets_read if isempty(line) && !iszero(num_blocks) continue end - block_sets = _parse_dimensions(line) + block_sets = _dim_to_set.(_split(line)) block_sets_read = true if length(block_sets) != num_blocks error( @@ -399,7 +397,7 @@ function Base.read!(io::IO, model::Model{T}) where {T<:Real} continue end objective_read = true - c = parse.(T, split(line)) + c = parse.(T, _split(line)) if length(c) != num_vars error( "The number of variables ($num_vars) does not match the length of the list of coefficients for the objective function vector of coefficients ($(length(c))).", @@ -416,7 +414,7 @@ function Base.read!(io::IO, model::Model{T}) where {T<:Real} if isempty(line) continue end - values = split(line) + values = _split(line) if length(values) != 5 error( "Invalid line specifying entry: $line. There are $(length(values)) values instead of 5.", diff --git a/test/FileFormats/SDPA/SDPA.jl b/test/FileFormats/SDPA/SDPA.jl index 4e3dcd4c8f..bd0777f58e 100644 --- a/test/FileFormats/SDPA/SDPA.jl +++ b/test/FileFormats/SDPA/SDPA.jl @@ -323,26 +323,19 @@ function test_examples() end # See https://github.com/jump-dev/MathOptInterface.jl/issues/1541 -function _spacer(char) - return [" ", "$char", " $char", "$char ", " $char "] -end +_spacer(char) = [" ", "$char", " $char", "$char ", " $char "] function test_dim_reader() - for before in _spacer('{') - for sep in _spacer(',') - for after in _spacer('}') - line = string(before, "-4", sep, "2", after) - exp = [ - MOI.Nonnegatives(4), - MOI.PositiveSemidefiniteConeTriangle(2), - ] - @test MOI.FileFormats.SDPA._parse_dimensions(line) == exp - line = string(before, "2", sep, "-4", after) - @test MOI.FileFormats.SDPA._parse_dimensions(line) == - exp[2:-1:1] - end + fn(line) = SDPA._dim_to_set.(SDPA._split(line)) + for (a, b) in ['{' => '}', '(' => ')'] + for pre in _spacer(a), sep in _spacer(','), suf in _spacer(b) + @test fn("$(pre)-4$(sep)2$suf") == + [MOI.Nonnegatives(4), MOI.PositiveSemidefiniteConeTriangle(2)] + @test fn("$(pre)2$(sep)-4$suf") == + [MOI.PositiveSemidefiniteConeTriangle(2), MOI.Nonnegatives(4)] end end + return end function test_integer_before_variables() diff --git a/test/FileFormats/SDPA/models/issue_1541.dat-s b/test/FileFormats/SDPA/models/issue_1541.dat-s index 01fe412015..db61d88134 100644 --- a/test/FileFormats/SDPA/models/issue_1541.dat-s +++ b/test/FileFormats/SDPA/models/issue_1541.dat-s @@ -1,7 +1,7 @@ 3 =mdim 2 =nblocks {-4, 2} --0.0 -1 -2 +{-0.0,-1e+0, -2} 0 1 1 1 -1 1 1 1 1 -1 1 1 2 2 1 From bc869ec1d53bf2d35ddc94cc24b0d35a43e808c5 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Tue, 20 May 2025 17:00:01 +1200 Subject: [PATCH 9/9] Prep for v1.40.1 (#2760) --- Project.toml | 2 +- docs/make.jl | 8 ++------ docs/src/changelog.md | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/Project.toml b/Project.toml index 27fb65d397..8dbd1cfa26 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "MathOptInterface" uuid = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" -version = "1.40.0" +version = "1.40.1" [deps] BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" diff --git a/docs/make.jl b/docs/make.jl index 83a82dd7b6..28f21658e7 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -154,12 +154,8 @@ Documenter.DocMeta.setdocmeta!( linkcheck_ignore = [ # Ignore the PDF link, because it hasn't been built yet. "MathOptInterface.pdf", - # Ignore tags, because prepping for a new release will otherwise cause - # it to fail. - r"https://github.com/jump-dev/MathOptInterface.jl/releases/tag/v([0-9]).([0-9]+).([0-9]+)", - # Ignore issue and pull request links, because there are many of them, - # and they sometimes time-out the linkcheck. - r"https://github.com/jump-dev/MathOptInterface.jl/issues/([0-9]+)", + # Ignore the very many GitHub links + r"https://github.com/jump-dev/.+", "https://arxiv.org/abs/2002.03447", ], modules = [MathOptInterface], diff --git a/docs/src/changelog.md b/docs/src/changelog.md index 51122236b1..b782ff82e8 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -7,6 +7,24 @@ 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.40.1 (May 20, 2025) + +### Fixed + + - Fixed a missing [`dual_set`](@ref) for [`HermitianPositiveSemidefiniteConeTriangle`](@ref) + (#2749) + - Fixed [`Utilities.get_fallback`](@ref) of [`DualObjectiveValue`](@ref) with + [`HyperRectangle`](@ref) (#2755) + - Fixed reading SDPA files with `{}` punctuation (#2759) + +### Other + + - Fixed a flakey doctest that depended on the runtime of a function (#2748) + - Changed to use `Base.only` when appropriate (#2751) + - Removed the experimental warning from Nonlinear module docstring (#2752) + - Changed to use `throw_if_scalar_and_constant_not_zero` when appropriate (#2753) + - Improved the text of `showerror` for `NotAllowedError` (#2757) + ## v1.40.0 (May 4, 2025) ### Added