diff --git a/CHANGES.mp.md b/CHANGES.mp.md index bbe4dacc8..1a8d2345a 100644 --- a/CHANGES.mp.md +++ b/CHANGES.mp.md @@ -3,6 +3,8 @@ Summary of recent updates to the AMPL MP Library ## unreleased +- Option tech:writemodel:index to choose the iteration + when solver model is exported. - SCIP (and any solver with linear objective and non-linear constraints): improve reformulation of QP objectives. diff --git a/doc/source/model-guide.rst b/doc/source/model-guide.rst index 5b1cb9b17..7b8613253 100644 --- a/doc/source/model-guide.rst +++ b/doc/source/model-guide.rst @@ -13,9 +13,9 @@ Ever wondered how to model logical and non-linear constraints? For example: - Piecewise-linear approximation of *y = sin(x)*. -Moreover, MP supports :ref:`multiple-objectives`. MP automates many of such modeling tasks by reformulating AMPL models suitably for the given solver. +Moreover, MP supports :ref:`multiple-objectives`. A series of small modeling tasks like these are handled in the `MP Modeling Series `_, while below follows a comprehensive guide. diff --git a/include/mp/ampls-ccallbacks.h b/include/mp/ampls-ccallbacks.h index 85aa41e1b..c60d26ceb 100644 --- a/include/mp/ampls-ccallbacks.h +++ b/include/mp/ampls-ccallbacks.h @@ -17,7 +17,7 @@ typedef struct AMPLS_ModelTraits_T { } AMPLS_ModelTraits; -typedef void (*Checker_AMPLS_ModeltTraits)(const AMPLS_ModelTraits*); +typedef void (*Checker_AMPLS_ModeltTraits)(AMPLS_ModelTraits*); /// Set of callbacks provided to a driver for licensing issues diff --git a/include/mp/backend-std.h b/include/mp/backend-std.h index 094f93735..5174ce48d 100644 --- a/include/mp/backend-std.h +++ b/include/mp/backend-std.h @@ -173,7 +173,7 @@ class StdBackend : /// Redefine this if you want some extensions /// to be written after solving instead. /// This is for compatibility wiht ASL drivers - /// where 'writeprob' was used for native-format model + /// where 'writeprob' was used both for native-format model /// and solution output #218. virtual std::set NativeResultExtensions() const { return {".sol", ".ilp", ".mst", ".hnt", ".bas", ".json"}; } @@ -276,9 +276,11 @@ class StdBackend : auto get_sol = [this]() { return GetSolution(); }; - int i=0; + int i_solve=0; while (GetMM().PrepareSolveIteration(get_stt, get_sol)) { - // ExportModel({"/tmp/model" + std::to_string(++i) + ".lp"}); + if (++i_solve==storedOptions_.writemodel_index_ + && exportFileMode() > 0) + ExportModel(export_file_names()); Solve(); } } @@ -808,6 +810,7 @@ class StdBackend : /// For write prob std::vector export_files_; + int writemodel_index_ = 0; std::vector just_export_files_; /// For write sol std::vector export_sol_files_; @@ -936,12 +939,22 @@ class StdBackend : } if (IMPL_HAS_STD_FEATURE(WRITE_PROBLEM)) { - AddListOption("tech:writemodel writeprob writemodel tech:exportfile", + AddListOption("tech:writemodel tech:writeprob writeprob writemodel tech:exportfile", "Specifies files where to export the model before " "solving (repeat the option for several files.) " - "File name extensions can be ``.lp[.7z]``, ``.mps``, etc.", + "File name extensions can be ``.lp[.7z]``, ``.mps``, etc." + "\n" + "To write a model during iterative solve (e.g., with obj:multi=2), " + "use tech:writemodel:index.", storedOptions_.export_files_); + AddStoredOption("tech:writemodel:index tech:writeprob:index writeprobindex writemodelindex", + "During iterative solve (e.g., with obj:multi=2), " + "the iteration before which to write solver model. " + "0 means before iteration is initialized; positive value - " + "before solving that iteration. Default 0.", + storedOptions_.writemodel_index_); + AddListOption("tech:writemodelonly justwriteprob justwritemodel", "Specifies files where to export the model, no solving " "(option can be repeated.) " diff --git a/include/mp/flat/constr_2_expr.h b/include/mp/flat/constr_2_expr.h index 8c7b929de..dcccec431 100644 --- a/include/mp/flat/constr_2_expr.h +++ b/include/mp/flat/constr_2_expr.h @@ -152,8 +152,9 @@ class Constraints2Expr { bool ConvertWithExpressions( const ComplementarityConstraint& con, int i, - ConstraintAcceptanceLevel , ExpressionAcceptanceLevel ) { // TODO check acc for NLCompl - if (1==stage_cvt2expr_ + ConstraintAcceptanceLevel , ExpressionAcceptanceLevel ) { + if (false // TODO check acc for NLCompl + && 1==stage_cvt2expr_ && !con.GetExpression().is_variable()) { // already a variable ConvertComplementarityExpr(con, i); return true; @@ -353,11 +354,11 @@ class Constraints2Expr { pre::AutoLinkScope auto_link_scope{ *(Impl*)this, obj_src }; if (qobj.GetQPTerms().empty()) exprResVar = MPD( AssignResultVar2Args( - LinearFunctionalConstraint{ {lt_in_expr, 1.0} } ) ); + LinearFunctionalConstraint{ {lt_in_expr, 0.0} } ) ); else // Move QP terms into the expr exprResVar = MPD( AssignResultVar2Args( QuadraticFunctionalConstraint - { {{lt_in_expr, std::move(qobj.GetQPTerms())}, 1.0} } ) ); + { {{lt_in_expr, std::move(qobj.GetQPTerms())}, 0.0} } ) ); MPD( AddInitExprContext(exprResVar, // Context is compulsory obj::MAX==qobj.obj_sense_true() // no need to propagate ? Context::CTX_POS : Context::CTX_NEG) ); @@ -415,11 +416,15 @@ class Constraints2Expr { lt_out_vars.reserve(nvars); result.reserve(ltin.size() - nvars); int v=0; + double c=0.0; for (size_t i=0; i void PropagateResult(ComplementarityConstraint& con, double lb, double ub, Context ctx) { - internal::Unused(lb, ub, ctx); + internal::Unused(ctx); MPD( PropagateResult2Args(con.GetExpression().GetBody(), lb, ub, Context::CTX_MIX) ); MPD( PropagateResultOfInitExpr(con.GetVariable(), lb, ub, Context::CTX_MIX) ); diff --git a/include/mp/flat/converter.h b/include/mp/flat/converter.h index a25f551f6..6d582ec86 100644 --- a/include/mp/flat/converter.h +++ b/include/mp/flat/converter.h @@ -273,13 +273,13 @@ class FlatConverter : constr_depth_ = 1; // Workaround. TODO have maps as special constraints MP_DISPATCH( ConvertMaps() ); MP_DISPATCH( PreprocessFlatFinal() ); // final flat model prepro - MP_DISPATCH( ConsiderEmulatingMultiobj() ); if constexpr (IfAcceptingNLOutput()) { if (IfWantNLOutput()) { MPD( Convert2NL() ); MPD( PreprocessNLFinal() ); } } + MP_DISPATCH( ConsiderEmulatingMultiobj() ); // After NL conversion } catch (const ConstraintConversionFailure& cff) { MP_RAISE(cff.message()); } @@ -310,6 +310,7 @@ class FlatConverter : /// if provided (>=0) int AcceptanceLevelCommon() const { return options_.accAll_; } +public: /// Option to actually use expressions if available bool IfWantNLOutput() const { return options_.accExpr_==1; } @@ -322,6 +323,7 @@ class FlatConverter : ExpressionAcceptanceLevel::Recommended == ModelAPI::ExpressionInterfaceAcceptanceLevel(); } +protected: /// Finish exporting the reformulation graph void CloseGraphExporter() { value_presolver_.FinishExportingLinkEntries(); diff --git a/include/mp/flat/converter_multiobj.h b/include/mp/flat/converter_multiobj.h index 3c4904ee2..188f53b83 100644 --- a/include/mp/flat/converter_multiobj.h +++ b/include/mp/flat/converter_multiobj.h @@ -137,7 +137,7 @@ class MOManager { const auto& i0_vec = pr_level.second; obj_new_.push_back(obj_orig.at(i0_vec.front())); obj_new_.back().set_sense(obj_orig.front().obj_sense()); // "Legacy" obj:multi:weight - obj_new_.back().set_sense_true(obj_orig.front().obj_sense()); + obj_new_.back().set_sense_true(obj_orig.front().obj_sense_true()); obj_new_.back().GetLinTerms() *= objwgt.at(i0_vec.front()); // Use weight obj_new_.back().GetQPTerms() *= objwgt.at(i0_vec.front()); obj_new_tola_.push_back(objtola.at(i0_vec.front())); diff --git a/include/mp/flat/sol_check.h b/include/mp/flat/sol_check.h index 26fc6a50c..42ef4da94 100644 --- a/include/mp/flat/sol_check.h +++ b/include/mp/flat/sol_check.h @@ -32,6 +32,8 @@ class SolutionChecker { try { // protect std::vector x_back = x; if (MPCD( sol_check_mode() ) & (1+2+4+8+16)) { + if (MPCD( IfWantNLOutput() )) + x = RecomputeAuxVars(x, true); // Compute expressions msgreal = DoCheckSol(x, duals, obj, {}, x_back, false); } if (MPCD( sol_check_mode() ) & (32+64+128+256+512)) { @@ -64,8 +66,9 @@ class SolutionChecker { // a summary in the final solve message. // For now, do this via warnings? if (MPCD( sol_check_fail() )) - MP_RAISE_WITH_CODE(int(sol::MP_SOLUTION_CHECK), // failure - warn); + MP_RAISE_WITH_CODE(int(sol::MP_SOLUTION_CHECK), + "Solution check failed - reporting as fatal:\n" + + warn); else MPD( AddWarning( MPD( GetEnv() ).GetSolCheckWarningKey(true), @@ -108,7 +111,9 @@ class SolutionChecker { }; /// Recompute auxiliary variables - ArrayRef RecomputeAuxVars(ArrayRef x) { + /// @param fExprOnly: only NL expressions + ArrayRef RecomputeAuxVars( + ArrayRef x, bool fExprOnly=false) { VarInfoRecomp vir { MPCD( sol_feas_tol() ), true, // currently not relevant for recomputation @@ -120,8 +125,14 @@ class SolutionChecker { MPCD( sol_round() ), MPCD( sol_prec() ) }; vir.get_x().set_p_var_info(&vir); - for (auto i=vir.size(); i--; ) - vir[i]; // touch the variable to be recomputed + if (fExprOnly) { + for (auto i=vir.size(); i--; ) + if ( !MPCD( IsProperVar(i) ) ) + vir[i]; // touch the variable to be recomputed + } else { + for (auto i=vir.size(); i--; ) + vir[i]; + } return std::move(vir.get_x().get_x()); } diff --git a/solvers/scipmp/CHANGES.scipmp.md b/solvers/scipmp/CHANGES.scipmp.md index e2526c31f..b48d8f341 100644 --- a/solvers/scipmp/CHANGES.scipmp.md +++ b/solvers/scipmp/CHANGES.scipmp.md @@ -2,6 +2,14 @@ Summary of recent updates to SCIP for AMPL ========================================== +## unreleased +- MINLP expression trees (option acc:_expr.) + - Using the expression tree API, available since SCIP v8, + to model nonlinear expressions. Earlier the expressions + were submitted to the solver in a flat form equating + an auxiliary variable to each expression. + + ## 20240724 - Option *acc:_all* - Useful to disable all reformulations (acc:_all=2), diff --git a/solvers/scipmp/scipmpbackend.cc b/solvers/scipmp/scipmpbackend.cc index c5054f40b..7375866bd 100644 --- a/solvers/scipmp/scipmpbackend.cc +++ b/solvers/scipmp/scipmpbackend.cc @@ -140,8 +140,6 @@ void ScipBackend::SetInterrupter(mp::Interrupter *inter) { } void ScipBackend::Solve() { - if (!storedOptions_.exportFile_.empty()) - ExportModel(storedOptions_.exportFile_); if (!storedOptions_.paramRead_.empty()) SCIP_CCALL( SCIPreadParams(getSCIP(), storedOptions_.paramRead_.c_str()) ); if (!storedOptions_.logFile_.empty()) @@ -368,12 +366,6 @@ void ScipBackend::InitCustomOptions() { "0*/1/2/3/4/5: Whether to write SCIP log lines (chatter) to stdout and to file (native output level of SCIP).", "display/verblevel", 0, 5); - AddStoredOption("tech:exportfile writeprob writemodel", - "Specifies the name of a file where to export the model before " - "solving it. This file name can have extension ``.lp``, ``.mps``, etc. " - "Default = \"\" (don't export the model).", - storedOptions_.exportFile_); - AddStoredOption("tech:logfile logfile", "Log file name.", storedOptions_.logFile_); @@ -963,6 +955,9 @@ void ScipBackend::AddMIPStart(ArrayRef x0, ArrayRef sparsity) { SCIP_CCALL( SCIPaddSolFree(getSCIP(), &solution, &keep) ); } +void ScipBackend::DoWriteProblem(const std::string& name) { + ExportModel(name); +} } // namespace mp diff --git a/solvers/scipmp/scipmpbackend.h b/solvers/scipmp/scipmpbackend.h index 46b93d17c..e1bc9a601 100644 --- a/solvers/scipmp/scipmpbackend.h +++ b/solvers/scipmp/scipmpbackend.h @@ -81,6 +81,12 @@ class ScipBackend : void AddMIPStart(ArrayRef x0, ArrayRef sparsity) override; + /** + * EXPORT PROBLEM + **/ + ALLOW_STD_FEATURE(WRITE_PROBLEM, true) + void DoWriteProblem(const std::string& name) override; + /** * Get MIP Gap @@ -157,7 +163,7 @@ class ScipBackend : private: /// These options are stored in the class struct Options { - std::string exportFile_, logFile_, paramRead_; + std::string logFile_, paramRead_; int concurrent_ = 0; int heuristics_ = 0; int cuts_ = 0; diff --git a/test/end2end/cases/categorized/fast/multi_obj/modellist.json b/test/end2end/cases/categorized/fast/multi_obj/modellist.json index b77ef0ab8..0ff8e2043 100644 --- a/test/end2end/cases/categorized/fast/multi_obj/modellist.json +++ b/test/end2end/cases/categorized/fast/multi_obj/modellist.json @@ -260,6 +260,20 @@ "_sobj[2]": -17 } }, + { + "name" : "obj_abs_01 obj:multi=2 writeprobindex", + "tags" : ["linear", "integer", "multiobj"], + "options": { + "ANYSOLVER_options": "multiobj=2 writeprob=obj_abs_01.lp writeprobindex=2", + "scip_options": "multiobj=2 writeprob=obj_abs_01.cip writeprobindex=2", + "gcg_options": "multiobj=2 writeprob=obj_abs_01.lp writeprobindex=2", + "mosek_options": "multiobj=2 writeprob=obj_abs_01.jtask writeprobindex=2" + }, + "values": { + "_sobj[1]": 13, + "_sobj[2]": -17 + } + }, { "name" : "obj_abs_02", "tags" : ["linear", "integer", "multiobj"],