diff --git a/check/meson.build b/check/meson.build index 1cf8e5663d..95f6d77736 100644 --- a/check/meson.build +++ b/check/meson.build @@ -71,10 +71,11 @@ foreach test : test_array ) endforeach -if get_option('with_c') - test('test_capi', - executable('capi_unit_tests', 'TestCAPI.c', - include_directories: _incdirs, - link_with : highslib, - )) -endif + +test('test_capi', + executable('capi_unit_tests', + sources: ['TestCAPI.c', highs_conf_file], + include_directories: _incdirs, + link_with : highslib, + ) + ) diff --git a/meson_options.txt b/meson_options.txt index 9f763abd82..1964eabef6 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -13,9 +13,6 @@ option('with_fortran', option('with_csharp', type : 'boolean', value : false) -option('with_c', - type : 'boolean', - value : false) option('debug_sol', type: 'boolean', value: false) @@ -29,3 +26,6 @@ option('with_tests', option('with_pybind11', type : 'boolean', value : false) +option('do_install', + type : 'boolean', + value : true) diff --git a/src/highs_bindings.cpp b/src/highs_bindings.cpp index 625ae68d2f..e1b01e2f0b 100644 --- a/src/highs_bindings.cpp +++ b/src/highs_bindings.cpp @@ -8,6 +8,7 @@ #include "Highs.h" #include "lp_data/HighsCallback.h" +#include "simplex/SimplexConst.h" namespace py = pybind11; using namespace pybind11::literals; @@ -442,7 +443,6 @@ std::tuple highs_getCol( std::tuple, dense_array_t> highs_getColEntries(Highs* h, HighsInt col) { - double cost, lower, upper; HighsInt get_num_col; HighsInt get_num_nz; HighsInt col_ = static_cast(col); @@ -462,7 +462,7 @@ highs_getColEntries(Highs* h, HighsInt col) { std::tuple highs_getRow(Highs* h, HighsInt row) { - double cost, lower, upper; + double lower, upper; HighsInt get_num_row; HighsInt get_num_nz; HighsInt row_ = static_cast(row); @@ -473,7 +473,6 @@ std::tuple highs_getRow(Highs* h, std::tuple, dense_array_t> highs_getRowEntries(Highs* h, HighsInt row) { - double cost, lower, upper; HighsInt get_num_row; HighsInt get_num_nz; HighsInt row_ = static_cast(row); @@ -624,7 +623,7 @@ HighsStatus highs_setCallback( return h->setCallback((HighsCallbackFunctionType) nullptr, nullptr); else return h->setCallback( - [fn, data](int callbackType, const std::string& msg, + [fn](int callbackType, const std::string& msg, const HighsCallbackDataOut* dataOut, HighsCallbackDataIn* dataIn, void* d) { return fn(callbackType, msg, dataOut, dataIn, py::handle(reinterpret_cast(d))); @@ -633,20 +632,25 @@ HighsStatus highs_setCallback( } PYBIND11_MODULE(_core, m) { - // enum classes + // To keep a smaller diff, for reviewers, the declarations are not moved, but + // keep in mind: + // C++ enum classes :: don't need .export_values() + // C++ enums, need .export_values() + // Quoting [1]: + // "The enum_::export_values() function exports the enum entries into the + // parent scope, which should be skipped for newer C++11-style strongly typed + // enums." + // [1]: https://pybind11.readthedocs.io/en/stable/classes.html py::enum_(m, "ObjSense") .value("kMinimize", ObjSense::kMinimize) .value("kMaximize", ObjSense::kMaximize); - // // .export_values(); py::enum_(m, "MatrixFormat") .value("kColwise", MatrixFormat::kColwise) .value("kRowwise", MatrixFormat::kRowwise) .value("kRowwisePartitioned", MatrixFormat::kRowwisePartitioned); - // // .export_values(); py::enum_(m, "HessianFormat") .value("kTriangular", HessianFormat::kTriangular) .value("kSquare", HessianFormat::kSquare); - // .export_values(); py::enum_(m, "SolutionStatus") .value("kSolutionStatusNone", SolutionStatus::kSolutionStatusNone) .value("kSolutionStatusInfeasible", @@ -677,7 +681,6 @@ PYBIND11_MODULE(_core, m) { .value("kSolutionLimit", HighsModelStatus::kSolutionLimit) .value("kInterrupt", HighsModelStatus::kInterrupt) .value("kMemoryLimit", HighsModelStatus::kMemoryLimit); - // .export_values(); py::enum_(m, "HighsPresolveStatus") .value("kNotPresolved", HighsPresolveStatus::kNotPresolved) .value("kNotReduced", HighsPresolveStatus::kNotReduced) @@ -689,59 +692,60 @@ PYBIND11_MODULE(_core, m) { .value("kTimeout", HighsPresolveStatus::kTimeout) .value("kNullError", HighsPresolveStatus::kNullError) .value("kOptionsError", HighsPresolveStatus::kOptionsError); - // .export_values(); py::enum_(m, "HighsBasisStatus") .value("kLower", HighsBasisStatus::kLower) .value("kBasic", HighsBasisStatus::kBasic) .value("kUpper", HighsBasisStatus::kUpper) .value("kZero", HighsBasisStatus::kZero) .value("kNonbasic", HighsBasisStatus::kNonbasic); - // .export_values(); py::enum_(m, "HighsVarType") .value("kContinuous", HighsVarType::kContinuous) .value("kInteger", HighsVarType::kInteger) .value("kSemiContinuous", HighsVarType::kSemiContinuous) .value("kSemiInteger", HighsVarType::kSemiInteger); - // .export_values(); py::enum_(m, "HighsOptionType") .value("kBool", HighsOptionType::kBool) .value("kInt", HighsOptionType::kInt) .value("kDouble", HighsOptionType::kDouble) .value("kString", HighsOptionType::kString); - // .export_values(); py::enum_(m, "HighsInfoType") .value("kInt64", HighsInfoType::kInt64) .value("kInt", HighsInfoType::kInt) .value("kDouble", HighsInfoType::kDouble); - // .export_values(); py::enum_(m, "HighsStatus") .value("kError", HighsStatus::kError) .value("kOk", HighsStatus::kOk) .value("kWarning", HighsStatus::kWarning); - // .export_values(); py::enum_(m, "HighsLogType") .value("kInfo", HighsLogType::kInfo) .value("kDetailed", HighsLogType::kDetailed) .value("kVerbose", HighsLogType::kVerbose) .value("kWarning", HighsLogType::kWarning) .value("kError", HighsLogType::kError); - // .export_values(); py::enum_(m, "IisStrategy") .value("kIisStrategyMin", IisStrategy::kIisStrategyMin) .value("kIisStrategyFromLpRowPriority", IisStrategy::kIisStrategyFromLpRowPriority) .value("kIisStrategyFromLpColPriority", IisStrategy::kIisStrategyFromLpColPriority) - .value("kIisStrategyMax", IisStrategy::kIisStrategyMax); - // .export_values(); + .value("kIisStrategyMax", IisStrategy::kIisStrategyMax) + .export_values(); py::enum_(m, "IisBoundStatus") .value("kIisBoundStatusDropped", IisBoundStatus::kIisBoundStatusDropped) .value("kIisBoundStatusNull", IisBoundStatus::kIisBoundStatusNull) .value("kIisBoundStatusFree", IisBoundStatus::kIisBoundStatusFree) .value("kIisBoundStatusLower", IisBoundStatus::kIisBoundStatusLower) .value("kIisBoundStatusUpper", IisBoundStatus::kIisBoundStatusUpper) - .value("kIisBoundStatusBoxed", IisBoundStatus::kIisBoundStatusBoxed); - // .export_values(); + .value("kIisBoundStatusBoxed", IisBoundStatus::kIisBoundStatusBoxed) + .export_values(); + py::enum_(m, "HighsDebugLevel") + .value("kHighsDebugLevelNone", HighsDebugLevel::kHighsDebugLevelNone) + .value("kHighsDebugLevelCheap", HighsDebugLevel::kHighsDebugLevelCheap) + .value("kHighsDebugLevelCostly", HighsDebugLevel::kHighsDebugLevelCostly) + .value("kHighsDebugLevelExpensive", HighsDebugLevel::kHighsDebugLevelExpensive) + .value("kHighsDebugLevelMin", HighsDebugLevel::kHighsDebugLevelMin) + .value("kHighsDebugLevelMax", HighsDebugLevel::kHighsDebugLevelMax) + .export_values(); // Classes py::class_(m, "HighsSparseMatrix") .def(py::init<>()) @@ -1158,7 +1162,23 @@ PYBIND11_MODULE(_core, m) { .value("kSimplexStrategyPrimal", SimplexStrategy::kSimplexStrategyPrimal) .value("kSimplexStrategyMax", SimplexStrategy::kSimplexStrategyMax) .value("kSimplexStrategyNum", SimplexStrategy::kSimplexStrategyNum) - .export_values(); // needed since it isn't an enum class + .export_values(); + py::enum_(simplex_constants, "SimplexCrashStrategy") + .value("kSimplexCrashStrategyMin", SimplexCrashStrategy::kSimplexCrashStrategyMin) + .value("kSimplexCrashStrategyOff", SimplexCrashStrategy::kSimplexCrashStrategyOff) + .value("kSimplexCrashStrategyLtssfK", SimplexCrashStrategy::kSimplexCrashStrategyLtssfK) + .value("kSimplexCrashStrategyLtssf", SimplexCrashStrategy::kSimplexCrashStrategyLtssf) + .value("kSimplexCrashStrategyBixby", SimplexCrashStrategy::kSimplexCrashStrategyBixby) + .value("kSimplexCrashStrategyLtssfPri", SimplexCrashStrategy::kSimplexCrashStrategyLtssfPri) + .value("kSimplexCrashStrategyLtsfK", SimplexCrashStrategy::kSimplexCrashStrategyLtsfK) + .value("kSimplexCrashStrategyLtsfPri", SimplexCrashStrategy::kSimplexCrashStrategyLtsfPri) + .value("kSimplexCrashStrategyLtsf", SimplexCrashStrategy::kSimplexCrashStrategyLtsf) + .value("kSimplexCrashStrategyBixbyNoNonzeroColCosts", + SimplexCrashStrategy::kSimplexCrashStrategyBixbyNoNonzeroColCosts) + .value("kSimplexCrashStrategyBasic", SimplexCrashStrategy::kSimplexCrashStrategyBasic) + .value("kSimplexCrashStrategyTestSing", SimplexCrashStrategy::kSimplexCrashStrategyTestSing) + .value("kSimplexCrashStrategyMax", SimplexCrashStrategy::kSimplexCrashStrategyMax) + .export_values(); py::enum_(simplex_constants, "SimplexUnscaledSolutionStrategy") .value( diff --git a/src/highs_options.cpp b/src/highs_options.cpp deleted file mode 100644 index c043b0d697..0000000000 --- a/src/highs_options.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include -#include - -#include -#include - -#include "lp_data/HighsOptions.h" - -namespace py = pybind11; - -bool log_to_console = false; -bool output_flag = true; - -// HighsLogOptions highs_log_options = {nullptr, &output_flag, &log_to_console, -// nullptr}; -HighsLogOptions highs_log_options = {}; - -class HighsOptionsManager { - public: - HighsOptionsManager() { - for (const auto& record : highs_options_.records) { - record_type_lookup_.emplace(record->name, record->type); - } - } - - const HighsOptions& get_highs_options() const { return highs_options_; } - - const std::map& get_record_type_lookup() const { - return record_type_lookup_; - } - - template - bool check_option(const std::string& name, const T value) { - std::lock_guard guard(highs_options_mutex); - HighsInt idx = 0; - const OptionStatus idx_status = getOptionIndex( - highs_log_options, name.c_str(), highs_options_.records, idx); - - if (OptionStatus::kOk != idx_status) { - return false; - } - - OptionRecordType& record = - static_cast(*highs_options_.records.at(idx)); - const OptionStatus check_status = - checkOptionValue(highs_log_options, record, value); - if (OptionStatus::kIllegalValue == check_status) { - return false; - } - - return true; - } - - private: - HighsOptions highs_options_; - std::mutex highs_options_mutex; - std::map record_type_lookup_; -}; - -PYBIND11_MODULE(_highs_options, m) { - py::class_(m, "HighsOptionsManager") - .def(py::init<>()) - .def("get_option_type", - [](const HighsOptionsManager& manager, const std::string& name) { - const auto& lookup = manager.get_record_type_lookup().find(name); - if (manager.get_record_type_lookup().end() == lookup) { - return -1; - } - return static_cast(lookup->second); - }) - .def("get_all_option_types", &HighsOptionsManager::get_record_type_lookup) - .def("get_highs_options_records", - [](const HighsOptionsManager& manager) { - std::vector records_names; - for (const auto& record : manager.get_highs_options().records) { - records_names.push_back(record->name); - } - return records_names; - }) - .def("check_int_option", - [](HighsOptionsManager& self, const std::string& name, int value) { - return self.check_option(name, value); - }) - .def( - "check_double_option", - [](HighsOptionsManager& self, const std::string& name, double value) { - return self.check_option(name, value); - }) - .def("check_string_option", [](HighsOptionsManager& self, - const std::string& name, - const std::string& value) { - return self.check_option(name, value); - }); -} diff --git a/src/meson.build b/src/meson.build index 56b83517ea..e61d88af82 100644 --- a/src/meson.build +++ b/src/meson.build @@ -117,45 +117,45 @@ _incdirs += include_directories('.') # --------------------- Libraries _cupdlp_srcs = [ - 'pdlp/cupdlp/cupdlp_solver.c', - 'pdlp/cupdlp/cupdlp_scaling_cuda.c', - 'pdlp/cupdlp/cupdlp_restart.c', - 'pdlp/cupdlp/cupdlp_proj.c', - 'pdlp/cupdlp/cupdlp_linalg.c', 'pdlp/cupdlp/cupdlp_cs.c', - 'pdlp/cupdlp/cupdlp_utils.c', + 'pdlp/cupdlp/cupdlp_linalg.c', + 'pdlp/cupdlp/cupdlp_proj.c', + 'pdlp/cupdlp/cupdlp_restart.c', + 'pdlp/cupdlp/cupdlp_scaling_cuda.c', + 'pdlp/cupdlp/cupdlp_solver.c', 'pdlp/cupdlp/cupdlp_step.c', + 'pdlp/cupdlp/cupdlp_utils.c', ] _basiclu_srcs = [ 'ipm/basiclu/basiclu_factorize.c', - 'ipm/basiclu/basiclu_solve_dense.c', - 'ipm/basiclu/lu_build_factors.c', - 'ipm/basiclu/lu_factorize_bump.c', - 'ipm/basiclu/lu_initialize.c', - 'ipm/basiclu/lu_markowitz.c', - 'ipm/basiclu/lu_setup_bump.c', - 'ipm/basiclu/lu_solve_sparse.c', 'ipm/basiclu/basiclu_get_factors.c', + 'ipm/basiclu/basiclu_initialize.c', + 'ipm/basiclu/basiclu_object.c', + 'ipm/basiclu/basiclu_solve_dense.c', 'ipm/basiclu/basiclu_solve_for_update.c', + 'ipm/basiclu/basiclu_solve_sparse.c', + 'ipm/basiclu/basiclu_update.c', + 'ipm/basiclu/lu_build_factors.c', 'ipm/basiclu/lu_condest.c', + 'ipm/basiclu/lu_dfs.c', + 'ipm/basiclu/lu_factorize_bump.c', 'ipm/basiclu/lu_file.c', + 'ipm/basiclu/lu_garbage_perm.c', + 'ipm/basiclu/lu_initialize.c', 'ipm/basiclu/lu_internal.c', + 'ipm/basiclu/lu_markowitz.c', 'ipm/basiclu/lu_matrix_norm.c', - 'ipm/basiclu/lu_singletons.c', - 'ipm/basiclu/lu_solve_symbolic.c', - 'ipm/basiclu/lu_update.c', - 'ipm/basiclu/basiclu_initialize.c', - 'ipm/basiclu/basiclu_solve_sparse.c', 'ipm/basiclu/lu_pivot.c', - 'ipm/basiclu/lu_solve_dense.c', - 'ipm/basiclu/lu_solve_triangular.c', - 'ipm/basiclu/basiclu_object.c', - 'ipm/basiclu/basiclu_update.c', - 'ipm/basiclu/lu_dfs.c', - 'ipm/basiclu/lu_garbage_perm.c', 'ipm/basiclu/lu_residual_test.c', + 'ipm/basiclu/lu_setup_bump.c', + 'ipm/basiclu/lu_singletons.c', + 'ipm/basiclu/lu_solve_dense.c', 'ipm/basiclu/lu_solve_for_update.c', + 'ipm/basiclu/lu_solve_sparse.c', + 'ipm/basiclu/lu_solve_symbolic.c', + 'ipm/basiclu/lu_solve_triangular.c', + 'ipm/basiclu/lu_update.c', ] _ipx_srcs = [ @@ -194,85 +194,87 @@ _ipx_srcs = [ _srcs = [ '../extern/filereaderlp/reader.cpp', + 'interfaces/highs_c_api.cpp', 'io/Filereader.cpp', - 'io/FilereaderLp.cpp', 'io/FilereaderEms.cpp', + 'io/FilereaderLp.cpp', 'io/FilereaderMps.cpp', - 'io/HighsIO.cpp', 'io/HMPSIO.cpp', 'io/HMpsFF.cpp', + 'io/HighsIO.cpp', 'io/LoadOptions.cpp', + 'ipm/IpxWrapper.cpp', 'lp_data/Highs.cpp', 'lp_data/HighsCallback.cpp', 'lp_data/HighsDebug.cpp', 'lp_data/HighsIis.cpp', + 'lp_data/HighsDeprecated.cpp', 'lp_data/HighsInfo.cpp', 'lp_data/HighsInfoDebug.cpp', - 'lp_data/HighsDeprecated.cpp', 'lp_data/HighsInterface.cpp', 'lp_data/HighsLp.cpp', 'lp_data/HighsLpUtils.cpp', 'lp_data/HighsModelUtils.cpp', + 'lp_data/HighsOptions.cpp', 'lp_data/HighsRanging.cpp', 'lp_data/HighsSolution.cpp', 'lp_data/HighsSolutionDebug.cpp', 'lp_data/HighsSolve.cpp', 'lp_data/HighsStatus.cpp', - 'lp_data/HighsOptions.cpp', - 'mip/HighsMipSolver.cpp', - 'mip/HighsMipSolverData.cpp', + 'mip/HighsCliqueTable.cpp', + 'mip/HighsConflictPool.cpp', + 'mip/HighsCutGeneration.cpp', + 'mip/HighsCutPool.cpp', + 'mip/HighsDebugSol.cpp', 'mip/HighsDomain.cpp', 'mip/HighsDynamicRowMatrix.cpp', + 'mip/HighsGFkSolve.cpp', + 'mip/HighsImplications.cpp', + 'mip/HighsLpAggregator.cpp', 'mip/HighsLpRelaxation.cpp', - 'mip/HighsSeparation.cpp', - 'mip/HighsSeparator.cpp', - 'mip/HighsTableauSeparator.cpp', + 'mip/HighsMipSolver.cpp', + 'mip/HighsMipSolverData.cpp', 'mip/HighsModkSeparator.cpp', + 'mip/HighsNodeQueue.cpp', + 'mip/HighsObjectiveFunction.cpp', 'mip/HighsPathSeparator.cpp', - 'mip/HighsCutGeneration.cpp', - 'mip/HighsSearch.cpp', - 'mip/HighsConflictPool.cpp', - 'mip/HighsCutPool.cpp', - 'mip/HighsCliqueTable.cpp', - 'mip/HighsGFkSolve.cpp', - 'mip/HighsTransformedLp.cpp', - 'mip/HighsLpAggregator.cpp', - 'mip/HighsDebugSol.cpp', - 'mip/HighsImplications.cpp', 'mip/HighsPrimalHeuristics.cpp', 'mip/HighsPseudocost.cpp', 'mip/HighsRedcostFixing.cpp', - 'mip/HighsNodeQueue.cpp', - 'mip/HighsObjectiveFunction.cpp', + 'mip/HighsSearch.cpp', + 'mip/HighsSeparation.cpp', + 'mip/HighsSeparator.cpp', + 'mip/HighsTableauSeparator.cpp', + 'mip/HighsTransformedLp.cpp', 'model/HighsHessian.cpp', 'model/HighsHessianUtils.cpp', 'model/HighsModel.cpp', 'parallel/HighsTaskExecutor.cpp', + 'pdlp/CupdlpWrapper.cpp', + 'presolve/HPresolve.cpp', + 'presolve/HPresolveAnalysis.cpp', + 'presolve/HighsPostsolveStack.cpp', + 'presolve/HighsSymmetry.cpp', 'presolve/ICrash.cpp', 'presolve/ICrashUtil.cpp', 'presolve/ICrashX.cpp', - 'presolve/HighsPostsolveStack.cpp', - 'presolve/HighsSymmetry.cpp', - 'presolve/HPresolve.cpp', - 'presolve/HPresolveAnalysis.cpp', 'presolve/PresolveComponent.cpp', 'qpsolver/a_asm.cpp', 'qpsolver/a_quass.cpp', 'qpsolver/basis.cpp', + 'qpsolver/perturbation.cpp', 'qpsolver/quass.cpp', 'qpsolver/ratiotest.cpp', 'qpsolver/scaling.cpp', - 'qpsolver/perturbation.cpp', 'simplex/HEkk.cpp', 'simplex/HEkkControl.cpp', 'simplex/HEkkDebug.cpp', - 'simplex/HEkkPrimal.cpp', 'simplex/HEkkDual.cpp', + 'simplex/HEkkDualMulti.cpp', 'simplex/HEkkDualRHS.cpp', 'simplex/HEkkDualRow.cpp', - 'simplex/HEkkDualMulti.cpp', 'simplex/HEkkInterface.cpp', - 'simplex/HighsSimplexAnalysis.cpp', + 'simplex/HEkkPrimal.cpp', 'simplex/HSimplex.cpp', 'simplex/HSimplexDebug.cpp', 'simplex/HSimplexNla.cpp', @@ -280,6 +282,7 @@ _srcs = [ 'simplex/HSimplexNlaFreeze.cpp', 'simplex/HSimplexNlaProductForm.cpp', 'simplex/HSimplexReport.cpp', + 'simplex/HighsSimplexAnalysis.cpp', 'test/DevKkt.cpp', 'test/KktCh2.cpp', 'util/HFactor.cpp', @@ -287,6 +290,8 @@ _srcs = [ 'util/HFactorExtend.cpp', 'util/HFactorRefactor.cpp', 'util/HFactorUtils.cpp', + 'util/HSet.cpp', + 'util/HVectorBase.cpp', 'util/HighsHash.cpp', 'util/HighsLinearSumBounds.cpp', 'util/HighsMatrixPic.cpp', @@ -294,19 +299,15 @@ _srcs = [ 'util/HighsSort.cpp', 'util/HighsSparseMatrix.cpp', 'util/HighsUtils.cpp', - 'util/HSet.cpp', - 'util/HVectorBase.cpp', 'util/stringutil.cpp', ] highslib_srcs = [ highs_conf_file, - _srcs, - 'ipm/IpxWrapper.cpp', - 'pdlp/CupdlpWrapper.cpp', - _cupdlp_srcs, - _basiclu_srcs, - _ipx_srcs + files(_srcs), + files(_cupdlp_srcs), + files(_basiclu_srcs), + files(_ipx_srcs), ] @@ -317,6 +318,9 @@ if get_option('default_library') == 'static' symbol_visibility = 'inlineshidden' endif +do_install = get_option('do_install') + +if not get_option('with_pybind11') highslib = library('highs', highslib_srcs, dependencies: _deps, @@ -325,7 +329,13 @@ highslib = library('highs', include_directories: _incdirs, gnu_symbol_visibility: symbol_visibility, pic: true, - install: true) + install: do_install) + +highs_dep = declare_dependency(link_with: highslib, + dependencies: _deps, + include_directories: _incdirs, + ) +endif # --------------- Interfaces @@ -342,7 +352,7 @@ if get_option('with_fortran') link_with: highslib, include_directories: _incdirs, pic: true, - install: true) + install: do_install) endif # C# @@ -358,69 +368,60 @@ if get_option('with_csharp') link_with: highslib, include_directories: _incdirs, pic: true, - install: true) + install: do_install) endif -# C -if get_option('with_c') - add_languages('c', required: true) - if not is_windows - m_dep = cppc.find_library('m', required: false) - _deps += m_dep - endif - _c_src = [ - 'interfaces/highs_c_api.cpp', - ] - c_lib = library('HighsC', - _c_src, - dependencies: _deps, - cpp_args: _args, - link_with: highslib, - include_directories: _incdirs, - pic: true, - install: true) -endif - -highs_dep = declare_dependency(link_with: highslib, - dependencies: _deps, - include_directories: _incdirs, - ) +highspy_cpp = files([ + 'highs_bindings.cpp' +]) if get_option('with_pybind11') py = import('python').find_installation(pure: false) + highslib = library('highs', + highslib_srcs, + dependencies: _deps, + cpp_args: _args, + c_args: _args, + include_directories: _incdirs, + gnu_symbol_visibility: symbol_visibility, + pic: true, + install: do_install, + install_dir: py.get_install_dir() / 'highspy') + + highs_dep = declare_dependency(link_with: highslib, + dependencies: _deps, + include_directories: _incdirs, + ) + _deps += highs_dep pyb11_dep = [ # py_dep is auto-added for Python >= 3.9, so it can be dropped here when # that is the minimum supported Python version py.dependency(), dependency('pybind11') ] + _deps += pyb11_dep highspy_cpp = files([ 'highs_bindings.cpp' ]) - highsoptions_cpp = files([ - 'highs_options.cpp' - ]) py.extension_module( - '_highs', + '_core', sources : highspy_cpp, dependencies: [pyb11_dep, highs_dep], cpp_args: _args, - install: true, + install: do_install, subdir: 'highspy', include_directories: _incdirs, ) - py.extension_module( - '_highs_options', - sources : highsoptions_cpp, - dependencies: [pyb11_dep, highs_dep], - cpp_args: _args, - install: true, - subdir: 'highspy', - include_directories: _incdirs, - ) + highs_py_srcs = files( + 'highspy/__init__.py', + 'highspy/highs.py',) + py.install_sources(highs_py_srcs, + pure: false, + subdir: 'highspy' + ) endif diff --git a/src/simplex/HApp.h b/src/simplex/HApp.h index 1e3b7daea5..5e48ae9cd3 100644 --- a/src/simplex/HApp.h +++ b/src/simplex/HApp.h @@ -254,12 +254,27 @@ inline HighsStatus solveLpSimplex(HighsLpSolverObject& solver_object) { scaled_model_status == HighsModelStatus::kOptimal && (num_unscaled_primal_infeasibilities || num_unscaled_dual_infeasibilities); - if (scaled_optimality_but_unscaled_infeasibilities) + // Determine whether the unscaled solution has primal + // infeasibilities after the scaled LP has been solved to the + // objective target + const bool scaled_objective_target_but_unscaled_primal_infeasibilities = + scaled_model_status == HighsModelStatus::kObjectiveTarget && + highs_info.num_primal_infeasibilities > 0; + // Determine whether the unscaled solution has dual + // infeasibilities after the scaled LP has been solved to the + // objective bound + const bool scaled_objective_bound_but_unscaled_dual_infeasibilities = + scaled_model_status == HighsModelStatus::kObjectiveBound && + highs_info.num_dual_infeasibilities > 0; + if (scaled_optimality_but_unscaled_infeasibilities || + scaled_objective_target_but_unscaled_primal_infeasibilities || + scaled_objective_bound_but_unscaled_dual_infeasibilities) highsLogDev(options.log_options, HighsLogType::kInfo, - "Have num/max/sum primal (%" HIGHSINT_FORMAT - "/%g/%g) and dual (%" HIGHSINT_FORMAT + "After unscaling with status %s, have num/max/sum primal " + "(%" HIGHSINT_FORMAT "/%g/%g) and dual (%" HIGHSINT_FORMAT "/%g/%g) " "unscaled infeasibilities\n", + utilModelStatusToString(scaled_model_status).c_str(), highs_info.num_primal_infeasibilities, highs_info.max_primal_infeasibility, highs_info.sum_primal_infeasibilities, @@ -274,8 +289,8 @@ inline HighsStatus solveLpSimplex(HighsLpSolverObject& solver_object) { scaled_model_status == HighsModelStatus::kInfeasible || scaled_model_status == HighsModelStatus::kUnboundedOrInfeasible || scaled_model_status == HighsModelStatus::kUnbounded || - scaled_model_status == HighsModelStatus::kObjectiveBound || - scaled_model_status == HighsModelStatus::kObjectiveTarget || + scaled_objective_bound_but_unscaled_dual_infeasibilities || + scaled_objective_target_but_unscaled_primal_infeasibilities || scaled_model_status == HighsModelStatus::kUnknown); // Handle the case when refinement will not take place if (!refine_solution) { @@ -322,21 +337,31 @@ inline HighsStatus solveLpSimplex(HighsLpSolverObject& solver_object) { options.dual_simplex_cost_perturbation_multiplier; HighsInt simplex_dual_edge_weight_strategy = ekk_info.dual_edge_weight_strategy; + // #1865 exposed that this should not be + // HighsModelStatus::kObjectiveBound, but + // HighsModelStatus::kObjectiveTarget, since if the latter is + // the model status for the scaled LP, any primal + // infeasibilities should be small, but must be cleaned up + // before (hopefully) a few phase 2 primal simplex iterations + // are required to attain the target for the unscaled LP + // + // In #1865, phase 2 primal simplex was forced with large primal + // infeasibilities if (num_unscaled_primal_infeasibilities == 0 || - scaled_model_status == HighsModelStatus::kObjectiveBound) { - // Only dual infeasibilities, or primal infeasibilities do not - // matter due to solution status, so use primal simplex phase - // 2 + scaled_model_status == HighsModelStatus::kObjectiveTarget) { + // Only dual infeasibilities, or objective target reached (in + // primal phase 2) - so primal infeasibilities should be small + // - so use primal simplex phase 2 options.simplex_strategy = kSimplexStrategyPrimal; - if (scaled_model_status == HighsModelStatus::kObjectiveBound) { - highsLogDev( - options.log_options, HighsLogType::kInfo, - "solveLpSimplex: Calling primal simplex after " - "scaled_model_status == HighsModelStatus::kObjectiveBound: solve " - "= %d; tick = %d; iter = %d\n", - (int)ekk_instance.debug_solve_call_num_, - (int)ekk_instance.debug_initial_build_synthetic_tick_, - (int)ekk_instance.iteration_count_); + if (scaled_model_status == HighsModelStatus::kObjectiveTarget) { + highsLogDev(options.log_options, HighsLogType::kInfo, + "solveLpSimplex: Calling primal simplex after " + "scaled_model_status == " + "HighsModelStatus::kObjectiveTarget: solve " + "= %d; tick = %d; iter = %d\n", + (int)ekk_instance.debug_solve_call_num_, + (int)ekk_instance.debug_initial_build_synthetic_tick_, + (int)ekk_instance.iteration_count_); } } else { // Using dual simplex, so force Devex if starting from an advanced @@ -349,11 +374,21 @@ inline HighsStatus solveLpSimplex(HighsLpSolverObject& solver_object) { // // Solve the unscaled LP with scaled NLA // - // Force the simplex solver to start in phase 2 unless solving - // the LP directly as unscaled + // Force the simplex solver to start in phase 1 if solving the + // LP directly as unscaled, or using primal simplex to clean up + // small dual infeasibilities after the scaled LP yielded model + // status HighsModelStatus::kObjectiveTarget. Otherwise force + // the simplex solver to start in phase 2 // - const bool force_phase2 = options.simplex_unscaled_solution_strategy != - kSimplexUnscaledSolutionStrategyDirect; + const bool force_phase1 = + (options.simplex_unscaled_solution_strategy == + kSimplexUnscaledSolutionStrategyDirect) || + (scaled_model_status == HighsModelStatus::kObjectiveTarget); + const bool force_phase2 = + (options.simplex_unscaled_solution_strategy != + kSimplexUnscaledSolutionStrategyDirect) && + (scaled_model_status != HighsModelStatus::kObjectiveTarget); + assert(force_phase2 == !force_phase1); return_status = ekk_instance.solve(force_phase2); solved_unscaled_lp = true; if (scaled_model_status != HighsModelStatus::kObjectiveBound && @@ -362,7 +397,6 @@ inline HighsStatus solveLpSimplex(HighsLpSolverObject& solver_object) { // for the first time in which case we again call solve with primal // simplex if not dual feasible const bool objective_bound_refinement = - ekk_instance.model_status_ == HighsModelStatus::kObjectiveBound && ekk_info.num_dual_infeasibilities > 0; if (objective_bound_refinement) { options.simplex_strategy = kSimplexStrategyPrimal; diff --git a/src/simplex/HEkkDual.cpp b/src/simplex/HEkkDual.cpp index a9f53ce0ee..e05633b376 100644 --- a/src/simplex/HEkkDual.cpp +++ b/src/simplex/HEkkDual.cpp @@ -2807,10 +2807,10 @@ bool HEkkDual::reachedExactObjectiveBound() { exact_dual_objective_value - objective_bound; std::string action; if (exact_dual_objective_value > objective_bound) { - highsLogDev(ekk_instance_.options_->log_options, HighsLogType::kDetailed, - "HEkkDual::solvePhase2: %12g = Objective > ObjectiveUB\n", - ekk_instance_.info_.updated_dual_objective_value, - objective_bound); + highsLogDev( + ekk_instance_.options_->log_options, HighsLogType::kDetailed, + "HEkkDual::solvePhase2: %12g = Objective > ObjectiveUB = %12g\n", + ekk_instance_.info_.updated_dual_objective_value, objective_bound); action = "Have DualUB bailout"; if (ekk_instance_.info_.costs_perturbed || ekk_instance_.info_.costs_shifted) { diff --git a/src/simplex/HEkkPrimal.cpp b/src/simplex/HEkkPrimal.cpp index 51d9c8abee..5d6509c972 100644 --- a/src/simplex/HEkkPrimal.cpp +++ b/src/simplex/HEkkPrimal.cpp @@ -2812,6 +2812,15 @@ void HEkkPrimal::shiftBound(const bool lower, const HighsInt iVar, shift = infeasibility + feasibility; bound -= shift; new_infeasibility = bound - value; + if (new_infeasibility >= 0) { + printf( + "HEkkPrimal::shiftBound LB = %g; random_value = %g; value = %g; " + "feasibility = %g; infeasibility = %g; shift = %g; bound = %g; " + "new_infeasibility = %g; \n", + old_bound, random_value, value, feasibility, infeasibility, shift, + bound, new_infeasibility); + fflush(stdout); + } assert(new_infeasibility < 0); } else { // Bound to shift is upper