Skip to content

Commit

Permalink
multiobj=2: force emulation #239
Browse files Browse the repository at this point in the history
  • Loading branch information
glebbelov committed Jun 17, 2024
1 parent 944a9d2 commit f7e6cab
Show file tree
Hide file tree
Showing 16 changed files with 106 additions and 25 deletions.
5 changes: 3 additions & 2 deletions doc/source/features-guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ solver but are emulated by MP).
+------------------------------+-------+--------+---------+--------+---------+------+------+--------+-------+
| Feature | Copt_ | CPLEX_ | Gurobi_ | Mosek_ | Xpress_ | CBC_ | GCG_ | HiGHS_ | SCIP_ |
+==============================+=======+========+=========+========+=========+======+======+========+=======+
| :ref:`writeprob` | |y| | |y| | |y| | |y| | |y| | |y| | |n| | |y| | |n| |
| :ref:`writeprob` | |y| | |y| | |y| | |y| | |y| | |y| | |y| | |y| | |y| |
+------------------------------+-------+--------+---------+--------+---------+------+------+--------+-------+
| Solution Export | |n| | |y| | |y| | |n| | |y| | |n| | |n| | |y| | |n| |
+------------------------------+-------+--------+---------+--------+---------+------+------+--------+-------+
Expand Down Expand Up @@ -974,7 +974,8 @@ for the functionalities available on your solver.
- Values:

* **0** - No (default)
* **1** - Yes
* **1** - Yes, natively supported if available, otherwise emulated
* **2** - Yes, emulated
* - **Example**
- Use :ref:`multiObjectiveDiet`

Expand Down
5 changes: 4 additions & 1 deletion doc/source/modeling-mo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ Multiple objectives
:width: 200
:align: right

To consider multiple objectives in an AMPL model, use
To consider multiple objectives in an AMPL model,
either natively supported by the solver, or emulated, use
:ref:`solver option <solver-options>` ``obj:multi``.
Otherwise, only the 1st objective is considered
(or any objective specified by ``obj:no``.)
Expand Down Expand Up @@ -49,3 +50,5 @@ as described in the ``obj:multi`` option description.
suffix objpriority (2-e)*S_range + 1 + S[i] - min {j in I} S[j];
Suffixes ``.objabstol`` and ``.objreltol`` allow for objective degradation.
However their exact meaning can vary for a solver's native multi-objective
mode (``obj:multi=1``), in particular for LPs. Consult the solver documentation.
2 changes: 1 addition & 1 deletion include/mp/backend-std.h
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ class StdBackend :

