├── VERSION ├── sage_numerical_backends_coin ├── __init__.py ├── coin_backend.pxd └── coin_backend.pyx ├── .dockerignore ├── MANIFEST.in ├── CODE_OF_CONDUCT.md ├── pyproject.toml ├── .dir-locals.el ├── Dockerfile-sage_conda-cbc_conda ├── CONTRIBUTING.md ├── .cruft.json ├── Dockerfile-sage_apt ├── environment.yml ├── Dockerfile-cbc_coinbrew ├── Dockerfile-cbc_bintray ├── Dockerfile ├── tox.ini ├── check_sage_testsuite.py ├── Dockerfile-cbc_spkg ├── .gitignore ├── README.md ├── Dockerfile-sage_binary ├── .github └── workflows │ ├── ci-sage.yml │ ├── build.yml │ └── dist.yml ├── setup.py └── LICENSE /VERSION: -------------------------------------------------------------------------------- 1 | 10.4 2 | -------------------------------------------------------------------------------- /sage_numerical_backends_coin/__init__.py: -------------------------------------------------------------------------------- 1 | # not an empty file 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .eggs 2 | .tox 3 | dist 4 | build 5 | *.c 6 | */*.cpp 7 | */*.so 8 | */__pycache__ 9 | *.egg-info 10 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include VERSION 3 | include *.pyx 4 | include sage_numerical_backends_coin/*.pyx 5 | exclude sage_numerical_backends_coin/*.cpp 6 | include *.py 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | This is an open-source project maintained by the SageMath community. 2 | 3 | The [Code of Conduct](https://github.com/sagemath/sage/blob/develop/CODE_OF_CONDUCT.md) 4 | of the Sage community applies. 5 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | 'setuptools >= 68.1.1', 4 | 'pkgconfig', 5 | 'cython >=3.0, != 3.0.3, <4.0', 6 | 'cysignals >=1.10.2', 7 | 'sagemath', 8 | ] 9 | build-backend = "setuptools.build_meta" 10 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ;;; Directory Local Variables 2 | ;;; For more information see (info "(emacs) Directory Variables") 3 | 4 | ((makefile-mode 5 | ;; But use tabs in Makefiles 6 | (indent-tabs-mode . t)) 7 | (nil 8 | ;; Use space instead of tabs for indentation 9 | (indent-tabs-mode . nil))) 10 | -------------------------------------------------------------------------------- /Dockerfile-sage_conda-cbc_conda: -------------------------------------------------------------------------------- 1 | FROM continuumio/miniconda3:latest as conda-sagemath-coinor 2 | ADD environment.yml /tmp/environment.yml 3 | # https://jcrist.github.io/conda-docker-tips.html: it's best to have RUN commands that install things using a package manager (like conda) also cleanup extraneous files after the install. 4 | RUN conda env create -f /tmp/environment.yml && conda clean -afy 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | This is an open-source project maintained by the SageMath community. 2 | 3 | Contributions of all sorts are heartily welcomed. 4 | 5 | See https://github.com/sagemath/sage/blob/develop/CONTRIBUTING.md for general 6 | guidance on how to contribute. 7 | 8 | Open issues or submit pull requests at https://github.com/sagemath/sage-numerical-backends-coin 9 | and join https://groups.google.com/group/sage-devel to discuss. 10 | -------------------------------------------------------------------------------- /.cruft.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "https://github.com/mkoeppe/sage", 3 | "commit": "808a57f85b59ef62feb1ba14d0d433ed052d04b2", 4 | "checkout": "sagemath-environment-cookiecutter", 5 | "context": { 6 | "cookiecutter": { 7 | "project_name": "coin", 8 | "_template": "https://github.com/mkoeppe/sage" 9 | } 10 | }, 11 | "directory": "pkgs/sage-project-cookiecutter/sage_project_cookiecutter/sagemath-upstream-package-template" 12 | } 13 | -------------------------------------------------------------------------------- /Dockerfile-sage_apt: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest as ubuntu-sagemath-coinor 2 | 3 | RUN DEBIAN_FRONTEND=noninteractive apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y sagemath python-pkgconfig python-setuptools python-sphinx cython && DEBIAN_FRONTEND=noninteractive apt-get -y clean 4 | 5 | RUN DEBIAN_FRONTEND=noninteractive apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y coinor-cbc coinor-libcbc-dev && DEBIAN_FRONTEND=noninteractive apt-get clean 6 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | ## This is a conda environment file. 2 | ## Use it as follows: 3 | ## 4 | ## conda env create -f environment.yml 5 | ## 6 | ## See https://towardsdatascience.com/a-guide-to-conda-environments-bc6180fc533 7 | ## or https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html 8 | name: sagecoin 9 | channels: 10 | - conda-forge 11 | - defaults 12 | dependencies: 13 | - cxx-compiler 14 | - python=3.7.3 # fixed to avoid https://github.com/ContinuumIO/anaconda-issues/issues/11195 15 | - sage>=8.9 16 | - pip 17 | - pkgconfig 18 | - setuptools 19 | - coincbc 20 | -------------------------------------------------------------------------------- /Dockerfile-cbc_coinbrew: -------------------------------------------------------------------------------- 1 | # -*- Dockerfile -*- 2 | ## Adapted from https://github.com/tkralphs/optimization-suite-docker/blob/master/image/Dockerfile 3 | ARG BASE_IMAGE=ubuntu:latest 4 | FROM ${BASE_IMAGE} 5 | 6 | RUN apt-get -y install --no-install-recommends \ 7 | git subversion gcc g++ make wget gfortran patch pkg-config file \ 8 | libblas-dev liblapack-dev \ 9 | ca-certificates 10 | RUN git clone https://github.com/coin-or/coinbrew /var/coin-or 11 | WORKDIR /var/coin-or 12 | RUN ./coinbrew fetch COIN-OR-OptimizationSuite --skip="ThirdParty/Blas ThirdParty/Lapack" --no-prompt 13 | ARG NUMPROC=8 14 | RUN ./coinbrew build COIN-OR-OptimizationSuite --skip="ThirdParty/Blas ThirdParty/Lapack" --no-prompt --prefix=/usr/local/ --parallel-jobs=${NUMPROC} --test 15 | WORKDIR / 16 | RUN ldconfig 17 | -------------------------------------------------------------------------------- /Dockerfile-cbc_bintray: -------------------------------------------------------------------------------- 1 | # -*- Dockerfile -*- 2 | ARG BASE_IMAGE=mkoeppe/sage_binary:latest 3 | 4 | FROM ${BASE_IMAGE} 5 | ARG COIN_VERSION=master 6 | ARG COIN_COMPILER=gcc7 7 | ARG CBC_FILE=Cbc-${COIN_VERSION}-linux-x86_64-${COIN_COMPILER}.tgz 8 | ARG COIN_PREFIX=/usr/local 9 | 10 | RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends --yes ca-certificates liblapack3 liblapack-dev libblas-dev && apt-get --yes clean 11 | RUN curl -L -o /tmp/cbc.tgz "https://bintray.com/coin-or/download/download_file?file_path=${CBC_FILE}" && (cd ${COIN_PREFIX} && tar xfz /tmp/cbc.tgz) 12 | # Fix up bad prefix in installed .pc files 13 | # see https://gitlab.freedesktop.org/dbus/dbus/commit/216627825dd6fd5675b4c521b8b67b7850d7ad4c 14 | RUN ldconfig && cd ${COIN_PREFIX}/lib/pkgconfig && sed -i.bak 's|^prefix=/home/travis/.*|prefix=${pcfiledir}/../..|' *.pc && rm -f *.pc.bak 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # -*- Dockerfile -*- for testing 2 | # docker build . -f Dockerfile-test 3 | ARG BASE_IMAGE=mkoeppe/sage_binary-cbc_spkg:latest 4 | FROM ${BASE_IMAGE} 5 | ADD . /src 6 | WORKDIR /src 7 | RUN sage setup.py test && sage -python -m pip install . 8 | RUN (sage setup.py check_sage_testsuite || echo "Ignoring failures") 9 | RUN ./patch_and_check.sh 10 | ## Traceback (most recent call last): 11 | ## File "/usr/local/opt/sage/sage-8.9/src/bin/sage-runtests", line 179, in 12 | ## err = DC.run() 13 | ## File "/usr/local/opt/sage/sage-8.9/local/lib/python2.7/site-packages/sage/doctest/control.py", line 1206, in run 14 | ## self.test_safe_directory() 15 | ## File "/usr/local/opt/sage/sage-8.9/local/lib/python2.7/site-packages/sage/doctest/control.py", line 643, in test_safe_directory 16 | ## .format(os.getcwd())) 17 | ## RuntimeError: refusing to run doctests from the current directory '/usr/local/opt/sage/sage-8.9/local/lib/python2.7/site-packages/sage' since untrusted users could put files in this directory, making it unsafe to run Sage code from 18 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | # run 'tox -e local' to skip the sage testsuite 3 | # run 'tox -p auto' to run all of the following in parallel: 4 | envlist = local-sage_testsuite, docker-sage_binary-cbc_coinbrew, docker-sage_apt-cbc_bintray 5 | skipsdist = true 6 | 7 | [testenv] 8 | # https://blog.ionelmc.ro/2015/04/14/tox-tricks-and-patterns/#partial-environment-reuse 9 | envdir = 10 | local: {toxworkdir}/local 11 | docker: {toxworkdir}/docker 12 | whitelist_externals = 13 | bash 14 | sage 15 | docker: docker 16 | passenv = 17 | SAGE_ROOT 18 | SAGE_LOCAL 19 | HOME 20 | setenv = 21 | sage_apt: SAGE_FACTOR=apt 22 | sage_binary: SAGE_FACTOR=binary 23 | cbc_spkg: CBC_FACTOR=spkg 24 | cbc_coinbrew: CBC_FACTOR=coinbrew 25 | cbc_bintray: CBC_FACTOR=bintray 26 | commands = 27 | docker: docker build -t mkoeppe/sage_{env:SAGE_FACTOR}:latest -f Dockerfile-sage_{env:SAGE_FACTOR} --build-arg SAGE_VERSION=9.0 . 28 | docker: docker build -t mkoeppe/sage_{env:SAGE_FACTOR}-cbc_{env:CBC_FACTOR}:latest -f Dockerfile-cbc_{env:CBC_FACTOR} --build-arg BASE_IMAGE=mkoeppe/sage_{env:SAGE_FACTOR}:latest . 29 | docker: docker build --build-arg BASE_IMAGE=mkoeppe/sage_{env:SAGE_FACTOR}-cbc_{env:CBC_FACTOR}:latest . 30 | local: sage setup.py test 31 | sage_testsuite: bash -c 'sage setup.py check_sage_testsuite || echo "Ignoring failures"' 32 | 33 | ## docker build -t mkoeppe/sage_binary:9.0 -f Dockerfile-sage_binary --build-arg SAGE_VERSION=9.0 . 34 | ## docker build -t mkoeppe/sage_binary-cbc_spkg:9.0 -f Dockerfile-cbc_spkg --build-arg BASE_IMAGE=mkoeppe/sage_binary:9.0 . 35 | ## docker build --build-arg BASE_IMAGE=mkoeppe/sage_binary-cbc_spkg:9.0 . 36 | -------------------------------------------------------------------------------- /check_sage_testsuite.py: -------------------------------------------------------------------------------- 1 | """ 2 | Check portions of the sage test suite, with default mip solver set to ours. 3 | 4 | sage: M = MixedIntegerLinearProgram() 5 | sage: M.get_backend() 6 | <...sage_numerical_backends_coin...> 7 | """ 8 | 9 | import sage_numerical_backends_coin.coin_backend as coin_backend, sage.numerical.backends as backends, sys 10 | sys.modules['sage.numerical.backends.coin_backend'] = backends.coin_backend = coin_backend 11 | 12 | from sage.numerical.backends.generic_backend import default_mip_solver 13 | default_mip_solver('COIN') 14 | 15 | from sage.doctest.control import DocTestController, DocTestDefaults 16 | options = DocTestDefaults(nthreads=0, long=False, optional="sage,coin", force_lib=True, abspath=True) 17 | 18 | import os, sage.env, glob, sys 19 | abs_file = os.path.abspath("check_sage_testsuite.py") 20 | os.chdir(os.path.join(sage.env.SAGE_LIB, 'sage')) 21 | files = ["coding", 22 | "combinat/designs", "combinat/integer_vector.py", "combinat/posets/", 23 | "game_theory/", 24 | "geometry/polyhedron/base.py", "geometry/cone.py", 25 | "graphs/", 26 | "topology/simplicial_complex.py", 27 | "knots/", 28 | "matroids/", 29 | "sat/"] 30 | 31 | files += glob.glob("numerical/*.py") + glob.glob("numerical/*.pyx") 32 | 33 | # First verify that we installed the default backend 34 | DC = DocTestController(options, [abs_file]) 35 | err = DC.run() 36 | if err != 0: 37 | print("Error: Setting the default solver did not work", file=sys.stderr) 38 | sys.exit(2) 39 | 40 | # from $SAGE_SRC/bin/sage-runtests 41 | DC = DocTestController(options, files) 42 | err = DC.run() 43 | if err != 0: 44 | sys.exit(1) 45 | 46 | sys.exit(0) 47 | -------------------------------------------------------------------------------- /Dockerfile-cbc_spkg: -------------------------------------------------------------------------------- 1 | # -*- Dockerfile -*- 2 | ARG BASE_IMAGE=mkoeppe/sage_binary:9.0 3 | FROM ${BASE_IMAGE} 4 | 5 | ## ## Install a package from source ....... this does not work very well. 6 | ARG NUMPROC=8 7 | RUN MAKE="make -j${NUMPROC}" sage -i cbc 8 | ## # Above triggers a rebuild of sagelib: 9 | ## # [sagelib-8.9] Building interpreters for fast_callable 10 | ## # [sagelib-8.9] running build_cython 11 | ## # [sagelib-8.9] Enabling Cython debugging support 12 | ## # [sagelib-8.9] Updating Cython code.... 13 | ## # [sagelib-8.9] Finished Cythonizing, time: 4.51 seconds. 14 | ## # [sagelib-8.9] creating build/lib.linux-x86_64-2.7/sage 15 | ## # [sagelib-8.9] creating build/lib.linux-x86_64-2.7/sage/libs 16 | ## # [sagelib-8.9] creating build/lib.linux-x86_64-2.7/sage/libs/flint 17 | ## # [sagelib-8.9] copying ./sage/libs/flint/fmpz_mat.pxd -> build/lib.linux-x86_64-2.7/sage/libs/flint 18 | ## # [sagelib-8.9] copying ./sage/libs/flint/fmpz_poly_q.pxd -> build/lib.linux-x86_64-2.7/sage/libs/flint 19 | ## # ... 20 | ## # [sagelib-8.9] running build_ext 21 | ## # [sagelib-8.9] building 'sage.algebras.quatalg.quaternion_algebra_element' extension 22 | ## # [sagelib-8.9] building 'sage.algebras.letterplace.free_algebra_element_letterplace' extension 23 | ## # ... 24 | ## # [sagelib-8.9] creating build/temp.linux-x86_64-2.7/build/cythonized/sage/algebras/quatalg 25 | ## # [sagelib-8.9] gcc -fno-strict-aliasing -g -O2 -DNDEBUG -g -fwrapv -O3 -Wall -Wno-unused -fPIC -I./sage/libs/flint -I./sage/cpython -I./sage/libs/ntl -I/usr/local/opt/sage/sage-8.9/local/include -I/usr/local/opt/sage/sage-8.9/src -I/usr/local/opt/sage/sage-8.9/src/sage/ext -I/usr/local/opt/sage/sage-8.9/local/include/python2.7 -I/usr/local/opt/sage/sage-8.9/local/lib/python2.7/site-packages/numpy/core/include -Ibuild/cythonized -I/usr/local/opt/sage/sage-8.9/local/include/python2.7 -c build/cythonized/sage/algebras/quatalg/quaternion_algebra_element.cpp -o build/temp.linux-x86_64-2.7/build/cythonized/sage/algebras/quatalg/quaternion_algebra_element.o -fno-strict-aliasing -DCYTHON_CLINE_IN_TRACEBACK=1 -std=c++11 26 | ## # ... 27 | ## # [sagelib-8.9] gcc: internal compiler error: Killed (program cc1plus) 28 | #RUN MAKE="make -j${NUMPROC}" sage -i lrslib 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | *.c 9 | *.cpp 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 97 | __pypackages__/ 98 | 99 | # Celery stuff 100 | celerybeat-schedule 101 | celerybeat.pid 102 | 103 | # SageMath parsed files 104 | *.sage.py 105 | 106 | # Environments 107 | .env 108 | .venv 109 | env/ 110 | venv/ 111 | ENV/ 112 | env.bak/ 113 | venv.bak/ 114 | 115 | # Spyder project settings 116 | .spyderproject 117 | .spyproject 118 | 119 | # Rope project settings 120 | .ropeproject 121 | 122 | # mkdocs documentation 123 | /site 124 | 125 | # mypy 126 | .mypy_cache/ 127 | .dmypy.json 128 | dmypy.json 129 | 130 | # Pyre type checker 131 | .pyre/ 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sage-numerical-backends-coin: COIN-OR mixed integer linear programming backend for SageMath 2 | 3 | [![PyPI](https://img.shields.io/pypi/v/sage-numerical-backends-coin)](https://pypi.org/project/sage-numerical-backends-coin/ "PyPI: sage-numerical-backends-coin") 4 | [![GitHub Workflow Status](https://github.com/sagemath/sage-numerical-backends-coin/workflows/Build%20and%20test%20Python%20package/badge.svg)](https://github.com/sagemath/sage-numerical-backends-coin/actions "GitHub Actions: sage-numerical-backends-coin") 5 | 6 | `CoinBackend` has previously been available as part of the [SageMath](http://www.sagemath.org/) source tree, 7 | from which it is built as an "optional extension" when the `cbc` Sage package is installed. 8 | However, it has not been available in binary distributions. 9 | 10 | The present standalone Python package `sage-numerical-backends-coin` has been created from the SageMath sources, version 9.0.beta10; the in-tree version of `CoinBackend` has been removed in Sage ticket https://trac.sagemath.org/ticket/28175. SageMath 9.1 and later makes the package available as an optional Sage package (SPKG). 11 | 12 | The current version of this package can also be installed on top of various Sage installations using pip. 13 | (Your installation of Sage must be based on Python 3; if your SageMath is version 9.2 or newer, it is.) 14 | 15 | ## Installation 16 | 17 | CBC can either be installed using its Sage package using 18 | 19 | $ sage -i cbc 20 | 21 | or any of the methods explained at https://github.com/coin-or/Cbc . 22 | 23 | This package finds the CBC installation by means of ``pkgconfig``. 24 | 25 | Install this package from PyPI using 26 | 27 | $ sage -pip install sage-numerical-backends-coin 28 | 29 | or from GitHub using 30 | 31 | $ sage -pip install git+https://github.com/sagemath/sage-numerical-backends-coin 32 | 33 | (See [`.github/workflows/build.yml`](.github/workflows/build.yml) for details about package prerequisites on various systems.) 34 | 35 | ## Using this package 36 | 37 | After a successful installation, Sage will automatically make this new backend 38 | the default MIP solver. 39 | 40 | To select the `'Coin'` solver explicitly as the default MIP backend, additionally use the following command. 41 | 42 | sage: default_mip_solver('Coin') 43 | 44 | To make these settings permanent, add this command to your `~/.sage/init.sage` file. 45 | Note that this setting will not affect doctesting (`sage -t`) because this file is ignored in doctesting mode. 46 | 47 | ## Running doctests 48 | 49 | To run the (limited) testsuite of this package, use: 50 | 51 | $ sage setup.py test 52 | 53 | To run the Sage testsuite with the default MIP solver set to the backend provided by this package, use: 54 | 55 | $ sage setup.py check_sage_testsuite 56 | 57 | ## Running tests with tox 58 | 59 | The doctests can also be invoked using `tox`: 60 | 61 | $ tox -e local 62 | $ tox -e local-sage_testsuite 63 | 64 | If you have `docker` installed, more tests can be run: 65 | 66 | $ tox -e docker-sage_binary-cbc_coinbrew 67 | 68 | See `tox.ini` for the available options. 69 | -------------------------------------------------------------------------------- /Dockerfile-sage_binary: -------------------------------------------------------------------------------- 1 | # -*- Dockerfile -*- 2 | # docker build . -f Dockerfile-sagemath-binary -t mkoeppe/sagemath-binary --build-arg http_proxy=http://host.docker.internal:3128 3 | # FIXME: Clean up setup vs. tester images in the 3 repos. 4 | ARG BASE_IMAGE=ubuntu:latest 5 | FROM ${BASE_IMAGE} AS sage-run 6 | 7 | ARG SAGE_SERVER=http://files.sagemath.org/linux/64bit/ 8 | ARG SAGE_VERSION=9.0 9 | ARG SAGE_OS=Ubuntu_18.04 10 | ARG SAGE_ARCH=x86_64 11 | ARG SAGE_TARNAME=sage-${SAGE_VERSION}-${SAGE_OS}-${SAGE_ARCH} 12 | ARG SAGE_INSTALL_BASE=/usr/local/opt/sage 13 | ARG SAGE_INSTALL_DIR=${SAGE_INSTALL_BASE}/sage-${SAGE_VERSION} 14 | 15 | # use bsdtar as tar fails sometimes with "Directory renamed before its status could be extracted" 16 | # This is a known issue with overlayfs: https://github.com/coreos/bugs/issues/1095 17 | # python is needed for 'relocate-once.py' 18 | RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends --yes curl bsdtar python && apt-get --yes clean 19 | ## To run sage, additionally need: 20 | RUN DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends --yes \ 21 | libpng-dev libfreetype6 \ 22 | && apt-get --yes clean 23 | # We ignore errors in the first sage start up (which triggers relocate-once.py) so that an intermediate image is created 24 | # that can be investigated. 25 | # sagemath binary files comes with uid:gid=999:999, and bsdtar has no --owner=NAME, --group=NAME options. 26 | RUN curl -o /tmp/SageMath.tar.bz2 ${SAGE_SERVER}/${SAGE_TARNAME}.tar.bz2 && umask 22 && mkdir -p ${SAGE_INSTALL_BASE} && cd ${SAGE_INSTALL_BASE} && bsdtar xf /tmp/SageMath.tar.bz2 && chown -R root:root SageMath && mv SageMath ${SAGE_INSTALL_DIR} && rm -f /tmp/SageMath.tar.bz2 && ln -sf ${SAGE_INSTALL_DIR}/sage /usr/local/bin && (sage < /dev/null || true) 27 | 28 | 29 | FROM sage-run as sage-build 30 | 31 | ## To build optional packages, additionally need: 32 | ## (similar to https://doc.sagemath.org/html/en/installation/source.html) 33 | RUN DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends --yes \ 34 | gcc g++ gfortran make m4 35 | ## To build optional package, to avoid installing the following from source, one needs: 36 | RUN DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends --yes \ 37 | patch pkg-config libfreetype6-dev git 38 | ## Patch away 'cannot build Sage as root' safeguard; see https://trac.sagemath.org/ticket/2890 39 | ## And patch away recompiling of sagelib after installing packages, which rebuilds everything because src/build/lib.... has been nuked 40 | RUN cd $(sage -sh -c 'echo $SAGE_ROOT') \ 41 | && for a in configure configure.in m4/ax_check_root.m4; do \ 42 | if [ -r $a ]; then sed -i.bak -e 's/id -u/echo 12345/' $a; fi; \ 43 | done \ 44 | && for a in src/Makefile src/Makefile.in; do \ 45 | if [ -r $a ]; then \ 46 | sed -i.bak -e 's/^sage:/rebuild-sage-lib:/' $a; \ 47 | echo >> $a; echo 'sage:' >> $a; \ 48 | fi; \ 49 | done 50 | -------------------------------------------------------------------------------- /.github/workflows/ci-sage.yml: -------------------------------------------------------------------------------- 1 | name: Run Sage CI 2 | 3 | ## This GitHub Actions workflow provides: 4 | ## 5 | ## - portability testing, by building and testing this project on many platforms 6 | ## (Linux variants, macOS) 7 | ## 8 | ## - continuous integration, by building and testing other software 9 | ## that depends on this project. 10 | ## 11 | ## It runs on every pull request and push of a tag to the GitHub repository. 12 | ## 13 | ## The testing can be monitored in the "Actions" tab of the GitHub repository. 14 | ## 15 | ## After all jobs have finished (or are canceled) and a short delay, 16 | ## tar files of all logs are made available as "build artifacts". 17 | ## 18 | ## This GitHub Actions workflow uses the portability testing framework 19 | ## of SageMath (https://www.sagemath.org/). For more information, see 20 | ## https://doc.sagemath.org/html/en/developer/portability_testing.html 21 | 22 | ## The workflow consists of two jobs: 23 | ## 24 | ## - First, it builds a source distribution of the project 25 | ## and generates a script "update-pkgs.sh". It uploads them 26 | ## as a build artifact named upstream. 27 | ## 28 | ## - Second, it checks out a copy of the SageMath source tree. 29 | ## It downloads the upstream artifact and replaces the project's 30 | ## package in the SageMath distribution by the newly packaged one 31 | ## from the upstream artifact, by running the script "update-pkgs.sh". 32 | ## Then it builds a small portion of the Sage distribution. 33 | ## 34 | ## Many copies of the second step are run in parallel for each of the tested 35 | ## systems/configurations. 36 | 37 | on: 38 | pull_request: 39 | push: 40 | tags: 41 | - '*' 42 | workflow_dispatch: 43 | # Allow to run manually 44 | 45 | concurrency: 46 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 47 | cancel-in-progress: true 48 | 49 | env: 50 | # Ubuntu packages to install so that the project's "make dist" can succeed 51 | DIST_PREREQ: python3-pip 52 | # Name of this project in the Sage distribution 53 | SPKG: sage_numerical_backends_coin 54 | REMOVE_PATCHES: "*" 55 | 56 | jobs: 57 | 58 | dist: 59 | runs-on: ubuntu-latest 60 | steps: 61 | - name: Check out ${{ env.SPKG }} 62 | uses: actions/checkout@v3 63 | with: 64 | path: build/pkgs/${{ env.SPKG }}/src 65 | - name: Install prerequisites 66 | run: | 67 | sudo DEBIAN_FRONTEND=noninteractive apt-get update 68 | sudo DEBIAN_FRONTEND=noninteractive apt-get install $DIST_PREREQ 69 | python3 -m pip install --user build 70 | - name: Run make dist, prepare upstream artifact 71 | run: | 72 | (cd build/pkgs/${{ env.SPKG }}/src && python3 -m build --sdist) \ 73 | && mkdir -p upstream && cp build/pkgs/${{ env.SPKG }}/src/dist/*.tar.gz upstream/${{ env.SPKG }}-git.tar.gz \ 74 | && echo "sage-package create ${{ env.SPKG }} --version git --tarball ${{ env.SPKG }}-git.tar.gz --type=optional" > upstream/update-pkgs.sh \ 75 | && if [ -n "${{ env.REMOVE_PATCHES }}" ]; then echo "(cd ../build/pkgs/${{ env.SPKG }}/patches && rm -f ${{ env.REMOVE_PATCHES }}; :)" >> upstream/update-pkgs.sh; fi \ 76 | && ls -l upstream/ 77 | - uses: actions/upload-artifact@v3 78 | with: 79 | path: upstream 80 | name: upstream 81 | 82 | linux: 83 | uses: sagemath/sage/.github/workflows/docker.yml@develop 84 | with: 85 | # Sage distribution packages to build 86 | targets: SAGE_CHECK=no SAGE_CHECK_PACKAGES=sage_numerical_backends_coin sagelib sage_numerical_backends_coin 87 | # Standard setting: Test the current beta release of Sage: 88 | sage_repo: sagemath/sage 89 | sage_ref: develop 90 | upstream_artifact: upstream 91 | # Docker targets (stages) to tag 92 | docker_targets: "with-targets" 93 | docker_push_repository: ghcr.io/${{ github.repository }}/sage-numerical-backends-coin- 94 | needs: [dist] 95 | 96 | macos: 97 | uses: sagemath/sage/.github/workflows/macos.yml@develop 98 | with: 99 | # Sage distribution packages to build 100 | targets: SAGE_CHECK=no SAGE_CHECK_PACKAGES=sage_numerical_backends_coin sagelib sage_numerical_backends_coin 101 | # Standard setting: Test the current beta release of Sage: 102 | sage_repo: sagemath/sage 103 | sage_ref: develop 104 | upstream_artifact: upstream 105 | needs: [dist] 106 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | ## -*- encoding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | from setuptools import setup 7 | from setuptools import Extension 8 | from setuptools.command.test import test as TestCommand # for tests 9 | from Cython.Build import cythonize 10 | from Cython.Compiler.Errors import CompileError 11 | from codecs import open # To open the README file with proper encoding 12 | from sage.config import get_include_dirs 13 | 14 | # For the tests 15 | class SageTest(TestCommand): 16 | def run_tests(self): 17 | # Passing optional=sage avoids using sage.misc.package.list_packages, 18 | # which gives an error on Debian unstable as of 2019-12-27: 19 | # FileNotFoundError: [Errno 2] No such file or directory: '/usr/share/sagemath/build/pkgs' 20 | errno = os.system("PYTHONPATH=`pwd` sage -t --force-lib --optional=sage sage_numerical_backends_coin") 21 | if errno != 0: 22 | sys.exit(1) 23 | 24 | class SageTestSage(SageTest): 25 | def run_tests(self): 26 | errno = os.system("PYTHONPATH=`pwd` sage -c 'load(\"check_sage_testsuite.py\")'") 27 | if errno != 0: 28 | sys.exit(1) 29 | 30 | # Get information from separate files (README, VERSION) 31 | def readfile(filename): 32 | with open(filename, encoding='utf-8') as f: 33 | return f.read() 34 | 35 | # Cython modules 36 | import pkgconfig 37 | 38 | try: 39 | cbc_pc = pkgconfig.parse('cbc') 40 | except pkgconfig.PackageNotFoundError: # exception handling from sage trac #28883 for pkgconfig version 1.5.1 41 | from collections import defaultdict 42 | cbc_pc = defaultdict(list, {'libraries': ['Cbc']}) 43 | 44 | if cbc_pc: 45 | print("Using pkgconfig: {}".format(sorted(cbc_pc.items())), file=sys.stderr) 46 | cbc_libs = cbc_pc['libraries'] 47 | cbc_library_dirs = cbc_pc['library_dirs'] 48 | cbc_include_dirs = cbc_pc['include_dirs'] 49 | 50 | 51 | ext_modules = [Extension('sage_numerical_backends_coin.coin_backend', 52 | sources=[os.path.join('sage_numerical_backends_coin', 53 | 'coin_backend.pyx')], 54 | libraries=cbc_libs, 55 | include_dirs=list(map(str, get_include_dirs())) + cbc_include_dirs, 56 | library_dirs=cbc_library_dirs, 57 | extra_compile_args=['-std=c++11']) 58 | ] 59 | 60 | compile_time_env = {'HAVE_SAGE_CPYTHON_STRING': True, 61 | 'HAVE_ADD_COL_UNTYPED_ARGS': True} 62 | print("Using compile_time_env: {}".format(compile_time_env), file=sys.stderr) 63 | 64 | if any(x in sys.argv 65 | for x in ['build', 'build_ext', 'bdist_wheel', 'install']): 66 | ext_modules = cythonize(ext_modules, include_path=list(map(str, get_include_dirs())) + sys.path, 67 | compile_time_env=compile_time_env) 68 | 69 | setup( 70 | name="sage_numerical_backends_coin", 71 | version=readfile("VERSION").strip(), 72 | description="COIN-OR backend for Sage MixedIntegerLinearProgram", 73 | long_description = readfile("README.md"), # get the long description from the README 74 | long_description_content_type='text/markdown', # https://packaging.python.org/guides/making-a-pypi-friendly-readme/ 75 | url="https://github.com/mkoeppe/sage-numerical-backends-coin", 76 | # Author list obtained by running the following command on sage 9.0.beta9: 77 | # for f in coin_backend.p*; do git blame -w -M -C -C --line-porcelain "$f" | grep -I '^author '; done | sort -f | uniq -ic | sort -n 78 | # cut off at < 10 lines of attribution. 79 | author='Nathann Cohen, Yuan Zhou, John Perry, Zeyi Wang, Martin Albrecht, Jori Mäntysalo, Matthias Koeppe, Erik M. Bray, Jeroen Demeyer, Nils Bruin, Julien Puydt, Dima Pasechnik, and others', 80 | author_email='mkoeppe@math.ucdavis.edu', 81 | license='GPLv2+', # This should be consistent with the LICENCE file 82 | classifiers=['Development Status :: 5 - Production/Stable', 83 | "Intended Audience :: Science/Research", 84 | 'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)', 85 | "Programming Language :: Python", 86 | 'Programming Language :: Python :: 3', 87 | 'Programming Language :: Python :: 3.6', 88 | 'Programming Language :: Python :: 3.7', 89 | 'Programming Language :: Python :: 3.8', 90 | 'Programming Language :: Python :: 3.9', 91 | 'Programming Language :: Python :: 3.10', 92 | 'Programming Language :: Python :: 3.11', 93 | 'Programming Language :: Python :: 3.12', 94 | ], 95 | ext_modules=ext_modules, 96 | cmdclass = {'test': SageTest, 'check_sage_testsuite': SageTestSage}, # adding a special setup command for tests 97 | keywords=['milp', 'linear-programming', 'optimization'], 98 | packages=['sage_numerical_backends_coin'], 99 | package_dir={'sage_numerical_backends_coin': 'sage_numerical_backends_coin'}, 100 | package_data={'sage_numerical_backends_coin': ['*.pxd']}, 101 | install_requires = [# 'sage>=8', ### On too many distributions, sage is actually not known as a pip package 102 | 'sphinx'], 103 | ) 104 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and test Python package 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build_docker_prereq: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: whoan/docker-build-with-cache-action@v2 11 | if: github.event_name == 'push' && endsWith(github.event.ref, '/master') 12 | with: 13 | username: "${{ secrets.DOCKER_USERNAME }}" 14 | password: "${{ secrets.DOCKER_PASSWORD }}" 15 | dockerfile: "Dockerfile-sage_conda-cbc_conda" 16 | image_name: "mkoeppe/conda-sagemath-coinor" 17 | docker_build_and_test: 18 | needs: [build_docker_prereq] 19 | runs-on: ubuntu-latest 20 | container: "mkoeppe/conda-sagemath-coinor" 21 | steps: 22 | - uses: actions/checkout@v1 23 | - name: Test package 24 | shell: bash -l {0} 25 | run: | 26 | conda activate sagecoin 27 | sage --version 28 | python3 setup.py test 29 | homebrew_build_and_test: 30 | runs-on: macos-latest 31 | steps: 32 | - uses: actions/checkout@v1 33 | - name: Install homebrew cbc and sage 34 | run: | 35 | brew cask install sage --no-quarantine 36 | brew install pkg-config coin-or-tools/coinor/cbc 37 | - name: Test package 38 | run: | 39 | sage --version 40 | PKG_CONFIG_PATH=/usr/local/lib/pkgconfig/ sage setup.py test 41 | - name: Run parts of the Sage test suite 42 | run: | 43 | PKG_CONFIG_PATH=/usr/local/lib/pkgconfig/ sage setup.py check_sage_testsuite || echo "Ignoring failures" 44 | - name: Install package 45 | run: | 46 | PKG_CONFIG_PATH=/usr/local/lib/pkgconfig/ sage -python -m pip install --no-dependencies -v -v . 47 | - name: Patch in 48 | run: ./patch_and_check.sh 49 | conda_build_and_test: 50 | runs-on: ${{ matrix.os }} 51 | strategy: 52 | matrix: 53 | os: [ubuntu-latest, macos-latest] 54 | steps: 55 | - uses: actions/checkout@v1 56 | - uses: goanpeca/setup-miniconda@v1 57 | with: 58 | auto-update-conda: true 59 | activate-environment: sagecoin 60 | environment-file: environment.yml 61 | python-version: 3.7 62 | auto-activate-base: false 63 | - name: Test package 64 | shell: bash -l {0} 65 | run: | 66 | sage --version 67 | python3 setup.py test 68 | - name: Run parts of the Sage test suite 69 | shell: bash -l {0} 70 | run: sage setup.py check_sage_testsuite || echo "Ignoring failures" 71 | - name: Install package 72 | shell: bash -l {0} 73 | # sage -pip does not work on Ubuntu bionic 74 | run: sage -python -m pip install . 75 | - name: Patch in 76 | shell: bash -l {0} 77 | run: ./patch_and_check.sh 78 | ubuntu_sagemath_build_and_test: 79 | runs-on: ubuntu-latest 80 | steps: 81 | - uses: actions/checkout@v1 82 | - name: Install SageMath and CBC 83 | run: | 84 | sudo DEBIAN_FRONTEND=noninteractive apt-get update 85 | sudo DEBIAN_FRONTEND=noninteractive apt-get install sagemath coinor-cbc coinor-libcbc-dev python-pkgconfig python-setuptools python-sphinx cython 86 | - name: Show package info 87 | run: | 88 | sage -python -m pip list 89 | - name: Test package 90 | run: | 91 | sage --version 92 | sage setup.py test 93 | - name: Install package 94 | run: | 95 | sage -python -m pip install --no-dependencies -v -v . 96 | - name: Patch in 97 | run: | 98 | sudo sage patch_into_sage_module.py 99 | sage check_get_solver_with_name.py 100 | docker_ubuntu_sagemath_build_and_test: 101 | runs-on: ubuntu-latest 102 | container: ${{ matrix.container }} 103 | strategy: 104 | matrix: 105 | # debian:oldstable = stretch only has Sage 7.4, too old. 106 | container: [ "ubuntu:disco", "ubuntu:devel", "debian:stable", "debian:unstable" ] 107 | steps: 108 | - uses: actions/checkout@v1 109 | - name: Install SageMath and CBC 110 | run: | 111 | DEBIAN_FRONTEND=noninteractive apt-get update 112 | DEBIAN_FRONTEND=noninteractive apt-get --yes install sagemath coinor-cbc coinor-libcbc-dev coinor-libcgl-dev coinor-libclp-dev cython g++ python-pip python3-pip libpari-dev 113 | - name: Show package info 114 | run: | 115 | sage -python -m pip list 116 | - name: Test package 117 | run: | 118 | sage --version 119 | sage setup.py test 120 | - name: Install package 121 | run: | 122 | sage -python -m pip install --no-dependencies -v -v . 123 | - name: Patch in 124 | run: ./patch_and_check.sh 125 | docker_fedora_sagemath_build_and_test: 126 | runs-on: ubuntu-latest 127 | container: ${{ matrix.container }} 128 | strategy: 129 | matrix: 130 | container: [ "fedora:latest" ] 131 | steps: 132 | - uses: actions/checkout@v1 133 | - name: Install SageMath and CBC 134 | # Bizarre - the Sage doctester fails if the ipywidgets package is not installed. It is not in Fedora. 135 | # File "/usr/lib64/python3.7/site-packages/sage/doctest/forker.py", line 172, in init_sage 136 | # import sage.repl.ipython_kernel.all_jupyter 137 | # File "/usr/lib64/python3.7/site-packages/sage/repl/ipython_kernel/all_jupyter.py", line 7, in 138 | # from .widgets_sagenb import (input_box, text_control, slider, 139 | # File "/usr/lib64/python3.7/site-packages/sage/repl/ipython_kernel/widgets_sagenb.py", line 30, in 140 | # from ipywidgets.widgets import (IntSlider, IntRangeSlider, FloatSlider, 141 | # ModuleNotFoundError: No module named 'ipywidgets' 142 | run: | 143 | yum install -y sagemath coin-or-Cbc-devel python-pip pkg-config python-pkgconfig python3-cysignals-devel gcc-c++ pari-devel 144 | sage -python -m pip install ipywidgets 145 | - name: Show package info 146 | run: | 147 | sage -python -m pip list 148 | - name: Test package 149 | run: | 150 | sage --version 151 | sage setup.py test 152 | - name: Install package 153 | run: | 154 | sage -python -m pip install --no-dependencies -v -v . 155 | - name: Patch in 156 | run: ./patch_and_check.sh 157 | -------------------------------------------------------------------------------- /.github/workflows/dist.yml: -------------------------------------------------------------------------------- 1 | name: Distributions 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | concurrency: 9 | # Cancel previous runs of this workflow for the same branch 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | 15 | sdists_for_pypi: 16 | name: Build sdist (and upload to PyPI on release tags) 17 | runs-on: ubuntu-latest 18 | env: 19 | CAN_DEPLOY: ${{ secrets.SAGEMATH_PYPI_API_TOKEN != '' }} 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: actions/setup-python@v4 23 | - name: make sdist 24 | run: | 25 | python3 -m pip install build 26 | python3 -m build --sdist 27 | - uses: actions/upload-artifact@v4 28 | with: 29 | path: "dist/*.tar.gz" 30 | name: dist 31 | - uses: pypa/gh-action-pypi-publish@release/v1 32 | with: 33 | user: __token__ 34 | password: ${{ secrets.SAGEMATH_PYPI_API_TOKEN }} 35 | skip_existing: true 36 | verbose: true 37 | if: env.CAN_DEPLOY == 'true' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 38 | 39 | build_wheels: 40 | name: Build wheels on ${{ matrix.os }}, arch ${{ matrix.arch }} 41 | runs-on: ${{ matrix.os }} 42 | needs: sdists_for_pypi 43 | strategy: 44 | fail-fast: false 45 | matrix: 46 | os: [ubuntu-latest] 47 | arch: [x86_64, i686, aarch64] 48 | build: [manylinux, musllinux] 49 | include: 50 | - os: macos-13 51 | arch: x86_64 52 | build: macosx 53 | - os: macos-14 54 | arch: arm64 55 | build: macosx 56 | env: 57 | # SPKGs to install as system packages 58 | SPKGS: _bootstrap _prereq 59 | # Non-Python packages to install as spkgs 60 | TARGETS_PRE: zlib-ensure bzip2-ensure pkgconf-ensure openblas-ensure coinbrew-no-deps cbc-no-deps 61 | # 62 | CIBW_BUILD: "*${{ matrix.build }}*" 63 | # Disable building PyPy wheels on all platforms 64 | CIBW_SKIP: "pp*" 65 | # 66 | CIBW_ARCHS: ${{ matrix.arch }} 67 | # https://cibuildwheel.readthedocs.io/en/stable/options/#requires-python 68 | CIBW_PROJECT_REQUIRES_PYTHON: ">=3.9" 69 | # Environment during wheel build 70 | CIBW_ENVIRONMENT: "PATH=$(pwd)/local/bin:$PATH CPATH=$(pwd)/local/include:$CPATH LIBRARY_PATH=$(pwd)/local/lib:$LIBRARY_PATH LD_LIBRARY_PATH=$(pwd)/local/lib:$LD_LIBRARY_PATH PKG_CONFIG_PATH=$(pwd)/local/share/pkgconfig:$PKG_CONFIG_PATH ACLOCAL_PATH=/usr/share/aclocal PYTHONPATH=$(pwd)/src" 71 | # Use 'build', not 'pip wheel' 72 | CIBW_BUILD_FRONTEND: build 73 | steps: 74 | - uses: actions/checkout@v4 75 | with: 76 | repository: mkoeppe/sage 77 | ref: cbc-2.10.11 78 | 79 | - name: Set up QEMU 80 | if: runner.os == 'Linux' && matrix.arch != 'x86_64' && matrix.arch != 'i686' 81 | uses: docker/setup-qemu-action@v3 82 | with: 83 | platforms: all 84 | 85 | - uses: actions/download-artifact@v4 86 | with: 87 | name: dist 88 | path: dist 89 | 90 | - uses: actions/setup-python@v5 91 | # As of 2024-02-03, the macOS M1 runners do not have preinstalled python or pipx. 92 | # Installing pipx follows the approach of https://github.com/pypa/cibuildwheel/pull/1743 93 | id: python 94 | with: 95 | python-version: "3.8 - 3.12" 96 | update-environment: false 97 | 98 | - name: Build platform wheels 99 | # We build the wheel from the sdist. 100 | # But we must run cibuildwheel with the unpacked source directory, not a tarball, 101 | # so that SAGE_ROOT is copied into the build containers. 102 | # 103 | # In the CIBW_BEFORE_ALL phase, we install libraries using the Sage distribution. 104 | # https://cibuildwheel.readthedocs.io/en/stable/options/#before-all 105 | run: | 106 | "${{ steps.python.outputs.python-path }}" -m pip install pipx 107 | export PATH=build/bin:$PATH 108 | export CIBW_BEFORE_ALL="( $(sage-print-system-package-command debian --yes --no-install-recommends install wget $(sage-get-system-packages debian $SPKGS)) || $(sage-print-system-package-command fedora --yes --no-install-recommends install wget $(sage-get-system-packages fedora $SPKGS | sed s/pkg-config/pkgconfig/)) || ( $(sage-print-system-package-command homebrew --yes --no-install-recommends install wget $(sage-get-system-packages homebrew $SPKGS)) || $(sage-print-system-package-command alpine --yes --no-install-recommends install wget $(sage-get-system-packages alpine $SPKGS)) || echo error ignored) ) && ./bootstrap && ./configure --enable-build-as-root --enable-fat-binary && MAKE=\"make -j6\" make V=0 $TARGETS_PRE" 109 | mkdir -p unpacked 110 | for pkg in sage*numerical*backends*coin; do 111 | (cd unpacked && tar xfz - ) < dist/$pkg*.tar.gz 112 | "${{ steps.python.outputs.python-path }}" -m pipx run cibuildwheel==2.18.0 unpacked/$pkg* 113 | done 114 | 115 | - uses: actions/upload-artifact@v4 116 | with: 117 | name: ${{ matrix.os }}-${{ matrix.build }}-${{ matrix.arch }}-wheels 118 | path: ./wheelhouse/*.whl 119 | 120 | pypi-publish: 121 | # This needs to be a separate job because pypa/gh-action-pypi-publish cannot run on macOS 122 | # https://github.com/pypa/gh-action-pypi-publish 123 | name: Upload wheels to PyPI 124 | needs: build_wheels 125 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 126 | runs-on: ubuntu-latest 127 | env: 128 | CAN_DEPLOY: ${{ secrets.SAGEMATH_PYPI_API_TOKEN != '' }} 129 | steps: 130 | 131 | - uses: actions/download-artifact@v4 132 | with: 133 | pattern: "*-*-wheels" 134 | path: wheelhouse 135 | merge-multiple: true 136 | 137 | - name: Publish package distributions to PyPI 138 | uses: pypa/gh-action-pypi-publish@release/v1 139 | with: 140 | user: __token__ 141 | password: ${{ secrets.SAGEMATH_PYPI_API_TOKEN }} 142 | packages_dir: wheelhouse/ 143 | skip_existing: true 144 | verbose: true 145 | if: env.CAN_DEPLOY == 'true' 146 | -------------------------------------------------------------------------------- /sage_numerical_backends_coin/coin_backend.pxd: -------------------------------------------------------------------------------- 1 | #***************************************************************************** 2 | # Copyright (C) 2010-2015 Nathann Cohen 3 | # Copyright (C) 2010 Martin Albrecht 4 | # Copyright (C) 2012 John Perry 5 | # Copyright (C) 2012-2019 Jeroen Demeyer 6 | # Copyright (C) 2013 Julien Puydt 7 | # Copyright (C) 2014 Nils Bruin 8 | # Copyright (C) 2014-2018 Dima Pasechnik 9 | # Copyright (C) 2015 Yuan Zhou 10 | # Copyright (C) 2015 Zeyi Wang 11 | # Copyright (C) 2016 Matthias Koeppe 12 | # Copyright (C) 2017 Jori Mäntysalo 13 | # Copyright (C) 2018 Erik M. Bray 14 | # Copyright (C) 2019 David Coudert 15 | # 16 | # This program is free software: you can redistribute it and/or modify 17 | # it under the terms of the GNU General Public License as published by 18 | # the Free Software Foundation, either version 2 of the License, or 19 | # (at your option) any later version. 20 | # https://www.gnu.org/licenses/ 21 | #***************************************************************************** 22 | 23 | from sage.numerical.backends.generic_backend cimport GenericBackend 24 | 25 | from libcpp cimport bool 26 | 27 | 28 | cdef extern from "CbcStrategy.hpp": 29 | cdef cppclass CbcStrategy: 30 | pass 31 | cdef cppclass CbcStrategyDefault(CbcStrategy): 32 | CbcStrategyDefault() 33 | 34 | cdef extern from "CoinPackedVectorBase.hpp": 35 | cdef cppclass CoinPackedVectorBase: 36 | pass 37 | 38 | cdef extern from "CoinPackedVector.hpp": 39 | cdef cppclass CoinPackedVector(CoinPackedVectorBase): 40 | void insert(float, float) 41 | 42 | cdef extern from "CoinShallowPackedVector.hpp": 43 | cdef cppclass CoinShallowPackedVector: 44 | void insert(float, float) 45 | int * getIndices () 46 | double * getElements () 47 | int getNumElements () 48 | 49 | cdef extern from "CoinPackedMatrix.hpp": 50 | cdef cppclass CoinPackedMatrix: 51 | void setDimensions(int, int) 52 | void appendRow(CoinPackedVector) 53 | CoinShallowPackedVector getVector(int) 54 | 55 | cdef extern from "CoinMessageHandler.hpp": 56 | cdef cppclass CoinMessageHandler: 57 | void setLogLevel (int) 58 | int LogLevel () 59 | 60 | 61 | cdef extern from "OsiSolverParameters.hpp": 62 | cdef enum OsiIntParam: 63 | OsiMaxNumIteration = 0, OsiMaxNumIterationHotStart, OsiNameDiscipline, OsiLastIntParam 64 | 65 | cdef extern from "OsiSolverInterface.hpp": 66 | 67 | cdef cppclass OsiSolverInterface: 68 | 69 | # clone 70 | OsiSolverInterface * clone(bool copyData) 71 | 72 | # info about LP -- see also info about variable data 73 | int getNumCols() 74 | int getNumRows() 75 | double * getObjCoefficients() 76 | double getObjSense() 77 | double * getRowLower() 78 | double * getRowUpper() 79 | CoinPackedMatrix * getMatrixByRow() 80 | #string getRowName(int rowIndex, unsigned maxLen=?) 81 | #string setObjName(int ndx, string name) 82 | #string getObjName(unsigned maxLen=?) 83 | #void setObjName(string name) 84 | 85 | # info about solution or solver 86 | int isAbandoned() 87 | int isProvenPrimalInfeasible() 88 | int isProvenDualInfeasible() 89 | int isPrimalObjectiveLimitReached() 90 | int isDualObjectiveLimitReached() 91 | int isIterationLimitReached() 92 | int isProvenOptimal() 93 | double getObjValue() 94 | double * getColSolution() 95 | 96 | # initialization 97 | int setIntParam(OsiIntParam key, int value) 98 | void setObjSense(double s) 99 | 100 | # set upper, lower bounds 101 | void setColLower(double * array) 102 | void setColLower(int elementIndex, double elementValue) 103 | void setColUpper(double * array) 104 | void setColUpper(int elementIndex, double elementValue) 105 | 106 | # set variable data 107 | void setContinuous(int index) 108 | void setInteger(int index) 109 | void setObjCoeff( int elementIndex, double elementValue ) 110 | void addCol(int numberElements, int * rows, double * elements, double collb, double colub, double obj) 111 | 112 | # info about variable data -- see also info about solution or solver 113 | int isContinuous(int colNumber) 114 | double * getColLower() 115 | double * getColUpper() 116 | 117 | # add, delete rows 118 | void addRow(CoinPackedVectorBase & vec, double rowlb, double rowub) 119 | void deleteRows(int num, int *) 120 | 121 | # io 122 | void writeMps(char *filename, char *extension, double objSense) 123 | void writeLp(char *filename, char *extension, double epsilon, int numberAcross, int decimals, double objSense, bool useRowNames) 124 | 125 | # miscellaneous 126 | double getInfinity() 127 | 128 | # info about basis status 129 | void getBasisStatus(int * cstat, int * rstat) 130 | int setBasisStatus(int * cstat, int * rstat) 131 | 132 | # Enable Simplex 133 | void enableSimplexInterface(bool doingPrimal) 134 | 135 | # Get tableau 136 | void getBInvARow(int row, double* z, double * slack) 137 | void getBInvACol(int col, double* vec) 138 | 139 | # Get indices of basic variables 140 | void getBasics(int* index) 141 | 142 | # Get objective coefficients 143 | double * getRowPrice() 144 | double * getReducedCost() 145 | 146 | #Solve initial LP relaxation 147 | void initialSolve() 148 | 149 | # Resolve an LP relaxation after problem modification 150 | void resolve() 151 | 152 | cdef extern from "CbcModel.hpp": 153 | cdef cppclass CbcModel: 154 | # default constructor 155 | CbcModel() 156 | # constructor from solver 157 | CbcModel(OsiSolverInterface & si) 158 | # assigning, owning solver 159 | void assignSolver(OsiSolverInterface * & solver) 160 | void setModelOwnsSolver(bool ourSolver) 161 | # get solver 162 | OsiSolverInterface * solver() 163 | # copy constructor 164 | CbcModel(CbcModel & rhs) 165 | # shut up 166 | void setLogLevel(int value) 167 | int logLevel() 168 | # assign strategy 169 | void setStrategy(CbcStrategy & strategy) 170 | # threads 171 | void setNumberThreads (int) 172 | int getSolutionCount() 173 | # solve 174 | void branchAndBound() 175 | # not sure we need this but it can't hurt 176 | CoinMessageHandler * messageHandler () 177 | void CbcMain0(CbcModel m) 178 | 179 | cdef extern from "ClpSimplex.hpp": 180 | cdef cppclass ClpSimplex: 181 | void setNumberThreads(int) 182 | 183 | cdef extern from "OsiClpSolverInterface.hpp": 184 | 185 | cdef cppclass OsiClpSolverInterface(OsiSolverInterface): 186 | 187 | # ordinary constructor 188 | OsiClpSolverInterface() 189 | # copy constructor 190 | OsiClpSolverInterface(OsiClpSolverInterface &si) 191 | # log level 192 | void setLogLevel(int value) 193 | 194 | 195 | cdef class CoinBackend(GenericBackend): 196 | 197 | cdef OsiSolverInterface * si 198 | cdef CbcModel * model 199 | cdef int log_level 200 | 201 | cdef list col_names, row_names 202 | cdef str prob_name 203 | 204 | cpdef __copy__(self) 205 | cpdef get_basis_status(self) 206 | cpdef int set_basis_status(self, list cstat, list rstat) except -1 207 | cpdef get_binva_row(self, int i) 208 | cpdef get_binva_col(self, int j) 209 | cpdef get_basics(self) 210 | cpdef get_row_price(self) 211 | cpdef get_reduced_cost(self) 212 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /sage_numerical_backends_coin/coin_backend.pyx: -------------------------------------------------------------------------------- 1 | # distutils: language = c++ 2 | # distutils: libraries = Cbc CbcSolver Cgl Clp CoinUtils OsiCbc OsiClp Osi 3 | """ 4 | COIN Backend 5 | """ 6 | 7 | #***************************************************************************** 8 | # Copyright (C) 2010-2015 Nathann Cohen 9 | # Copyright (C) 2010 Martin Albrecht 10 | # Copyright (C) 2012 John Perry 11 | # Copyright (C) 2012-2019 Jeroen Demeyer 12 | # Copyright (C) 2013 Julien Puydt 13 | # Copyright (C) 2014 Nils Bruin 14 | # Copyright (C) 2014-2018 Dima Pasechnik 15 | # Copyright (C) 2015 Yuan Zhou 16 | # Copyright (C) 2015 Zeyi Wang 17 | # Copyright (C) 2016 Matthias Koeppe 18 | # Copyright (C) 2017 Jori Mäntysalo 19 | # Copyright (C) 2018 Erik M. Bray 20 | # Copyright (C) 2019 David Coudert 21 | # 22 | # This program is free software: you can redistribute it and/or modify 23 | # it under the terms of the GNU General Public License as published by 24 | # the Free Software Foundation, either version 2 of the License, or 25 | # (at your option) any later version. 26 | # https://www.gnu.org/licenses/ 27 | #***************************************************************************** 28 | 29 | from cysignals.memory cimport check_malloc, sig_free 30 | from cysignals.signals cimport sig_on, sig_off 31 | 32 | from sage.numerical.mip import MIPSolverException 33 | from copy import copy 34 | 35 | IF HAVE_SAGE_CPYTHON_STRING: 36 | from sage.cpython.string cimport str_to_bytes 37 | from sage.cpython.string import FS_ENCODING 38 | from sage.parallel.ncpus import ncpus 39 | 40 | 41 | cdef class CoinBackend(GenericBackend): 42 | 43 | """ 44 | MIP Backend that uses the COIN solver (CBC). 45 | 46 | TESTS: 47 | 48 | General backend testsuite:: 49 | 50 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 51 | sage: p = CoinBackend() 52 | sage: TestSuite(p).run() # known bug on 32 bit (#21550) 53 | sage: TestSuite(p).run(skip=["_test_pickling", "_test_solve"]) 54 | """ 55 | 56 | def __cinit__(self, maximization = True): 57 | """ 58 | Cython constructor 59 | 60 | EXAMPLES:: 61 | 62 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 63 | sage: p = CoinBackend() 64 | 65 | """ 66 | 67 | # Coin devs seem to favor OsiClpSolverInterface 68 | self.si = new OsiClpSolverInterface() 69 | self.model = new CbcModel(self.si[0]) 70 | self.prob_name = None 71 | self.row_names = [] 72 | self.col_names = [] 73 | self.set_verbosity(0) 74 | 75 | if maximization: 76 | self.set_sense(+1) 77 | else: 78 | self.set_sense(-1) 79 | 80 | self.obj_constant_term = 0.0 81 | 82 | def __dealloc__(self): 83 | r""" 84 | Destructor function 85 | """ 86 | del self.model 87 | del self.si 88 | 89 | def __reduce__(self): 90 | r""" 91 | Explicitly disallows pickling for backend instances. 92 | """ 93 | raise NotImplementedError("__reduce__ not implemented for %s" % type(self)) 94 | 95 | cpdef int add_variable(self, lower_bound=0.0, upper_bound=None, binary=False, continuous=False, integer=False, obj=0.0, name=None) except -1: 96 | r""" 97 | Add a variable. 98 | 99 | This amounts to adding a new column to the matrix. By default, 100 | the variable is both positive and real. 101 | 102 | INPUT: 103 | 104 | - ``lower_bound`` - the lower bound of the variable (default: 0) 105 | 106 | - ``upper_bound`` - the upper bound of the variable (default: ``None``) 107 | 108 | - ``binary`` - whether the variable is binary (default: ``False``). 109 | 110 | - ``continuous`` - whether the variable is continuous (default: ``True``). 111 | 112 | - ``integer`` - whether the variable is integer (default: ``False``). 113 | 114 | - ``obj`` - (optional) coefficient of this variable in the objective function (default: 0.0) 115 | 116 | - ``name`` - an optional name for the newly added variable (default: ``None``). 117 | 118 | OUTPUT: The index of the newly created variable 119 | 120 | EXAMPLES:: 121 | 122 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 123 | sage: p = CoinBackend() 124 | sage: p.ncols() 125 | 0 126 | sage: p.add_variable() 127 | 0 128 | sage: p.ncols() 129 | 1 130 | sage: p.add_variable(binary=True) 131 | 1 132 | sage: p.add_variable(lower_bound=-2.0, integer=True) 133 | 2 134 | sage: p.add_variable(continuous=True, integer=True) 135 | Traceback (most recent call last): 136 | ... 137 | ValueError: ... 138 | sage: p.add_variable(name='x',obj=1.0) 139 | 3 140 | sage: p.col_name(3) 141 | 'x' 142 | sage: p.objective_coefficient(3) 143 | 1.0 144 | """ 145 | 146 | # for some reason, Cython is not accepting the line below, which appears 147 | #cdef int vtype = int(bool(binary)) + int(bool(continuous)) + int(bool(integer)) 148 | cdef int vtype = int(binary) + int(continuous) + int(integer) 149 | if vtype == 0: 150 | continuous = True 151 | elif vtype != 1: 152 | raise ValueError("Exactly one parameter of 'binary', 'integer' and 'continuous' must be 'True'.") 153 | 154 | self.si.addCol(0, NULL, NULL, 0, self.si.getInfinity(), 0) 155 | 156 | cdef int n 157 | n = self.si.getNumCols() - 1 158 | 159 | if lower_bound != 0.0: 160 | self.variable_lower_bound(n, lower_bound) 161 | if upper_bound is not None: 162 | self.variable_upper_bound(n, upper_bound) 163 | 164 | if binary: 165 | self.set_variable_type(n,0) 166 | elif integer: 167 | self.set_variable_type(n,1) 168 | 169 | if name: 170 | self.col_names.append(name) 171 | else: 172 | self.col_names.append("") 173 | 174 | if obj: 175 | self.si.setObjCoeff(n, obj) 176 | 177 | return n 178 | 179 | cpdef int add_variables(self, int number, lower_bound=0.0, upper_bound=None, binary=False, continuous=False, integer=False, obj=0.0, names=None) except -1: 180 | """ 181 | Add ``number`` new variables. 182 | 183 | This amounts to adding new columns to the matrix. By default, 184 | the variables are both positive and real. 185 | 186 | INPUT: 187 | 188 | - ``n`` - the number of new variables (must be > 0) 189 | 190 | - ``lower_bound`` - the lower bound of the variable (default: 0) 191 | 192 | - ``upper_bound`` - the upper bound of the variable (default: ``None``) 193 | 194 | - ``binary`` - ``True`` if the variable is binary (default: ``False``). 195 | 196 | - ``continuous`` - ``True`` if the variable is binary (default: ``True``). 197 | 198 | - ``integer`` - ``True`` if the variable is binary (default: ``False``). 199 | 200 | - ``obj`` - (optional) coefficient of all variables in the objective function (default: 0.0) 201 | 202 | - ``names`` - optional list of names (default: ``None``) 203 | 204 | OUTPUT: The index of the variable created last. 205 | 206 | EXAMPLES:: 207 | 208 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 209 | sage: p = CoinBackend() 210 | sage: p.ncols() 211 | 0 212 | sage: p.add_variables(5) 213 | 4 214 | sage: p.ncols() 215 | 5 216 | sage: p.add_variables(2, lower_bound=-2.0, integer=True, obj=42.0, names=['a','b']) 217 | 6 218 | 219 | TESTS: 220 | 221 | Check that arguments are used:: 222 | 223 | sage: p.col_bounds(5) # tol 1e-8 224 | (-2.0, None) 225 | sage: p.is_variable_integer(5) 226 | True 227 | sage: p.col_name(5) 228 | 'a' 229 | sage: p.objective_coefficient(5) # tol 1e-8 230 | 42.0 231 | """ 232 | #cdef int vtype = int(bool(binary)) + int(bool(continuous)) + int(bool(integer)) 233 | cdef int vtype = int(binary) + int(continuous) + int(integer) 234 | if vtype == 0: 235 | continuous = True 236 | elif vtype != 1: 237 | raise ValueError("Exactly one parameter of 'binary', 'integer' and 'continuous' must be 'True'.") 238 | 239 | cdef int n 240 | n = self.si.getNumCols() 241 | 242 | cdef int i 243 | 244 | for 0<= i < number: 245 | 246 | self.si.addCol(0, NULL, NULL, 0, self.si.getInfinity(), 0) 247 | 248 | if lower_bound != 0.0: 249 | self.variable_lower_bound(n + i, lower_bound) 250 | if upper_bound is not None: 251 | self.variable_upper_bound(n + i, upper_bound) 252 | 253 | if binary: 254 | self.set_variable_type(n + i,0) 255 | elif integer: 256 | self.set_variable_type(n + i,1) 257 | 258 | if obj: 259 | self.si.setObjCoeff(n + i, obj) 260 | 261 | if names is not None: 262 | for name in names: 263 | self.col_names.append(name) 264 | else: 265 | self.col_names.extend(['' for i in range(number)]) 266 | 267 | return n + number -1 268 | 269 | cpdef set_variable_type(self, int variable, int vtype) noexcept: 270 | r""" 271 | Sets the type of a variable 272 | 273 | INPUT: 274 | 275 | - ``variable`` (integer) -- the variable's id 276 | 277 | - ``vtype`` (integer) : 278 | 279 | * 1 Integer 280 | * 0 Binary 281 | * -1 Real 282 | 283 | EXAMPLES:: 284 | 285 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 286 | sage: p = CoinBackend() 287 | sage: p.ncols() 288 | 0 289 | sage: p.add_variable() 290 | 0 291 | sage: p.set_variable_type(0,1) 292 | sage: p.is_variable_integer(0) 293 | True 294 | """ 295 | 296 | if vtype == 1: 297 | self.si.setInteger(variable) 298 | elif vtype == 0: 299 | self.si.setColLower(variable, 0) 300 | self.si.setInteger(variable) 301 | self.si.setColUpper(variable, 1) 302 | else: 303 | self.si.setContinuous(variable) 304 | 305 | cpdef set_sense(self, int sense) noexcept: 306 | r""" 307 | Sets the direction (maximization/minimization). 308 | 309 | INPUT: 310 | 311 | - ``sense`` (integer) : 312 | 313 | * +1 => Maximization 314 | * -1 => Minimization 315 | 316 | EXAMPLES:: 317 | 318 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 319 | sage: p = CoinBackend() 320 | sage: p.is_maximization() 321 | True 322 | sage: p.set_sense(-1) 323 | sage: p.is_maximization() 324 | False 325 | """ 326 | self.si.setObjSense(-sense) 327 | 328 | cpdef objective_coefficient(self, int variable, coeff=None) noexcept: 329 | """ 330 | Set or get the coefficient of a variable in the objective function 331 | 332 | INPUT: 333 | 334 | - ``variable`` (integer) -- the variable's id 335 | 336 | - ``coeff`` (double) -- its coefficient or ``None`` for 337 | reading (default: ``None``) 338 | 339 | EXAMPLES:: 340 | 341 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 342 | sage: p = CoinBackend() 343 | sage: p.add_variable() 344 | 0 345 | sage: p.objective_coefficient(0) 346 | 0.0 347 | sage: p.objective_coefficient(0,2) 348 | sage: p.objective_coefficient(0) 349 | 2.0 350 | """ 351 | if coeff is not None: 352 | self.si.setObjCoeff(variable, coeff) 353 | else: 354 | return self.si.getObjCoefficients()[variable] 355 | 356 | cpdef set_objective(self, list coeff, d = 0.0) noexcept: 357 | r""" 358 | Sets the objective function. 359 | 360 | INPUT: 361 | 362 | - ``coeff`` -- a list of real values, whose ith element is the 363 | coefficient of the ith variable in the objective function. 364 | 365 | - ``d`` (double) -- the constant term in the linear function (set to `0` by default) 366 | 367 | EXAMPLES:: 368 | 369 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 370 | sage: p = CoinBackend() 371 | sage: p.add_variables(5) 372 | 4 373 | sage: p.set_objective([1, 1, 2, 1, 3]) 374 | sage: [p.objective_coefficient(x) for x in range(5)] 375 | [1.0, 1.0, 2.0, 1.0, 3.0] 376 | 377 | Constants in the objective function are respected:: 378 | 379 | sage: p = MixedIntegerLinearProgram(solver=CoinBackend) 380 | sage: v = p.new_variable(nonnegative=True) 381 | sage: x,y = v[0], v[1] 382 | sage: p.add_constraint(2*x + 3*y, max = 6) 383 | sage: p.add_constraint(3*x + 2*y, max = 6) 384 | sage: p.set_objective(x + y + 7) 385 | sage: p.set_integer(x); p.set_integer(y) 386 | sage: p.solve() 387 | 9.0 388 | """ 389 | 390 | cdef int i 391 | 392 | for i,v in enumerate(coeff): 393 | self.si.setObjCoeff(i, v) 394 | 395 | self.obj_constant_term = d 396 | 397 | cpdef set_verbosity(self, int level) noexcept: 398 | r""" 399 | Sets the log (verbosity) level 400 | 401 | INPUT: 402 | 403 | - ``level`` (integer) -- From 0 (no verbosity) to 3. 404 | 405 | EXAMPLES:: 406 | 407 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 408 | sage: p = CoinBackend() 409 | sage: p.set_verbosity(2) 410 | 411 | """ 412 | self.model.setLogLevel(level) 413 | 414 | cpdef remove_constraint(self, int i) noexcept: 415 | r""" 416 | Remove a constraint from self. 417 | 418 | INPUT: 419 | 420 | - ``i`` -- index of the constraint to remove 421 | 422 | EXAMPLES:: 423 | 424 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 425 | sage: p = MixedIntegerLinearProgram(solver=CoinBackend) 426 | sage: v = p.new_variable(nonnegative=True) 427 | sage: x,y = v[0], v[1] 428 | sage: p.add_constraint(2*x + 3*y, max = 6) 429 | sage: p.add_constraint(3*x + 2*y, max = 6) 430 | sage: p.set_objective(x + y + 7) 431 | sage: p.set_integer(x); p.set_integer(y) 432 | sage: p.solve() 433 | 9.0 434 | sage: p.remove_constraint(0) 435 | sage: p.solve() 436 | 10.0 437 | sage: p.get_values([x,y]) 438 | [0, 3] 439 | 440 | TESTS: 441 | 442 | Removing fancy constraints does not make Sage crash:: 443 | 444 | sage: MixedIntegerLinearProgram(solver=CoinBackend).remove_constraint(-2) 445 | Traceback (most recent call last): 446 | ... 447 | ValueError: The constraint's index i must satisfy 0 <= i < number_of_constraints 448 | """ 449 | cdef int rows [1] 450 | 451 | if i < 0 or i >= self.si.getNumRows(): 452 | raise ValueError("The constraint's index i must satisfy 0 <= i < number_of_constraints") 453 | rows[0] = i 454 | self.si.deleteRows(1,rows) 455 | 456 | cpdef remove_constraints(self, constraints) noexcept: 457 | r""" 458 | Remove several constraints. 459 | 460 | INPUT: 461 | 462 | - ``constraints`` -- an iterable containing the indices of the rows to remove 463 | 464 | EXAMPLES:: 465 | 466 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 467 | sage: p = MixedIntegerLinearProgram(solver=CoinBackend) 468 | sage: v = p.new_variable(nonnegative=True) 469 | sage: x,y = v[0], v[1] 470 | sage: p.add_constraint(2*x + 3*y, max = 6) 471 | sage: p.add_constraint(3*x + 2*y, max = 6) 472 | sage: p.set_objective(x + y + 7) 473 | sage: p.set_integer(x); p.set_integer(y) 474 | sage: p.solve() 475 | 9.0 476 | sage: p.get_values(x) # random 477 | 2 478 | sage: p.get_values(y) # random 479 | 0 480 | sage: p.remove_constraints([0]) 481 | sage: p.solve() 482 | 10.0 483 | sage: p.get_values([x,y]) 484 | [0, 3] 485 | 486 | TESTS: 487 | 488 | Removing fancy constraints do not make Sage crash:: 489 | 490 | sage: MixedIntegerLinearProgram(solver=CoinBackend).remove_constraints([0, -2]) 491 | Traceback (most recent call last): 492 | ... 493 | ValueError: The constraint's index i must satisfy 0 <= i < number_of_constraints 494 | """ 495 | cdef int i, c 496 | cdef int m = len(constraints) 497 | cdef int * rows = check_malloc(m * sizeof(int *)) 498 | cdef int nrows = self.si.getNumRows() 499 | 500 | for i in xrange(m): 501 | 502 | c = constraints[i] 503 | if c < 0 or c >= nrows: 504 | sig_free(rows) 505 | raise ValueError("The constraint's index i must satisfy 0 <= i < number_of_constraints") 506 | 507 | rows[i] = c 508 | 509 | self.si.deleteRows(m,rows) 510 | sig_free(rows) 511 | 512 | cpdef add_linear_constraint(self, coefficients, lower_bound, upper_bound, name = None) noexcept: 513 | """ 514 | Add a linear constraint. 515 | 516 | INPUT: 517 | 518 | - ``coefficients`` an iterable with ``(c,v)`` pairs where ``c`` 519 | is a variable index (integer) and ``v`` is a value (real 520 | value). 521 | 522 | - ``lower_bound`` - a lower bound, either a real value or ``None`` 523 | 524 | - ``upper_bound`` - an upper bound, either a real value or ``None`` 525 | 526 | - ``name`` - an optional name for this row (default: ``None``) 527 | 528 | EXAMPLES:: 529 | 530 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 531 | sage: p = CoinBackend() 532 | sage: p.add_variables(5) 533 | 4 534 | sage: p.add_linear_constraint( zip(range(5), range(5)), 2.0, 2.0) 535 | sage: p.row(0) 536 | ([0, 1, 2, 3, 4], [0.0, 1.0, 2.0, 3.0, 4.0]) 537 | sage: p.row_bounds(0) 538 | (2.0, 2.0) 539 | sage: p.add_linear_constraint( zip(range(5), range(5)), 1.0, 1.0, name='foo') 540 | sage: p.row_name(1) 541 | 'foo' 542 | """ 543 | if lower_bound is None and upper_bound is None: 544 | raise ValueError("At least one of 'upper_bound' or 'lower_bound' must be set.") 545 | 546 | cdef int i 547 | cdef double c 548 | cdef CoinPackedVector* row 549 | row = new CoinPackedVector(); 550 | 551 | 552 | for i,c in coefficients: 553 | row.insert(i, c) 554 | 555 | self.si.addRow (row[0], 556 | lower_bound if lower_bound is not None else -self.si.getInfinity(), 557 | upper_bound if upper_bound is not None else +self.si.getInfinity()) 558 | if name is not None: 559 | self.row_names.append(name) 560 | else: 561 | self.row_names.append("") 562 | del *row 563 | 564 | cpdef row(self, int index) noexcept: 565 | r""" 566 | Returns a row 567 | 568 | INPUT: 569 | 570 | - ``index`` (integer) -- the constraint's id. 571 | 572 | OUTPUT: 573 | 574 | A pair ``(indices, coeffs)`` where ``indices`` lists the 575 | entries whose coefficient is nonzero, and to which ``coeffs`` 576 | associates their coefficient on the model of the 577 | ``add_linear_constraint`` method. 578 | 579 | EXAMPLES:: 580 | 581 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 582 | sage: p = CoinBackend() 583 | sage: p.add_variables(5) 584 | 4 585 | sage: p.add_linear_constraint(zip(range(5), range(5)), 2, 2) 586 | sage: p.row(0) 587 | ([0, 1, 2, 3, 4], [0.0, 1.0, 2.0, 3.0, 4.0]) 588 | sage: p.row_bounds(0) 589 | (2.0, 2.0) 590 | """ 591 | 592 | cdef list indices = [] 593 | cdef list values = [] 594 | cdef int * c_indices 595 | cdef int i 596 | cdef double * c_values 597 | cdef CoinPackedMatrix * M = self.si.getMatrixByRow() 598 | cdef CoinShallowPackedVector V = M.getVector(index) 599 | cdef int n = V.getNumElements() 600 | 601 | c_indices = V.getIndices() 602 | c_values = V.getElements() 603 | 604 | for 0<= i < n: 605 | indices.append(c_indices[i]) 606 | values.append(c_values[i]) 607 | 608 | return (indices, values) 609 | 610 | cpdef row_bounds(self, int i) noexcept: 611 | r""" 612 | Returns the bounds of a specific constraint. 613 | 614 | INPUT: 615 | 616 | - ``index`` (integer) -- the constraint's id. 617 | 618 | OUTPUT: 619 | 620 | A pair ``(lower_bound, upper_bound)``. Each of them can be set 621 | to ``None`` if the constraint is not bounded in the 622 | corresponding direction, and is a real value otherwise. 623 | 624 | EXAMPLES:: 625 | 626 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 627 | sage: p = CoinBackend() 628 | sage: p.add_variables(5) 629 | 4 630 | sage: p.add_linear_constraint(zip(range(5), range(5)), 2, 2) 631 | sage: p.row(0) 632 | ([0, 1, 2, 3, 4], [0.0, 1.0, 2.0, 3.0, 4.0]) 633 | sage: p.row_bounds(0) 634 | (2.0, 2.0) 635 | """ 636 | 637 | cdef double * ub 638 | cdef double * lb 639 | 640 | ub = self.si.getRowUpper() 641 | lb = self.si.getRowLower() 642 | 643 | return (lb[i] if lb[i] != - self.si.getInfinity() else None, 644 | ub[i] if ub[i] != + self.si.getInfinity() else None) 645 | 646 | cpdef col_bounds(self, int i) noexcept: 647 | r""" 648 | Returns the bounds of a specific variable. 649 | 650 | INPUT: 651 | 652 | - ``index`` (integer) -- the variable's id. 653 | 654 | OUTPUT: 655 | 656 | A pair ``(lower_bound, upper_bound)``. Each of them can be set 657 | to ``None`` if the variable is not bounded in the 658 | corresponding direction, and is a real value otherwise. 659 | 660 | EXAMPLES:: 661 | 662 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 663 | sage: p = CoinBackend() 664 | sage: p.add_variable() 665 | 0 666 | sage: p.col_bounds(0) 667 | (0.0, None) 668 | sage: p.variable_upper_bound(0, 5) 669 | sage: p.col_bounds(0) 670 | (0.0, 5.0) 671 | """ 672 | 673 | cdef double * ub 674 | cdef double * lb 675 | 676 | ub = self.si.getColUpper() 677 | lb = self.si.getColLower() 678 | 679 | return (lb[i] if lb[i] != - self.si.getInfinity() else None, 680 | ub[i] if ub[i] != + self.si.getInfinity() else None) 681 | 682 | IF HAVE_ADD_COL_UNTYPED_ARGS: 683 | cpdef add_col(self, indices, coeffs) noexcept: 684 | r""" 685 | Adds a column. 686 | 687 | INPUT: 688 | 689 | - ``indices`` (list of integers) -- this list contains the 690 | indices of the constraints in which the variable's 691 | coefficient is nonzero 692 | 693 | - ``coeffs`` (list of real values) -- associates a coefficient 694 | to the variable in each of the constraints in which it 695 | appears. Namely, the ith entry of ``coeffs`` corresponds to 696 | the coefficient of the variable in the constraint 697 | represented by the ith entry in ``indices``. 698 | 699 | .. NOTE:: 700 | 701 | ``indices`` and ``coeffs`` are expected to be of the same 702 | length. 703 | 704 | EXAMPLES:: 705 | 706 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 707 | sage: p = CoinBackend() 708 | sage: p.ncols() 709 | 0 710 | sage: p.nrows() 711 | 0 712 | sage: p.add_linear_constraints(5, 0, None) 713 | sage: p.add_col(list(range(5)), list(range(5))) 714 | sage: p.nrows() 715 | 5 716 | """ 717 | 718 | cdef list list_indices 719 | cdef list list_coeffs 720 | 721 | if type(indices) is not list: 722 | list_indices = list(indices) 723 | else: 724 | list_indices = indices 725 | 726 | if type(coeffs) is not list: 727 | list_coeffs = list(coeffs) 728 | else: 729 | list_coeffs = coeffs 730 | 731 | cdef int n = len(list_indices) 732 | cdef int * c_indices = check_malloc(n*sizeof(int)) 733 | cdef double * c_values = check_malloc(n*sizeof(double)) 734 | cdef int i 735 | 736 | for 0<= i< n: 737 | c_indices[i] = list_indices[i] 738 | c_values[i] = list_coeffs[i] 739 | 740 | self.si.addCol (n, c_indices, c_values, 0, self.si.getInfinity(), 0) 741 | 742 | self.col_names.append("") 743 | sig_free(c_indices) 744 | sig_free(c_values) 745 | ELSE: 746 | cpdef add_col(self, list list_indices, list list_coeffs) noexcept: 747 | r""" 748 | Adds a column. 749 | 750 | INPUT: 751 | 752 | - ``indices`` (list of integers) -- this list contains the 753 | indices of the constraints in which the variable's 754 | coefficient is nonzero 755 | 756 | - ``coeffs`` (list of real values) -- associates a coefficient 757 | to the variable in each of the constraints in which it 758 | appears. Namely, the ith entry of ``coeffs`` corresponds to 759 | the coefficient of the variable in the constraint 760 | represented by the ith entry in ``indices``. 761 | 762 | .. NOTE:: 763 | 764 | ``indices`` and ``coeffs`` are expected to be of the same 765 | length. 766 | """ 767 | cdef int n = len(list_indices) 768 | cdef int * c_indices = check_malloc(n*sizeof(int)) 769 | cdef double * c_values = check_malloc(n*sizeof(double)) 770 | cdef int i 771 | 772 | for 0<= i< n: 773 | c_indices[i] = list_indices[i] 774 | c_values[i] = list_coeffs[i] 775 | 776 | self.si.addCol (n, c_indices, c_values, 0, self.si.getInfinity(), 0) 777 | 778 | self.col_names.append("") 779 | sig_free(c_indices) 780 | sig_free(c_values) 781 | 782 | cpdef int solve(self) except -1: 783 | r""" 784 | Solves the problem. 785 | 786 | .. NOTE:: 787 | 788 | This method raises ``MIPSolverException`` exceptions when 789 | the solution can not be computed for any reason (none 790 | exists, or the LP solver was not able to find it, etc...) 791 | 792 | EXAMPLES:: 793 | 794 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 795 | sage: p = CoinBackend() 796 | sage: p.add_linear_constraints(5, 0, None) 797 | sage: p.add_col(list(range(5)), [1,2,3,4,5]) 798 | sage: p.solve() 799 | 0 800 | 801 | TESTS:: 802 | 803 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 804 | sage: p = CoinBackend() 805 | sage: p.add_variable() 806 | 0 807 | sage: p.add_linear_constraint([(0, 1)], None, 4) 808 | sage: p.add_linear_constraint([(0, 1)], 6, None) 809 | sage: p.objective_coefficient(0,1) 810 | sage: p.solve() 811 | Traceback (most recent call last): 812 | ... 813 | MIPSolverException: ... 814 | """ 815 | 816 | # set up the model 817 | cdef OsiSolverInterface * si = self.si 818 | 819 | cdef CbcModel * model 820 | cdef int old_logLevel = self.model.logLevel() 821 | 822 | model = new CbcModel(si[0]) 823 | del self.model 824 | self.model = model 825 | 826 | #we immediately commit to the new model so that the user has access 827 | #to it even when something goes wrong. 828 | 829 | model.setLogLevel(old_logLevel) 830 | 831 | # multithreading 832 | import multiprocessing 833 | model.setNumberThreads(ncpus()) 834 | 835 | model.branchAndBound() 836 | 837 | if model.solver().isAbandoned(): 838 | raise MIPSolverException("CBC : The solver has abandoned!") 839 | 840 | elif model.solver().isProvenPrimalInfeasible() or model.solver().isProvenDualInfeasible(): 841 | raise MIPSolverException("CBC : The problem or its dual has been proven infeasible!") 842 | 843 | elif (model.solver().isPrimalObjectiveLimitReached() or model.solver().isDualObjectiveLimitReached()): 844 | raise MIPSolverException("CBC : The objective limit has been reached for the problem or its dual!") 845 | 846 | elif model.solver().isIterationLimitReached(): 847 | raise MIPSolverException("CBC : The iteration limit has been reached!") 848 | 849 | elif not model.solver().isProvenOptimal(): 850 | raise MIPSolverException("CBC : Unknown error") 851 | 852 | return 0 853 | 854 | cpdef get_objective_value(self) noexcept: 855 | r""" 856 | Returns the value of the objective function. 857 | 858 | .. NOTE:: 859 | 860 | Has no meaning unless ``solve`` or ``set_basis_status`` has been called before. 861 | 862 | EXAMPLES:: 863 | 864 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 865 | sage: p = CoinBackend() 866 | sage: p.add_variables(2) 867 | 1 868 | sage: p.add_linear_constraint([(0, 1), (1, 2)], None, 3) 869 | sage: p.set_objective([2, 5]) 870 | sage: p.solve() 871 | 0 872 | sage: p.get_objective_value() 873 | 7.5 874 | sage: p.get_variable_value(0) 875 | 0.0 876 | sage: p.get_variable_value(1) 877 | 1.5 878 | """ 879 | return self.model.solver().getObjValue() + self.obj_constant_term 880 | 881 | cpdef get_variable_value(self, int variable) noexcept: 882 | r""" 883 | Returns the value of a variable given by the solver. 884 | 885 | .. NOTE:: 886 | 887 | Has no meaning unless ``solve`` or ``set_basis_status`` has been called before. 888 | 889 | EXAMPLES:: 890 | 891 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 892 | sage: p = CoinBackend() 893 | sage: p.add_variables(2) 894 | 1 895 | sage: p.add_linear_constraint([(0, 1), (1, 2)], None, 3) 896 | sage: p.set_objective([2, 5]) 897 | sage: p.solve() 898 | 0 899 | sage: p.get_objective_value() 900 | 7.5 901 | sage: p.get_variable_value(0) 902 | 0.0 903 | sage: p.get_variable_value(1) 904 | 1.5 905 | sage: p = MixedIntegerLinearProgram(solver=CoinBackend) 906 | sage: x = p.new_variable(nonnegative=True) 907 | sage: p.set_min(x[0], 0.0) 908 | sage: p.get_values(x) 909 | {0: 0.0} 910 | """ 911 | 912 | cdef double * solution 913 | cdef double v 914 | solution = self.model.solver().getColSolution() 915 | if solution == NULL: 916 | v = 0.0 917 | else: 918 | v = solution[variable] 919 | if self.is_variable_continuous(variable): 920 | return v 921 | else: 922 | return int(round(v)) 923 | 924 | cpdef int ncols(self) noexcept: 925 | r""" 926 | Returns the number of columns/variables. 927 | 928 | EXAMPLES:: 929 | 930 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 931 | sage: p = CoinBackend() 932 | sage: p.ncols() 933 | 0 934 | sage: p.add_variables(2) 935 | 1 936 | sage: p.ncols() 937 | 2 938 | """ 939 | 940 | return self.si.getNumCols() 941 | 942 | cpdef int nrows(self) noexcept: 943 | r""" 944 | Returns the number of rows/constraints. 945 | 946 | EXAMPLES:: 947 | 948 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 949 | sage: p = CoinBackend() 950 | sage: p.nrows() 951 | 0 952 | sage: p.add_linear_constraints(2, 2, None) 953 | sage: p.nrows() 954 | 2 955 | """ 956 | return self.si.getNumRows() 957 | 958 | 959 | cpdef bint is_variable_binary(self, int index) noexcept: 960 | r""" 961 | Tests whether the given variable is of binary type. 962 | 963 | INPUT: 964 | 965 | - ``index`` (integer) -- the variable's id 966 | 967 | EXAMPLES:: 968 | 969 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 970 | sage: p = CoinBackend() 971 | sage: p.ncols() 972 | 0 973 | sage: p.add_variable() 974 | 0 975 | sage: p.set_variable_type(0,0) 976 | sage: p.is_variable_binary(0) 977 | True 978 | 979 | """ 980 | 981 | return (0 == self.si.isContinuous(index) and 982 | self.variable_lower_bound(index) == 0 and 983 | self.variable_upper_bound(index) == 1) 984 | 985 | cpdef bint is_variable_integer(self, int index) noexcept: 986 | r""" 987 | Tests whether the given variable is of integer type. 988 | 989 | INPUT: 990 | 991 | - ``index`` (integer) -- the variable's id 992 | 993 | EXAMPLES:: 994 | 995 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 996 | sage: p = CoinBackend() 997 | sage: p.ncols() 998 | 0 999 | sage: p.add_variable() 1000 | 0 1001 | sage: p.set_variable_type(0,1) 1002 | sage: p.is_variable_integer(0) 1003 | True 1004 | """ 1005 | return (0 == self.si.isContinuous(index) and 1006 | (self.variable_lower_bound(index) != 0 or 1007 | self.variable_upper_bound(index) != 1)) 1008 | 1009 | cpdef bint is_variable_continuous(self, int index) noexcept: 1010 | r""" 1011 | Tests whether the given variable is of continuous/real type. 1012 | 1013 | INPUT: 1014 | 1015 | - ``index`` (integer) -- the variable's id 1016 | 1017 | EXAMPLES:: 1018 | 1019 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 1020 | sage: p = CoinBackend() 1021 | sage: p.ncols() 1022 | 0 1023 | sage: p.add_variable() 1024 | 0 1025 | sage: p.is_variable_continuous(0) 1026 | True 1027 | sage: p.set_variable_type(0,1) 1028 | sage: p.is_variable_continuous(0) 1029 | False 1030 | 1031 | """ 1032 | return 1 == self.si.isContinuous(index) 1033 | 1034 | 1035 | cpdef bint is_maximization(self) noexcept: 1036 | r""" 1037 | Tests whether the problem is a maximization 1038 | 1039 | EXAMPLES:: 1040 | 1041 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 1042 | sage: p = CoinBackend() 1043 | sage: p.is_maximization() 1044 | True 1045 | sage: p.set_sense(-1) 1046 | sage: p.is_maximization() 1047 | False 1048 | """ 1049 | 1050 | return self.si.getObjSense() == -1 1051 | 1052 | cpdef variable_upper_bound(self, int index, value = False): 1053 | r""" 1054 | Returns or defines the upper bound on a variable 1055 | 1056 | INPUT: 1057 | 1058 | - ``index`` (integer) -- the variable's id 1059 | 1060 | - ``value`` -- real value, or ``None`` to mean that the 1061 | variable has not upper bound. When set to ``False`` 1062 | (default), the method returns the current value. 1063 | 1064 | EXAMPLES:: 1065 | 1066 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 1067 | sage: p = CoinBackend() 1068 | sage: p.add_variable() 1069 | 0 1070 | sage: p.col_bounds(0) 1071 | (0.0, None) 1072 | sage: p.variable_upper_bound(0, 5) 1073 | sage: p.col_bounds(0) 1074 | (0.0, 5.0) 1075 | 1076 | TESTS: 1077 | 1078 | :trac:`14581`:: 1079 | 1080 | sage: P = MixedIntegerLinearProgram(solver=CoinBackend) 1081 | sage: v = P.new_variable(nonnegative=True) 1082 | sage: x = v["x"] 1083 | sage: P.set_max(x, 0) 1084 | sage: P.get_max(x) 1085 | 0.0 1086 | 1087 | """ 1088 | cdef double * ub 1089 | 1090 | if value is False: 1091 | ub = self.si.getColUpper() 1092 | return ub[index] if ub[index] != + self.si.getInfinity() else None 1093 | else: 1094 | self.si.setColUpper(index, value if value is not None else +self.si.getInfinity()) 1095 | 1096 | cpdef variable_lower_bound(self, int index, value = False): 1097 | r""" 1098 | Returns or defines the lower bound on a variable 1099 | 1100 | INPUT: 1101 | 1102 | - ``index`` (integer) -- the variable's id 1103 | 1104 | - ``value`` -- real value, or ``None`` to mean that the 1105 | variable has not lower bound. When set to ``False`` 1106 | (default), the method returns the current value. 1107 | 1108 | EXAMPLES:: 1109 | 1110 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 1111 | sage: p = CoinBackend() 1112 | sage: p.add_variable() 1113 | 0 1114 | sage: p.col_bounds(0) 1115 | (0.0, None) 1116 | sage: p.variable_lower_bound(0, 5) 1117 | sage: p.col_bounds(0) 1118 | (5.0, None) 1119 | 1120 | TESTS: 1121 | 1122 | :trac:`14581`:: 1123 | 1124 | sage: P = MixedIntegerLinearProgram(solver=CoinBackend) 1125 | sage: v = P.new_variable(nonnegative=True) 1126 | sage: x = v["x"] 1127 | sage: P.set_min(x, 5) 1128 | sage: P.set_min(x, 0) 1129 | sage: P.get_min(x) 1130 | 0.0 1131 | """ 1132 | cdef double * lb 1133 | 1134 | if value is False: 1135 | lb = self.si.getColLower() 1136 | return lb[index] if lb[index] != - self.si.getInfinity() else None 1137 | else: 1138 | self.si.setColLower(index, value if value is not None else -self.si.getInfinity()) 1139 | 1140 | IF HAVE_SAGE_CPYTHON_STRING: 1141 | cpdef write_mps(self, filename, int modern): 1142 | r""" 1143 | Writes the problem to a .mps file 1144 | 1145 | INPUT: 1146 | 1147 | - ``filename`` (string) 1148 | 1149 | EXAMPLES:: 1150 | 1151 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 1152 | sage: import tempfile 1153 | sage: p = CoinBackend() 1154 | sage: p.add_variables(2) 1155 | 1 1156 | sage: p.add_linear_constraint([(0, 1), (1, 2)], None, 3) 1157 | sage: p.set_objective([2, 5]) 1158 | sage: with tempfile.TemporaryDirectory() as f: 1159 | ....: p.write_mps(os.path.join(f, "lp_problem.mps"), 0) 1160 | """ 1161 | 1162 | cdef char * mps = "mps" 1163 | filename = str_to_bytes(filename, FS_ENCODING, 'surrogateescape') 1164 | self.si.writeMps(filename, mps, -1 if self.is_maximization() else 1) 1165 | 1166 | cpdef write_lp(self, filename): 1167 | r""" 1168 | Writes the problem to a .lp file 1169 | 1170 | INPUT: 1171 | 1172 | - ``filename`` (string) 1173 | 1174 | EXAMPLES:: 1175 | 1176 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 1177 | sage: import tempfile 1178 | sage: p = CoinBackend() 1179 | sage: p.add_variables(2) 1180 | 1 1181 | sage: p.add_linear_constraint([(0, 1), (1, 2)], None, 3) 1182 | sage: p.set_objective([2, 5]) 1183 | sage: with tempfile.TemporaryDirectory() as f: 1184 | ....: p.write_lp(os.path.join(f, "lp_problem.lp")) 1185 | """ 1186 | 1187 | cdef char * lp = "lp" 1188 | filename = str_to_bytes(filename, FS_ENCODING, 'surrogateescape') 1189 | self.si.writeLp(filename, lp, 0.00001, 10, 5, -1 if self.is_maximization() else 1, 1) 1190 | ELSE: 1191 | cpdef write_mps(self, char *filename, int modern): 1192 | r""" 1193 | Writes the problem to a .mps file 1194 | 1195 | INPUT: 1196 | 1197 | - ``filename`` (string) 1198 | 1199 | EXAMPLES:: 1200 | 1201 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 1202 | sage: import tempfile 1203 | sage: p = CoinBackend() 1204 | sage: p.add_variables(2) 1205 | 1 1206 | sage: p.add_linear_constraint([(0, 1), (1, 2)], None, 3) 1207 | sage: p.set_objective([2, 5]) 1208 | sage: with tempfile.TemporaryDirectory() as f: 1209 | ....: p.write_mps(os.path.join(f, "lp_problem.mps"), 0) 1210 | """ 1211 | 1212 | cdef char * mps = "mps" 1213 | self.si.writeMps(filename, mps, -1 if self.is_maximization() else 1) 1214 | 1215 | cpdef write_lp(self, char *filename): 1216 | r""" 1217 | Writes the problem to a .lp file 1218 | 1219 | INPUT: 1220 | 1221 | - ``filename`` (string) 1222 | 1223 | EXAMPLES:: 1224 | 1225 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 1226 | sage: import tempfile 1227 | sage: p = CoinBackend() 1228 | sage: p.add_variables(2) 1229 | 1 1230 | sage: p.add_linear_constraint([(0, 1), (1, 2)], None, 3) 1231 | sage: p.set_objective([2, 5]) 1232 | sage: with tempfile.TemporaryDirectory() as f: 1233 | ....: p.write_lp(os.path.join(f, "lp_problem.lp")) 1234 | """ 1235 | 1236 | cdef char * lp = "lp" 1237 | self.si.writeLp(filename, lp, 0.00001, 10, 5, -1 if self.is_maximization() else 1, 1) 1238 | 1239 | IF HAVE_SAGE_CPYTHON_STRING: 1240 | cpdef problem_name(self, name=None): 1241 | r""" 1242 | Returns or defines the problem's name 1243 | 1244 | INPUT: 1245 | 1246 | - ``name`` (``str``) -- the problem's name. When set to 1247 | ``None`` (default), the method returns the problem's name. 1248 | 1249 | EXAMPLES:: 1250 | 1251 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 1252 | sage: p = CoinBackend() 1253 | sage: p.problem_name("There once was a french fry") 1254 | sage: print(p.problem_name()) 1255 | There once was a french fry 1256 | """ 1257 | if name is None: 1258 | if self.prob_name is not None: 1259 | return self.prob_name 1260 | else: 1261 | return "" 1262 | else: 1263 | self.prob_name = str(name) 1264 | ELSE: 1265 | cpdef problem_name(self, char * name = NULL): 1266 | r""" 1267 | Returns or defines the problem's name 1268 | 1269 | INPUT: 1270 | 1271 | - ``name`` (``char *``) -- the problem's name. When set to 1272 | ``NULL`` (default), the method returns the problem's name. 1273 | 1274 | """ 1275 | if name == NULL: 1276 | if self.prob_name is not None: 1277 | return self.prob_name 1278 | else: 1279 | return "" 1280 | else: 1281 | self.prob_name = str(name) 1282 | 1283 | cpdef row_name(self, int index): 1284 | r""" 1285 | Returns the ``index`` th row name 1286 | 1287 | INPUT: 1288 | 1289 | - ``index`` (integer) -- the row's id 1290 | 1291 | EXAMPLES:: 1292 | 1293 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 1294 | sage: p = CoinBackend() 1295 | sage: p.add_linear_constraints(1, 2, None, names=['Empty constraint 1']) 1296 | sage: print(p.row_name(0)) 1297 | Empty constraint 1 1298 | """ 1299 | if self.row_names is not None: 1300 | return self.row_names[index] 1301 | else: 1302 | return "" 1303 | 1304 | cpdef col_name(self, int index): 1305 | r""" 1306 | Returns the ``index`` th col name 1307 | 1308 | INPUT: 1309 | 1310 | - ``index`` (integer) -- the col's id 1311 | 1312 | EXAMPLES:: 1313 | 1314 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 1315 | sage: p = CoinBackend() 1316 | sage: p.add_variable(name='I am a variable') 1317 | 0 1318 | sage: print(p.col_name(0)) 1319 | I am a variable 1320 | """ 1321 | if self.col_names is not None: 1322 | return self.col_names[index] 1323 | else: 1324 | return "" 1325 | 1326 | cpdef __copy__(self): 1327 | """ 1328 | Returns a copy of self. 1329 | 1330 | EXAMPLES:: 1331 | 1332 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 1333 | sage: p = MixedIntegerLinearProgram(solver=CoinBackend) 1334 | sage: b = p.new_variable(nonnegative=True) 1335 | sage: p.add_constraint(b[1] + b[2] <= 6) 1336 | sage: p.set_objective(b[1] + b[2]) 1337 | sage: copy(p).solve() 1338 | 6.0 1339 | """ 1340 | # create new backend 1341 | cdef CoinBackend p = type(self)(maximization = (1 if self.is_maximization() else -1)) 1342 | 1343 | # replace solver with copy of self's solver 1344 | del p.si 1345 | p.si = self.si.clone(1) 1346 | p.row_names = copy(self.row_names) 1347 | p.col_names = copy(self.col_names) 1348 | p.obj_constant_term = self.obj_constant_term 1349 | # Maybe I should copy this, not sure -- seems complicated, though 1350 | p.prob_name = self.prob_name 1351 | 1352 | return p 1353 | 1354 | cpdef get_basis_status(self): 1355 | """ 1356 | Retrieve status information for column and row variables. 1357 | 1358 | This method returns status as integer codes: 1359 | 1360 | * 0: free 1361 | * 1: basic 1362 | * 2: nonbasic at upper bound 1363 | * 3: nonbasic at lower bound 1364 | 1365 | OUTPUT: 1366 | 1367 | - ``cstat`` -- The status of the column variables 1368 | 1369 | - ``rstat`` -- The status of the row variables 1370 | 1371 | .. NOTE:: 1372 | 1373 | Logical variables associated with rows are all assumed to have +1 1374 | coefficients, so for a <= constraint the logical will be at lower 1375 | bound if the constraint is tight. 1376 | 1377 | Behaviour is undefined unless ``solve`` or ``set_basis_status`` 1378 | has been called before. 1379 | 1380 | 1381 | EXAMPLES:: 1382 | 1383 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 1384 | sage: p = CoinBackend() 1385 | sage: p.add_variables(2) 1386 | 1 1387 | sage: p.add_linear_constraint([(0, 2), (1, 3)], None, 6) 1388 | sage: p.add_linear_constraint([(0, 3), (1, 2)], None, 6) 1389 | sage: p.set_objective([1, 1], 7) 1390 | sage: p.solve() 1391 | 0 1392 | sage: p.get_basis_status() 1393 | ([1, 1], [3, 3]) 1394 | 1395 | sage: p = CoinBackend() 1396 | sage: p.add_variables(2) 1397 | 1 1398 | sage: p.add_linear_constraint([(0, 2), (1, -3)], None, 6) 1399 | sage: p.add_linear_constraint([(0, 3), (1, 2)], None, 6) 1400 | sage: p.set_objective([1, 1]) 1401 | sage: p.solve() 1402 | 0 1403 | sage: p.get_basis_status() 1404 | ([3, 1], [1, 3]) 1405 | 1406 | sage: p = CoinBackend() 1407 | sage: p.add_variables(3) 1408 | 2 1409 | sage: p.add_linear_constraint(zip([0, 1, 2], [8, 6, 1]), None, 48) 1410 | sage: p.add_linear_constraint(zip([0, 1, 2], [4, 2, 1.5]), None, 20) 1411 | sage: p.add_linear_constraint(zip([0, 1, 2], [2, 1.5, 0.5]), None, 8) 1412 | sage: p.set_objective([60, 30, 20]) 1413 | sage: p.solve() 1414 | 0 1415 | sage: p.get_basis_status() 1416 | ([1, 3, 1], [1, 3, 3]) 1417 | 1418 | 1419 | sage: lp = MixedIntegerLinearProgram(solver=CoinBackend) 1420 | sage: v = lp.new_variable(nonnegative=True) 1421 | sage: x,y,z = v[0], v[1], v[2] 1422 | sage: lp.add_constraint(8*x + 6*y + z, max = 48) 1423 | sage: lp.add_constraint(4*x + 2*y + 1.5*z, max = 20) 1424 | sage: lp.add_constraint(2*x + 1.5*y + 0.5*z, max = 8) 1425 | sage: lp.set_objective(60*x + 30*y + 20*z) 1426 | sage: lp_coin = lp.get_backend() 1427 | sage: lp_coin.solve() 1428 | 0 1429 | sage: lp_coin.get_basis_status() 1430 | ([1, 3, 1], [1, 3, 3]) 1431 | 1432 | """ 1433 | cdef int n = self.model.solver().getNumCols() 1434 | cdef int m = self.model.solver().getNumRows() 1435 | cdef int * c_cstat = check_malloc(n * sizeof(int)) 1436 | cdef int * c_rstat = check_malloc(m * sizeof(int)) 1437 | cdef list cstat 1438 | cdef list rstat 1439 | # enableSimplexInterface must be set to use getBasisStatus(). 1440 | # See projects.coin-or.org/Osi/ticket/84 1441 | self.model.solver().enableSimplexInterface(True) 1442 | try: 1443 | sig_on() # To catch SIGABRT 1444 | self.model.solver().getBasisStatus(c_cstat, c_rstat) 1445 | sig_off() 1446 | except RuntimeError: # corresponds to SIGABRT 1447 | raise MIPSolverException('CBC : Signal sent, getBasisStatus() fails') 1448 | else: 1449 | cstat = [c_cstat[j] for j in range(n)] 1450 | rstat = [c_rstat[j] for j in range(m)] 1451 | return (cstat, rstat) 1452 | finally: 1453 | sig_free(c_cstat) 1454 | sig_free(c_rstat) 1455 | 1456 | cpdef int set_basis_status(self, list cstat, list rstat) except -1: 1457 | """ 1458 | Set the status of column and row variables 1459 | and update the basis factorization and solution. 1460 | 1461 | This method returns status as integer codes: 1462 | 1463 | INPUT: 1464 | 1465 | - ``cstat`` -- The status of the column variables 1466 | 1467 | - ``rstat`` -- The status of the row variables 1468 | 1469 | .. NOTE:: 1470 | 1471 | Status information should be coded as: 1472 | 1473 | * 0: free 1474 | * 1: basic 1475 | * 2: nonbasic at upper bound 1476 | * 3: nonbasic at lower bound 1477 | 1478 | Logical variables associated with rows are all assumed to have +1 1479 | coefficients, so for a <= constraint the logical will be at lower 1480 | bound if the constraint is tight. 1481 | 1482 | OUTPUT: 1483 | 1484 | Returns 0 if all goes well, 1 if something goes wrong. 1485 | 1486 | EXAMPLES:: 1487 | 1488 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 1489 | 1490 | sage: p = CoinBackend() 1491 | sage: p.add_variables(2) 1492 | 1 1493 | sage: p.add_linear_constraint([(0, 2), (1, -3)], None, 6) 1494 | sage: p.add_linear_constraint([(0, 3), (1, 2)], None, 6) 1495 | sage: p.set_objective([1, 1]) 1496 | 1497 | sage: p.set_basis_status([3, 3], [1, 1]) 1498 | 0 1499 | sage: p.get_objective_value() 1500 | 0.0 1501 | sage: p.set_basis_status([1, 3], [1, 3]) 1502 | 0 1503 | sage: p.get_objective_value() 1504 | 2.0 1505 | sage: p.set_basis_status([3, 1], [1, 3]) 1506 | 0 1507 | sage: p.get_objective_value() 1508 | 3.0 1509 | sage: p.get_basis_status() 1510 | ([3, 1], [1, 3]) 1511 | 1512 | sage: p = CoinBackend() 1513 | sage: p.add_variables(3) 1514 | 2 1515 | sage: p.add_linear_constraint(zip([0, 1, 2], [8, 6, 1]), None, 48) 1516 | sage: p.add_linear_constraint(zip([0, 1, 2], [4, 2, 1.5]), None, 20) 1517 | sage: p.add_linear_constraint(zip([0, 1, 2], [2, 1.5, 0.5]), None, 8) 1518 | sage: p.set_objective([60, 30, 20]) 1519 | sage: p.set_basis_status([3, 3, 3], [1, 1, 1]) 1520 | 0 1521 | sage: p.get_objective_value() 1522 | 0.0 1523 | sage: p.set_basis_status([1, 3, 3], [1, 1, 3]) 1524 | 0 1525 | sage: p.get_objective_value() 1526 | 240.0 1527 | sage: p.get_basis_status() 1528 | ([1, 3, 3], [1, 1, 3]) 1529 | sage: p.set_basis_status([1, 3, 1], [1, 3, 2]) 1530 | 0 1531 | sage: p.get_basis_status() 1532 | ([1, 3, 1], [1, 3, 3]) 1533 | sage: p.get_objective_value() 1534 | 280.0 1535 | """ 1536 | cdef int n = len(cstat) 1537 | cdef int m = len(rstat) 1538 | cdef int * c_cstat 1539 | cdef int * c_rstat 1540 | cdef int result 1541 | 1542 | # set up the model 1543 | cdef OsiSolverInterface * si = self.si 1544 | 1545 | cdef CbcModel * model 1546 | cdef int old_logLevel = self.model.logLevel() 1547 | 1548 | model = new CbcModel(si[0]) 1549 | del self.model 1550 | self.model = model 1551 | 1552 | #we immediately commit to the new model so that the user has access 1553 | #to it even when something goes wrong. 1554 | 1555 | model.setLogLevel(old_logLevel) 1556 | 1557 | # multithreading 1558 | import multiprocessing 1559 | model.setNumberThreads(ncpus()) 1560 | 1561 | if n != self.model.solver().getNumCols() or m != self.model.solver().getNumRows(): 1562 | raise ValueError("Must provide the status of every column and row variables") 1563 | c_cstat = check_malloc(n * sizeof(int)) 1564 | c_rstat = check_malloc(m * sizeof(int)) 1565 | for i in range(n): 1566 | c_cstat[i] = cstat[i] 1567 | for i in range(m): 1568 | c_rstat[i] = rstat[i] 1569 | # enableSimplexInterface must be set to use getBasisStatus(). 1570 | # See projects.coin-or.org/Osi/ticket/84 1571 | self.model.solver().enableSimplexInterface(True) 1572 | try: 1573 | sig_on() # To catch SIGABRT 1574 | result = self.model.solver().setBasisStatus(c_cstat, c_rstat) 1575 | self.model.solver().setIntParam(OsiMaxNumIteration, 0) 1576 | self.model.solver().resolve() 1577 | sig_off() 1578 | except RuntimeError: # corresponds to SIGABRT 1579 | raise MIPSolverException('CBC : Signal sent, setBasisStatus() fails') 1580 | else: 1581 | return result 1582 | finally: 1583 | sig_free(c_cstat) 1584 | sig_free(c_rstat) 1585 | 1586 | cpdef get_binva_row(self, int i): 1587 | """ 1588 | Return the i-th row of the tableau and the slacks. 1589 | 1590 | .. NOTE:: 1591 | 1592 | Has no meaning unless ``solve`` or ``set_basis_status`` 1593 | has been called before. 1594 | 1595 | EXAMPLES:: 1596 | 1597 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 1598 | 1599 | sage: p = CoinBackend() 1600 | sage: p.add_variables(2) 1601 | 1 1602 | sage: p.add_linear_constraint([(0, 2), (1, -3)], None, 6) 1603 | sage: p.add_linear_constraint([(0, 3), (1, 2)], None, 6) 1604 | sage: p.set_objective([1, 1]) 1605 | 1606 | sage: p.set_basis_status([3, 3], [1, 1]) 1607 | 0 1608 | sage: p.get_binva_row(0) 1609 | ([2.0, -3.0], [1.0, 0.0]) 1610 | sage: p.get_binva_row(1) 1611 | ([3.0, 2.0], [0.0, 1.0]) 1612 | 1613 | sage: p.set_basis_status([1, 3], [1, 3]) 1614 | 0 1615 | sage: p.get_binva_row(0) 1616 | ([0.0, -4.333333333333333], [1.0, -0.6666666666666666]) 1617 | sage: p.get_binva_row(1) 1618 | ([1.0, 0.6666666666666666], [0.0, 0.3333333333333333]) 1619 | 1620 | sage: p.set_basis_status([3, 1], [1, 3]) 1621 | 0 1622 | sage: p.get_binva_row(0) 1623 | ([6.5, 0.0], [1.0, 1.5]) 1624 | sage: p.get_binva_row(1) 1625 | ([1.5, 1.0], [0.0, 0.5]) 1626 | 1627 | """ 1628 | cdef int n = self.model.solver().getNumCols() 1629 | cdef int m = self.model.solver().getNumRows() 1630 | if i < 0 or i >= m: 1631 | raise ValueError("i = %s. The i-th row of the tableau doesn't exist" % i) 1632 | 1633 | cdef double * c_slack = check_malloc(m * sizeof(double)) 1634 | cdef double * c_z = check_malloc(n * sizeof(double)) 1635 | cdef list slack 1636 | cdef list ithrow 1637 | # enableSimplexInterface must be set to use getBasisStatus(). 1638 | # See projects.coin-or.org/Osi/ticket/84 1639 | self.model.solver().enableSimplexInterface(True) 1640 | try: 1641 | sig_on() # To catch SIGABRT 1642 | self.model.solver().getBInvARow(i, c_z, c_slack) 1643 | sig_off() 1644 | except RuntimeError: # corresponds to SIGABRT 1645 | raise MIPSolverException('CBC : Signal sent, getBinvARow() fails') 1646 | else: 1647 | slack = [c_slack[j] for j in range(m)] 1648 | ithrow = [c_z[j] for j in range(n)] 1649 | return (ithrow, slack) 1650 | finally: 1651 | sig_free(c_slack) 1652 | sig_free(c_z) 1653 | 1654 | cpdef get_binva_col(self, int j): 1655 | """ 1656 | Return the j-th column of the tableau. 1657 | 1658 | EXAMPLES:: 1659 | 1660 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 1661 | 1662 | sage: p = CoinBackend() 1663 | sage: p.add_variables(2) 1664 | 1 1665 | sage: p.add_linear_constraint([(0, 2), (1, -3)], None, 6) 1666 | sage: p.add_linear_constraint([(0, 3), (1, 2)], None, 6) 1667 | sage: p.set_objective([1, 1]) 1668 | 1669 | sage: p.set_basis_status([3, 3], [1, 1]) 1670 | 0 1671 | sage: p.get_binva_col(0) 1672 | [2.0, 3.0] 1673 | sage: p.get_binva_col(1) 1674 | [-3.0, 2.0] 1675 | 1676 | sage: p.set_basis_status([1, 3], [1, 3]) 1677 | 0 1678 | sage: p.get_binva_col(0) 1679 | [-0.0, 1.0] 1680 | sage: p.get_binva_col(1) 1681 | [-4.333333333333333, 0.6666666666666666] 1682 | 1683 | sage: p.set_basis_status([3, 1], [1, 3]) 1684 | 0 1685 | sage: p.get_binva_col(0) 1686 | [6.5, 1.5] 1687 | sage: p.get_binva_col(1) 1688 | [-0.0, 1.0] 1689 | """ 1690 | cdef int n = self.model.solver().getNumCols() 1691 | cdef int m = self.model.solver().getNumRows() 1692 | if j < 0 or j >= n + m: 1693 | # it seems that when n <= j < m+n, 1694 | # getBInvACol(j) is getBinvCol(j-n) 1695 | raise ValueError("j = %s. The j-th column of the tableau doesn't exist" % j) 1696 | 1697 | cdef double * c_vec = check_malloc(m * sizeof(double)) 1698 | cdef list jthcol 1699 | # enableSimplexInterface must be set to use getBasisStatus(). 1700 | # See projects.coin-or.org/Osi/ticket/84 1701 | self.model.solver().enableSimplexInterface(True) 1702 | try: 1703 | sig_on() # To catch SIGABRT 1704 | self.model.solver().getBInvACol(j, c_vec) 1705 | sig_off() 1706 | except RuntimeError: # corresponds to SIGABRT 1707 | raise MIPSolverException('CBC : Signal sent, getBinvACol() fails') 1708 | else: 1709 | jthcol = [c_vec[i] for i in range(m)] 1710 | return jthcol 1711 | finally: 1712 | sig_free(c_vec) 1713 | 1714 | cpdef get_basics(self): 1715 | r""" 1716 | Returns indices of basic variables. 1717 | 1718 | The order of indices match the order of elements in the vectors returned 1719 | by get_binva_col() and the order of rows in get_binva_row(). 1720 | 1721 | .. NOTE:: 1722 | 1723 | Has no meaning unless ``solve`` or ``set_basis_status`` 1724 | has been called before. 1725 | 1726 | EXAMPLES:: 1727 | 1728 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 1729 | sage: p = CoinBackend() 1730 | sage: p.add_variables(2) 1731 | 1 1732 | sage: p.add_linear_constraint([(0, 2), (1, -3)], None, 6) 1733 | sage: p.add_linear_constraint([(0, 3), (1, 2)], None, 6) 1734 | sage: p.set_objective([1, 1]) 1735 | sage: p.solve() 1736 | 0 1737 | sage: p.get_basics() 1738 | [2, 1] 1739 | """ 1740 | cdef int m = self.model.solver().getNumRows() 1741 | cdef int * c_indices = check_malloc(m * sizeof(int)) 1742 | cdef list indices 1743 | self.model.solver().enableSimplexInterface(True) 1744 | try: 1745 | sig_on() # To catch SIGABRT 1746 | self.model.solver().getBasics(c_indices) 1747 | sig_off() 1748 | except RuntimeError: # corresponds to SIGABRT 1749 | raise MIPSolverException('CBC : Signal sent, getBasics() fails') 1750 | else: 1751 | indices = [c_indices[j] for j in range(m)] 1752 | return indices 1753 | finally: 1754 | sig_free(c_indices) 1755 | 1756 | cpdef get_row_price(self): 1757 | r""" 1758 | Returns dual variable values. 1759 | 1760 | .. NOTE:: 1761 | 1762 | Has no meaning unless ``solve`` or ``set_basis_status`` 1763 | has been called before. 1764 | 1765 | EXAMPLES:: 1766 | 1767 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 1768 | sage: p = CoinBackend() 1769 | sage: p.add_variables(2) 1770 | 1 1771 | sage: p.add_linear_constraint([(0, 2), (1, -3)], None, 6) 1772 | sage: p.add_linear_constraint([(0, 3), (1, 2)], None, 6) 1773 | sage: p.set_objective([1, 1]) 1774 | sage: p.solve() 1775 | 0 1776 | sage: p.get_row_price() 1777 | [0.0, -0.5] 1778 | """ 1779 | cdef int m = self.model.solver().getNumRows() 1780 | cdef list price 1781 | cdef double * c_price 1782 | self.model.solver().enableSimplexInterface(True) 1783 | try: 1784 | sig_on() # To catch SIGABRT 1785 | c_price = self.model.solver().getRowPrice() 1786 | sig_off() 1787 | except RuntimeError: # corresponds to SIGABRT 1788 | raise MIPSolverException('CBC : Signal sent, getRowPrice() fails') 1789 | else: 1790 | price = [c_price[j] for j in range(m)] 1791 | return price 1792 | 1793 | cpdef get_reduced_cost(self): 1794 | r""" 1795 | Returns reduced costs. 1796 | 1797 | .. NOTE:: 1798 | 1799 | Has no meaning unless ``solve`` or ``set_basis_status`` 1800 | has been called before. 1801 | 1802 | EXAMPLES:: 1803 | 1804 | sage: from sage_numerical_backends_coin.coin_backend import CoinBackend 1805 | sage: p = CoinBackend() 1806 | sage: p.add_variables(2) 1807 | 1 1808 | sage: p.add_linear_constraint([(0, 2), (1, -3)], None, 6) 1809 | sage: p.add_linear_constraint([(0, 3), (1, 2)], None, 6) 1810 | sage: p.set_objective([1, 1]) 1811 | sage: p.solve() 1812 | 0 1813 | sage: p.get_reduced_cost() 1814 | [0.5, 0.0] 1815 | """ 1816 | cdef int n = self.model.solver().getNumCols() 1817 | cdef list cost 1818 | cdef double * c_cost 1819 | self.model.solver().enableSimplexInterface(True) 1820 | try: 1821 | sig_on() # To catch SIGABRT 1822 | c_cost = self.model.solver().getReducedCost() 1823 | sig_off() 1824 | except RuntimeError: # corresponds to SIGABRT 1825 | raise MIPSolverException('CBC : Signal sent, getReducedCost() fails') 1826 | else: 1827 | cost = [c_cost[i] for i in range(n)] 1828 | return cost 1829 | --------------------------------------------------------------------------------