From 48e0d826a254c7f500789adb8c1e0b11bacf4ddb Mon Sep 17 00:00:00 2001 From: Gleb Belov Date: Tue, 30 Jul 2024 16:39:26 +1000 Subject: [PATCH] Tests for obj reformulations, also with .objweight --- CHANGES.mp.md | 9 ++++++ include/mp/flat/converter.h | 13 +++++--- include/mp/flat/converter_multiobj.h | 13 +++++--- solvers/gurobi/gurobibackend.cc | 2 +- solvers/mosek/mosekbackend.cc | 3 +- solvers/visitor/visitorbackend.cc | 2 +- .../categorized/fast/multi_obj/modellist.json | 30 +++++++++++++++++++ .../categorized/fast/multi_obj/obj_abs_01.mod | 20 +++++++++++++ .../categorized/fast/multi_obj/obj_abs_02.mod | 20 +++++++++++++ .../cases/categorized/fast/qp/modellist.json | 8 +++++ 10 files changed, 108 insertions(+), 12 deletions(-) create mode 100644 test/end2end/cases/categorized/fast/multi_obj/obj_abs_01.mod create mode 100644 test/end2end/cases/categorized/fast/multi_obj/obj_abs_02.mod diff --git a/CHANGES.mp.md b/CHANGES.mp.md index 252a7f88a..bbe4dacc8 100644 --- a/CHANGES.mp.md +++ b/CHANGES.mp.md @@ -2,6 +2,15 @@ Summary of recent updates to the AMPL MP Library ================================================ +## unreleased +- SCIP (and any solver with linear objective + and non-linear constraints): improve reformulation + of QP objectives. +- Fix reformulation of non-linear objective expressions + for the multi-objective case (option obj:multi) when + negative objective weights are used (obj:multi:weight.) + + ## 20240724 - Option *acc:_all* - Useful to disable all reformulations (acc:_all=2), diff --git a/include/mp/flat/converter.h b/include/mp/flat/converter.h index 80822a3ed..9ba44e581 100644 --- a/include/mp/flat/converter.h +++ b/include/mp/flat/converter.h @@ -1143,11 +1143,16 @@ class FlatConverter : GetEnv().AddOption("cvt:quadobj passquadobj", ModelAPIAcceptsQuadObj() ? - "0/1*: Multiply out and pass quadratic objective terms to the solver, " - "vs. linear approximation." + "0/1*: Pass quadratic objective terms to the solver. " + "If the solver accepts quadratic constraints, " + "such a constraint will be created with those, " + "otherwise linearly approximated." : - "0*/1: Multiply out and pass quadratic objective terms to the solver, " - "vs. linear approximation.", + "0*/1: Pass quadratic objective terms to the solver, " + "If the solver accepts quadratic constraints, " + "such a constraint will be created with those, " + "otherwise linearly approximated." +, options_.passQuadObj_, 0, 1); GetEnv().AddOption("cvt:quadcon passquadcon", "Convenience option. " diff --git a/include/mp/flat/converter_multiobj.h b/include/mp/flat/converter_multiobj.h index 02c5e32ad..7142fc50d 100644 --- a/include/mp/flat/converter_multiobj.h +++ b/include/mp/flat/converter_multiobj.h @@ -118,6 +118,10 @@ class MOManager { std::vector objpr = MPD( ReadIntSuffix( {"objpriority", suf::OBJ} ) ); // int only objpr.resize(obj_orig.size(), 0.0); // blend objectives by default std::vector objwgt = MPD( GetMOWeightsLegacy() ); + if (objwgt.empty()) { + objwgt.resize(obj_orig.size(), 1.0); // Default "intuitive" weights + FlipDiffSenseSigns(objwgt); // Backend / Emulator want "legacy" + } std::vector objtola = MPD( ReadDblSuffix( {"objabstol", suf::OBJ} ) ); objtola.resize(obj_orig.size(), 0.0); std::vector objtolr = MPD( ReadDblSuffix( {"objreltol", suf::OBJ} ) ); @@ -235,7 +239,7 @@ class MOManager { /// The "intuitive" objective weights /// @return Always a full vector (for all objs) - /// @note All these methods assume the obj list is completed + /// @note Assume the obj list is completed ArrayRef GetMOWeights() { std::vector objwgt = MPD( GetMOWeightsLegacy() ); const auto& obj_orig = MPD( get_objectives() ); // no linking @@ -248,13 +252,13 @@ class MOManager { } /// @return Legacy weights (relative to the 1st obj), - /// if .objweight provided, or default + /// if .objweight provided + /// @note Assume the obj list is completed ArrayRef GetMOWeightsLegacy() { std::vector objw = MPD( ReadDblSuffix( {"objweight", suf::OBJ} ) ); const auto& obj_orig = MPD( get_objectives() ); // no linking if (objw.empty()) { - objw.resize(obj_orig.size(), 1.0); // Default "intuitive" weights - FlipDiffSenseSigns(objw); // Backend / Emulator want "legacy" + // pass } else if (2==MPD( GetEnv() ).multiobj_weight()) { // user gave "intuitive" values FlipDiffSenseSigns(objw); // Backend / Emulator want "legacy" } @@ -262,6 +266,7 @@ class MOManager { } /// Convert between the options of obj:multi:weight + /// @note Assume the obj list is completed void FlipDiffSenseSigns(std::vector& objw) { const auto& obj = MPD( get_objectives() ); if (obj.size() > 1) { diff --git a/solvers/gurobi/gurobibackend.cc b/solvers/gurobi/gurobibackend.cc index 1f54da0d4..6f64939aa 100644 --- a/solvers/gurobi/gurobibackend.cc +++ b/solvers/gurobi/gurobibackend.cc @@ -374,7 +374,7 @@ SolutionBasis GurobiBackend::GetBasis() { {{{ CG_Linear, std::move(constt) }}} } ); varstt = mv.GetVarValues()(); constt = mv.GetConValues()(); - assert(varstt.size()); + assert(varstt.size()); // not for constraints } return { std::move(varstt), std::move(constt) }; } diff --git a/solvers/mosek/mosekbackend.cc b/solvers/mosek/mosekbackend.cc index c6a47162a..16c0eeb23 100644 --- a/solvers/mosek/mosekbackend.cc +++ b/solvers/mosek/mosekbackend.cc @@ -905,8 +905,7 @@ SolutionBasis MosekBackend::GetBasis() { {{{ CG_Algebraic, std::move(constt) }}} }); varstt = mv.GetVarValues()(); constt = mv.GetConValues()(); - assert(varstt.size()); - assert(constt.size()); + assert(varstt.size()); // not for constraints } return { std::move(varstt), std::move(constt) }; } diff --git a/solvers/visitor/visitorbackend.cc b/solvers/visitor/visitorbackend.cc index 1ca62fcb9..413766e83 100644 --- a/solvers/visitor/visitorbackend.cc +++ b/solvers/visitor/visitorbackend.cc @@ -645,7 +645,7 @@ SolutionBasis VisitorBackend::GetBasis() { {{{ CG_Linear, std::move(constt) }}} }); varstt = mv.GetVarValues()(); constt = mv.GetConValues()(); - assert(varstt.size()); + assert(varstt.size()); // not for constraints, can be QCP } return { std::move(varstt), std::move(constt) }; } diff --git a/test/end2end/cases/categorized/fast/multi_obj/modellist.json b/test/end2end/cases/categorized/fast/multi_obj/modellist.json index c6ea652de..b77ef0ab8 100644 --- a/test/end2end/cases/categorized/fast/multi_obj/modellist.json +++ b/test/end2end/cases/categorized/fast/multi_obj/modellist.json @@ -248,5 +248,35 @@ "values": { "z": 1 } + }, + { + "name" : "obj_abs_01", + "tags" : ["linear", "integer", "multiobj"], + "options": { + "ANYSOLVER_options": "multiobj=1" + }, + "values": { + "_sobj[1]": 13, + "_sobj[2]": -17 + } + }, + { + "name" : "obj_abs_02", + "tags" : ["linear", "integer", "multiobj"], + "options": { + "ANYSOLVER_options": "multiobj=1" + }, + "values": { + "_sobj[1]": 13, + "_sobj[2]": 4.333333333333333333 + } + }, + { + "name" : "obj_abs_02 obj:no=2", + "tags" : ["linear", "integer"], + "options": { + "ANYSOLVER_options": "obj:no=2" + }, + "objective": 4.3333333333333333333 } ] diff --git a/test/end2end/cases/categorized/fast/multi_obj/obj_abs_01.mod b/test/end2end/cases/categorized/fast/multi_obj/obj_abs_01.mod new file mode 100644 index 000000000..f4328e6f0 --- /dev/null +++ b/test/end2end/cases/categorized/fast/multi_obj/obj_abs_01.mod @@ -0,0 +1,20 @@ +####################################### +## obj_abs_01.mod +## Test objective sense-aware reformulation +## Test .objweight in obj:multi mode +## abs() +####################################### + +var x >=-2 <=5; +var y >=-12 <=3; + + +suffix objpriority IN; +suffix objweight IN; + +## The 1st objective is dummy, to enable the multi-objective approach +minimize ObjD: 3*x - 2*y suffix objpriority 1, suffix objweight 1; +maximize Obj1: abs(x) - 2*abs(y) suffix objpriority 2, suffix objweight -2; + +s.t. C1: 3*x - 2*y == 13; + diff --git a/test/end2end/cases/categorized/fast/multi_obj/obj_abs_02.mod b/test/end2end/cases/categorized/fast/multi_obj/obj_abs_02.mod new file mode 100644 index 000000000..a6b59dba7 --- /dev/null +++ b/test/end2end/cases/categorized/fast/multi_obj/obj_abs_02.mod @@ -0,0 +1,20 @@ +####################################### +## obj_abs_02.mod +## Test objective sense-aware reformulation +## Test .objweight in obj:multi mode +## abs() +####################################### + +var x >=-2 <=5; +var y >=-12 <=3; + + +suffix objpriority IN; +suffix objweight IN; + +## The 1st objective is dummy, to enable the multi-objective approach +minimize ObjD: 3*x - 2*y suffix objpriority 1, suffix objweight -1; +maximize Obj1: abs(x) - 2*abs(y) suffix objpriority 2, suffix objweight 3; + +s.t. C1: 3*x - 2*y == 13; + diff --git a/test/end2end/cases/categorized/fast/qp/modellist.json b/test/end2end/cases/categorized/fast/qp/modellist.json index 60220e505..20df4ff71 100644 --- a/test/end2end/cases/categorized/fast/qp/modellist.json +++ b/test/end2end/cases/categorized/fast/qp/modellist.json @@ -91,6 +91,14 @@ "objective" : 0, "tags" : ["quadratic_obj"] }, + { + "name" : "quad_obj_repeated cvt:quadobj=0", + "objective" : 0, + "tags" : ["quadratic_obj"], + "options": { + "ANYSOLVER_options": "cvt:quadobj=0" + } + }, { "name" : "prod_bin_var_01", "objective" : 5,