/// Standard extras
virtual void InputStdExtras() {
if (multiobj() && multiobj_has_native()) {
if (multiobj()==1 && multiobj_has_native()) { // multiobj native
if (auto suf = ReadSuffix(suf_objpriority))
ObjPriorities( suf );
if (auto suf = GetMM().GetObjWeightsAdapted())
Expand Down
5 changes: 3 additions & 2 deletions include/mp/flat/converter_multiobj.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ class MOManager {
/// @note This should be called first.
void ConsiderEmulatingMultiobj() {
status_ = MOManagerStatus::NOT_ACTIVE;
if (MPCD(num_objs())>1 // have multiple objectives
&& MPCD(GetEnv()).multiobj_has_native()==false)
if (MPCD(num_objs())>1 // have multiple objectives
&& (MPCD(GetEnv()).multiobj_has_native()==false
|| MPCD(GetEnv()).multiobj()>1)) // force emulation
SetupMultiobjEmulation();
// Anything todo otherwise?
}
Expand Down
6 changes: 4 additions & 2 deletions include/mp/solver-base.h
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,11 @@ class BasicSolver : private ErrorHandler,
void notify_end_opts() { opts_read_=true; }

/// Returns true if multiobjective optimization is enabled.
/// In particular, 1 if enabled and preferred native handling,
/// or 2 if forcing emulation.
/// Both multiobj and objno are used in NLReader to select
/// the objective(s), solvers should not use these options.
bool multiobj() const { return multiobj_ && objno_<0; }
int multiobj() const { return (objno_<0) ? multiobj_ : 0; }

/// Whether the solver natively supports multiobj
bool multiobj_has_native() const { return multiobj_has_native_; }
Expand Down Expand Up @@ -612,7 +614,7 @@ class BasicSolver : private ErrorHandler,
Stats stats_;


bool multiobj_ {false};
int multiobj_ {0};
bool multiobj_has_native_ {false};
int multiobj_weight_ {2};

Expand Down
6 changes: 6 additions & 0 deletions solvers/cplexmp/cplexmpmodelapi.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,14 @@ void CplexModelAPI::SetLinearObjective( int iobj, const LinearObjective& lo ) {
CPLEX_CALL( CPXchgobjsen (env(), lp(),
obj::Type::MAX==lo.obj_sense() ? CPX_MAX : CPX_MIN) );
NoteCPLEXMainObjSense(lo.obj_sense());
if (obj_ind_save_.size()) {
std::vector<double> obj_coef_0(obj_ind_save_.size(), 0.0);
CPLEX_CALL( CPXchgobj (env(), lp(), obj_ind_save_.size(),
obj_ind_save_.data(), obj_coef_0.data()) );
}
CPLEX_CALL( CPXchgobj (env(), lp(), lo.num_terms(),
lo.vars().data(), lo.coefs().data()) );
obj_ind_save_ = lo.vars();
} else {
CPXsetnumobjs(env(), lp(), iobj+1);
auto status = CPXmultiobjsetobj(env(), lp(), iobj,
Expand Down
3 changes: 2 additions & 1 deletion solvers/cplexmp/cplexmpmodelapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,9 @@ class CplexModelAPI :
ACCEPT_CONSTRAINT(PLConstraint, Recommended, CG_General)
void AddConstraint(const PLConstraint& cc);

private:
private:
obj::Type main_obj_sense_;
std::vector<int> obj_ind_save_; // to zero out last objective

protected:
/// First objective's sense
Expand Down
5 changes: 1 addition & 4 deletions solvers/gurobi/gurobibackend.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2102,10 +2102,7 @@ void GurobiBackend::InitCustomOptions() {
/// std feature MULTIOBJ is set.
/// Change the help text
AddToOptionDescription("obj:multi",
"Degradation suffixes .objabstol, .objreltol are handled "
"differently for LP, see Gurobi documentation.\n\n"
"The objectives must all be linear.\n\n"
"Objective-specific "
"For Gurobi's native handling (obj:multi=1), objective-specific "
"tolerances and method values may be assigned via "
"keywords of the form obj_n_<name>, such as obj_1_method for the "
"first objective.");
Expand Down
6 changes: 6 additions & 0 deletions solvers/gurobi/gurobimodelapi.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ void GurobiModelAPI::SetLinearObjective( int iobj, const LinearObjective& lo ) {
GrbSetIntAttr( GRB_INT_ATTR_MODELSENSE,
obj::Type::MAX==lo.obj_sense() ? GRB_MAXIMIZE : GRB_MINIMIZE);
NoteGurobiMainObjSense(lo.obj_sense());
if (obj_ind_save_.size()) {
std::vector<double> obj_coef_0(obj_ind_save_.size(), 0.0);
GrbSetDblAttrList( GRB_DBL_ATTR_OBJ, obj_ind_save_, obj_coef_0 );
}
GrbSetDblAttrList( GRB_DBL_ATTR_OBJ, lo.vars(), lo.coefs() );
obj_ind_save_ = lo.vars();
} else {
GRB_CALL( GRBsetobjectiven(model(), iobj, 0, // default priority 0
/// Gurobi allows opposite sense by weight sign
Expand All @@ -47,6 +52,7 @@ void GurobiModelAPI::SetLinearObjective( int iobj, const LinearObjective& lo ) {

void GurobiModelAPI::SetQuadraticObjective(int iobj, const QuadraticObjective &qo) {
if (1>iobj) {
GRB_CALL( GRBdelq(model()) ); // delete current QP terms
SetLinearObjective(iobj, qo); // add the linear part
const auto& qt = qo.GetQPTerms();
GRB_CALL( GRBaddqpterms(model(), qt.size(),
Expand Down
3 changes: 3 additions & 0 deletions solvers/gurobi/gurobimodelapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ class GurobiModelAPI :
/// The sense of the main objective
obj::Type main_obj_sense_;

/// To zero out last objective
std::vector<int> obj_ind_save_;

/// These options are stored in the class as variables
/// for direct access
struct Options {
Expand Down
20 changes: 17 additions & 3 deletions solvers/xpress/xpressmodelapi.cc
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,13 @@ void XpressmpModelAPI::SetLinearObjective( int iobj, const LinearObjective& lo )
if (iobj<1) {
if (lo.obj_sense() == obj::Type::MAX)
XPRESSMP_CCALL(XPRSchgobjsense(lp(), XPRS_OBJ_MAXIMIZE));
if (obj_ind_save_.size()) {
std::vector<double> obj_coef_0(obj_ind_save_.size(), 0.0);
XPRESSMP_CCALL(XPRSchgobj(lp(), obj_ind_save_.size(),
obj_ind_save_.data(), obj_coef_0.data()));
}
XPRESSMP_CCALL(XPRSchgobj(lp(), lo.num_terms(), lo.vars().data(), lo.coefs().data()));

obj_ind_save_ = lo.vars();
} else {
// All objectives must have the same sense, so we will have to automatically
// set a conflicting objective's weight to -1
Expand All @@ -85,17 +90,26 @@ void XpressmpModelAPI::SetLinearObjective( int iobj, const LinearObjective& lo )

void XpressmpModelAPI::SetQuadraticObjective(int iobj, const QuadraticObjective& qo) {
if (1 > iobj) {
fmt::format("Setting first quadratic objective\n");
// fmt::format("Setting first quadratic objective\n");
SetLinearObjective(iobj, qo); // add the linear part
const auto& qt = qo.GetQPTerms();
std::vector<double> coeffs(qt.coefs());
for (std::size_t i = 0; i < qt.size(); i++)
if (qt.pvars1()[i] == qt.pvars2()[i]) coeffs[i] *= 2;

fmt::format("Quadratic part is made of {} terms\n", qt.size());
if (qobj_ind1_save_.size()) {
assert(qobj_ind1_save_.size()==qobj_ind2_save_.size());
std::vector<double> qobj_coef_0(qobj_ind1_save_.size(), 0.0);
XPRESSMP_CCALL(XPRSchgmqobj(lp(), qobj_ind1_save_.size(),
qobj_ind1_save_.data(), qobj_ind2_save_.data(),
qobj_coef_0.data()));
}
// fmt::format("Quadratic part is made of {} terms\n", qt.size());
XPRESSMP_CCALL(XPRSchgmqobj(lp(), qt.size(),
(int*)qt.pvars1(), (int*)qt.pvars2(),
(double*)coeffs.data()));
qobj_ind1_save_ = qt.vars1();
qobj_ind2_save_ = qt.vars2();
}
else {
throw std::runtime_error("Multiple quadratic objectives not supported");
Expand Down
4 changes: 4 additions & 0 deletions solvers/xpress/xpressmodelapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ class XpressmpModelAPI :

void AddGlobalConstraint(NLParams& params);
void AddGlobalConstraint(int resultVar, int argumentVar, int functionId);


private:
std::vector<int> obj_ind_save_, qobj_ind1_save_, qobj_ind2_save_;
};

} // namespace mp
Expand Down
20 changes: 15 additions & 5 deletions src/solver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -667,11 +667,16 @@ void BasicSolver::InitMetaInfoAndOptions(
"0*/1: whether to assist testing & debugging, e.g., "
"by outputting auxiliary information.")));

static const mp::OptionValueInfo values_multiobj_[] = {
{ "0", "Single objective, see option obj:no (default)", 0},
{ "1", "Multi-objective, solver's native handling if available", 1},
{ "2", "Multi-objective, force emulation", 2}
};
if ((flags & MULTIPLE_OBJ) != 0) {
AddOption(OptionPtr(new BoolOption(multiobj_, "obj:multi multiobj",
"0*/1: Whether to use multi-objective optimization.\n"
"\n"
"When obj:multi = 1 and several objectives are present, suffixes "
AddStoredOption("obj:multi multiobj",
"Whether to use multi-objective optimization:\n"
"\n.. value-table::\n\n"
"When obj:multi>0 and several objectives are present, suffixes "
".objpriority, .objweight, .objreltol, and .objabstol on the "
"objectives are relevant. Objectives with greater .objpriority "
"values (integer values) have higher priority. Objectives with "
Expand All @@ -681,7 +686,12 @@ void BasicSolver::InitMetaInfoAndOptions(
"Objectives "
"with positive .objabstol or .objreltol are allowed to be "
"degraded by lower priority objectives by amounts not exceeding "
"the .objabstol (absolute) and .objreltol (relative) limits. ")));
"the .objabstol (absolute) and .objreltol (relative) limits.\n"
"\n"
"Note that with solver's native handling (when obj:multi=1 and supported), "
"some solvers might have special rules for the tolerances, especially for LP, "
"and not allow quadratic objectives. See the solver documentation.",
multiobj_, values_multiobj_);

static const mp::OptionValueInfo values_multiobjweight_[] = {
{ "1", "relative to the sense of the 1st objective", 1},
Expand Down
33 changes: 31 additions & 2 deletions test/end2end/cases/categorized/fast/multi_obj/modellist.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@
"_sobj[4]": 32.604395604395734
}
},
{
"name" : "dietobj_1000 multiobj=2 obj_pr_1",
"Comment": "Modify obj priorities and weights via suffixes in dietobj_pr_1",
"tags" : ["linear", "continuous", "multiobj"],
"files" : ["dietobj_1000.mod", "dietobj.dat", "dietobj_pr_1.inc"],
"options": { "ANYSOLVER_options": "multiobj=2" },
"values": {
"total_cost[\"A&P\"]": 924.1546153846151,
"total_cost[\"JEWEL\"]": 925.6467032967034,
"total_cost[\"VONS\"]": 918.8280219780219,
"total_number": 32.604395604395734,
"_sobj[4]": 32.604395604395734
}
},
{
"name" : "dietobj_1000 multiobj=1 obj_pr_2",
"Comment": "Modify obj priorities and weights via suffixes in dietobj_pr_2",
Expand Down Expand Up @@ -92,7 +106,8 @@
"tags" : ["linear", "integer", "multiobj"],
"options": {
"ANYSOLVER_options": "multiobj=1",
"gurobi_options": "multiobj=1 mip:gap=0"
"gurobi_options": "multiobj=1 mip:gap=0",
"xpress_options": "multiobj=1 mip:gap=0"
},
"values": {
"_sobj[1]": 2.75,
Expand All @@ -104,7 +119,21 @@
"tags" : ["linear", "integer", "multiobj"],
"options": {
"ANYSOLVER_options": "multiobj=1",
"gurobi_options": "multiobj=1 mip:gap=0"
"gurobi_options": "multiobj=1 mip:gap=0",
"xpress_options": "multiobj=1 mip:gap=0"
},
"values": {
"_sobj[1]": 2.75,
"_sobj[2]": 3.5
}
},
{
"name" : "multiobj_reltol_MIP multiobj=2",
"tags" : ["linear", "integer", "multiobj"],
"options": {
"ANYSOLVER_options": "multiobj=2",
"gurobi_options": "multiobj=1 mip:gap=0",
"xpress_options": "multiobj=1 mip:gap=0"
},
"values": {
"_sobj[1]": 2.75,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ subj to c5_keep_z: y >= z;

suffix objpriority IN;
suffix objabstol IN;
suffix objreltol IN; # Add for XPRESS, all 0

let xobj.objpriority := 10;
let yobj.objpriority := 1;

let xobj.objabstol := 0.25;
let xobj.objreltol := 1e-10; # To have the suffix

# Need mip:gap=0 due to the way tolerances are applied:
# https://www.gurobi.com/documentation/current/refman/working_with_multiple_obje.html.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ subj to c4: y >= 2*x - 2;
subj to c5_keep_z: y >= z;

suffix objpriority IN;
suffix objreltol IN;
suffix objreltol IN;
suffix objabstol IN; # For XPRESS, all 0

let xobj.objpriority := 10;
let yobj.objpriority := 1;

let xobj.objreltol:= 0.25/3;
let xobj.objreltol:= 0.25/3;
let xobj.objabstol := 1e-10; # To have the suffix

# Need mip:gap=0 due to the way tolerances are applied:
# https://www.gurobi.com/documentation/current/refman/working_with_multiple_obje.html.
Expand Down

0 comments on commit f7e6cab

Please sign in to comment.