From 26002c45c76db2b77b76cb9abdcb373f9393ad3b Mon Sep 17 00:00:00 2001 From: Gleb Belov Date: Mon, 29 Jul 2024 21:41:06 +1000 Subject: [PATCH] SCIP: QP objective Represent as 1 constraint (was split before) Also: fix obj sense propagation using obj weights in obj:multi mode #239 --- include/mp/flat/converter.h | 17 ++++++++++ include/mp/flat/converter_multiobj.h | 34 ++++++++++++++----- include/mp/flat/problem_flattener.h | 25 ++++++++------ .../cases/categorized/fast/qp/modellist.json | 3 +- test/end2end/scripts/python/Solver.py | 2 ++ 5 files changed, 62 insertions(+), 19 deletions(-) diff --git a/include/mp/flat/converter.h b/include/mp/flat/converter.h index 6ac479e2b..80822a3ed 100644 --- a/include/mp/flat/converter.h +++ b/include/mp/flat/converter.h @@ -101,6 +101,23 @@ class FlatConverter : } } + /// Propagate objective contexts + void PropagateObjContexts() { + const auto& objs = MPD( get_objectives() ); + const auto objwgt = MPD( GetMOWeights() ); + if (GetEnv().multiobj()) // only in obj:multi mode + assert(objs.size() == objwgt.size()); + for (size_t i=0; i 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); // We handle "legacy" format below - } std::vector objtola = MPD( ReadDblSuffix( {"objabstol", suf::OBJ} ) ); objtola.resize(obj_orig.size(), 0.0); std::vector objtolr = MPD( ReadDblSuffix( {"objreltol", suf::OBJ} ) ); @@ -237,9 +233,29 @@ class MOManager { MPD( SetObjectiveTo( MPD(GetModelAPI()), 0, obj_new_[i_current_obj_]) ); } + /// The "intuitive" objective weights + /// @return Always a full vector (for all objs) + /// @note All these methods assume the obj list is completed + ArrayRef GetMOWeights() { + std::vector objwgt = MPD( GetMOWeightsLegacy() ); + const auto& obj_orig = MPD( get_objectives() ); // no linking + if (objwgt.empty()) { + objwgt.resize(obj_orig.size(), 1.0); // Default "intuitive" weights + } else { + FlipDiffSenseSigns(objwgt); // We handle "legacy" format below + } + return objwgt; + } + + /// @return Legacy weights (relative to the 1st obj), + /// if .objweight provided, or default ArrayRef GetMOWeightsLegacy() { std::vector objw = MPD( ReadDblSuffix( {"objweight", suf::OBJ} ) ); - if (objw.size() && 2==MPD( GetEnv() ).multiobj_weight()) { // user wants "intuitive" + 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" + } else if (2==MPD( GetEnv() ).multiobj_weight()) { // user gave "intuitive" values FlipDiffSenseSigns(objw); // Backend / Emulator want "legacy" } return objw; @@ -248,9 +264,11 @@ class MOManager { /// Convert between the options of obj:multi:weight void FlipDiffSenseSigns(std::vector& objw) { const auto& obj = MPD( get_objectives() ); - for (auto i=obj.size(); --i; ) // forall i>1 - if (obj[i].obj_sense() != obj.front().obj_sense()) - objw[i] = -objw[i]; + if (obj.size() > 1) { + for (auto i=obj.size(); --i; ) // forall i>1 + if (obj[i].obj_sense() != obj.front().obj_sense()) + objw[i] = -objw[i]; + } } private: diff --git a/include/mp/flat/problem_flattener.h b/include/mp/flat/problem_flattener.h index 362d540dc..9a2333bad 100644 --- a/include/mp/flat/problem_flattener.h +++ b/include/mp/flat/problem_flattener.h @@ -174,6 +174,7 @@ class ProblemFlattener : MPD( ExportObj(i) ); MP_DISPATCH( Convert( GetModel().obj(i) ) ); } + GetFlatCvt().PropagateObjContexts(); // all now, because of GetMOWeights() ////////////////////////// Algebraic constraints ifFltCon_ = 1; @@ -340,17 +341,18 @@ class ProblemFlattener : } /// Sort/merge terms, otherwise we lose repeated terms /// in Gurobi where we just set 'obj attributes' - /// to variables + /// to variables. + /// Context is propagated after adding all objectives. le.sort_terms(); eexpr.GetQPTerms().sort_terms(); - /// Propagate context - auto ctx = obj::MAX==obj.type() ? Context::CTX_POS : Context::CTX_NEG; - GetFlatCvt().PropagateResult2LinTerms(le, - GetFlatCvt().MinusInfty(), - GetFlatCvt().Infty(), ctx); - GetFlatCvt().PropagateResult2QuadTerms(eexpr.GetQPTerms(), - GetFlatCvt().MinusInfty(), - GetFlatCvt().Infty(), ctx); + if (!GetFlatCvt().IfPassQuadObj() // SCIP 10 + && eexpr.GetQPTerms().size()) { + EExpr qpnew; + qpnew.GetQPTerms() = std::move(eexpr.GetQPTerms()); + int qpres = Convert2Var(std::move(qpnew)); + eexpr.GetQPTerms().clear(); // explicitly remove obj qp terms + le.add_term(1.0, qpres); + } /// Add linear / quadratic obj LinearObjective lo { obj.type(), std::move(le.coefs()), std::move(le.vars()) }; @@ -1200,7 +1202,10 @@ class ProblemFlattener : /// What about common expressions? int IfMultOutQPTerms() const { return IfFlatteningAConstraint() ? - GetFlatCvt().IfPassQuadCon() : GetFlatCvt().IfPassQuadObj(); + GetFlatCvt().IfPassQuadCon() : + // For objectives, still multiply out + // if we move the QP terms into contraints (SCIP) + ( GetFlatCvt().IfPassQuadObj() || GetFlatCvt().IfPassQuadCon() ); } /// Quadratize Pow2 exactly when we pass QP terms diff --git a/test/end2end/cases/categorized/fast/qp/modellist.json b/test/end2end/cases/categorized/fast/qp/modellist.json index da02253c6..60220e505 100644 --- a/test/end2end/cases/categorized/fast/qp/modellist.json +++ b/test/end2end/cases/categorized/fast/qp/modellist.json @@ -82,7 +82,8 @@ "tags" : ["quadratic_obj"], "options": { "highs_options": "timelim=3", - "comment_highs": "takes too long as of Sept 2022" + "comment_highs": "takes too long as of Sept 2022", + "scip_options": "timelim=3" } }, { diff --git a/test/end2end/scripts/python/Solver.py b/test/end2end/scripts/python/Solver.py index 49b7ce38b..b611869d9 100644 --- a/test/end2end/scripts/python/Solver.py +++ b/test/end2end/scripts/python/Solver.py @@ -1028,6 +1028,8 @@ def _setNThreads(self, threads): def __init__(self, exeName, timeout=None, nthreads=None, otherOptions=None): stags = {ModelTags.continuous, ModelTags.integer, ModelTags.binary, ModelTags.quadratic, + ModelTags.quadratic_obj, + ModelTags.quadratic_obj_nonconvex, ModelTags.quadraticnonconvex, ModelTags.socp, ModelTags.socp_hard_to_recognize, ModelTags.sos,