From 3ab597fc370569912dcfec42a6a580308a6aa630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 11 Mar 2019 15:09:07 +0100 Subject: [PATCH 01/10] Implement optimizer attributes in caching optimizer --- src/Utilities/cachingoptimizer.jl | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Utilities/cachingoptimizer.jl b/src/Utilities/cachingoptimizer.jl index 91a6761d8f..9f1a181ff4 100644 --- a/src/Utilities/cachingoptimizer.jl +++ b/src/Utilities/cachingoptimizer.jl @@ -80,6 +80,11 @@ function reset_optimizer(m::CachingOptimizer, optimizer::MOI.AbstractOptimizer) @assert MOI.is_empty(optimizer) m.optimizer = optimizer m.state = EMPTY_OPTIMIZER + for attr in MOI.get(m.model_cache, MOI.ListOfOptimizerAttributesSet()) + value = MOI.get(m.model_cache, attr) + optimizer_value = attribute_value_map(m.model_to_optimizer_map, value) + MOI.set(m.optimizer, attr, optimizer_value) + end return end @@ -502,6 +507,14 @@ function MOI.get(m::CachingOptimizer, IdxT::Type{<:MOI.Index}, name::String) end # TODO: MOI.set for MOI.AbstractOptimizerAttribute. +function MOI.set(model::CachingOptimizer, attr::MOI.AbstractOptimizerAttribute, + value) + optimizer_value = attribute_value_map(model.model_to_optimizer_map, value) + if model.optimizer !== nothing + MOI.set(model.optimizer, attr, optimizer_value) + end + MOI.set(model.model_cache, attr, value) +end function MOI.get(model::CachingOptimizer, attr::MOI.AbstractOptimizerAttribute) if state(model) == NO_OPTIMIZER From 91f2c5952600d620ec95abb37fefd68dba6b7287 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 11 Mar 2019 22:36:02 +0100 Subject: [PATCH 02/10] Add LogLevel --- docs/src/apireference.md | 3 ++- src/attributes.jl | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/src/apireference.md b/docs/src/apireference.md index 5f498f19f8..a0fd36cc42 100644 --- a/docs/src/apireference.md +++ b/docs/src/apireference.md @@ -84,10 +84,11 @@ AbstractOptimizer optimize! ``` -List of attributes optimizers attributes +List of optimizers attributes ```@docs SolverName +LogLevel ``` List of attributes useful for optimizers diff --git a/src/attributes.jl b/src/attributes.jl index 77363430ac..ec8805a3b7 100644 --- a/src/attributes.jl +++ b/src/attributes.jl @@ -337,6 +337,17 @@ An optimizer attribute for the string identifying the solver/optimizer. """ struct SolverName <: AbstractOptimizerAttribute end +""" + LogLevel(value::Int) + +An optimizer attribute for level of verbosity of the optimizer. `0` means +no output and for positive values, the higher the values the more output is +allowed. +""" +struct LogLevel <: AbstractOptimizerAttribute + value::Int +end + ## Model attributes """ From f63a0e0324b06d6c0e86688d0512a05026d100f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 12 Mar 2019 02:37:16 +0100 Subject: [PATCH 03/10] Remove resolved TODO --- src/Utilities/cachingoptimizer.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Utilities/cachingoptimizer.jl b/src/Utilities/cachingoptimizer.jl index 9f1a181ff4..e8c671517c 100644 --- a/src/Utilities/cachingoptimizer.jl +++ b/src/Utilities/cachingoptimizer.jl @@ -506,7 +506,6 @@ function MOI.get(m::CachingOptimizer, IdxT::Type{<:MOI.Index}, name::String) return MOI.get(m.model_cache, IdxT, name) end -# TODO: MOI.set for MOI.AbstractOptimizerAttribute. function MOI.set(model::CachingOptimizer, attr::MOI.AbstractOptimizerAttribute, value) optimizer_value = attribute_value_map(model.model_to_optimizer_map, value) From d2cabea74af3b0e3842b7187110562458d9db692 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 12 Mar 2019 02:37:34 +0100 Subject: [PATCH 04/10] Change LogLevel docstring --- src/attributes.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/attributes.jl b/src/attributes.jl index ec8805a3b7..5e327ea8a8 100644 --- a/src/attributes.jl +++ b/src/attributes.jl @@ -341,8 +341,7 @@ struct SolverName <: AbstractOptimizerAttribute end LogLevel(value::Int) An optimizer attribute for level of verbosity of the optimizer. `0` means -no output and for positive values, the higher the values the more output is -allowed. +no output and the meaning is solver specific for positive values. """ struct LogLevel <: AbstractOptimizerAttribute value::Int From f896a2e86449cc4b0562bbcf385db1e3a15a1c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Tue, 12 Mar 2019 02:38:59 +0100 Subject: [PATCH 05/10] Add conflict resolution --- src/attributes.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/attributes.jl b/src/attributes.jl index 5e327ea8a8..60e79e2d9d 100644 --- a/src/attributes.jl +++ b/src/attributes.jl @@ -342,6 +342,8 @@ struct SolverName <: AbstractOptimizerAttribute end An optimizer attribute for level of verbosity of the optimizer. `0` means no output and the meaning is solver specific for positive values. +If a solver specific attribute controlling the output level is also set, the +last attributes set gets supremacy over the other one. """ struct LogLevel <: AbstractOptimizerAttribute value::Int From 08de48ec9ee8bd3ca020b138f950e34c1a55a04d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 13 Mar 2019 11:56:23 +0100 Subject: [PATCH 06/10] Update TODO --- src/Utilities/cachingoptimizer.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Utilities/cachingoptimizer.jl b/src/Utilities/cachingoptimizer.jl index e8c671517c..9366c852fc 100644 --- a/src/Utilities/cachingoptimizer.jl +++ b/src/Utilities/cachingoptimizer.jl @@ -517,12 +517,13 @@ end function MOI.get(model::CachingOptimizer, attr::MOI.AbstractOptimizerAttribute) if state(model) == NO_OPTIMIZER + # TODO: Copyable attributes (e.g., Silent, TimeLimit) are also be stored + # in the cache so we could return the value stored in the cache instead. + # However, for non-copyable attributes( e.g. `SolverName`) the error is + # appropriate. error("Cannot query $(attr) from caching optimizer because no " * "optimizer is attached.") end - # TODO: Copyable attributes (e.g., TimeLimit) could also be stored in the - # cache. When MOI.set is implemented for MOI.AbstractOptimizerAttribute, - # make sure this case is handled correctly. return attribute_value_map(model.optimizer_to_model_map, MOI.get(model.optimizer, attr)) end From 3b9254cd29bb4ee31ae09caaf0c4d001d1eae1c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 13 Mar 2019 12:04:26 +0100 Subject: [PATCH 07/10] Rename LogLevel to Silent --- docs/src/apireference.md | 2 +- src/attributes.jl | 25 +++++++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/src/apireference.md b/docs/src/apireference.md index a0fd36cc42..d46b9be883 100644 --- a/docs/src/apireference.md +++ b/docs/src/apireference.md @@ -88,7 +88,7 @@ List of optimizers attributes ```@docs SolverName -LogLevel +Silent ``` List of attributes useful for optimizers diff --git a/src/attributes.jl b/src/attributes.jl index 60e79e2d9d..09af3aac4c 100644 --- a/src/attributes.jl +++ b/src/attributes.jl @@ -338,16 +338,25 @@ An optimizer attribute for the string identifying the solver/optimizer. struct SolverName <: AbstractOptimizerAttribute end """ - LogLevel(value::Int) + Silent -An optimizer attribute for level of verbosity of the optimizer. `0` means -no output and the meaning is solver specific for positive values. -If a solver specific attribute controlling the output level is also set, the -last attributes set gets supremacy over the other one. +An optimizer attribute for silencing the output of an optimizer. When `set` +to `true`, it takes precedence over any other attribute controlling verbosity +and requires the solver to produce no output. The default value is `false` +which has no effect, the verbosity is controlled by other attributes. + + +## Note + +Every optimizer should have verbosity on by default. For instance, if a solver +has a solver-specific attribute `LogLevel` which is `0` by default, the MOI +implementation should set it to `1` by default. If the user sets `Silent` +to `true`, then the `LogLevel` should be set to `0`, even if the user +specifically sets a value of `LogLevel`. If the value of `Silent` is `false` +then the `LogLevel` set to the solver is the value given by the user for this +solver-specific parameter or `1` if none is given. """ -struct LogLevel <: AbstractOptimizerAttribute - value::Int -end +struct Silent <: AbstractOptimizerAttribute end ## Model attributes From 4e7f664e1873c6d4626a1aa60b12cc314dc01dda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 13 Mar 2019 12:16:28 +0100 Subject: [PATCH 08/10] Implement optimizer attributes in mock --- src/Utilities/mockoptimizer.jl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Utilities/mockoptimizer.jl b/src/Utilities/mockoptimizer.jl index eac73ebe4c..9f2fd0f4ec 100644 --- a/src/Utilities/mockoptimizer.jl +++ b/src/Utilities/mockoptimizer.jl @@ -151,7 +151,9 @@ MOI.set(mock::MockOptimizer, ::MockModelAttribute, value::Integer) = (mock.attri function MOI.supports(mock::MockOptimizer, attr::MOI.AbstractModelAttribute) return MOI.supports(mock.inner_model, attr) end -function MOI.set(mock::MockOptimizer, attr::MOI.AbstractModelAttribute, value) +function MOI.set(mock::MockOptimizer, + attr::Union{MOI.AbstractModelAttribute, + MOI.AbstractOptimizerAttribute}, value) MOI.set(mock.inner_model, attr, value) end MOI.set(mock::MockOptimizer, attr::MOI.ObjectiveFunction, value) = MOI.set(mock.inner_model, attr, xor_variables(value)) @@ -164,7 +166,11 @@ MOI.set(mock::MockOptimizer, ::MockConstraintAttribute, idx::MOI.ConstraintIndex MOI.set(mock::MockOptimizer, ::MOI.ConstraintDual, idx::MOI.ConstraintIndex, value) = (mock.condual[xor_index(idx)] = value) MOI.set(mock::MockOptimizer, ::MOI.ConstraintBasisStatus, idx::MOI.ConstraintIndex, value) = (mock.con_basis[xor_index(idx)] = value) -MOI.get(mock::MockOptimizer, attr::MOI.AbstractModelAttribute) = MOI.get(mock.inner_model, attr) +function MOI.get(mock::MockOptimizer, + attr::Union{MOI.AbstractModelAttribute, + MOI.AbstractOptimizerAttribute}) + return MOI.get(mock.inner_model, attr) +end MOI.get(mock::MockOptimizer, attr::Union{MOI.ListOfVariableIndices, MOI.ListOfConstraintIndices}) = xor_index.(MOI.get(mock.inner_model, attr)) MOI.get(mock::MockOptimizer, attr::MOI.ObjectiveFunction) = xor_variables(MOI.get(mock.inner_model, attr)) From 0ec161b82cbabef400346ad689e5822ad655677e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 13 Mar 2019 12:16:56 +0100 Subject: [PATCH 09/10] Add tests --- test/Utilities/cachingoptimizer.jl | 33 ++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/test/Utilities/cachingoptimizer.jl b/test/Utilities/cachingoptimizer.jl index 5897d9cbad..de2edc9231 100644 --- a/test/Utilities/cachingoptimizer.jl +++ b/test/Utilities/cachingoptimizer.jl @@ -30,16 +30,45 @@ exception = ErrorException( "Cannot query $(attr) from caching optimizer because no optimizer" * " is attached.") - @test_throws Exception MOI.get(model, MOI.SolverName()) + @test_throws exception MOI.get(model, attr) + attr = MOI.Silent() + exception = ErrorException( + "Cannot query $(attr) from caching optimizer because no optimizer" * + " is attached.") + @test_throws exception MOI.get(model, attr) attr = MOI.ResultCount() exception = ErrorException( "Cannot query $(attr) from caching optimizer because no optimizer" * " is attached.") - @test_throws Exception MOI.get(model, MOI.ResultCount()) + @test_throws exception MOI.get(model, attr) end end +@testset "Copyable solver attributes" begin + cache = MOIU.UniversalFallback(ModelForCachingOptimizer{Float64}()) + cached = MOIU.CachingOptimizer(cache, MOIU.MANUAL) + MOI.set(cached, MOI.Silent(), true) + mock = MOIU.MockOptimizer(MOIU.UniversalFallback(ModelForMock{Float64}())) + MOIU.reset_optimizer(cached, mock) + @test MOI.get(mock, MOI.Silent()) + @test MOI.get(cached, MOI.Silent()) + MOI.set(cached, MOI.Silent(), false) + @test !MOI.get(mock, MOI.Silent()) + @test !MOI.get(cached, MOI.Silent()) + mock = MOIU.MockOptimizer(MOIU.UniversalFallback(ModelForMock{Float64}())) + MOIU.reset_optimizer(cached, mock) + @test !MOI.get(mock, MOI.Silent()) + @test !MOI.get(cached, MOI.Silent()) + MOI.set(cached, MOI.Silent(), true) + @test MOI.get(mock, MOI.Silent()) + @test MOI.get(cached, MOI.Silent()) + mock = MOIU.MockOptimizer(MOIU.UniversalFallback(ModelForMock{Float64}())) + MOIU.reset_optimizer(cached, mock) + @test MOI.get(mock, MOI.Silent()) + @test MOI.get(cached, MOI.Silent()) +end + @testset "CachingOptimizer MANUAL mode" begin m = MOIU.CachingOptimizer(ModelForCachingOptimizer{Float64}(), MOIU.MANUAL) @test MOIU.state(m) == MOIU.NO_OPTIMIZER From 78ed02bb7f05e861b98d3453286e50a6d6197789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Wed, 13 Mar 2019 12:56:33 -0300 Subject: [PATCH 10/10] [ci skip] Address comments --- src/Utilities/cachingoptimizer.jl | 8 ++++---- src/attributes.jl | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Utilities/cachingoptimizer.jl b/src/Utilities/cachingoptimizer.jl index 9366c852fc..72c24a09cc 100644 --- a/src/Utilities/cachingoptimizer.jl +++ b/src/Utilities/cachingoptimizer.jl @@ -517,10 +517,10 @@ end function MOI.get(model::CachingOptimizer, attr::MOI.AbstractOptimizerAttribute) if state(model) == NO_OPTIMIZER - # TODO: Copyable attributes (e.g., Silent, TimeLimit) are also be stored - # in the cache so we could return the value stored in the cache instead. - # However, for non-copyable attributes( e.g. `SolverName`) the error is - # appropriate. + # TODO: Copyable attributes (e.g., `Silent`, `TimeLimit`) should also be + # stored in the cache so we could return the value stored in the cache + # instead. However, for non-copyable attributes( e.g. `SolverName`) the + # error is appropriate. error("Cannot query $(attr) from caching optimizer because no " * "optimizer is attached.") end diff --git a/src/attributes.jl b/src/attributes.jl index 09af3aac4c..2fd45705f8 100644 --- a/src/attributes.jl +++ b/src/attributes.jl @@ -343,18 +343,18 @@ struct SolverName <: AbstractOptimizerAttribute end An optimizer attribute for silencing the output of an optimizer. When `set` to `true`, it takes precedence over any other attribute controlling verbosity and requires the solver to produce no output. The default value is `false` -which has no effect, the verbosity is controlled by other attributes. - +which has no effect. In this case the verbosity is controlled by other +attributes. ## Note Every optimizer should have verbosity on by default. For instance, if a solver -has a solver-specific attribute `LogLevel` which is `0` by default, the MOI -implementation should set it to `1` by default. If the user sets `Silent` -to `true`, then the `LogLevel` should be set to `0`, even if the user -specifically sets a value of `LogLevel`. If the value of `Silent` is `false` -then the `LogLevel` set to the solver is the value given by the user for this -solver-specific parameter or `1` if none is given. +has a solver-specific log level attribute, the MOI implementation should set it +to `1` by default. If the user sets `Silent` to `true`, then the log level +should be set to `0`, even if the user specifically sets a value of log level. +If the value of `Silent` is `false` then the log level set to the solver is the +value given by the user for this solver-specific parameter or `1` if none is +given. """ struct Silent <: AbstractOptimizerAttribute end