diff --git a/doc/source/components.rst b/doc/source/components.rst index ef24a9ae6..7a4099d06 100644 --- a/doc/source/components.rst +++ b/doc/source/components.rst @@ -142,11 +142,18 @@ The library is zero-overhead: it does not store the model, nor does it require any intermediate objects to represent model information. -The API is provided by classes `mp::NLSOL`, `mp::NLWriter2`, -`mp::NLFeeder2`, `mp::SOLReader2`, `mp::SOLHandler2`. -See -`example `_ -solving a small non-linear model. +- **NL Writer C++ API** is provided by classes + `mp::NLSOL`, `mp::NLFeeder2`, `mp::SOLHandler2`. + See + `example `_ + solving a small non-linear model. + +- **NL Writer C API** is provided by structs + `NLSOL_C`, `NLFeeder2_C`, `SOLHandler2_C`. + *Currently only linear models are supported.* + See + `example `_ + solving a small linear model. .. _recommended-driver-logic: diff --git a/doc/source/index.rst b/doc/source/index.rst index 0846657b5..112b66b4b 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -50,7 +50,7 @@ The documentation is divided in two sections: * :ref:`howto` illustrates how to get familiar with the framework and how to quickly obtain the boilerplate code necessary to develop a new solver driver * :ref:`components` shows the main components of the framework, - including NL reader and writer API + including NL reader and NL writer APIs * :ref:`howto-test` illustrates how to execute tests during development * :ref:`cppreference` contains the reference to all the classes in the framework @@ -73,7 +73,7 @@ Developer documentation ----------------------- .. toctree:: - :maxdepth: 2 + :maxdepth: 3 Developer docs diff --git a/nl-writer2/examples/c/nlsol_ex_c.c b/nl-writer2/examples/c/nlsol_ex_c.c index 8b12d3cc0..a38d62e14 100644 --- a/nl-writer2/examples/c/nlsol_ex_c.c +++ b/nl-writer2/examples/c/nlsol_ex_c.c @@ -24,7 +24,7 @@ int main(int argc, const char* const* argv) { "where is ipopt, gurobi, minos, ...;\n" "binary/text is the NL format (default: binary.)\n" "Examples:\n" - " highs \"\" text /tmp/stub\n" + " highs \"writeprob=/tmp/stub.lp\" text /tmp/stub\n" " gurobi \"mip:return_gap=1\""); exit(0); } diff --git a/nl-writer2/examples/c/nlsol_ex_c_model.c b/nl-writer2/examples/c/nlsol_ex_c_model.c index db6614d87..704c3ba0c 100644 --- a/nl-writer2/examples/c/nlsol_ex_c_model.c +++ b/nl-writer2/examples/c/nlsol_ex_c_model.c @@ -1,4 +1,5 @@ #include +#include #include #include @@ -37,6 +38,10 @@ CAPIExample MakeCAPIExample_Linear_01() { static const SparseEntry obj_linpart[] = { {0, 13}, {1, 1.0} }; + /// Solution + static double sol_dual[] = {NAN, NAN}; + static double sol_primal[] = {NAN, NAN}; + CAPIExample result = { .n_var = 2, .n_var_int = 1, @@ -59,7 +64,14 @@ CAPIExample MakeCAPIExample_Linear_01() { .obj_sense = 1, .obj_linpart = obj_linpart, .n_obj_nz = 2, - .obj_name = "TotalSum" + .obj_name = "TotalSum", + + .binary_nl = 1, + + .sol_dual_ = sol_dual, + .sol_primal_ = sol_primal, + .objno_ = -2, + .solve_code_ = -100 }; return result; } @@ -73,5 +85,18 @@ void DestroyCAPIExample_Linear_01(CAPIExample* pEx) { } void PrintSolution_C(CAPIExample* pex, const char* stub) { - assert(0); + printf( + "\n ********** SOLUTION (%s.sol) ***********\n", + stub); + printf("%s\n", "DUALS."); + for (int i=0; in_con; ++i) + printf(" %10s = %.17g\n", + pex->con_name[i], pex->sol_dual_[i]); + printf("%s\n", "PRIMALS."); + for (int i=0; in_con; ++i) + printf(" %7s = %.17g\n", + pex->var_name[i], pex->sol_primal_[i]); + + printf("\nObjno used: %d, solve_result_num: %d\n", + pex->objno_, pex->solve_code_); } diff --git a/nl-writer2/examples/c/nlsol_ex_c_model.h b/nl-writer2/examples/c/nlsol_ex_c_model.h index b58df8ab7..bcf7b8936 100644 --- a/nl-writer2/examples/c/nlsol_ex_c_model.h +++ b/nl-writer2/examples/c/nlsol_ex_c_model.h @@ -100,6 +100,12 @@ typedef struct CAPIExample { /// Some technical stuff int binary_nl; + ///////////////////// SOLUTION ////////////////////// + double* sol_dual_; + double* sol_primal_; + int objno_; + int solve_code_; + } CAPIExample; /// Create linear example data diff --git a/nl-writer2/examples/c/nlsol_ex_c_sol.c b/nl-writer2/examples/c/nlsol_ex_c_sol.c index 3db9d08ea..791b58e3a 100644 --- a/nl-writer2/examples/c/nlsol_ex_c_sol.c +++ b/nl-writer2/examples/c/nlsol_ex_c_sol.c @@ -2,14 +2,51 @@ #include "nlsol_ex_c_sol.h" +/// Declare, implementation in nlsol_ex_c_nl.c +NLHeader_C CAPI_ex_Header(void* pex_void); + +void OnDualSolution( + void* p_user_data, int nvals, void* p_api_data) { + CAPIExample* pex = (CAPIExample*)p_user_data; + for (int i=0; isol_dual_[i] = NLW2_ReadSolVal(p_api_data); +} + +void OnPrimalSolution( + void* p_user_data, int nvals, void* p_api_data) { + CAPIExample* pex = (CAPIExample*)p_user_data; + for (int i=0; isol_primal_[i] = NLW2_ReadSolVal(p_api_data); +} + +void OnObjno(void* p_user_data, int on) { + CAPIExample* pex = (CAPIExample*)p_user_data; + pex->objno_ = on; +} + +void OnSolveCode(void* p_user_data, int sc) { + CAPIExample* pex = (CAPIExample*)p_user_data; + pex->solve_code_ = sc; +} + SOLHandler2_C MakeSOLHandler2_C(CAPIExample* pex) { - SOLHandler2_C result; + SOLHandler2_C result + = NLW2_MakeSOLHandler2_C_Default(); result.p_user_data_ = pex; + result.Header = CAPI_ex_Header; + // solve message: use default (print it) + // AMPL options: use default + result.OnDualSolution = OnDualSolution; + result.OnPrimalSolution = OnPrimalSolution; + result.OnObjno = OnObjno; + result.OnSolveCode = OnSolveCode; + return result; } void DestroySOLHandler2_C(SOLHandler2_C* p) { p->p_user_data_ = NULL; + NLW2_DestroySOLHandler2_C_Default(p); } diff --git a/nl-writer2/include/api/c/nl-feeder2-c-impl.h b/nl-writer2/include/api/c/nl-feeder2-c-impl.h index 37cb09c56..9e5caef4b 100644 --- a/nl-writer2/include/api/c/nl-feeder2-c-impl.h +++ b/nl-writer2/include/api/c/nl-feeder2-c-impl.h @@ -200,64 +200,6 @@ class NLFeeder2_C_Impl ///////////////// 5. CONSTRAINT BOUNDS & COMPLEMENTARITY /////// - /// \rst - /// Algebraic constraint bounds (for a single constraint): - /// either range (lb, ub), - /// or complementarity info (k, cvar), when k>0. - /// - /// For a complementarity constraint to hold, if cvar is at - /// its lower bound, then body >= 0; if cvar is at its upper - /// bound, then body <= 0; - /// and if cvar is strictly between its bounds, then body = 0. - /// The integer k in a complementarity constraint line indicates - /// which bounds on cvar are finite: 1 and 3 imply a finite - /// lower bound; 2 and 3 imply a finite upper bound; 0 (which - /// should not occur) would imply no finite bounds, i.e., - /// body = 0 must always hold. - /// - /// Example: - /// - /// .. code-block:: ampl - /// - /// ampl: var x; var y; var z; - /// ampl: s.t. Compl1: x+y >= 3 complements x-z <= 15; - /// ampl: s.t. Compl2: -2 <= 2*y+3*z <= 13 complements 6*z-2*x; - /// ampl: expand; - /// subject to Compl1: - /// 3 <= x + y - /// complements - /// x - z <= 15; - /// - /// subject to Compl2: - /// -2 <= 2*y + 3*z <= 13 - /// complements - /// -2*x + 6*z; - /// - /// ampl: solexpand; - /// Nonsquare complementarity system: - /// 4 complementarities including 2 equations - /// 5 variables - /// subject to Compl1.L: - /// x + y + Compl1$cvar = 0; - /// - /// subject to Compl1.R: - /// -15 + x - z <= 0 - /// complements - /// Compl1$cvar <= -3; - /// - /// subject to Compl2.L: - /// 2*y + 3*z - Compl2$cvar = 0; - /// - /// subject to Compl2.R: - /// -2*x + 6*z - /// complements - /// -2 <= Compl2$cvar <= 13; - /// - /// \endrst - struct AlgConRange { - double L{}, U{}; - int k{0}, cvar{0}; // k>0 means complementarity to cvar - }; /** Bounds/complementarity for all algebraic constraints * (\a num_algebraic_cons). @@ -293,7 +235,7 @@ class NLFeeder2_C_Impl bnd.U = bnd_c->U; crw.WriteAlgConRange(bnd); }; - NLF().FeedVarBounds(NLF().p_user_data_, &crw_c); + NLF().FeedConBounds(NLF().p_user_data_, &crw_c); } @@ -376,7 +318,13 @@ class NLFeeder2_C_Impl * csw.Write(col_size[i]); */ template - void FeedColumnSizes(ColSizeWriter& ) { } + void FeedColumnSizes(ColSizeWriter& csw) { + assert(NLF().FeedColumnSizes); + std::function f = [&csw](int cs) { + csw.Write(cs); + }; + NLF().FeedColumnSizes(NLF().p_user_data_, &f); + } ///////////////////// 12. INITIAL GUESSES ///////////////////// diff --git a/nl-writer2/include/api/c/nl-feeder2-c.h b/nl-writer2/include/api/c/nl-feeder2-c.h index 4b4945e6c..41a9d7fa4 100644 --- a/nl-writer2/include/api/c/nl-feeder2-c.h +++ b/nl-writer2/include/api/c/nl-feeder2-c.h @@ -20,7 +20,7 @@ extern "C" { void NLW2_WriteSparseDblEntry( void* p_api_data_, int index, double value); /// Write next variable's Lb, Ub -void NLW2_WriteVarLbUb(void* p_api_data, int lb, int ub); +void NLW2_WriteVarLbUb(void* p_api_data, double lb, double ub); /// Write next Jacobian column size void NLW2_WriteColSize(void* p_api_data, int sz); diff --git a/nl-writer2/include/api/c/sol-handler2-c-impl.h b/nl-writer2/include/api/c/sol-handler2-c-impl.h index 4db1f8fef..f5c9287fc 100644 --- a/nl-writer2/include/api/c/sol-handler2-c-impl.h +++ b/nl-writer2/include/api/c/sol-handler2-c-impl.h @@ -12,6 +12,10 @@ namespace mp { +/// Declare VecReader<> +template +class VecReader; + /// Wrap SOLHandler2_C into a C++ class, /// in order to interface it for NLReader2 class SOLHandler2_C_Impl @@ -21,6 +25,153 @@ class SOLHandler2_C_Impl SOLHandler2_C_Impl(SOLHandler2_C* psh2) : solh2_c_(*psh2) { } + /** The NLHeader used to write the NL file. */ + NLHeader Header() const { + assert(SH().Header); + auto h_c = SH().Header(SH().p_user_data_); + NLHeader hdr; + *(ProblemInfo_C*)(&hdr) = h_c.pi; + *(NLInfo_C*)(&hdr) = h_c.nli; + + return hdr; + } + + /** Receive solve message. + * The message always ends with '\n'. + * + * @param nbs: number of backspaces + * in the original solve message. + * So many characters should be skipped + * from the message if printed straightaway. + * AMPL solver drivers can supply the message + * with initial backspaces to indicate + * that so many characters should be skipped + * when printing. For example, if the driver prints + * MINOS 5.51: + * and exits, and the message starts with that again, + * this part should be skipped. + */ + void OnSolveMessage(const char* s, int nbs) { + assert(SH().OnSolveMessage); + SH().OnSolveMessage(SH().p_user_data_, s, nbs); + } + + /** + * Can be ignored by external systems. + * @return non-zero to stop solution input. + */ + int OnAMPLOptions(const AMPLOptions& ao) { + assert(SH().OnAMPLOptions); + + AMPLOptions_C ao_c; + ao_c.n_options_ = (int)ao.options_.size(); + std::memcpy(ao_c.options_, ao.options_.data(), + ao.options_.size() * sizeof(ao.options_[0])); + ao_c.has_vbtol_ = ao.has_vbtol_; + ao_c.vbtol_ = ao.vbtol_; + + return SH().OnAMPLOptions(SH().p_user_data_, ao_c); + } + + /** + * Dual values for algebraic constraints, + * if provided in the solution. + * Number of values <= NumAlgCons(). + * Implementation: + * + * duals.reserve(rd.Size()); + * while (rd.Size()) + * duals.push_back(rd.ReadNext()); + */ + template + void OnDualSolution(VecReader& vr) { + assert(SH().OnDualSolution); + + static_assert( + std::is_same >::value, + "internal error: " + "SOLHandler2_C_Impl::OnDualSolution() requires " + "a VecReader"); + + if (auto nvals = vr.Size()) { + SH().OnDualSolution(SH().p_user_data_, nvals, &vr); + } + } + + /** + * Variable values, if provided. + * Number of values <= NumVars(). + */ + template + void OnPrimalSolution(VecReader& vr) { + assert(SH().OnPrimalSolution); + + static_assert( + std::is_same >::value, + "internal error: " + "SOLHandler2_C_Impl::OnPrimalSolution() requires " + "a VecReader"); + + if (auto nvals = vr.Size()) { + SH().OnPrimalSolution(SH().p_user_data_, nvals, &vr); + } + } + + /** + * Receive notification of the objective index + * used by the driver (solver option 'objno'). + */ + void OnObjno(int on) { + assert(SH().OnObjno); + SH().OnObjno(SH().p_user_data_, on); + } + + /** + * Receive notification of the solve code. + */ + void OnSolveCode(int sc) { + assert(SH().OnSolveCode); + SH().OnSolveCode(SH().p_user_data_, sc); + } + + /** + * OnIntSuffix(). + * + * For constraints, can include values for + * logical constraints (after algebraic.) + * Sparse representation - can be empty + * (i.e., all values zero.) + * + * const auto& si = sr.SufInfo(); + * int kind = si.Kind(); + * int nmax = nitems_max[kind & 3]; + * const std::string& name = si.Name(); + * const std::string& table = si.Table(); + * while (sr.Size()) { + * std::pair val = sr.ReadNext(); + * if (val.first<0 || val.first>=nmax) { + * sr.SetError(mp::SOL_Read_Bad_Suffix, + * "bad suffix element index"); + * return; + * } + * suf[val.first] = val.second; + * } + * if (mp::SOL_Read_OK == sr.ReadResult()) // Can check + * RegisterSuffix(kind, name, table, suf); + */ +// template +// void OnIntSuffix(SuffixReader& ) { } + + /** + * Same as OnIntSuffix(), but + * sr.ReadNext() returns pair + */ +// template +// void OnDblSuffix(SuffixReader& ) { } + +protected: + const SOLHandler2_C& SH() const { return solh2_c_; } + private: /// Just store copy const SOLHandler2_C solh2_c_; diff --git a/nl-writer2/include/api/c/sol-handler2-c.h b/nl-writer2/include/api/c/sol-handler2-c.h index 5e17874da..73f3f7c28 100644 --- a/nl-writer2/include/api/c/sol-handler2-c.h +++ b/nl-writer2/include/api/c/sol-handler2-c.h @@ -7,11 +7,26 @@ #ifndef SOLHANDLER2C_H #define SOLHANDLER2C_H +#include "mp/nl-header-c.h" #ifdef __cplusplus // Can be used from C++ extern "C" { #endif +/// Callback: read next dual/variable value +double NLW2_ReadSolVal(void* p_api_data); + +/** + * AMPL internal options. + */ +typedef struct AMPLOptions_C { + int n_options_; + int options_[MAX_AMPL_OPTIONS]; + int has_vbtol_; + double vbtol_; +} AMPLOptions_C; + + /// Wrap mp::SOLHandler2 for C API. /// /// SOLHandler2_C: reads solution details on request @@ -21,11 +36,112 @@ extern "C" { /// To fill some **default methods**, /// call NLW2_MakeSOLHandler2_C_Default() / NLW2_Destroy...(). typedef struct SOLHandler2_C { - /// User data + /// User data, provided to the methods as the 1st arg void* p_user_data_; + /** The NLHeader used to write the NL file. */ + NLHeader_C (*Header)(void* p_user_data); + + /** Receive solve message. + * The message always ends with '\n'. + * + * @param nbs: number of backspaces + * in the original solve message. + * So many characters should be skipped + * from the message if printed straightaway. + * AMPL solver drivers can supply the message + * with initial backspaces to indicate + * that so many characters should be skipped + * when printing. For example, if the driver prints + * MINOS 5.51: + * and exits, and the message starts with that again, + * this part should be skipped. + */ + void (*OnSolveMessage)( + void* p_user_data, const char* s, int nbs); + + /** + * Can be ignored by external systems. + * @return non-zero to stop solution input. + */ + int (*OnAMPLOptions)( + void* p_user_data, AMPLOptions_C ); + + /** + * Dual values for algebraic constraints, + * if provided in the solution. + * Number of values <= NumAlgCons(). + * Implementation: + * + * duals.reserve(nvals); + * while (nvals--) + * duals.push_back(NLW2_ReadSolVal(p_api_data)); + */ + void (*OnDualSolution)( + void* p_user_data, int nvals, void* p_api_data); + + /** + * Variable values, if provided. + * Number of values <= NumVars(). + */ + void (*OnPrimalSolution)( + void* p_user_data, int nvals, void* p_api_data); + + /** + * Receive notification of the objective index + * used by the driver (solver option 'objno'). + */ + void (*OnObjno)(void* p_user_data, int ); + + /** + * Receive notification of the solve code. + */ + void (*OnSolveCode)(void* p_user_data, int ); + + /** + * OnIntSuffix(). + * + * For constraints, can include values for + * logical constraints (after algebraic.) + * Sparse representation - can be empty + * (i.e., all values zero.) + * + * const auto& si = sr.SufInfo(); + * int kind = si.Kind(); + * int nmax = nitems_max[kind & 3]; + * const std::string& name = si.Name(); + * const std::string& table = si.Table(); + * while (sr.Size()) { + * std::pair val = sr.ReadNext(); + * if (val.first<0 || val.first>=nmax) { + * sr.SetError(mp::SOL_Read_Bad_Suffix, + * "bad suffix element index"); + * return; + * } + * suf[val.first] = val.second; + * } + * if (mp::SOL_Read_OK == sr.ReadResult()) // Can check + * RegisterSuffix(kind, name, table, suf); + */ +// template +// void OnIntSuffix(SuffixReader& ) { } + + /** + * Same as OnIntSuffix(), but + * sr.ReadNext() returns pair + */ +// template +// void OnDblSuffix(SuffixReader& ) { } + } SOLHandler2_C; +/// Create an SOLHandler2_C with default methods +SOLHandler2_C NLW2_MakeSOLHandler2_C_Default(); + +/// Destroy an SOLHandler2_C +/// created with NLW2_Make...Default() +void NLW2_DestroySOLHandler2_C_Default(SOLHandler2_C* ); + #ifdef __cplusplus } // extern "C" #endif diff --git a/nl-writer2/src/c_api.cc b/nl-writer2/src/c_api.cc index 3b5ad17a6..3ae3f98f8 100644 --- a/nl-writer2/src/c_api.cc +++ b/nl-writer2/src/c_api.cc @@ -36,7 +36,7 @@ void NLW2_WriteSparseDblEntry( } /// Var bound writer -void NLW2_WriteVarLbUb(void* vbw, int lb, int ub) { +void NLW2_WriteVarLbUb(void* vbw, double lb, double ub) { auto& f = *(std::function*)(vbw); f(lb, ub); } @@ -174,7 +174,7 @@ void NLW2_FeedLinearConExpr_C_Default(void* , int , void* ) { } * csw.Write(col_size[i]); */ void NLW2_FeedColumnSizes_C_Default(void* , void* ) -{ assert(0 && "this probably always needs implementation"); } +{ assert(0 && "this needs implementation"); } ///////////////////// 12. INITIAL GUESSES ///////////////////// @@ -260,6 +260,8 @@ NLFeeder2_C NLW2_MakeNLFeeder2_C_Default() { result.p_user_data_ = NULL; + result.Header = NULL; // User should provide this + // Default options result.want_nl_comments_ = 0; result.output_precision_ = 0; @@ -498,6 +500,63 @@ void NLW2_DestroyNLUtils_C_Default(NLUtils_C* ) { } +/////////////////// SOLHandler2_C ///////////////////// + +/// Callbacks + +double NLW2_ReadSolVal(void* p_api_data) { + // In contrast to NLWriter2_C, + // here we know the type + return ((mp::VecReader*)p_api_data) + ->ReadNext(); +} + + +/// Default methods +void NLW2_OnSolveMessage_C_Default( + void* p_user_data, const char* s, int nbs) { + if (nbs < (int)strlen(s)) { + printf("%s\n", s+nbs); + fflush(stdout); + } +} +int NLW2_OnAMPLOptions_C_Default( + void* p_user_data, AMPLOptions_C ) { return 0; } +void NLW2_OnDualSolution_C_Default( + void* p_user_data, int nvals, void* p_api_data) { } +void NLW2_OnPrimalSolution_C_Default( + void* p_user_data, int nvals, void* p_api_data) { } +void NLW2_OnObjno_C_Default(void* p_user_data, int ) { } +void NLW2_OnSolveCode_C_Default(void* p_user_data, int ) { } +// void OnIntSuffix(SuffixReader& ) { } +// void OnDblSuffix(SuffixReader& ) { } + + +SOLHandler2_C NLW2_MakeSOLHandler2_C_Default() { + SOLHandler2_C result; + + std::memset(&result, 0, sizeof(result)); // all 0 + + result.p_user_data_ = NULL; + + result.Header = NULL; // User should provide this + + result.OnSolveMessage = NLW2_OnSolveMessage_C_Default; + result.OnAMPLOptions = NLW2_OnAMPLOptions_C_Default; + result.OnDualSolution = NLW2_OnDualSolution_C_Default; + result.OnPrimalSolution = NLW2_OnPrimalSolution_C_Default; + result.OnObjno = NLW2_OnObjno_C_Default; + result.OnSolveCode = NLW2_OnSolveCode_C_Default; + // void OnIntSuffix(SuffixReader& ) { } + // void OnDblSuffix(SuffixReader& ) { } + + return result; +} + +void NLW2_DestroySOLHandler2_C_Default(SOLHandler2_C* ) +{ } + + //////////// NLSOL_C API ////////////// /// Typedef our specialization of NLSOL