From b7673382ffc0520406c83d5d1d8006bd61397ba2 Mon Sep 17 00:00:00 2001 From: Gleb Belov Date: Wed, 31 May 2023 16:52:54 +1000 Subject: [PATCH] Exponential cone LHS as AffineExpr #192 Recognize exp cones where LHS is an affine expression --- CHANGES.mp.md | 7 +- CMakeLists.txt | 2 +- include/mp/flat/redef/conic/cones.h | 100 ++++++++---------- solvers/gurobi/CHANGES.gurobi.md | 5 + solvers/highsmp/CHANGES.highsmp.md | 11 +- solvers/highsmp/highsmp-lib.c | 2 +- solvers/mosek/CHANGES.mosek.md | 7 ++ .../conic/expcones_06__kelly__affexp_lhs.mod | 16 +++ .../categorized/fast/conic/modellist.json | 5 + 9 files changed, 94 insertions(+), 61 deletions(-) create mode 100644 test/end2end/cases/categorized/fast/conic/expcones_06__kelly__affexp_lhs.mod diff --git a/CHANGES.mp.md b/CHANGES.mp.md index 38e638441..4902771c9 100644 --- a/CHANGES.mp.md +++ b/CHANGES.mp.md @@ -1,9 +1,12 @@ Summary of recent updates to the AMPL MP Library ================================================ - -## TBD +## 20230531 +- Cones: recognize (affine_expr) >= y * exp(z/y) + as exponential cone. - Cones: recognize xy >= 1 as rotated SOC. +- Wrong solver options are gracefully reported via + solve_message. ## 20230515 diff --git a/CMakeLists.txt b/CMakeLists.txt index d533600ce..0ce2a71ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -214,7 +214,7 @@ endmacro() include_directories(include) set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) -set(MP_DATE 20230515) +set(MP_DATE 20230531) set(MP_SYSINFO "${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_PROCESSOR}") diff --git a/include/mp/flat/redef/conic/cones.h b/include/mp/flat/redef/conic/cones.h index 07b28c080..7f5140e8a 100644 --- a/include/mp/flat/redef/conic/cones.h +++ b/include/mp/flat/redef/conic/cones.h @@ -631,8 +631,6 @@ class Convert1ExpC : public MCKeeper { bool valid_ {false}; }; - /// LHS of ax >= by * exp( cz / by ) - using LhsTraits = SubexprTraits<1>; /// RHS using RhsTraits = SubexprTraits<2>; @@ -651,30 +649,20 @@ class Convert1ExpC : public MCKeeper { if (1 == body.GetQPTerms().size()) { const auto& lt = body.GetLinTerms(); // But we have c0*x + c1*y*zz <=> 0 const auto& qt = body.GetQPTerms(); - LhsTraits lhst; - if (0.0 == std::fabs(rhs) && // rhs==0: - 1 == lt.size()) // ax >= by exp(cz / by) ? - lhst = ClassifyLhs(lt.coef(0)*sens, lt.var(0)); - else if (0 == lt.size()) // const >= by exp(cz / by) ? - lhst = ClassifyLhs(-rhs*sens, -1); - - if (lhst) { - if (auto rhst = ClassifyRhsQuadr( - -qt.coef(0)*sens, qt.var1(0), qt.var2(0))) - return AddExpCone(lhst, rhst); - else if (auto rhst = ClassifyRhsQuadr( - -qt.coef(0)*sens, qt.var2(0), qt.var1(0))) - return AddExpCone(lhst, rhst); - } + + if (auto rhst = ClassifyRhsQuadr( + -qt.coef(0)*sens, qt.var1(0), qt.var2(0))) + return AddExpCone(lt, -rhs, -1, sens, rhst); + else if (auto rhst = ClassifyRhsQuadr( + -qt.coef(0)*sens, qt.var2(0), qt.var1(0))) + return AddExpCone(lt, -rhs, -1, sens, rhst); } return false; } /// DoRun. - /// Body: linear, i.e., var1 (or const) >= var2 (or const). - /// In particular, the following cases. - /// /// - /// Considering const>=0. + /// Body: linear, ax >= b exp(cz / b) + /// /// Accept non-(+-1) coefficients. /// /// @param body: linear constraint body. @@ -685,24 +673,10 @@ class Convert1ExpC : public MCKeeper { bool DoRun(const LinTerms& lt, int sens, double rhs) { assert((sens==1 || sens==-1) && "sens 1 or -1 only"); - if (0.0 == std::fabs(rhs) && // rhs==0: y==const - 2 == lt.size()) { // ax >= b exp(cz / b) - if (auto rhst = ClassifyRhsLin(-lt.coef(0)*sens, lt.var(0))) { - // c2*x >= -c1*exp() - if (auto lhst = ClassifyLhs(lt.coef(1)*sens, lt.var(1))) { - return AddExpCone(lhst, rhst); - } - } else if (auto rhst = ClassifyRhsLin(-lt.coef(1)*sens, lt.var(1))) { - // c1*x >= -c2*exp() - if (auto lhst = ClassifyLhs(lt.coef(0)*sens, lt.var(0))) { - return AddExpCone(lhst, rhst); - } - } - } else if (1 == lt.size()) { // const >= b exp(cz / b) - if (auto lhst = ClassifyLhs(-rhs*sens, -1)) { - if (auto rhst = ClassifyRhsLin(-lt.coef(0)*sens, lt.var(0))) { - return AddExpCone(lhst, rhst); - } + for (size_t i=0; i= -c[i]*exp(...) + return AddExpCone(lt, -rhs, i, sens, rhst); } } return false; @@ -710,15 +684,6 @@ class Convert1ExpC : public MCKeeper { protected: - LhsTraits ClassifyLhs(double a, int x) { - LhsTraits result {{a}, {x}}; - if ((x<0 && a>=0.0) || // Lhs is const, >=0 - (x>=0 && a>=0.0 && MC().lb(x)>=0.0) || // c*v >= 0 - (x>=0 && a<=0.0 && MC().ub(x)>=0.0)) - result.valid_ = true; - return result; - } - /// The RHS is just b * (v=exp(z))? RhsTraits ClassifyRhsLin(double b, int v) { assert(v>=0); @@ -758,7 +723,7 @@ class Convert1ExpC : public MCKeeper { const auto& ae = pConLin->GetAffineExpr(); if (0.0 == std::fabs(ae.constant_term() && 1==ae.GetBody().size())) { - const auto& body = ae.GetBody(); // can we ever get here? + const auto& body = ae.GetBody(); // tested by expcones_06.mod if (y == body.var(0)) { // v2 = z / (c1*y) result.coefs_ = {b, b/body.coef(0)}; result.vars_ = {y, z}; @@ -773,21 +738,46 @@ class Convert1ExpC : public MCKeeper { return result; } - bool AddExpCone(LhsTraits l, RhsTraits r) { - assert(l.vars2del_.empty()); + /// LHS: sens * (lt+cterm [no lt_i_skip element]) + bool AddExpCone(const LinTerms& lt, double cterm, + int lt_i_skip, int sens, + RhsTraits r) { for (auto v2d: r.vars2del_) // unuse result vars MC().DecrementVarUsage(v2d); std::array args - {l.vars_[0], r.vars_[0], r.vars_[1]}; + {0, r.vars_[0], r.vars_[1]}; + std::array coefs + {1.0*sens, r.coefs_[0], r.coefs_[1]}; + auto n_lterms = lt.size() - (lt_i_skip>=0); + if (0==n_lterms) { // LHS = const + args[0] = -1; // to be replaced by fixed var==1 + coefs[0] = cterm * sens; + } else if (1==n_lterms && 0.0==std::fabs(cterm)) { + for (size_t i=0; i= y * exp(z/y) + as exponential cone. +- Cones: recognize xy >= 1 as rotated SOC. +- Wrong solver options are gracefully reported via + solve_message. + ## 20230515 - *Exponential cones*. MP driver recognizes exponential diff --git a/test/end2end/cases/categorized/fast/conic/expcones_06__kelly__affexp_lhs.mod b/test/end2end/cases/categorized/fast/conic/expcones_06__kelly__affexp_lhs.mod new file mode 100644 index 000000000..a57f64746 --- /dev/null +++ b/test/end2end/cases/categorized/fast/conic/expcones_06__kelly__affexp_lhs.mod @@ -0,0 +1,16 @@ +# parameter values +param b default 1.25; +param p default 0.51; + +# decision variables +var q1; +var q2 >= 0; +var w >= 0; + +# objective +maximize ElogR: + p*q1 + (1-p)*q2; + +# conic constraints +s.t. T1: 1 + b*w >= exp( q1 ); +s.t. T2: -1 + w +10*q2 <= -5 * q2 * exp( q1 / (q2*5) ); diff --git a/test/end2end/cases/categorized/fast/conic/modellist.json b/test/end2end/cases/categorized/fast/conic/modellist.json index 4d733c6e5..3ed9229af 100644 --- a/test/end2end/cases/categorized/fast/conic/modellist.json +++ b/test/end2end/cases/categorized/fast/conic/modellist.json @@ -83,5 +83,10 @@ "name" : "expcones_05__const", "objective" : -0.8988331541, "tags" : ["expcones"] + }, + { + "name" : "expcones_06__kelly__affexp_lhs", + "objective" : 0.1116637435, + "tags" : ["expcones"] } ]