Skip to content

Commit

Permalink
New format for cvt:socp; new option cvt:socp2qc #192 #229
Browse files Browse the repository at this point in the history
  • Loading branch information
glebbelov committed Jan 12, 2024
1 parent ff311a7 commit 155f992
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 77 deletions.
118 changes: 92 additions & 26 deletions include/mp/flat/converter.h
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,13 @@ class FlatConverter :
return ModelAPI::AcceptsNonconvexQC();
}

/// Ask if the solver can recognize SOCP corner cases
/// (non-std representations such as xy>=1, see tests)
/// from quadratic representations
static bool ModelAPICanSOCPCornerCasesFromQC() {
return ModelAPI::CanSOCPCornerCasesFromQC();
}

/// Whether the solver can mix conic quadratic
/// (entered via dedicated API)
/// and direct quadratic constraints
Expand All @@ -895,7 +902,7 @@ class FlatConverter :
}

/// Whether the ModelAPI accepts quadratic cones
int ModelAPIAcceptsQuadraticCones() {
int ModelAPIAcceptsQuadraticCones() const {
return
std::max(
(int)GetConstraintAcceptance((QuadraticConeConstraint*)nullptr),
Expand All @@ -908,6 +915,10 @@ class FlatConverter :
int NumQC2SOCPAttempted() const { return nQC2SOCPAttempted_; }
int NumQC2SOCPSucceeded() const { return nQC2SOCPSucceeded_; }

/// Number of exp cones recognized
void IncExpConeCounter() { ++nExpConesRecognized_; }
int NumExpConesRecognized() const { return nExpConesRecognized_; }

/// Whether the ModelAPI accepts exp cones
int ModelAPIAcceptsExponentialCones() {
return
Expand All @@ -925,8 +936,9 @@ class FlatConverter :

int passQuadObj_ = ModelAPIAcceptsQuadObj();
int passQuadCon_ = ModelAPIAcceptsQC();
int passSOCPCones_ = 0;
int passExpCones_ = 0;
int passSOCPCones_ = 0;
int passSOCP2QC_ = 0;
int passExpCones_ = 0;

int relax_ = 0;

Expand Down Expand Up @@ -972,10 +984,42 @@ class FlatConverter :


private:
std::string solchkfailtext_ {
const std::string solchkfailtext_ {
"Fail on MP solution check violations, with solve result "
+ std::to_string(sol::MP_SOLUTION_CHECK) + '.'
};

int DefaultSOCPMode() const {
return
!ModelAPIAcceptsQC() && !ModelAPIAcceptsQuadraticCones()
? 0
: ModelAPICanSOCPCornerCasesFromQC() ? 1
: 2;
}
int DefaultSOCP2QCMode() const {
return
((!ModelAPIAcceptsQC() || ModelAPICanMixConicQCAndQC())
&& ModelAPIAcceptsQuadraticCones())
? 0
: (!ModelAPICanMixConicQCAndQC()
&& ModelAPIAcceptsQuadraticCones()) ? 1
: 2;
}
std::string socp_mode_text_;
std::string socp2qc_mode_text_;
const mp::OptionValueInfo socp_values_[3] = {
{ "0", "Do not recognize SOCP forms", 0},
{ "1", "Recognize from non-quadratic expressions only (sqrt, abs)", 1},
{ "2", "Recognize from quadratic and non-quadratic SOCP forms", 2}
};
const mp::OptionValueInfo socp2qc_values_[3] = {
{ "0", "Do not convert", 0},
{ "1", "Convert if no other cone types found, and "
"not all original quadratics could be recognized as SOC, "
"in particular if the objective is quadratic", 1},
{ "2", "Always convert", 2}
};

void InitOwnOptions() {
/// Should be called after adding all constraint keepers
FlatModel::ConsiderAcceptanceOptions(*this, GetModelAPI(), GetEnv());
Expand Down Expand Up @@ -1012,23 +1056,36 @@ class FlatConverter :
"0*/1: Multiply out and pass quadratic constraint terms to the solver, "
"vs. linear approximation.",
options_.passQuadCon_, 0, 1);
if (ModelAPIAcceptsExponentialCones())
GetEnv().AddOption("cvt:expcones expcones",
ModelAPIAcceptsExponentialCones()>1 ?
"0/1*: Recognize exponential cones." :
"0*/1: Recognize exponential cones.",
options_.passExpCones_, 0, 1);
options_.passExpCones_ = ModelAPIAcceptsExponentialCones()>1;
if (ModelAPIAcceptsQuadraticCones())
GetEnv().AddOption("cvt:socp passsocp socp",
ModelAPIAcceptsQuadraticCones()>1 ?
"0/1*: Recognize quadratic cones vs passing them "
"as pure quadratic constraints." :
"0*/1: Recognize quadratic cones vs passing them "
"as pure quadratic constraints.",
options_.passSOCPCones_, 0, 1);
options_.passSOCPCones_ = ModelAPIAcceptsQuadraticCones()>1;
GetEnv().AddOption("alg:relax relax",
GetEnv().AddOption("cvt:expcones expcones",
ModelAPIAcceptsExponentialCones()>1 ?
"0/1*: Recognize exponential cones." :
"0*/1: Recognize exponential cones.",
options_.passExpCones_, 0, 1);
options_.passExpCones_ = ModelAPIAcceptsExponentialCones()>1;
// Should be after construction
socp_mode_text_ =
"Second-Order Cone recognition mode:\n"
"\n.. value-table::\n"
"Recognized SOCP forms can be further converted to "
"(SOCP-standardized) quadratic constraints, see cvt:socp2qc. "
"Default: " + std::to_string(DefaultSOCPMode()) + ".";
GetEnv().AddStoredOption("cvt:socp socpmode socp",
socp_mode_text_.c_str(),
options_.passSOCPCones_, socp_values_);
options_.passSOCPCones_ = DefaultSOCPMode();
socp2qc_mode_text_ =
"Mode to convert recognized SOCP forms to "
"SOCP-standardized quadratic constraints:\n"
"\n.. value-table::\n"
"Such conversion can be necessary "
"if the solver does not accept "
"a mix of conic and quadratic constraints/objectives. "
"Default: " + std::to_string(DefaultSOCP2QCMode()) + ".";
GetEnv().AddStoredOption("cvt:socp2qc socp2qcmode socp2qc",
socp2qc_mode_text_.c_str(),
options_.passSOCP2QC_, socp2qc_values_);
options_.passSOCP2QC_ = DefaultSOCP2QCMode();
GetEnv().AddOption("alg:relax relax",
"0*/1: Whether to relax integrality of variables.",
options_.relax_, 0, 1);
GetEnv().AddStoredOption(
Expand Down Expand Up @@ -1125,11 +1182,19 @@ class FlatConverter :
bool IfQuadratizePowConstPosIntExp() const
{ return options_.passQuadCon_; }

/// Whether we pass SOCP cones
bool IfPassSOCPCones() const { return options_.passSOCPCones_; }
/// Recognition mode for SOCP cones
int IfPassSOCPCones() const { return options_.passSOCPCones_; }

/// Mode for SOCP -> QC conversion
int SOCP2QCMode() const { return options_.passSOCP2QC_; }

/// Whether we pass exp cones
bool IfPassExpCones() const { return options_.passExpCones_; }
/// Decide to convert SOCP -> QC
void Setup2ConvertSOCP2QC() { ifCvtSOCP2QC_=true; }
/// If decided to convert SOCP -> QC
bool IfConvertSOCP2QC() const { return ifCvtSOCP2QC_; }

/// Recognition mode for exp cones
int IfPassExpCones() const { return options_.passExpCones_; }


public:
Expand Down Expand Up @@ -1180,7 +1245,8 @@ class FlatConverter :
ConicConverter<Impl> conic_cvt_ { *static_cast<Impl*>(this) };
int nQC2SOCPAttempted_= 0;
int nQC2SOCPSucceeded_= 0;

int nExpConesRecognized_ = 0;
bool ifCvtSOCP2QC_ = 0;

std::vector<int> refcnt_vars_;
int constr_depth_ = 0; // tree depth of new constraints
Expand Down
15 changes: 13 additions & 2 deletions include/mp/flat/converter_model.h
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,25 @@ class FlatModel

///////////////////////////// OBJECTIVES ////////////////////////////
public:
/// List of objectives
using ObjList = std::vector<QuadraticObjective>;
/// Get list of objectives, const
const ObjList& get_objectives() const { return objs_; }
/// Get list of objectives
ObjList& get_objectives() { return objs_; }
/// N obj
int num_objs() const { return (int)objs_.size(); }
/// Get obj [i]
const QuadraticObjective& get_obj(int i) const
{ return get_objectives().at(i); }

public:
/// Has a QP objective?
bool HasQPObjective() const {
for (const auto& obj: get_objectives())
if (!obj.GetQPTerms().empty())
return true;
return false;
}
/// Add an objective
void AddObjective(QuadraticObjective&& obj)
{ get_objectives().push_back(std::move(obj)); }

Expand Down
5 changes: 5 additions & 0 deletions include/mp/flat/model_api_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@ class BasicFlatModelAPI {
/// (entered via dedicated API) and direct quadratic constraints
static constexpr bool CanMixConicQCAndQC() { return false; }

/// Ask if the solver can recognize SOCP corner cases
/// (non-std representations such as xy>=1, see tests)
/// from quadratic representations
static constexpr bool CanSOCPCornerCasesFromQC() { return false; }


private:
const FlatModelInfo* pfmi_ { nullptr };
Expand Down
105 changes: 67 additions & 38 deletions include/mp/flat/redef/conic/cones.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace mp {
///
/// @param MCType: ModelConverter, e.g., FlatConverter.
/// @param Con: the source constraint type.
/// @param ConvertBase: another class
/// @param CvtBase: another class
/// providing conversion methods DoRun
/// for quadratic and linear constraints.
template <class MCType, class Con, template <class > class CvtBase>
Expand Down Expand Up @@ -77,51 +77,38 @@ class ConicConverter : public MCKeeper<MCType> {
// We _could_ walk everything just once
// and see which cones are there.
// Or, even walk expression trees from exponents, etc.
RunQCones();
RunExpCones();
// But we walk & convert at once, because later we can
// reconvert SOCP to QC in std forms.
if (MC().IfPassSOCPCones() >= 2)
RunQConesFromQC();
if (MC().IfPassSOCPCones() >= 1)
RunQConesFromNonQC();
if (MC().IfPassExpCones())
RunExpCones();
if (IfNeedSOCP2QC())
SetupSOCP2QC();
WarnOnMix();
}


protected:
void RunQCones() {
if (MC().IfPassSOCPCones()) { // convert everything to QuadraticCones.
Walk<QuadConRange, Convert1QC>();
Walk<QuadConLE, Convert1QC>();
Walk<QuadConGE, Convert1QC>();

if (MC().GetNumberOfAddable((PowConstraint*)0)>0 ||
MC().GetNumberOfAddable((AbsConstraint*)0)>0) {
Walk<LinConRange, Convert1QC>();
Walk<LinConLE, Convert1QC>();
Walk<LinConGE, Convert1QC>();
}
void RunQConesFromQC() {
Walk<QuadConRange, Convert1QC>();
Walk<QuadConLE, Convert1QC>();
Walk<QuadConGE, Convert1QC>();
}

if ( ! MC().ModelAPICanMixConicQCAndQC()) { // cannot mix
if (MC().NumQC2SOCPAttempted() > MC().NumQC2SOCPSucceeded()
&& MC().NumQC2SOCPSucceeded()) {
MC().AddWarning("Mix QC+SOCP",
"Not all quadratic constraints could "
"be recognized\nas quadratic cones; "
"solver might not accept the model.\n"
"Try option cvt:socp=0 to leave all "
"as quadratic.");
}
}
} else
if (MC().IfPassQuadCon() &&
(MC().GetNumberOfAddable((PowConstraint*)0)>0 ||
MC().GetNumberOfAddable((AbsConstraint*)0)>0)) {
// Still collect QCones expressed by 2-norms.
// They are to be converted to quadratics.
Walk<LinConRange, Convert1QC>();
Walk<LinConLE, Convert1QC>();
Walk<LinConGE, Convert1QC>();
}
void RunQConesFromNonQC() {
if (MC().GetNumberOfAddable((PowConstraint*)0)>0 ||
MC().GetNumberOfAddable((AbsConstraint*)0)>0) {
Walk<LinConRange, Convert1QC>();
Walk<LinConLE, Convert1QC>();
Walk<LinConGE, Convert1QC>();
}
}

void RunExpCones() {
if (MC().IfPassExpCones() && // convert everything to ExpCones.
MC().GetNumberOfAddable((ExpConstraint*)0)>0) {
if (MC().GetNumberOfAddable((ExpConstraint*)0)>0) {
Walk<QuadConRange, Convert1ExpC>();
Walk<QuadConLE, Convert1ExpC>(); // also ExpA ??
Walk<QuadConGE, Convert1ExpC>();
Expand All @@ -132,6 +119,47 @@ class ConicConverter : public MCKeeper<MCType> {
}
}

bool IfNeedSOCP2QC() {
return
MC().SOCP2QCMode() >= 2 // compulsory
|| (1 == MC().SOCP2QCMode()
&& 0 == MC().NumExpConesRecognized()
// Might also have SOCP from sqrt(), abs().
// Some QC -> SOCP but not all, even if 0 succeeded.
&& (MC().NumQC2SOCPAttempted() > MC().NumQC2SOCPSucceeded()
|| MC().HasQPObjective()) // or a quadratic obj
); // Mosek 10 considers QP obj as a constraint for this
}

void SetupSOCP2QC() {
MC().Setup2ConvertSOCP2QC();
}

void WarnOnMix() {
if ( !MC().ModelAPICanMixConicQCAndQC()) { // cannot mix
if ((MC().NumExpConesRecognized() // exp cones
&& (MC().NumQC2SOCPAttempted() > MC().NumQC2SOCPSucceeded()
|| MC().HasQPObjective())) // and quadratics left in
|| // Some QC -> SOCP but not all
(((MC().NumQC2SOCPAttempted() > MC().NumQC2SOCPSucceeded()
&& !MC().IfConvertSOCP2QC()) // and not decided to convert
|| MC().HasQPObjective()) // or a quadratic obj
&& MC().NumQC2SOCPSucceeded()) // Warn only if some succeeded
) {
MC().AddWarning("Mix QC+cones",
"Not all quadratic constraints could "
"be recognized\nas quadratic cones; "
"or, the objective is quadratic;\n"
"additionally, further convertion back to QC\n"
"not desired (option cvt:socp2qc) or other cone types present;\n"
"solver might not accept the model.\n"
"Try to express all SOCP cones in standard forms,\n"
"not in the objective.\n"
"See mp.ampl.com/model-guide.html#");
}
}
}

/// Walk a single constraint type
template <class Con, template <class > class CvtBase>
void Walk() {
Expand Down Expand Up @@ -791,6 +819,7 @@ class Convert1ExpC : public MCKeeper<MCType> {
}
MC().AddConstraint(
ExponentialConeConstraint(args, coefs));
MC().IncExpConeCounter();
return true;
}

Expand Down
6 changes: 2 additions & 4 deletions include/mp/flat/redef/conic/qcones2qc.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ class QConeConverter :
/// Check whether the constraint
/// needs to be converted despite being accepted by ModelAPI.
bool IfNeedsConversion(const ItemType& , int ) {
return 0==GetMC().IfPassSOCPCones() &&
0!=GetMC().IfPassQuadCon();
return GetMC().IfConvertSOCP2QC();
}

/// Convert to
Expand Down Expand Up @@ -63,8 +62,7 @@ class RQConeConverter :
/// Check whether the constraint
/// needs to be converted despite being accepted by ModelAPI.
bool IfNeedsConversion(const ItemType& , int ) {
return 0==GetMC().IfPassSOCPCones() &&
0!=GetMC().IfPassQuadCon();
return GetMC().IfConvertSOCP2QC();
}

/// Convert to 2(c[0]*x[0]*c[1]*x[1]) >= sum(i>=2)((c[i]*x[i])^2).
Expand Down
5 changes: 5 additions & 0 deletions solvers/gurobi/gurobimodelapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ class GurobiModelAPI :
/// (Gurobi needs option nonconvex=2 for solving)
static constexpr bool AcceptsNonconvexQC() { return true; }

/// Ask if the solver can recognize SOCP corner cases
/// (non-std representations such as xy>=1, see tests)
/// from quadratic representations
static constexpr bool CanSOCPCornerCasesFromQC() { return true; }

/// If using quadratics,
/// QuadCon(LE/EQ/GE) should have 'Recommended'
/// and have an implementation.
Expand Down
Loading

0 comments on commit 155f992

Please sign in to comment.