diff --git a/include/mp/backend-std.h b/include/mp/backend-std.h index 52ec088cf..ba20ecf79 100644 --- a/include/mp/backend-std.h +++ b/include/mp/backend-std.h @@ -287,7 +287,7 @@ class StdBackend : /// Standard extras virtual void InputStdExtras() { - if (multiobj()) { + if (multiobj() && multiobj_has_native()) { if (auto suf = ReadSuffix(suf_objpriority)) ObjPriorities( suf ); if (auto suf = ReadSuffix(suf_objweight)) diff --git a/include/mp/flat/converter.h b/include/mp/flat/converter.h index 101d6faf1..8289346f9 100644 --- a/include/mp/flat/converter.h +++ b/include/mp/flat/converter.h @@ -10,6 +10,7 @@ #include "mp/env.h" #include "mp/format.h" #include "mp/solver-base.h" +#include "mp/suffix.h" #include "mp/flat/converter_model.h" #include "mp/flat/convert_functional.h" #include "mp/flat/constr_keeper.h" @@ -1294,6 +1295,22 @@ class FlatConverter : std::move(key), std::move(msg), replace); } + /// Provide suffix getters + void SetSuffixGetters( + std::function(const SuffixDef& )> sgi, + std::function(const SuffixDef& )> sgd) + { suf_get_int_=sgi; suf_get_dbl_=sgd; } + + +public: + /// Read int suffix + ArrayRef ReadIntSuffix(const SuffixDef& sd) + { assert(suf_get_int_); return suf_get_int_(sd); } + + /// Read double suffix + ArrayRef ReadDblSuffix(const SuffixDef& sd) + { assert(suf_get_dbl_); return suf_get_dbl_(sd); } + private: /// We store ModelApi in the converter for speed. @@ -1301,6 +1318,10 @@ class FlatConverter : ModelAPIType modelapi_; /// solve iteration int n_solve_iter_ {0}; + /// Suffix getter int + std::function(const SuffixDef& )> suf_get_int_; + /// Suffix getter double + std::function(const SuffixDef& )> suf_get_dbl_; /// ValuePresolver: should be init before constraint keepers /// and links pre::ValuePresolver value_presolver_ diff --git a/include/mp/flat/converter_multiobj.h b/include/mp/flat/converter_multiobj.h index 3dbec2ec2..2c0c5c1ce 100644 --- a/include/mp/flat/converter_multiobj.h +++ b/include/mp/flat/converter_multiobj.h @@ -104,15 +104,56 @@ class MOManager { MPD(set_skip_pushing_objs()); // could have a cleaner system of linking // via custom link restoring original objective values, // instead of current manual postsolving in ValuePresolver::PostsolveSolution(). - obj_new_ = MPD( get_objectives() ); // no linking + const auto& obj_orig = MPD( get_objectives() ); // no linking + ///////////////// Read / set default suffixes /////////////////// + std::vector objpr = MPD( ReadDblSuffix( {"objpriority", suf::OBJ} ) ); + objpr.resize(obj_orig.size(), 0.0); // blend objectives by default + std::vector objwgt = MPD( ReadDblSuffix( {"objweight", suf::OBJ} ) ); + objwgt.resize(obj_orig.size(), 1.0); + std::vector objtola = MPD( ReadDblSuffix( {"objabstol", suf::OBJ} ) ); + objtola.resize(obj_orig.size(), 0.0); + std::vector objtolr = MPD( ReadDblSuffix( {"objreltol", suf::OBJ} ) ); + objtolr.resize(obj_orig.size(), 0.0); + std::map, std::greater > pr_map; // Decreasing order + for (int i=0; i obj_new_; // ranked aggregated objectives + std::vector obj_new_tola_; + std::vector obj_new_tolr_; int i_current_obj_ {-1}; double objval_last_ {}; }; diff --git a/include/mp/flat/problem_flattener.h b/include/mp/flat/problem_flattener.h index c5c2fc1ff..c074e50e8 100644 --- a/include/mp/flat/problem_flattener.h +++ b/include/mp/flat/problem_flattener.h @@ -118,6 +118,9 @@ class ProblemFlattener : public: /// Convert the whole model, e.g., after reading from NL void ConvertModel() override { + GetFlatCvt().SetSuffixGetters( + [this](const SuffixDef& sd) { return GetModel().ReadIntSuffix(sd); }, + [this](const SuffixDef& sd) { return GetModel().ReadDblSuffix(sd); }); GetFlatCvt().StartModelInput(); MP_DISPATCH( ConvertStandardItems() ); GetFlatCvt().FinishModelInput(); // Chance to flush to the Backend diff --git a/include/mp/problem.h b/include/mp/problem.h index ed8d3060f..1080aa27e 100644 --- a/include/mp/problem.h +++ b/include/mp/problem.h @@ -1220,8 +1220,10 @@ class BasicProblem : public ExprFactory, public SuffixManager { /// take that ArrayRef ReadDblSuffix(const SuffixDef& sufdef) { auto suf_dbl = ReadSuffix_OneTypeOnly(sufdef); + std::printf(" SUFD %s: %ld elements\n", sufdef.name(), suf_dbl.size()); if (!suf_dbl) { auto suf_int = ReadSuffix_OneTypeOnly(sufdef.to_type()); + std::printf(" SUFI %s: %ld elements\n", sufdef.name(), suf_int.size()); if (suf_int) return std::vector(suf_int.begin(), suf_int.end()); } diff --git a/include/mp/solver-opt.h b/include/mp/solver-opt.h index 1535feac9..fe8d980f1 100644 --- a/include/mp/solver-opt.h +++ b/include/mp/solver-opt.h @@ -146,6 +146,7 @@ class SolverOption { { return inline_synonyms_; } /// Add additional "inline" synonyms void add_synonyms_front(const char* names_list); + /// Add "inline" synonyms to the back void add_synonyms_back(const char* names_list); /// Is hidden? @@ -155,19 +156,25 @@ class SolverOption { bool is_wildcard() const { return wc_headtails_.size(); } /// Checks if matches, then saves key & body bool wc_match(const std::string& key); + /// Wildcard head const std::string& wc_head() const { assert(is_wildcard()); return wc_headtails_[0].first; } + /// Wildcard tail const std::string& wc_tail() const { assert(is_wildcard()); return wc_headtails_[0].second; } + /// Last wildcard key const std::string& wc_key_last() const { return wc_key_last_; } + /// Last wildcard keybody const std::string& wc_keybody_last() const { return wc_body_last_; } /// Printing last parsed wc key in std form std::string wc_key_last__std_form() const { return wc_head() + wc_body_last_ + wc_tail(); } - /// Return/set the option description. + /// Option description. const char *description() const { return description_.c_str(); } + /// Set option description void set_description(const char* d) { description_=d; } + /// Add to option description void add_to_description(const char* d) { description_ += d; } /// Append the formatted description to the writer diff --git a/solvers/gurobi/gurobibackend.cc b/solvers/gurobi/gurobibackend.cc index ed60333ad..b088ab654 100644 --- a/solvers/gurobi/gurobibackend.cc +++ b/solvers/gurobi/gurobibackend.cc @@ -2101,16 +2101,7 @@ void GurobiBackend::InitCustomOptions() { /// Option "multiobj" is created internally if /// std feature MULTIOBJ is set. /// Change the help text - ReplaceOptionDescription("obj:multi", - "0*/1: Whether to do multi-objective optimization.\n" - "When obj:multi = 1 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 " - "the same .objpriority are weighted by .objweight. 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. " + AddToOptionDescription("obj:multi", "The objectives must all be linear. Objective-specific " "convergence tolerances and method values may be assigned via " "keywords of the form obj_n_, such as obj_1_method for the " diff --git a/solvers/highsmp/highsmpmodelapi.cc b/solvers/highsmp/highsmpmodelapi.cc index d03b7af83..cbea3dbe7 100644 --- a/solvers/highsmp/highsmpmodelapi.cc +++ b/solvers/highsmp/highsmpmodelapi.cc @@ -36,6 +36,7 @@ void HighsModelAPI::SetLinearObjective( int iobj, const LinearObjective& lo ) { for (auto i=lo.vars().size(); i--; ) objc_new[lo.vars()[i]] = lo.coefs()[i]; HIGHS_CCALL(Highs_changeColsCostByRange(lp(), 0, NumVars()-1, objc_new.data())); + std::printf(" HIGHS OPBJ SENSE: %d \n", lo.obj_sense()); HIGHS_CCALL(Highs_changeObjectiveSense(lp(), obj::Type::MAX==lo.obj_sense() ? kHighsObjSenseMaximize : kHighsObjSenseMinimize) ); diff --git a/solvers/visitor/visitormodelapi.h b/solvers/visitor/visitormodelapi.h index 33a1cb908..153e26143 100644 --- a/solvers/visitor/visitormodelapi.h +++ b/solvers/visitor/visitormodelapi.h @@ -31,12 +31,14 @@ class VisitorModelAPI : /// After void FinishProblemModificationPhase(); - /// TODO Implement the following functions using the solver's API + /// TODO Implement adding variables void AddVariables(const VarArrayDef& ); + /// TODO Implement setting (also changing) a linear (part of the) objective void SetLinearObjective( int iobj, const LinearObjective& lo ); /// Whether accepting quadratic objectives: /// 0 - no, 1 - convex, 2 - nonconvex static int AcceptsQuadObj() { return 0; } + /// TODO Implement setting (also changing) a quadratic objective void SetQuadraticObjective(int iobj, const QuadraticObjective& qo); //////////////////////////// GENERAL CONSTRAINTS //////////////////////////// diff --git a/src/solver.cc b/src/solver.cc index 03de941d9..bf80c777f 100644 --- a/src/solver.cc +++ b/src/solver.cc @@ -646,10 +646,16 @@ void BasicSolver::InitMetaInfoAndOptions( if ((flags & MULTIPLE_OBJ) != 0) { AddOption(OptionPtr(new BoolOption(multiobj_, "obj:multi multiobj", - "0*/1: Whether to use multi-objective optimization. " - "If set to 1 multi-objective optimization is performed using " - "lexicographic method with the first objective treated as the most " - "important, then the second objective and so on."))); + "0*/1: Whether to use multi-objective optimization.\n" + "\n" + "When obj:multi = 1 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 " + "the same .objpriority are weighted by .objweight. 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. " ))); } AddIntOption("tech:timing timing tech:report_times report_times", diff --git a/test/end2end/cases/categorized/fast/multi_obj/modellist.json b/test/end2end/cases/categorized/fast/multi_obj/modellist.json index d25476b40..8e377779b 100644 --- a/test/end2end/cases/categorized/fast/multi_obj/modellist.json +++ b/test/end2end/cases/categorized/fast/multi_obj/modellist.json @@ -45,8 +45,23 @@ "_sobj[4]": 53.19255547602 } }, + { + "name" : "dietobj_1000 multiobj=1 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=1" }, + "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 multiobj=1 obj:2:priority=10", + "Comment": "Modify obj priority via an option", "tags" : ["linear", "continuous", "multiobj", "obj_priority"], "files" : ["dietobj.mod", "dietobj.dat"], "options": { "ANYSOLVER_options": "multiobj=1 obj:2:priority=10" }, diff --git a/test/end2end/scripts/python/Model.py b/test/end2end/scripts/python/Model.py index b6b5540a9..ce0a99db8 100644 --- a/test/end2end/scripts/python/Model.py +++ b/test/end2end/scripts/python/Model.py @@ -51,7 +51,7 @@ class ModelTags(enum.Enum): mipstart=20008 multiobj=30009 - obj_priority=30010 + obj_priority=30010 # understands obj:2:priority etc. multisol = 40011 sstatus = 40012 # Basis I/O diff --git a/test/end2end/scripts/python/Solver.py b/test/end2end/scripts/python/Solver.py index 271bc6390..93a09c1e0 100644 --- a/test/end2end/scripts/python/Solver.py +++ b/test/end2end/scripts/python/Solver.py @@ -474,7 +474,11 @@ def __init__(self, exeName, timeout=None, nthreads=None, otherOptions=None): stags = {ModelTags.continuous, ModelTags.integer, ModelTags.binary, ModelTags.linear, ModelTags.quadratic, ModelTags.sos, ModelTags.return_mipgap, - ModelTags.sstatus} + ModelTags.sstatus, + ModelTags.multisol, + ModelTags.multiobj, + ModelTags.iis, + ModelTags.feasrelax} super().__init__(exeName, timeout, nthreads, otherOptions, stags) def _doParseSolution(self, st, stdout=None): @@ -828,7 +832,8 @@ def __init__(self, exeName, timeout=None, nthreads=None, ModelTags.logical, ModelTags.plinear, ModelTags.nonlinear, - ModelTags.log + ModelTags.log, + ModelTags.multiobj } # Direct/FlatConverter drivers with non-convex quadratics: if ModelTags.quadraticnonconvex in stags: @@ -980,7 +985,7 @@ def __init__(self, exeName, timeout=None, nthreads=None, ModelTags.return_mipgap, ModelTags.warmstart, ModelTags.mipstart, - ModelTags.multiobj, ModelTags.obj_priority, + ModelTags.multiobj, ModelTags.multisol, ModelTags.sstatus, ModelTags.iis, ModelTags.fixmodel,