diff --git a/CMakeLists.txt b/CMakeLists.txt index 97aa13bb2..e61256f03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,8 +27,8 @@ include(init) project(MP) -# Set the c++11 flag without GNU extensions. -set(CMAKE_CXX_STANDARD 11) +# Set the c++17 flag without GNU extensions. +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -258,7 +258,7 @@ function (add_mp_library name) PUBLIC ${add_mp_library_COMPILE_DEFINITIONS}) target_include_directories(${name} PUBLIC ${add_mp_library_INCLUDE_DIRECTORIES}) - set_property(TARGET ${name} PROPERTY CXX_STANDARD 11) + set_property(TARGET ${name} PROPERTY CXX_STANDARD 17) if (add_mp_library_DEPENDS) add_dependencies(${name} ${add_mp_library_DEPENDS}) endif () diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a02cbd8ee..86f4ef3fa 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,6 +22,8 @@ stages: inputs: {pathtoPublish: 'nl-writer2/nlwpy/wheelhouse'} - job: macos pool: {vmImage: 'macos-latest'} + variables: + MACOSX_DEPLOYMENT_TARGET: 10.15 steps: - task: UsePythonVersion@0 - bash: | @@ -45,9 +47,9 @@ stages: - task: PublishBuildArtifacts@1 inputs: {pathtoPublish: 'nl-writer2/nlwpy/wheelhouse'} -- stage: qemu - displayName: 'Build with QEMU' - jobs: +# - stage: qemu +# displayName: 'Build with QEMU' +# jobs: - job: qemu pool: {vmImage: 'Ubuntu-20.04'} strategy: diff --git a/nl-writer2/CMakeLists.txt b/nl-writer2/CMakeLists.txt index cc7e84659..b9132960a 100644 --- a/nl-writer2/CMakeLists.txt +++ b/nl-writer2/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.10) project(NLWriter2) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) diff --git a/nl-writer2/include/mp/nl-solver.h b/nl-writer2/include/mp/nl-solver.h index a4d85bc95..019ed5845 100644 --- a/nl-writer2/include/mp/nl-solver.h +++ b/nl-writer2/include/mp/nl-solver.h @@ -44,7 +44,7 @@ namespace mp { -class NLHeader; +struct NLHeader; /** \rst @@ -229,14 +229,15 @@ class NLSolver { bool ReadSolution(SOLHandler& solh); protected: - void Init(); - void Destroy(); + void InitAutoStub(); + void DestroyAutoStub(); mp::NLUtils& Utils() const { return *p_ut_; } private: mp::NLUtils utils_; mp::NLUtils* p_ut_ = nullptr; + std::string pathstr_; std::string filestub_; bool filestubCustom_ = false; NLW2_NLOptionsBasic_C nl_opts_; diff --git a/nl-writer2/include/mp/nl-solver.hpp b/nl-writer2/include/mp/nl-solver.hpp index da3abd704..1a3d14c59 100644 --- a/nl-writer2/include/mp/nl-solver.hpp +++ b/nl-writer2/include/mp/nl-solver.hpp @@ -29,7 +29,7 @@ namespace mp { template bool NLSolver::LoadModel(NLFeeder& nlf) { if (GetFileStub().empty()) - return (err_msg_="WriteNL error: provide filestub.", false); + InitAutoStub(); auto result = mp::WriteNLFile(GetFileStub(), nlf, Utils()); if (NLW2_WriteNL_OK != result.first) return (err_msg_ = "WriteNL error: " + result.second, false); diff --git a/nl-writer2/include/mp/sol-reader2.hpp b/nl-writer2/include/mp/sol-reader2.hpp index 5dc594500..55e2fae99 100644 --- a/nl-writer2/include/mp/sol-reader2.hpp +++ b/nl-writer2/include/mp/sol-reader2.hpp @@ -556,7 +556,7 @@ NLW2_SOLReadResultCode SOLReader2::gsufread(FILE* f) { if (!fgets(buf, sizeof(buf)-1, f)) return ReportEarlyEof(); if (!(L = strlen(buf)) || buf[--L] != '\n' - || L >= se - s) + || L >= (size_t)(se - s)) return ReportBadLine(buf); if (L) { if (buf[L-1] != '\r' || --L) { diff --git a/nl-writer2/nlwpy/src/nlw_bindings.cc b/nl-writer2/nlwpy/src/nlw_bindings.cc index 9ccd99479..4730d5c63 100644 --- a/nl-writer2/nlwpy/src/nlw_bindings.cc +++ b/nl-writer2/nlwpy/src/nlw_bindings.cc @@ -267,48 +267,48 @@ PYBIND11_MODULE(nlwpy, m) .. autosummary:: :toctree: _generate - NLW2_ObjSense -NLW2_VarType -NLW2_MatrixFormat -NLW2_HessianFormat - -NLW2_NLOptionsBasic - NLW2_MakeNLOptionsBasic_Default -NLW2_NLSuffix -NLW2_NLModel -NLW2_NLSolution -NLW2_NLSolver + ObjSense +VarType +MatrixFormat +HessianFormat + +NLOptionsBasic + MakeNLOptionsBasic_Default +NLSuffix +NLModel +NLSolution +NLSolver )pbdoc"; - py::enum_(m, "NLW2_ObjSense", py::arithmetic()) + py::enum_(m, "ObjSense", py::arithmetic()) .value("Minimize", NLW2_ObjSenseMinimize) .value("Maximize", NLW2_ObjSenseMaximize); // .export_values(); -- Leave them scoped - py::enum_(m, "NLW2_VarType", py::arithmetic()) + py::enum_(m, "VarType", py::arithmetic()) .value("Continuous", NLW2_VarTypeContinuous) .value("Integer", NLW2_VarTypeInteger); - py::enum_(m, "NLW2_MatrixFormat", py::arithmetic()) + py::enum_(m, "MatrixFormat", py::arithmetic()) .value("Rowwise", NLW2_MatrixFormatRowwise); - py::enum_(m, "NLW2_HessianFormat", py::arithmetic()) + py::enum_(m, "HessianFormat", py::arithmetic()) .value("Triangular", NLW2_HessianFormatTriangular) .value("Square", NLW2_HessianFormatSquare); /// NLOptionsBasic - py::class_(m, "NLW2_NLOptionsBasic") + py::class_(m, "NLOptionsBasic") .def(py::init<>()) .def_readwrite("n_text_mode_", &NLW2_NLOptionsBasic_C::n_text_mode_) .def_readwrite("want_nl_comments_", &NLW2_NLOptionsBasic_C::want_nl_comments_) .def_readwrite("flags_", &NLW2_NLOptionsBasic_C::flags_); - m.def("NLW2_MakeNLOptionsBasic_Default", &NLW2_MakeNLOptionsBasic_C_Default, R"pbdoc( + m.def("MakeNLOptionsBasic_Default", &NLW2_MakeNLOptionsBasic_C_Default, R"pbdoc( Use this to create default options for NLModel. )pbdoc"); /// NLSuffix - py::class_(m, "NLW2_NLSuffix") + py::class_(m, "NLSuffix") .def(py::init>()) .def(py::init>()) .def_readwrite("name_", &mp::NLSuffix::name_) @@ -317,7 +317,7 @@ NLW2_NLSolver .def_readwrite("values_", &mp::NLSuffix::values_); /// NLSuffixSet - py::class_(m, "NLW2_NLSuffixSet") + py::class_(m, "NLSuffixSet") .def("Find", // Find(): return None if not found [=](mp::NLSuffixSet const& ss, std::string const& name, int kind) -> py::object { @@ -335,7 +335,7 @@ NLW2_NLSolver ; /// NLModel - py::class_(m, "NLW2_NLModel") + py::class_(m, "NLModel") .def(py::init()) .def("SetCols", &NLWPY_NLModel::SetCols) .def("SetColNames", &NLWPY_NLModel::SetColNames) @@ -350,7 +350,7 @@ NLW2_NLSolver ; /// NLSolution - py::class_(m, "NLW2_NLSolution") + py::class_(m, "NLSolution") .def_readwrite("solve_result_", &mp::NLSolution::solve_result_) .def_readwrite("nbs_", &mp::NLSolution::nbs_) .def_readwrite("solve_message_", &mp::NLSolution::solve_message_) @@ -360,7 +360,7 @@ NLW2_NLSolver .def_readwrite("suffixes_", &mp::NLSolution::suffixes_); /// NLSolver - py::class_(m, "NLW2_NLSolver") + py::class_(m, "NLSolver") .def(py::init<>()) .def("SetFileStub", &mp::NLSolver::SetFileStub) .def("SetNLOptions", &mp::NLSolver::SetNLOptions) diff --git a/nl-writer2/nlwpy/tests/test.py b/nl-writer2/nlwpy/tests/test.py index babce431f..4eeef5f65 100644 --- a/nl-writer2/nlwpy/tests/test.py +++ b/nl-writer2/nlwpy/tests/test.py @@ -1,4 +1,4 @@ -''' +""" NL Writer Python API test. Copyright (C) 2024 AMPL Optimization Inc. @@ -18,175 +18,196 @@ of or in connection with the use or performance of this software. Author: Gleb Belov -''' +""" import sys import numpy as np from scipy.sparse import csr_matrix -import nlwpy as m +import nlwpy -assert m.__version__ == "0.0.1" +assert nlwpy.__version__ == "0.0.1b0" -nlwo = m.NLW2_MakeNLOptionsBasic_Default() +nlwo = nlwpy.MakeNLOptionsBasic_Default() assert 0 == nlwo.n_text_mode_ assert 0 == nlwo.want_nl_comments_ assert 1 == nlwo.flags_ + ## --------------------------------------------------------------- class ModelBuilder: - def GetModel(self): - nlme = m.NLW2_NLModel(self.prob_name_) - - nlme.SetCols(self.var_lb_, self.var_ub_, self.var_type_) - nlme.SetColNames(self.var_names_) - - if self.A_ is not None: - self.A_ = csr_matrix(self.A_) - nlme.SetRows(self.row_lb_, self.row_ub_, - self.A_format_, - self.A_.indptr, self.A_.indices, self.A_.data) - nlme.SetRowNames(self.row_names_) - - nlme.SetLinearObjective(self.obj_sense_, self.obj_c0_, - self.obj_c_) - - if self.Q_ is not None: - self.Q_ = csr_matrix(self.Q_) - nlme.SetHessian(self.Q_format_, - self.Q_.indptr, self.Q_.indices, self.Q_.data) - nlme.SetObjName(self.obj_name_) - - if len(self.ini_x_i_)>0: - nlme.SetWarmstart(self.ini_x_i_, self.ini_x_v_) - if len(self.ini_y_i_)>0: - nlme.SetDualWarmstart(self.ini_y_i_, self.ini_y_v_) - if len(self.bas_x_)>0: - suf = m.NLW2_NLSuffix("status", 0, self.bas_x_) - nlme.AddSuffix(suf) - if len(self.bas_y_)>0: - suf = m.NLW2_NLSuffix("status", 1, self.bas_y_) - nlme.AddSuffix(suf) - - return nlme - - def Check(self, sol): - result = True - if not self.ApproxEqual(sol.obj_val_, self.obj_val_ref_): - print("MIQP 1: wrong obj val ({:.17f} !~ {:.17f})".format( - sol.obj_val_, self.obj_val_ref_)) - result = False - - for i in range(len(sol.x_)): - if not self.ApproxEqual(self.x_ref_[i], sol.x_[i]): - print("MIQP 1: wrong x[{}] ({:.17f} !~ {:.17f})".format( - i+1, sol.x_[i], self.x_ref_[i])) - result = False - - ### Printing suffixes. - ### TODO replace by checking debug suffixes and ini guesses. - suffixes = sol.suffixes_ - print("Number of suffixes returned:", len(suffixes)) - for suf in suffixes: - print(" SUFFIX '{}' [{}]".format(suf.name_, suf.kind_)) - print(" Table: ", suf.table_) - print(" Values: ", *suf.values_) - sufMIPGapObj = suffixes.Find("relmipgap", 2) ## should be 2+4 - if sufMIPGapObj is not None: - print("FOUND: SUFFIX '{}' [{}].".format( - sufMIPGapObj.name_, sufMIPGapObj.kind_)) - sufVarStatus = suffixes.Find("status", 0) - if sufVarStatus is not None: - print("FOUND: SUFFIX '{}' [{}].".format( - sufVarStatus.name_, sufVarStatus.kind_)) - suffixes.clear() - - print("MIQP 1: solution check {}, obj={:.17f}.".format( - "ok" if result else "Failed", sol.obj_val_)) - return result - - def ApproxEqual(self, n, m): - return abs(n-m) \ - <= 1e-5 * min(1.0, abs(n)+abs(m)) - - def __init__(self): - self.prob_name_ = "nlwpy_prob" - self.var_lb_ = [0, -3, 0, -1, -1, -2] - self.var_ub_ = [0, 20, 1, 1e20, -1, 10] - self.var_type_ = [0, 1, 1, 1, 0, 0] - self.var_names_ = \ - ["x1_4", "x2_6", "x3_5", "x4_3", "x5_1", "x6_2"] - self.A_format_ = m.NLW2_MatrixFormat.Rowwise - self.A_ = np.array([ - [0,1,1,1,0,1], - [0,1,-1,-1,0,1]]) - self.row_lb_ = [15, 10] - self.row_ub_ = [15, np.inf] - self.row_names_ = ["C1", "C2"] - self.obj_sense_ = m.NLW2_ObjSense.Minimize - self.obj_c0_ = 3.24 - self.obj_c_ = [0,1,0,0,0,0] - self.Q_format_ = m.NLW2_HessianFormat.Square - self.Q_ = np.zeros([6, 6]) - self.Q_[3, 3] = 10 - self.Q_[3, 5] = 12 - self.Q_[4, 4] = 14 - self.obj_name_ = "obj[1]" - - ### Extra input - self.ini_x_i_ = [0,2] - self.ini_x_v_ = [5,4] - self.ini_y_i_ = [0] - self.ini_y_v_ = [-12] - self.bas_x_ = [3,4,1,4,1,3] ### Basis statuses - self.bas_y_ = [1,1] - - ### Solution - self.x_ref_ = [0, 5, 1, -1, -1, 10] - self.obj_val_ref_ = -39.76 + def GetModel(self): + nlme = nlwpy.NLModel(self.prob_name_) + + nlme.SetCols(self.var_lb_, self.var_ub_, self.var_type_) + nlme.SetColNames(self.var_names_) + + if self.A_ is not None: + self.A_ = csr_matrix(self.A_) + nlme.SetRows( + self.row_lb_, + self.row_ub_, + self.A_format_, + self.A_.indptr, + self.A_.indices, + self.A_.data, + ) + nlme.SetRowNames(self.row_names_) + + nlme.SetLinearObjective(self.obj_sense_, self.obj_c0_, self.obj_c_) + + if self.Q_ is not None: + self.Q_ = csr_matrix(self.Q_) + nlme.SetHessian( + self.Q_format_, self.Q_.indptr, self.Q_.indices, self.Q_.data + ) + nlme.SetObjName(self.obj_name_) + + if len(self.ini_x_i_) > 0: + nlme.SetWarmstart(self.ini_x_i_, self.ini_x_v_) + if len(self.ini_y_i_) > 0: + nlme.SetDualWarmstart(self.ini_y_i_, self.ini_y_v_) + if len(self.bas_x_) > 0: + suf = nlwpy.NLSuffix("status", 0, self.bas_x_) + nlme.AddSuffix(suf) + if len(self.bas_y_) > 0: + suf = nlwpy.NLSuffix("status", 1, self.bas_y_) + nlme.AddSuffix(suf) + + return nlme + + def Check(self, sol): + result = True + if not self.ApproxEqual(sol.obj_val_, self.obj_val_ref_): + print( + "MIQP 1: wrong obj val ({:.17f} !~ {:.17f})".format( + sol.obj_val_, self.obj_val_ref_ + ) + ) + result = False + + for i in range(len(sol.x_)): + if not self.ApproxEqual(self.x_ref_[i], sol.x_[i]): + print( + "MIQP 1: wrong x[{}] ({:.17f} !~ {:.17f})".format( + i + 1, sol.x_[i], self.x_ref_[i] + ) + ) + result = False + + ### Printing suffixes. + ### TODO replace by checking debug suffixes and ini guesses. + suffixes = sol.suffixes_ + print("Number of suffixes returned:", len(suffixes)) + for suf in suffixes: + print(" SUFFIX '{}' [{}]".format(suf.name_, suf.kind_)) + print(" Table: ", suf.table_) + print(" Values: ", *suf.values_) + sufMIPGapObj = suffixes.Find("relmipgap", 2) ## should be 2+4 + if sufMIPGapObj is not None: + print( + "FOUND: SUFFIX '{}' [{}].".format( + sufMIPGapObj.name_, sufMIPGapObj.kind_ + ) + ) + sufVarStatus = suffixes.Find("status", 0) + if sufVarStatus is not None: + print( + "FOUND: SUFFIX '{}' [{}].".format( + sufVarStatus.name_, sufVarStatus.kind_ + ) + ) + suffixes.clear() + + print( + "MIQP 1: solution check {}, obj={:.17f}.".format( + "ok" if result else "Failed", sol.obj_val_ + ) + ) + return result + + def ApproxEqual(self, n, m): + return abs(n - m) <= 1e-5 * min(1.0, abs(n) + abs(m)) + + def __init__(self): + self.prob_name_ = "nlwpy_prob" + self.var_lb_ = [0, -3, 0, -1, -1, -2] + self.var_ub_ = [0, 20, 1, 1e20, -1, 10] + self.var_type_ = [0, 1, 1, 1, 0, 0] + self.var_names_ = ["x1_4", "x2_6", "x3_5", "x4_3", "x5_1", "x6_2"] + self.A_format_ = nlwpy.MatrixFormat.Rowwise + self.A_ = np.array([[0, 1, 1, 1, 0, 1], [0, 1, -1, -1, 0, 1]]) + self.row_lb_ = [15, 10] + self.row_ub_ = [15, np.inf] + self.row_names_ = ["C1", "C2"] + self.obj_sense_ = nlwpy.ObjSense.Minimize + self.obj_c0_ = 3.24 + self.obj_c_ = [0, 1, 0, 0, 0, 0] + self.Q_format_ = nlwpy.HessianFormat.Square + self.Q_ = np.zeros([6, 6]) + self.Q_[3, 3] = 10 + self.Q_[3, 5] = 12 + self.Q_[4, 4] = 14 + self.obj_name_ = "obj[1]" + + ### Extra input + self.ini_x_i_ = [0, 2] + self.ini_x_v_ = [5, 4] + self.ini_y_i_ = [0] + self.ini_y_v_ = [-12] + self.bas_x_ = [3, 4, 1, 4, 1, 3] ### Basis statuses + self.bas_y_ = [1, 1] + + ### Solution + self.x_ref_ = [0, 5, 1, -1, -1, 10] + self.obj_val_ref_ = -39.76 + def SolveAndCheck(solver, sopts, binary, stub): - mb = ModelBuilder() - nlme = mb.GetModel() - nlse = m.NLW2_NLSolver() - nlopts = m.NLW2_MakeNLOptionsBasic_Default() - nlopts.n_text_mode_ = not binary - nlopts.want_nl_comments_ = 1 - nlse.SetNLOptions(nlopts) - nlse.SetFileStub(stub) - sol = nlse.Solve(nlme, solver, sopts) - if sol.solve_result_ > -2: ## Some method for this? - if (not mb.Check(sol)): - print("Solution check failed.") - return False - else: - print(nlse.GetErrorMessage()) - return False - - return True - -argc=len(sys.argv) -argv=sys.argv - -if argc<2: - print("AMPL NL Writer Python API example.\n" - "Usage:\n" - " python [\"\" [binary/text []]],\n\n" - "where is ipopt, gurobi, minos, ...;\n" - "binary/text is the NL format (default: binary.)\n" - "Examples:\n" - " python ipopt \"\" text /tmp/stub\n" - " python gurobi \"nonconvex=2 funcpieces=-2 funcpieceratio=1e-4\"") - sys.exit(0) - -solver = argv[1] if (argc>1) else "minos" -sopts = argv[2] if argc>2 else "" -binary = ((argc<=3) or "text" != argv[3]) -stub = argv[4] if argc>4 else "" + mb = ModelBuilder() + nlme = mb.GetModel() + nlse = nlwpy.NLSolver() + nlopts = nlwpy.MakeNLOptionsBasic_Default() + nlopts.n_text_mode_ = not binary + nlopts.want_nl_comments_ = 1 + nlse.SetNLOptions(nlopts) + nlse.SetFileStub(stub) + sol = nlse.Solve(nlme, solver, sopts) + if sol.solve_result_ > -2: ## Some method for this? + if not mb.Check(sol): + print("Solution check failed.") + return False + else: + print(nlse.GetErrorMessage()) + return False + + return True + + +argc = len(sys.argv) +argv = sys.argv + +if argc < 2: + print( + "AMPL NL Writer Python API example.\n" + "Usage:\n" + ' python ["" [binary/text []]],\n\n' + "where is ipopt, gurobi, minos, ...;\n" + "binary/text is the NL format (default: binary.)\n" + "Examples:\n" + ' python ipopt "" text /tmp/stub\n' + ' python gurobi "nonconvex=2 funcpieces=-2 funcpieceratio=1e-4"' + ) + sys.exit(0) + +solver = argv[1] if (argc > 1) else "minos" +sopts = argv[2] if argc > 2 else "" +binary = (argc <= 3) or "text" != argv[3] +stub = argv[4] if argc > 4 else "" if not SolveAndCheck(solver, sopts, binary, stub): - print("SolveAndCheck() failed.") - sys.exit(1) + print("SolveAndCheck() failed.") + sys.exit(1) ## --------------------------------------------------------------- print("Test finished.") diff --git a/nl-writer2/setup.py b/nl-writer2/setup.py index bcc15550c..3148ca749 100644 --- a/nl-writer2/setup.py +++ b/nl-writer2/setup.py @@ -4,7 +4,7 @@ import pybind11 import glob -__version__ = "0.0.1" +__version__ = "0.0.1b0" # The main interface is through Pybind11Extension. # * You can add cxx_std=11/14/17, and then build_ext can be removed. @@ -15,11 +15,36 @@ # Sort input source files if you glob sources to ensure bit-for-bit # reproducible builds (https://github.com/pybind/python_example/pull/53) + +def compile_args(): + from platform import system + + if system() == "Windows": + return ["/std:c++17"] + elif system() == "Linux": + ignore_warnings = [ + "-Wno-stringop-truncation", + "-Wno-catch-value", + "-Wno-unused-variable", + ] + return ["-std=c++17"] + ignore_warnings + elif system() == "Darwin": + ignore_warnings = [ + "-Wno-unused-variable", + ] + return [ + "-std=c++17", + "-mmacosx-version-min=10.15", + ] + ignore_warnings + else: + return [] + + ext_modules = [ Extension( "nlwpy", ["nlwpy/src/nlw_bindings.cc"] + glob.glob("./src/" + "*.cc"), - extra_compile_args=["-std=c++17"], + extra_compile_args=compile_args(), include_dirs=["include", pybind11.get_include()], # Example: passing in the version to the compiled code define_macros=[("VERSION_INFO", __version__)], diff --git a/nl-writer2/src/dtoa.cc b/nl-writer2/src/dtoa.cc index f61213b88..534df3feb 100644 --- a/nl-writer2/src/dtoa.cc +++ b/nl-writer2/src/dtoa.cc @@ -1506,7 +1506,7 @@ static unsigned int maxthreads = 0; #ifdef __cplusplus extern "C" double strtod(const char *s00, char **se); -extern "C" char *dtoa(double d, int mode, int ndigits, +extern "C" char *dtoa_nlw2(double d, int mode, int ndigits, int *decpt, int *sign, char **rve); #endif @@ -6191,7 +6191,7 @@ dtoa_r_dmgay(double dd, int mode, int ndigits, int *decpt, int *sign, char **rve } char * -dtoa(double dd, int mode, int ndigits, int *decpt, int *sign, char **rve) +dtoa_nlw2(double dd, int mode, int ndigits, int *decpt, int *sign, char **rve) { /* Sufficient space is allocated to the return value to hold the suppressed trailing zeros. diff --git a/nl-writer2/src/nl-solver.cc b/nl-writer2/src/nl-solver.cc index 49a4b9b2c..e188b349b 100644 --- a/nl-writer2/src/nl-solver.cc +++ b/nl-writer2/src/nl-solver.cc @@ -25,6 +25,14 @@ #include #include #include +#include +#include + +#if defined(_WIN32) || defined(_WIN64) + #include // _mktemp[_s] +#else + #include // mkdtemp +#endif #include "mp/nl-solver.hpp" #include "mp/nl-opcodes.h" @@ -519,24 +527,57 @@ double NLModel::ComputeObjValue(const double *x) const { } NLSolver::NLSolver() - : p_ut_(&utils_) { Init(); } + : p_ut_(&utils_) { } NLSolver::NLSolver(mp::NLUtils* put) - : p_ut_(put ? put : &utils_) { Init(); } + : p_ut_(put ? put : &utils_) { } -NLSolver::~NLSolver() { Destroy(); } +NLSolver::~NLSolver() { DestroyAutoStub(); } -void NLSolver::Init() { +void NLSolver::InitAutoStub() { // init file stub - char tmpn[L_tmpnam]; - tmpnam(tmpn); - filestub_ = tmpn; + std::random_device dev; + std::mt19937 prng(dev()); + std::uniform_int_distribution rand(0); + auto path = std::filesystem::temp_directory_path(); + path /= "nlw2_"; // via '/' + char rnds[64] = "rndhex"; + std::snprintf(rnds, sizeof(rnds)-1, "%lX", rand(prng)); + path += rnds; // no '/' + + path += "_XXXXXX"; + pathstr_ = path.string(); + +#if defined(_WIN32) || defined(_WIN64) + auto p1 = _mktemp((char*)pathstr_.c_str()); + assert(p1); + if (!std::filesystem::create_directory(pathstr_)) + Utils().myexit("Could not create temp dir '" + + pathstr_ + "'"); +#elif __APPLE__ + if (!std::filesystem::create_directory(pathstr_)) + Utils().myexit("Could not create temp dir '" + pathstr_ + "'"); +#else + if (!mkdtemp((char*)pathstr_.c_str())) + Utils().myexit("Could not create a temp dir\n" + "from pattern '" + pathstr_ + "'"); +#endif + path = pathstr_; + // Plus filename + std::snprintf(rnds, sizeof(rnds)-1, "%lX", rand(prng)); + path /= rnds; + filestub_ = path.string(); } -void NLSolver::Destroy() { - // try & delete .nl - if (!filestubCustom_) - std::remove((filestub_ + ".nl").c_str()); +void NLSolver::DestroyAutoStub() { + // delete temp folder if created + if (pathstr_.size()) { + std::error_code ec; + std::filesystem::remove_all(pathstr_, ec); + if (ec) + Utils().log_warning("Failed to remove temp dir '%s': %s", + pathstr_.c_str(), ec.message().c_str()); + } } void NLSolver::SetFileStub(std::string stub) { diff --git a/solvers/CMakeLists.txt b/solvers/CMakeLists.txt index 9d98f29ab..59f135c42 100644 --- a/solvers/CMakeLists.txt +++ b/solvers/CMakeLists.txt @@ -147,7 +147,7 @@ function(add_ampl_solver name) if(add_ampl_solver_VERSION) target_compile_definitions(${static_lib} PUBLIC DRIVER_DATE=${add_ampl_solver_VERSION}) endif() - set_property(TARGET ${static_lib} PROPERTY CXX_STANDARD 11) + set_property(TARGET ${static_lib} PROPERTY CXX_STANDARD 17) if (use_dll_runtime) target_link_libraries(${static_lib} PUBLIC mp-dynrt) else () diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 760e36f42..4f9f85771 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -54,7 +54,7 @@ function(add_mp_test name) cmake_parse_arguments(add_mp_test "" "" LIBS ${ARGN}) add_executable(${name} ${add_mp_test_UNPARSED_ARGUMENTS}) set_target_properties(${name} PROPERTIES OUTPUT_NAME ${name}) - set_property(TARGET ${name} PROPERTY CXX_STANDARD 11) + set_property(TARGET ${name} PROPERTY CXX_STANDARD 17) if (MINGW) set_target_properties(${name} PROPERTIES LINK_FLAGS "-static-libgcc -static-libstdc++")