diff --git a/.github/workflows/build-python-package.yml b/.github/workflows/build-python-package.yml new file mode 100644 index 0000000000..a36784356c --- /dev/null +++ b/.github/workflows/build-python-package.yml @@ -0,0 +1,216 @@ +name: build-python-package +on: [push, pull_request] +# on: [] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build_sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build sdist + run: | + python3 -m pip install setuptools + python3 setup.py sdist + + - name: Install sdist + run: | + ls dist + python3 -m pip install dist/*.tar.gz + + - name: Test highspy + run: | + python3 -m pip install pytest + python3 -m pytest $GITHUB_WORKSPACE/highspy/tests/test_highspy.py + + build_sdist_mac: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Build sdist + run: | + python3 -m pip install setuptools + python3 setup.py sdist + + - name: Install sdist + run: | + ls dist + python3 -m pip install dist/*.tar.gz + + - name: Test highspy + run: | + python3 -m pip install pytest + python3 -m pytest $GITHUB_WORKSPACE/highspy/tests/test_highspy.py + + build_sdist_win: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - name: Install correct python version + uses: actions/setup-python@v5 + with: + python-version: 3.9 + + - name: Build sdist + run: | + python -m pip install setuptools + python setup.py sdist + + - name: Install sdist + run: | + $item = Get-ChildItem dist + python -m pip install "$item" + python -c "import highspy; print(dir(highspy))" + + - name: Test highspy + run: | + python -m pip install pytest + python -m pytest ./highspy/tests/test_highspy.py + + build_wheel_linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build wheel + run: | + python3 --version + python3 -m pip install cibuildwheel + python3 -m cibuildwheel --only cp310-manylinux_x86_64 $GITHUB_WORKSPACE + + - name: Install wheel + run: | + ls wheelhouse + python3 -m pip install wheelhouse/*.whl + + - name: Test highspy + run: | + python3 -m pip install pytest + python3 -m pytest $GITHUB_WORKSPACE/highspy/tests/test_highspy.py + + # macos 12 is Intel + build_wheel_macos_12: + runs-on: macos-12 + strategy: + matrix: + python: [3.11] + steps: + - uses: actions/checkout@v4 + + - name: Install correct python version + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Build wheel + run: | + python3 -m pip install cibuildwheel + python3 -m cibuildwheel --only cp311-macosx_x86_64 $GITHUB_WORKSPACE + + - name: Install wheel + run: | + ls wheelhouse + python3 --version + python3 -m pip install wheelhouse/*.whl + python3 -c "import highspy; print(dir(highspy))" + + - name: Test highspy + run: | + python3 -m pip install pytest + python3 -m pytest $GITHUB_WORKSPACE/highspy/tests/test_highspy.py + + # macos 13 is Intel + build_wheel_macos_13: + runs-on: macos-13 + strategy: + matrix: + python: [3.11] + steps: + - uses: actions/checkout@v4 + + - name: Install correct python version + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Build wheel + run: | + python3 -m pip install cibuildwheel + python3 -m cibuildwheel --only cp311-macosx_x86_64 $GITHUB_WORKSPACE + + - name: Install wheel + run: | + ls wheelhouse + python3 --version + python3 -m pip install wheelhouse/*.whl + python3 -c "import highspy; print(dir(highspy))" + + - name: Test highspy + run: | + python3 -m pip install pytest + python3 -m pytest $GITHUB_WORKSPACE/highspy/tests/test_highspy.py + + # macos 14 is M1 (beta) + build_wheel_macos_14: + runs-on: macos-14 + strategy: + matrix: + python: [3.11] + steps: + - uses: actions/checkout@v4 + + - name: Install correct python version + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Build wheel + run: | + python3 -m pip install cibuildwheel + python3 -m cibuildwheel --only cp311-macosx_arm64 $GITHUB_WORKSPACE + + - name: Install wheel + run: | + ls wheelhouse + python3 --version + python3 -m pip install wheelhouse/*.whl + python3 -c "import highspy; print(dir(highspy))" + + - name: Test highspy + run: | + python3 -m pip install pytest + python3 -m pytest $GITHUB_WORKSPACE/highspy/tests/test_highspy.py + + build_wheel_windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - name: Install correct python version + uses: actions/setup-python@v5 + with: + python-version: 3.9 + + - name: Build wheel + run: | + python -m pip install cibuildwheel + python -m cibuildwheel --only cp39-win_amd64 $GITHUB_WORKSPACE + + - name: Install wheel + run: | + ls wheelhouse + $item = Get-ChildItem wheelhouse + python -m pip install "$item" + python -c "import highspy; print(dir(highspy))" + + - name: Test highspy + run: | + python -m pip install pytest + python -m pytest highspy/tests/test_highspy.py + \ No newline at end of file diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index dd800767f8..04051215a3 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -1,9 +1,11 @@ # Python release WIP name: Build wheels -on: [] -# release: -# types: -# - published +# on: [push] + +on: + release: + types: + - published concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -32,7 +34,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Build wheels - uses: pypa/cibuildwheel@v2.12.3 + uses: pypa/cibuildwheel@v2.16.5 env: CIBW_BUILD: ${{ matrix.python }}-${{ matrix.buildplat[1] }} - uses: actions/upload-artifact@v3 diff --git a/.github/workflows/test-python-macos.yml b/.github/workflows/test-python-macos.yml index 456e918f4b..5c0160f92c 100644 --- a/.github/workflows/test-python-macos.yml +++ b/.github/workflows/test-python-macos.yml @@ -24,3 +24,9 @@ jobs: run: | python3 -m pip install . pytest -v ./highspy/tests/ + + - name: Test Python Examples + run: | + python3 ./examples/call_highs_from_python_highspy.py + python3 ./examples/call_highs_from_python_mps.py + python3 ./examples/call_highs_from_python.py diff --git a/.github/workflows/test-python-ubuntu.yml b/.github/workflows/test-python-ubuntu.yml index b5850d7818..44cfeabda1 100644 --- a/.github/workflows/test-python-ubuntu.yml +++ b/.github/workflows/test-python-ubuntu.yml @@ -24,3 +24,9 @@ jobs: run: | python3 -m pip install . pytest -v ./highspy/tests/ + + - name: Test Python Examples + run: | + python3 ./examples/call_highs_from_python_highspy.py + python3 ./examples/call_highs_from_python_mps.py + python3 ./examples/call_highs_from_python.py \ No newline at end of file diff --git a/.github/workflows/test-python-win.yml b/.github/workflows/test-python-win.yml index 81bbf1f655..8919f66b02 100644 --- a/.github/workflows/test-python-win.yml +++ b/.github/workflows/test-python-win.yml @@ -26,3 +26,8 @@ jobs: python -m pip install . pytest -v ./highspy/tests/ + - name: Test Python Examples + run: | + python ./examples/call_highs_from_python_highspy.py + python ./examples/call_highs_from_python_mps.py + python ./examples/call_highs_from_python.py diff --git a/LICENSE b/LICENSE.txt similarity index 100% rename from LICENSE rename to LICENSE.txt diff --git a/README.md b/README.md index 81c521871e..f6d570da3e 100644 --- a/README.md +++ b/README.md @@ -5,27 +5,17 @@ [![PyPi](https://img.shields.io/pypi/v/highspy.svg)](https://pypi.python.org/pypi/highspy) [![PyPi](https://img.shields.io/pypi/dm/highspy.svg)](https://pypi.python.org/pypi/highspy) -## Table of Contents - - [HiGHS - Linear optimization software](#highs---linear-optimization-software) - - [Table of Contents](#table-of-contents) - [About HiGHS](#about-highs) - [Documentation](#documentation) - [Installation](#installation) + - [Build from source using CMake](#build-from-source-using-cmake) - [Precompiled binaries](#precompiled-binaries) - - [Compilation](#compilation) - - [Meson](#meson) - - [Python](#python) - [Interfaces](#interfaces) - - [Python](#python-1) - - [From PyPi](#from-pypi) - - [Build directly from Git](#build-directly-from-git) - - [Testing](#testing) - - [Google Colab Example](#google-colab-example) + - [Python](#python) - [Reference](#reference) ## About HiGHS ------------ HiGHS is a high performance serial and parallel solver for large scale sparse linear optimization problems of the form @@ -46,141 +36,83 @@ Documentation is available at https://ergo-code.github.io/HiGHS/. ## Installation -There are various ways to install the HiGHS library. These are detailed below. - -### Precompiled binaries --------------------- - -Precompiled static executables are available for a variety of platforms at -https://github.com/JuliaBinaryWrappers/HiGHSstatic_jll.jl/releases - -_These binaries are provided by the Julia community and are not officially supported by the HiGHS development team. If you have trouble using these libraries, please open a GitHub issue and tag `@odow` in your question._ - -See https://ergo-code.github.io/HiGHS/stable/installation/#Precompiled-Binaries. - -### Compilation ---------------- +### Build from source using CMake -HiGHS uses CMake as build system, and requires at least version 3.15. First setup a build folder and call CMake as follows - - mkdir build - cd build - cmake .. - -Then compile the code using - - cmake --build . - -This installs the executable `bin/highs`. - -As an alternative it is also possible to let `cmake` create the build folder and thus build everything from the HiGHS directory, as follows +HiGHS uses CMake as build system, and requires at least version 3.15. To generate build files in a new subdirectory called 'build', run: +```sh cmake -S . -B build cmake --build build +``` +This installs the executable `bin/highs` and the library `lib/highs`. +To test whether the compilation was successful, change into the build directory and run -To test whether the compilation was successful, run - +```sh ctest +``` HiGHS can read MPS files and (CPLEX) LP files, and the following command solves the model in `ml.mps` +```sh highs ml.mps - +``` HiGHS is installed using the command - cmake --install . - -with the optional setting of `--prefix = The installation prefix CMAKE_INSTALL_PREFIX` if it is to be installed anywhere other than the default location. +```sh + cmake --install build +``` -### Meson ------ +with the optional setting of `--prefix `, or the cmake option `CMAKE_INSTALL_PREFIX` if it is to be installed anywhere other than the default location. -HiGHs can also use the `meson` build interface: +As an alternative, HiGHS can be installed using the `meson` build interface: ``` sh meson setup bbdir -Dwith_tests=True meson test -C bbdir ``` +_The meson build files are provided by the community and are not officially supported by the HiGHS development team._ +### Precompiled binaries -### Python ------ +Precompiled static executables are available for a variety of platforms at +https://github.com/JuliaBinaryWrappers/HiGHSstatic_jll.jl/releases -Installing from PyPI through your Python package manager of choice (e.g., `pip`) will also -install the HiGHS library if not already present. HiGHS is available as `highspy` on [PyPi](https://pypi.org/project/highspy/). +_These binaries are provided by the Julia community and are not officially supported by the HiGHS development team. If you have trouble using these libraries, please open a GitHub issue and tag `@odow` in your question._ -If `highspy` is not already installed, run: +See https://ergo-code.github.io/HiGHS/stable/installation/#Precompiled-Binaries. -```bash -$ pip install highspy -``` ## Interfaces + There are HiGHS interfaces for C, C#, FORTRAN, and Python in [HiGHS/src/interfaces](https://github.com/ERGO-Code/HiGHS/blob/master/src/interfaces), with example driver files in [HiGHS/examples](https://github.com/ERGO-Code/HiGHS/blob/master/examples). More on language and modelling interfaces can be found at https://ergo-code.github.io/HiGHS/stable/interfaces/other/. We are happy to give a reasonable level of support via email sent to highsopt@gmail.com. -### Python - -There are two ways to install the Python interface. Building directly -from Git assumes that you have already installed the HiGHS library. -Installing from PyPI through your Python package manager of choice (e.g., `pip`) will also install the HiGHS library if not already present. - -#### From PyPi +#### Python -HiGHS is available as `highspy` on [PyPi](https://pypi.org/project/highspy/). -This will not only install the Python interface, but also the HiGHS library -itself. +The python package `highspy` is a thin wrapper around HiGHS and is available on [PyPi](https://pypi.org/project/highspy/). It can be easily installed via `pip` by running -If `highspy` is not already installed, run: - -```bash +```sh $ pip install highspy ``` -#### Build directly from Git - -In order to build the Python interface, build and install the HiGHS -library as described above, ensure the shared library is in the -`LD_LIBRARY_PATH` environment variable, and then run - - pip install ./ - -from the HiGHS directory. - -You may also require - -* `pip install pybind11` -* `pip install pyomo` +Alternatively, `highspy` can be built from source. Download the HiGHS source code and run -#### Testing - -The installation can be tested using the example [minimal.py](https://github.com/ERGO-Code/HiGHS/blob/master/examples/minimal.py), yielding the output - - Running HiGHS 1.5.0 [date: 2023-02-22, git hash: d041b3da0] - Copyright (c) 2023 HiGHS under MIT licence terms - Presolving model - 2 rows, 2 cols, 4 nonzeros - 0 rows, 0 cols, 0 nonzeros - 0 rows, 0 cols, 0 nonzeros - Presolve : Reductions: rows 0(-2); columns 0(-2); elements 0(-4) - Reduced to empty - Solving the original LP from the solution after postsolve - Model status : Optimal - Objective value : 1.0000000000e+00 - HiGHS run time : 0.00 - -or the more didactic [call_highs_from_python.py](https://github.com/ERGO-Code/HiGHS/blob/master/examples/call_highs_from_python.py). +```sh +pip install . +``` +from the root directory. -#### Google Colab Example +The HiGHS C++ library no longer needs to be separately installed. The python package `highspy` depends on the `numpy` package and `numpy` will be installed as well, if it is not already present. -The [Google Colab Example Notebook](https://colab.research.google.com/drive/1JmHF53OYfU-0Sp9bzLw-D2TQyRABSjHb?usp=sharing) demonstrates how to call HiGHS via the Python interface `highspy`. +The installation can be tested using the small example [call_highs_from_python_highspy.py](https://github.com/ERGO-Code/HiGHS/blob/master/examples/call_highs_from_python_highspy.py). +The [Google Colab Example Notebook](https://colab.research.google.com/drive/1JmHF53OYfU-0Sp9bzLw-D2TQyRABSjHb?usp=sharing) also demonstrates how to call `highspy`. ## Reference - If you use HiGHS in an academic context, please acknowledge this and cite the following article. Parallelizing the dual revised simplex method diff --git a/examples/call_highs_from_python.py b/examples/call_highs_from_python.py index e7723fb9af..978094bdba 100644 --- a/examples/call_highs_from_python.py +++ b/examples/call_highs_from_python.py @@ -1,41 +1,45 @@ -# NB Must have installed highspy, either by using pip install highspy, -# or by following the instructions on -# https://github.com/ERGO-Code/HiGHS#python -# # The paths to MPS file instances assumes that this is run in the -# directory of this file (ie highs/examples) or any other subdirectory -# of HiGHS +# root directory of HiGHS + import numpy as np -import highspy._highs -import highspy._highs.cb as hscb +import highspy +import highspy.cb as hscb -h = highspy._highs.Highs() -inf = highspy._highs.kHighsInf +h = highspy.Highs() +inf = highspy.kHighsInf alt_inf = h.getInfinity() -print('highspy._highs.kHighsInf = ', inf, '; h.getInfinity() = ', alt_inf) +print('highspy.kHighsInf = ', inf, + 'h.getInfinity() = ', alt_inf) + +# Define a callback + -# NB The callbacks are not available in -# https://pypi.org/project/highspy/ (HiGHS v1.5.3). To use them, -# highspy must be installed locally using (at least) HiGHS v1.6.0 def user_interrupt_callback( - callback_type, message, data_out, data_in, user_callback_data + callback_type, + message, + data_out, + data_in, + user_callback_data ): - dev_run = True # or any other condition + dev_run = True + # dev_run = False - # Constants for iteration limits or objective targets, adjust as per requirement - ADLITTLE_SIMPLEX_ITERATION_LIMIT = 100 - ADLITTLE_IPM_ITERATION_LIMIT = 100 + # Constants for iteration limits or objective targets, adjust as required + SIMPLEX_ITERATION_LIMIT = 100 + IPM_ITERATION_LIMIT = 100 EGOUT_OBJECTIVE_TARGET = 1.0 # Callback for MIP Improving Solution if callback_type == hscb.HighsCallbackType.kCallbackMipImprovingSolution: + # Assuming it is a list or array assert user_callback_data is not None, "User callback data is None!" - local_callback_data = user_callback_data[0] # Assuming it is a list or array + local_callback_data = user_callback_data[0] if dev_run: - print( - f"userCallback(type {callback_type}; data {local_callback_data:.4g}): {message} with objective {data_out.objective_function_value} and solution[0] = {data_out.mip_solution[0]}" - ) + print(f"userCallback(type {callback_type};") + print(f"data {local_callback_data:.4g}): {message}") + print(f"with objective {data_out.objective_function_value}") + print(f"and solution[0] = {data_out.mip_solution[0]}") # Check and update the objective function value assert ( @@ -51,32 +55,37 @@ def user_interrupt_callback( elif callback_type == hscb.HighsCallbackType.kCallbackSimplexInterrupt: if dev_run: - print( - f"userInterruptCallback(type {callback_type}): {message} with iteration count = {data_out.simplex_iteration_count}" - ) + print(f"userInterruptCallback(type {callback_type}): {message}") + print("with iteration count = ", + data_out.simplex_iteration_count) + data_in.user_interrupt = ( - data_out.simplex_iteration_count > ADLITTLE_SIMPLEX_ITERATION_LIMIT + data_out.simplex_iteration_count > SIMPLEX_ITERATION_LIMIT ) elif callback_type == hscb.HighsCallbackType.kCallbackIpmInterrupt: if dev_run: - print( - f"userInterruptCallback(type {callback_type}): {message} with iteration count = {data_out.ipm_iteration_count}" - ) + print(f"userInterruptCallback(type {callback_type}): {message}") + print(f"with iteration count = {data_out.ipm_iteration_count}") + data_in.user_interrupt = ( - data_out.ipm_iteration_count > ADLITTLE_IPM_ITERATION_LIMIT + data_out.ipm_iteration_count > IPM_ITERATION_LIMIT ) elif callback_type == hscb.HighsCallbackType.kCallbackMipInterrupt: if dev_run: - print( - f"userInterruptCallback(type {callback_type}): {message} with Bounds ({data_out.mip_dual_bound:.4g}, {data_out.mip_primal_bound:.4g}); Gap = {data_out.mip_gap:.4g}; Objective = {data_out.objective_function_value:.4g}" - ) + print(f"userInterruptCallback(type {callback_type}): {message}") + print(f"Dual bound = {data_out.mip_dual_bound:.4g}") + print(f"Primal bound = {data_out.mip_primal_bound:.4g}") + print(f"Gap = {data_out.mip_gap:.4g}") + print(f"Objective = {data_out.objective_function_value:.4g}") + data_in.user_interrupt = ( data_out.objective_function_value < EGOUT_OBJECTIVE_TARGET ) +# Define model h.addVar(-inf, inf) h.addVar(-inf, inf) h.changeColsCost(2, np.array([0, 1]), np.array([0, 1], dtype=np.double)) @@ -89,10 +98,14 @@ def user_interrupt_callback( values = np.array([-1, 1, 1, 1], dtype=np.double) h.addRows(num_cons, lower, upper, num_new_nz, starts, indices, values) h.setOptionValue("log_to_console", True) + +# Set callback and run h.setCallback(user_interrupt_callback, None) h.startCallback(hscb.HighsCallbackType.kCallbackLogging) h.run() h.stopCallback(hscb.HighsCallbackType.kCallbackLogging) + +# Get solution num_var = h.getNumCol() solution = h.getSolution() basis = h.getBasis() @@ -102,32 +115,39 @@ def user_interrupt_callback( print("Optimal objective = ", info.objective_function_value) print("Iteration count = ", info.simplex_iteration_count) print( - "Primal solution status = ", h.solutionStatusToString(info.primal_solution_status) + "Primal solution status = ", h.solutionStatusToString( + info.primal_solution_status) ) -print("Dual solution status = ", h.solutionStatusToString(info.dual_solution_status)) +print("Dual solution status = ", + h.solutionStatusToString(info.dual_solution_status)) print("Basis validity = ", h.basisValidityToString(info.basis_validity)) for icol in range(num_var): - print(icol, solution.col_value[icol], h.basisStatusToString(basis.col_status[icol])) + print(icol, solution.col_value[icol], + h.basisStatusToString(basis.col_status[icol])) + +# ~~~ # Read in and solve avgas h.readModel("check/instances/avgas.mps") + # h.writeModel("ml.mps") + h.run() lp = h.getLp() num_nz = h.getNumNz() -print( - "LP has ", lp.num_col_, " columns", lp.num_row_, " rows and ", num_nz, " nonzeros" -) +print("LP has ", lp.num_col_, + " columns", lp.num_row_, + " rows and ", num_nz, " nonzeros.") +# ~~~ # Clear so that incumbent model is empty h.clear() # Now define the blending model as a HighsLp instance -# -lp = highspy._highs.HighsLp() +lp = highspy.HighsLp() lp.num_col_ = 2 lp.num_row_ = 2 -lp.sense_ = highspy._highs.ObjSense.kMaximize +lp.sense_ = highspy.ObjSense.kMaximize lp.col_cost_ = np.array([8, 10], dtype=np.double) lp.col_lower_ = np.array([0, 0], dtype=np.double) lp.col_upper_ = np.array([inf, inf], dtype=np.double) @@ -137,7 +157,11 @@ def user_interrupt_callback( lp.a_matrix_.index_ = np.array([0, 1, 0, 1]) lp.a_matrix_.value_ = np.array([0.3, 0.7, 0.5, 0.5], dtype=np.double) h.passModel(lp) + +# Solve h.run() + +# Print solution solution = h.getSolution() basis = h.getBasis() info = h.getInfo() @@ -146,24 +170,30 @@ def user_interrupt_callback( print("Optimal objective = ", info.objective_function_value) print("Iteration count = ", info.simplex_iteration_count) print( - "Primal solution status = ", h.solutionStatusToString(info.primal_solution_status) + "Primal solution status = ", h.solutionStatusToString( + info.primal_solution_status) ) -print("Dual solution status = ", h.solutionStatusToString(info.dual_solution_status)) +print("Dual solution status = ", + h.solutionStatusToString(info.dual_solution_status)) print("Basis validity = ", h.basisValidityToString(info.basis_validity)) num_var = h.getNumCol() num_row = h.getNumRow() print("Variables") for icol in range(num_var): - print(icol, solution.col_value[icol], h.basisStatusToString(basis.col_status[icol])) + print(icol, solution.col_value[icol], + h.basisStatusToString(basis.col_status[icol])) print("Constraints") for irow in range(num_row): - print(irow, solution.row_value[irow], h.basisStatusToString(basis.row_status[irow])) + print(irow, solution.row_value[irow], + h.basisStatusToString(basis.row_status[irow])) +# ~~~ # Clear so that incumbent model is empty h.clear() -# Now define the test-semi-definite0 model (from TestQpSolver.cpp) as a HighsModel instance -# -model = highspy._highs.HighsModel() + +# Now define the test-semi-definite0 model (from TestQpSolver.cpp) +# as a HighsModel instance +model = highspy.HighsModel() model.lp_.model_name_ = "semi-definite" model.lp_.num_col_ = 3 model.lp_.num_row_ = 1 @@ -172,7 +202,7 @@ def user_interrupt_callback( model.lp_.col_upper_ = np.array([inf, inf, inf], dtype=np.double) model.lp_.row_lower_ = np.array([2], dtype=np.double) model.lp_.row_upper_ = np.array([inf], dtype=np.double) -model.lp_.a_matrix_.format_ = highspy._highs.MatrixFormat.kColwise +model.lp_.a_matrix_.format_ = highspy.MatrixFormat.kColwise model.lp_.a_matrix_.start_ = np.array([0, 1, 2, 3]) model.lp_.a_matrix_.index_ = np.array([0, 0, 0]) model.lp_.a_matrix_.value_ = np.array([1.0, 1.0, 1.0], dtype=np.double) @@ -185,23 +215,24 @@ def user_interrupt_callback( h.passModel(model) h.run() +# ~~~ # Clear so that incumbent model is empty h.clear() num_col = 3 num_row = 1 -sense = highspy._highs.ObjSense.kMinimize +sense = highspy.ObjSense.kMinimize offset = 0 col_cost = np.array([1.0, 1.0, 2.0], dtype=np.double) col_lower = np.array([0, 0, 0], dtype=np.double) col_upper = np.array([inf, inf, inf], dtype=np.double) row_lower = np.array([2], dtype=np.double) row_upper = np.array([inf], dtype=np.double) -a_matrix_format = highspy._highs.MatrixFormat.kColwise +a_matrix_format = highspy.MatrixFormat.kColwise a_matrix_start = np.array([0, 1, 2, 3]) a_matrix_index = np.array([0, 0, 0]) a_matrix_value = np.array([1.0, 1.0, 1.0], dtype=np.double) a_matrix_num_nz = a_matrix_start[num_col] -hessian_format = highspy._highs.HessianFormat.kTriangular +hessian_format = highspy.HessianFormat.kTriangular hessian_start = np.array([0, 2, 2, 3]) hessian_index = np.array([0, 2, 2]) hessian_value = np.array([2.0, -1.0, 1.0], dtype=np.double) @@ -234,29 +265,38 @@ def user_interrupt_callback( h.run() h.writeSolution("", 1) +# ~~~ # Clear so that incumbent model is empty h.clear() print("25fv47 as HighsModel") h.readModel("check/instances/25fv47.mps") + h.presolve() presolved_lp = h.getPresolvedLp() + # Create a HiGHS instance to solve the presolved LP print('\nCreate Highs instance to solve presolved LP') -h1 = highspy._highs.Highs() +h1 = highspy.Highs() h1.passModel(presolved_lp) + +# Get and set options options = h1.getOptions() options.presolve = "off" options.solver = "ipm" + h1.passOptions(options) h1.writeOptions("") + h1.run() solution = h1.getSolution() basis = h1.getBasis() -print( - "\nCrossover then postsolve LP using solution and basis from other Highs instance" -) + +print("Crossover, then postsolve using solution and basis from another instance") + h.postsolve(solution, basis) + +# Get solution info = h.getInfo() model_status = h.getModelStatus() print("Model status = ", h.modelStatusToString(model_status)) diff --git a/examples/call_highs_from_python_highspy.py b/examples/call_highs_from_python_highspy.py new file mode 100644 index 0000000000..1cf6302a4a --- /dev/null +++ b/examples/call_highs_from_python_highspy.py @@ -0,0 +1,52 @@ + +import highspy +import numpy as np + +# Highs h +h = highspy.Highs() + +# Set up problem +inf = highspy.kHighsInf +h.addVars(2, np.array([-inf, -inf]), np.array([inf, inf])) +h.changeColsCost(2, np.array([0, 1]), np.array([0, 1], dtype=np.double)); +num_cons = 2 +lower = np.array([2, 0], dtype=np.double) +upper = np.array([inf, inf], dtype=np.double) +num_new_nz = 4 +starts = np.array([0, 2]) +indices = np.array([0, 1, 0, 1]) +values = np.array([-1, 1, 1, 1], dtype=np.double) +h.addRows(num_cons, lower, upper, num_new_nz, starts, indices, values) + +# Access LP +lp = h.getLp() +num_nz = h.getNumNz() +print('LP has ', lp.num_col_, ' columns', + lp.num_row_, ' rows and ', num_nz, ' nonzeros') + +print('Solving...') +# Disable output from HiGHS for very small LP +h.setOptionValue("log_to_console", False) + +# Solve problem +h.run() + +print('Problem solved.') +print() + +# Print solution information +# solution = h.getSolution() +# basis = h.getBasis() +info = h.getInfo() +model_status = h.getModelStatus() + +print('Model status = ', h.modelStatusToString(model_status)) +print('Optimal objective = ', info.objective_function_value) +print('Iteration count = ', info.simplex_iteration_count) +print('Primal solution status = ', + h.solutionStatusToString(info.primal_solution_status)) +print('Dual solution status = ', + h.solutionStatusToString(info.dual_solution_status)) +print('Basis validity = ', h.basisValidityToString(info.basis_validity)) + +h.clear() diff --git a/examples/call_highs_from_python_mps.py b/examples/call_highs_from_python_mps.py new file mode 100644 index 0000000000..69a74549b2 --- /dev/null +++ b/examples/call_highs_from_python_mps.py @@ -0,0 +1,39 @@ + +# The path to the MPS file instance assumes that this is run +# from the root directory. + +import highspy +import numpy as np + +# Highs h +h = highspy.Highs() + +# Solve from mps file +# Initialize an instance of Highs +# h = highspy.Highs() +# Here we are re-using the one from above. +h.readModel('check/instances/25fv47.mps') + +# Print +lp = h.getLp() +num_nz = h.getNumNz() +print('LP has ', lp.num_col_, ' columns', + lp.num_row_, ' rows and ', num_nz, ' nonzeros') + +# Solve the problem +h.run() + +# Print solution information +solution = h.getSolution() +basis = h.getBasis() +info = h.getInfo() +model_status = h.getModelStatus() +print('Model status = ', h.modelStatusToString(model_status)) +print() +print('Optimal objective = ', info.objective_function_value) +print('Iteration count = ', info.simplex_iteration_count) +print('Primal solution status = ', + h.solutionStatusToString(info.primal_solution_status)) +print('Dual solution status = ', + h.solutionStatusToString(info.dual_solution_status)) +print('Basis validity = ', h.basisValidityToString(info.basis_validity)) diff --git a/pyproject.toml b/pyproject.toml index 8f4b9ee4f4..35d4858f7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,47 +5,16 @@ [build-system] # Minimum requirements for the build system to execute. requires = [ - "setuptools>=45", + "setuptools>=42", "pybind11>=2.4", - "wheel>=0.2", - "numpy>=1.7", + "wheel", + "cmake>=3.12", + "numpy>=1.7" ] build-backend = "setuptools.build_meta" -[tool.mypy] -files = "setup.py" -python_version = "3.9" -strict = true -show_error_codes = true -enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] -warn_unreachable = true - -[[tool.mypy.overrides]] -module = ["ninja"] -ignore_missing_imports = true - [tool.cibuildwheel] -build = "cp312-*" +build = "*" skip = "cp3{6,7}-*" test-skip = "" - -# [tool.cibuildwheel.linux] -# manylinux-x86_64-image = "manylinux2014" -# manylinux-i686-image = "manylinux2014" -# repair-wheel-command = "auditwheel repair -w {dest_dir} {wheel}" - -# [tool.cibuildwheel.macos] -# archs = ["x86_64 arm64"] -# environment = { RUNNER_OS="macOS" } - -# repair-wheel-command = """\ -# "delocate-listdeps {wheel}", -# DYLD_LIBRARY_PATH=$REPAIR_LIBRARY_PATH delocate-wheel \ -# --require-archs {delocate_archs} -w {dest_dir} -v {wheel}\ -# """ - -# [tool.cibuildwheel.windows] -# # Use delvewheel on windows, and install the project so delvewheel can find it -# before-build = "pip install delvewheel meson ninja && meson setup bbdir && meson install -C bbdir" -# repair-wheel-command = "delvewheel repair --add-path c:/bin;c:/lib;c:/bin/src;c:/lib/src;D:/a/HiGHS/HiGHS/bbdir/src/ -w {dest_dir} {wheel}" diff --git a/setup.py b/setup.py index ff3d15d6a2..7aeec8c296 100644 --- a/setup.py +++ b/setup.py @@ -135,23 +135,22 @@ def build_extension(self, ext: CMakeExtension) -> None: # logic and declaration, and simpler if you include description/version in a file. setup( name="highspy", - version="1.7.0.dev3", + version="1.7.0", description = "A thin set of pybind11 wrappers to HiGHS", author="HiGHS developers", author_email="highsopt@gmail.com", url='https://github.com/ERGO-Code/HiGHS', long_description="", - license_files = ('LICENSE',), + license = 'MIT License', ext_modules=[CMakeExtension("highspy")], cmdclass={"build_ext": CMakeBuild}, zip_safe=False, - python_requires=">=3.9", + python_requires=">=3.8", install_requires=[ 'numpy', ], extras_require={"test": ["pytest>=6.0"]}, classifiers=[ - 'License :: MIT License Copyright (c) 2024 HiGHS', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10',