Technical Documentation: google/or-tools
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (`README.md`)
OR-Tools - Google Optimization Tools
Google's software suite for combinatorial optimization.
Table of Contents
About OR-Tools
Google Optimization Tools (a.k.a., OR-Tools) is an open-source, fast and
portable software suite for solving combinatorial optimization problems.
The suite contains:
- Two constraint programming solver (CP* and CP-SAT);
- Two linear programming solvers (Glop and PDLP);
- Wrappers around commercial and other open source solvers, including mixed
integer solvers; - Bin packing and knapsack algorithms;
- Algorithms for the Traveling Salesman Problem and Vehicle Routing Problem;
- Graph algorithms (shortest paths, min cost flow, max flow, linear sum
assignment).
We wrote OR-Tools in C++, but provide wrappers in Python, C# and Java.
Codemap
This software suite is composed of the following components:
- Makefile Top-level for
GNU Make based build. - makefiles Subsidiary Make files, CI and build system documentation.
- CMakeLists.txt Top-level for
CMake based build. - cmake Subsidiary CMake files, CI and build system documentation.
- WORKSPACE Top-level for
Bazel based build. - bazel Subsidiary Bazel files, CI and build system documentation.
- ortools Root directory for source code.
- base Basic utilities.
- algorithms Basic algorithms.
- samples Carefully crafted samples.
- graph Graph algorithms.
- samples Carefully crafted samples.
- linear_solver Linear solver wrapper.
- samples Carefully crafted samples.
- glop Simplex-based linear programming solver.
- samples Carefully crafted samples.
- pdlp First-order linear programming solver.
- samples Carefully crafted samples.
- lp_data Data structures for linear models.
- constraint_solver Constraint and Routing
solver. - sat SAT solver.
- bop Boolean solver based on SAT.
- util Utilities needed by the constraint solver
- examples Root directory for all examples.
- tools Delivery Tools (e.g. Windows GNU binaries, scripts, release dockers)
Installation
This software suite has been tested under:
- Ubuntu 18.04 LTS and up (64-bit);
- Apple macOS Mojave with Xcode 9.x (64-bit);
- Microsoft Windows with Visual Studio 2022 (64-bit).
OR-Tools currently builds with a Makefile, but also provides Bazel and CMake
support.
For installation instructions (both source and binary), please visit
https://developers.google.com/optimization/introduction/installing.
Build from source using Make (legacy)
We provide a Make based build.Please check the
Make build instructions.
Build from source using CMake
We provide a CMake based build.Please check the
CMake build instructions.
Build from source using Bazel
We provide a Bazel based build.Please check the
Bazel build instructions.
Quick Start
The best way to learn how to use OR-Tools is to follow the tutorials in our
developer guide:
https://developers.google.com/optimization/introduction/get_started
If you want to learn from code examples, take a look at the examples in the
examples directory.
Documentation
The complete documentation for OR-Tools is available at:
https://developers.google.com/optimization/
Contributing
The CONTRIBUTING.md file contains instructions on how to
submit the Contributor License Agreement before sending any pull requests (PRs).
Of course, if you're new to the project, it's usually best to discuss any
proposals and reach consensus before sending your first PR.
License
The OR-Tools software suite is licensed under the terms of the Apache License 2.0.
See LICENSE for more information.
Overview (`bazel/README.md`)
OR-Tools Bazel Build Instructions
| OS | C++ |
|---|---|
| amd64 Linux | |
| arm64 MacOS | |
| amd64 MacOS | |
| amd64 Windows |
Dockers [Alpine, Archlinux, Centos, Debian, Fedora, OpenSuse, Ubuntu]:
Introduction
OR-Tools comes with a Bazel based build (WORKSPACE) that can be
used on a wide range of platforms. If you don't have Bazel installed already,
you can download it for free from https://bazel.build/.
warning: Currently OR-Tools Bazel doesn't support Python, Java nor .Net,
please use the Makefile or CMake based build instead.
Requirement
You'll need:
Bazel >= 5.4.0.- A compiler with C++17 support.
Solvers supported
Here the list of supported solvers:
- CP-SAT
- GLOP
- GLPK
- PDLP
- SCIP
Dependencies
OR-Tools depends on several mandatory libraries.
- Eigen
- Google Abseil-cpp,
- Google Protobuf,
- Google Gtest,
- Bliss,
- SCIP,
- GLPK (GNU Linear Programming Kit)
Compilation
You must compile OR-Tools using at least C++17 (C++20 on windows):
on UNIX:
shbazel build -c opt --cxxopt=-std=c++17 ...on Windows when using MSVC:
shbazel build -c opt --cxxopt="/std:c++20" ...
Testing
You may run tests using:
on UNIX:
shbazel test -c opt --cxxopt=-std=c++17 ...on Windows when using MSVC:
shbazel test -c opt --cxxopt="/std:c++20" ...
Integration
To integrate OR-Tools in your own Bazel project,
you can take a look at the template project:
or-tools/bazel_or-tools.
Overview (`cmake/README.md`)
OR-Tools CMake Build Instructions
| OS | C++ | Python | Java | .NET |
|---|---|---|---|---|
| amd64 Linux | ||||
| arm64 MacOS | ||||
| amd64 MacOS | ||||
| amd64 Windows |
Dockers:
[AlmaLinux, Alpine, Archlinux, Debian, Fedora, OpenSuse, RockyLinux, Ubuntu]x
[C++, Python, Java, .Net]:
Introduction
OR-Tools comes with a CMake-based build (CMakeLists.txt)
that can be used on a wide range of platforms (the "C" stands for
cross-platform). If you don't have CMake installed already, you can download it
for free from http://www.cmake.org/.
CMake works by generating native Makefiles or build projects that can be used in
the compiler environment of your choice.You can either build OR-Tools with
CMake as a standalone project or incorporate it into an existing CMake project.
Requirement
You'll need:
CMake >= 3.24.- A C++20 compiler (GCC 10 or above)
Solvers supported
Here the list of supported solvers:
- CBC
- CLP
- CP-SAT
- CPLEX*
- GLOP
- GLPK*
- HiGHS*
- PDLP
- SCIP
- XPRESS
*: these solvers are disabled by default.
Dependencies
OR-Tools depends on several mandatory librariess, either as infrastructure or as
optimization solvers. You can either compile them all at configure time using
the option -DBUILD_DEPS=ON (OFF by default) or compile some of them using
the options below (see CMake Options below).
- zlib (
BUILD_ZLIB), - bzip2 (
BUILD_BZip2), - Google Abseil-cpp (
BUILD_absl), - Google Protobuf (
BUILD_Protobuf), - COIN-OR solvers:
- COIN-OR CoinUtils (
BUILD_CoinUtils), - COIN-OR Osi (
BUILD_Osi), - COIN-OR Clp (
BUILD_Clp), - COIN-OR Cgl (
BUILD_Cgl), - COIN-OR Cbc (
BUILD_Cbc),
note: You can disable the support of COIN-OR solvers
(i.e. Cbc and Clp solver) by using-DUSE_COINOR=OFF(ONby default).
- COIN-OR CoinUtils (
- HIGHS (
BUILD_HIGHS),
note: You must enable the support of HiGHS solver by using-DUSE_HIGHS=ON
(OFFby default). - SCIP (
BUILD_SCIP),
note: You can disable the support of SCIP solver by using-DUSE_SCIP=OFF
(ONby default).
OR-Tools can also optionally (disabled by default i.e. OFF) be compiled with
support for the following third-party solvers:
- GLPK (
BUILD_GLPK),
note: You must enable the support of GLPK solver by using-DUSE_GLPK=ON
(OFFby default). - CPLEX (
USE_CPLEX),
warning: Since these solvers are either proprietary (and require a specific
license) or available under the GPL, we can't test them on public CI and their
support may be broken.
Enabling CPLEX Support
To enable CPLEX support, configure with -DUSE_CPLEX=ON and-DCPLEX_ROOT=/absolute/path/to/CPLEX/root/dir, replacing/absolute/path/to/CPLEX/root/dir with the path to your CPLEX installation.CPLEX_ROOT can also be defined as an environment variable rather than an
option at configure time.
For ease of migration from legacy make third_party builds, CMake will also
read the CPLEX installation path from the UNIX_CPLEX_DIR environment variable,
if defined.
CMake Options
There are several options that can be passed to CMake to modify how the code
is built.
To set these options and parameters, use -D=<value>.
All CMake options are passed at configure time, i.e., by runningcmake -S. -B<your_chosen_build_directory> -DOPTION_ONE=ON -DOPTION_TWO=OFF ...
before running cmake --build <your_chosen_build_directory>
For example, to generate build files including dependencies in a new
subdirectory called 'build', run:
cmake -S. -Bbuild -DBUILD_DEPS:BOOL=ONand then build with:
cmake --build buildFollowing is a list of available options, for the full list run:
cmake -S. -Bbuild -LH| CMake Option | Default Value | Note |
|---|---|---|
CMAKE_BUILD_TYPE |
Release | see CMake documentation here |
BUILD_CXX |
ON | Build C++ |
BUILD_DOTNET |
OFF | Build .Net wrapper and packages |
BUILD_JAVA |
OFF | Build Java wrapper and packages |
BUILD_PYTHON |
OFF | Build Python wrapper and package |
BUILD_FLATZINC |
ON* | Build the flatzinc libraryForced to OFF if BUILD_CXX=OFF |
BUILD_GLOP |
OFF* | Build the standalone Glop libraryForced to OFF if BUILD_CXX=ON, otherwise default to ON |
| Dependencies | ||
BUILD_DEPS |
OFF* | Default to ON if BUILD_JAVA=ON or BUILD_PYTHON=ON or BUILD_DOTNET=ON |
BUILD_ZLIB |
OFF* | Build the zlib dynamic libraryForced to ON if BUILD_DEPS=ON |
BUILD_BZip2 |
OFF* | Build the bzip2 dynamic libraryForced to ON if BUILD_DEPS=ON |
BUILD_absl |
OFF* | Build the abseil-cpp dynamic librariesForced to ON if BUILD_DEPS=ON |
BUILD_Protobuf |
OFF* | Build the protobuf dynamic librariesForced to ON if BUILD_DEPS=ON |
BUILD_re2 |
OFF* | Build the re2 dynamic librariesForced to ON if BUILD_DEPS=ON |
BUILD_Eigen3 |
OFF* | Build the Eigen3 librariesForced to ON if BUILD_DEPS=ON |
| Coin-OR | ||
USE_COINOR |
ON* | Enable Coin-OR supportForced to OFF if BUILD_CXX=OFF |
BUILD_CoinUtils |
OFF* | Build the CoinUtils dynamic libraryForced to ON if USE_COINOR=ON and BUILD_DEPS=ON |
BUILD_Osi |
OFF* | Build the Osi dynamic libraryForced to ON if USE_COINOR=ON and BUILD_DEPS=ON |
BUILD_Clp |
OFF* | Build the Clp dynamic libraryForced to ON if USE_COINOR=ON and BUILD_DEPS=ON |
BUILD_Cgl |
OFF* | Build the Cgl dynamic libraryForced to ON if USE_COINOR=ON and BUILD_DEPS=ON |
BUILD_Cbc |
OFF* | Build the Cbc dynamic libraryForced to ON if USE_COINOR=ON and BUILD_DEPS=ON |
| GLPK | ||
USE_GLPK |
OFF* | Enable GLPK supportForced to OFF if BUILD_CXX=OFF |
BUILD_GLPK |
OFF* | Build the GLPK dynamic librariesForced to ON if USE_GLPK=ON and BUILD_DEPS=ON |
| HiGHS | ||
USE_HIGHS |
ON* | Enable HIGHS supportForced to OFF if BUILD_CXX=OFF |
BUILD_HIGHS |
OFF* | Build the HiGHS dynamic librariesForced to ON if USE_HIGHS=ON and BUILD_DEPS=ON |
| SCIP | ||
USE_SCIP |
ON* | Enable SCIP supportForced to OFF if BUILD_CXX=OFF |
BUILD_SCIP |
OFF* | Build the SCIP dynamic librariesForced to ON if USE_SCIP=ON and BUILD_DEPS=ON |
| CPLEX | ||
USE_CPLEX |
OFF | Enable CPLEX support |
| Documentation | ||
BUILD_DOC |
OFF* | Build all documentations |
BUILD_CXX_DOC |
OFF* | Build C++ documentationForced to ON if BUILD_DOC=ON |
BUILD_DOTNET_DOC |
OFF* | Build .Net documentationForced to ON if BUILD_DOC=ON |
BUILD_JAVA_DOC |
OFF* | Build Java documentationForced to ON if BUILD_DOC=ON |
BUILD_PYTHON_DOC |
OFF* | Build Python documentationForced to ON if BUILD_DOC=ON |
INSTALL_DOC |
OFF* | Install all documentationsForced to OFF if BUILD_CXX=OFF or BUILD_DOC=OFF |
| Samples | ||
BUILD_SAMPLES |
ON* | Build all samplesDefault to ON if BUILD_DEPS=ON |
BUILD_CXX_SAMPLES |
ON* | Build all C++ samplesForced to OFF if BUILD_CXX=OFF or BUILD_SAMPLE=OFF |
BUILD_DOTNET_SAMPLES |
ON* | Build all .Net samplesForced to OFF if BUILD_DOTNET=OFF or BUILD_SAMPLE=OFF |
BUILD_JAVA_SAMPLES |
ON* | Build all Java samplesForced to OFF if BUILD_JAVA=OFF or BUILD_SAMPLE=OFF |
BUILD_PYTHON_SAMPLES |
ON* | Build all Python samplesForced to OFF if BUILD_PYTHON=OFF or BUILD_SAMPLE=OFF |
| Examples | ||
BUILD_EXAMPLES |
ON* | Build all examplesDefault to ON if BUILD_DEPS=ON |
BUILD_CXX_EXAMPLES |
ON* | Build all C++ examplesForced to OFF if BUILD_CXX=OFF or BUILD_SAMPLE=OFF |
BUILD_DOTNET_EXAMPLES |
ON* | Build all .Net examplesForced to OFF if BUILD_DOTNET=OFF or BUILD_SAMPLE=OFF |
BUILD_JAVA_EXAMPLES |
ON* | Build all Java examplesForced to OFF if BUILD_JAVA=OFF or BUILD_SAMPLE=OFF |
BUILD_PYTHON_EXAMPLES |
ON* | Build all Python examplesForced to OFF if BUILD_PYTHON=OFF or BUILD_SAMPLE=OFF |
| .Net | ||
USE_DOTNET_46 |
OFF | Enable .Net Framework 4.6 supportOnly available if BUILD_DOTNET=ON |
USE_DOTNET_461 |
OFF | Enable .Net Framework 4.6.1 supportOnly available if BUILD_DOTNET=ON |
USE_DOTNET_462 |
OFF | Enable .Net Framework 4.6.2 supportOnly available if BUILD_DOTNET=ON |
USE_DOTNET_48 |
OFF | Enable .Net Framework 4.8 supportOnly available if BUILD_DOTNET=ON |
USE_DOTNET_STD_21 |
OFF | Enable .Net Standard 2.1 supportOnly available if BUILD_DOTNET=ON and not targeting arm64 platform |
USE_DOTNET_CORE_31 |
OFF | Enable .Net Core 3.1 LTS supportOnly available if BUILD_DOTNET=ON and not targeting arm64 platform |
USE_DOTNET_6 |
OFF | Enable .Net 6 LTS supportOnly available if BUILD_DOTNET=ON |
USE_DOTNET_7 |
OFF | Enable .Net 7 supportOnly available if BUILD_DOTNET=ON |
USE_DOTNET_8 |
ON | Enable .Net 8 LTS supportOnly available if BUILD_DOTNET=ON |
USE_DOTNET_9 |
OFF | Enable .Net 9 supportOnly available if BUILD_DOTNET=ON |
UNIVERSAL_DOTNET_PACKAGE |
OFF | Build a multi platform package (i.e. Google.OrTools will depends on all runtime packages)Only available if BUILD_DOTNET=ON |
| Java | ||
SKIP_GPG |
ON | Disable GPG signOnly available if BUILD_JAVA=ON |
UNIVERSAL_JAVA_PACKAGE |
OFF | Build a multi platform package (i.e. ortools-java will depends on all native packages)Only available if BUILD_JAVA=ON |
BUILD_FAT_JAR |
OFF | Build a ortools-java .jar that includes all of its own Maven dependencies, including the native packageOnly available if BUILD_JAVA=ON |
| Python | ||
BUILD_pybind11 |
BUILD_DEPS |
Static build the pybind11 librariesForced to ON if BUILD_DEPS=ONOnly available if BUILD_PYTHON=ON |
BUILD_pybind11_abseil |
BUILD_DEPS |
Static build the pybind11_abseil librariesForced to ON if BUILD_DEPS=ONOnly available if BUILD_PYTHON=ON |
BUILD_pybind11_protobuf |
BUILD_DEPS |
Static build the pybind11_protobuf librariesForced to ON if BUILD_DEPS=ONOnly available if BUILD_PYTHON=ON |
GENERATE_PYTHON_STUB |
ON | Generate python stub filesOnly available if BUILD_PYTHON=ON |
BUILD_VENV |
BUILD_TESTING |
Create python venv in BINARY_DIR/python/venvForced to ON if BUILD_TESTING=ONOnly available if BUILD_PYTHON=ON |
VENV_USE_SYSTEM_SITE_PACKAGES |
OFF | Python venv can use system site package (e.g. py3-numpy on Alpine)Only available if BUILD_PYTHON=ON and BUILD_VENV=ON |
FETCH_PYTHON_DEPS |
BUILD_DEPS |
Fetch python modules needed to build ortools packageOnly available if BUILD_PYTHON=ON |
Integrating OR-Tools in your CMake Project
You should be able to integrate OR-Tools in your C++ CMake project following one
of these methods.
For API/ABI compatibility reasons, if you will be using OR-Tools inside a larger
C++ project, we recommend using CMake and incorporate OR-Tools as a CMake
subproject (i.e. using add_sudirectory() or FetchContent).
Consuming OR-Tools in a CMake Project
If you already have OR-Tools installed in your system, you can use the CMake
commandfind_package()
to include OR-Tools in your C++ CMake Project.
note: You may need to setCMAKE_PREFIX_PATH
in order for CMake to find your OR-Tools installation.
cmake_minimum_required(VERSION 3.14)
project(myproj VERSION 1.0)
find_package(ortools CONFIG REQUIRED)
add_executable(myapp main.cpp)
target_link_libraries(myapp ortools::ortools)Include directories, compile definitions and compile options will be added
automatically to your target as needed.
Incorporating OR-Tools into a CMake Super Project
Using add_subdirectory
The recommendations below are similar to those for using CMake
within the googletest framework
Thus, you can use the CMake commandadd_subdirectory()
to include OR-Tools directly from a subdirectory of your C++ CMake project.
Note: The ortools::ortools target is in this case an ALIAS library target
for the ortools library target.
cmake_minimum_required(VERSION 3.18)
project(myproj VERSION 1.0)
add_subdirectory(or-tools)
add_executable(myapp main.cpp)
target_link_libraries(myapp ortools::ortools)Again, include directories, compile definitions and compile options will be
added automatically to your target as needed.
Using FetchContent
If you have CMake >= 3.18 you can use the built-in moduleFetchContent
instead. Note: The ortools::ortools target is in this case an ALIAS library
target for the ortools library target.
cmake_minimum_required(VERSION 3.18)
project(myproj VERSION 1.0 LANGUAGES CXX)
include(FetchContent)
FetchContent_Declare(
or-tools
GIT_REPOSITORY https://github.com/google/or-tools.git
GIT_TAG main
)
# After the following call, the CMake targets defined by OR-Tools
# will be defined and available to the rest of the build
FetchContent_MakeAvailable(or-tools)
add_executable(myapp main.cpp)
target_link_libraries(myapp ortools::ortools)note: You may need to use the option -DBUILD_DEPS=ON to get all the OR-Tools
dependencies as well.
Overview (`makefiles/README.md`)
OR-Tools Makefile Build Instructions
Introduction
warning: Makefile build is deprecated, please use CMake or Bazel based build.
OR-Tools comes with a GNU Make based build (Makefile) that can be
used on a wide range of platforms.
Requirement
You'll need:
GNU Make >= 4.3.- A C++20 compiler (gcc 10 or above)
Dependencies
OR-Tools depends on several mandatory libraries, either as infrastructure or as
optimization solvers. You can either compile all of these dependencies using the
target third_party or compile some of them on your own and give their
installation directories to the others using the Make variables below.
- zlib (
UNIX_ZLIB_DIRorWINDOWS_ZLIB_DIR), - Google Abseil-cpp (
UNIX_ABSL_DIRorWINDOWS_ABSL_DIR), - Google Protobuf (
UNIX_PROTOBUF_DIRorWINDOWS_PROTOBUF_DIR), - SCIP (
UNIX_SCIP_DIRorWINDOWS_SCIP_DIR), - COIN-OR solvers:
- COIN-OR CoinUtils (
UNIX_COINUTILS_DIRorWINDOWS_COINUTILS_DIR), - COIN-OR Osi (
UNIX_OSI_DIRorWINDOWS_OSI_DIR), - COIN-OR Clp (
UNIX_CLP_DIRorWINDOWS_CLP_DIR), - COIN-OR Cgl (
UNIX_CGL_DIRorWINDOWS_CGL_DIR), - COIN-OR Cbc (
UNIX_CBC_DIRorWINDOWS_CBC_DIR),
- COIN-OR CoinUtils (
OR-Tools can also optionally (disabled by default) be compiled with support for
the following third-party solvers:
- CPLEX (
UNIX_CPLEX_DIRorWINDOWS_CPLEX_DIR), - GLPK (
UNIX_GLPK_DIRorWINDOWS_GLPK_DIR), - GUROBI (
UNIX_GUROBI_DIRorWINDOWS_GUROBI_DIR), - XPRESS (
UNIX_XPRESS_DIRorWINDOWS_XPRESS_DIR)
warning: Since these solvers are either proprietary (and require a specific
license) or available under the GPL, we can't test them on public CI and their
support may be broken.
Overview (`examples/cpp/README.md`)
C++ examples
The following examples showcase how to use the different Operations Research
libraries.
Examples list
Constraint Solver examples:
- cryptarithm.cc Demonstrates the use of basic modeling objects
(integer variables, arithmetic constraints and expressions,
simple search). - golomb.cc Demonstrates how to handle objective functions and collect
solutions found during the search. - magic_square.cc Shows how to use the automatic search to solve your
problem. - costas_array.cc Solves the problem of Costas Array (a constrained
assignment problem used for radars) with two version. On version is
a feasibility version with hard constraints, the other version is
an optimization version with soft constraints and violation costs. - jobshop.cc Demonstrates scheduling of jobs on different machines.
- nqueens.cc Solves the n-queen problem. It also demonstrates how to break
symmetries during search. - network_routing_sat.cc Solves a multicommodity mono-routing
problem with capacity constraints and a max usage cost structure. - sports_scheduling_sat.cc Finds a soccer championship schedule. Its uses an
original approach where all constraints attached to either one team,
or one week are regrouped into one global 'AllowedAssignment' constraints. - dobble_ls.cc Shows how to write Local Search operators and Local Search
filters in a context of an assignment/partitioning problem. It also
shows how to write a simple constraint.
- cryptarithm.cc Demonstrates the use of basic modeling objects
Routing examples:
- cvrptw.cc Capacitated Vehicle Routing Problem with Time Windows.
- pdptw.cc Pickup and Delivery Problem with Time Windows.
Graph examples:
- flow_api.cc Demonstrates how to use Min-Cost Flow and Max-Flow api.
- linear_assignment_api.cc Demonstrates how to use the Linear Sum
Assignment solver. - dimacs_assignment.cc Solves DIMACS challenge on assignment
problems.
Linear and integer programming examples:
- linear_programming.cc Demonstrates how to use the linear solver
wrapper API to solve Linear Programming problems. - integer_programming.cc Demonstrates how to use the linear solver
wrapper API to solve Integer Programming problems. - linear_solver_protocol_buffers.cc Demonstrates how protocol
buffers can be used as input and output to the linear solver wrapper. - strawberry_fields_with_column_generation.cc Complex example that
demonstrates how to use dynamic column generation to solve a 2D
covering problem.
- linear_programming.cc Demonstrates how to use the linear solver
Execution
Running the examples will involve building them, then running them.
You can run the following command from the top directory:
make build SOURCE=examples/cpp/<example>.cc
make run SOURCE=examples/cpp/<example>.ccOverview (`examples/dotnet/README.md`)
.NetCoreApp examples
The following examples showcase how to use OrTools.
The project solution has examples for C#.
We recommend that all projects you create target net8.0,
as this allows you to compile for various frameworks and
keep up-to-date with the latest frameworks.
Wherever you have or-tools installed, be sure to PackageReference the Google.OrTools
from the project file.
Execution
Running the examples will involve building them, then running them.
You can run the following command:
dotnet build <example>.csproj
dotnet run --no-build --project <example>.csprojOverview (`examples/flatzinc/README.md`)
The majority of these examples are here curtesy of Hakan Kjellerstrand.
See http://www.hakank.org/minizinc for details.
Overview (`examples/java/README.md`)
Java examples
The following examples showcase how to use the different Operations Research libraries.
Execution
Running the examples will involve building them, then running them.
You can run the following command from the top directory:
make build SOURCE=examples/java/<example>.java
make run SOURCE=examples/java/<example>.javaOverview (`examples/notebook/README.md`)
Introduction
First and foremost, the Jupyter Notebook is an interactive environment for writing and running code.
Prerequisite: Python3
These notebooks are associated with the IPython3 kernel, therefore runs Python3 code.
Thus, to run these notebooks you'll need Python3 and python ortool package.
Once you have python3 installed, ensure you have the latest pip:
pip3 install --user --upgrade pipnote: on Ubuntu you can also use: apt-get install python3-pip
Installing Jupyter
Install Jupyter Notebook using:
pip3 install --user jupyterThen install our notebooks dependencies:
pip3 install --user plotly numpy svgwriteRunning the Notebooks
To run the notebook, run the following command at the Terminal (Mac/Linux) or Command Prompt (Windows):
jupyter notebookYou should see the notebook dashboard open in your browser.
Then go to your or-tools source directory and try to open a notebook (located in
examples/notebook).
note: You can run a code cell using Shift-Enter or pressing the >| button in the toolbar.
Overview (`examples/python/README.md`)
Python examples
The following examples showcase how to use OrTools.
These examples should work for any Python3.7+.
We recommend that all projects you create target Python 3.9,
as this allows you to keep up-to-date with the latest Python frameworks.
Wherever you have ortools package installed, be sure to import it from your python file.
Execution
For running the examples you can use the following command:
python3 -m pip install --upgrade --user ortools
python3 <example>.pyOverview (`ortools/algorithms/README.md`)
Various algorithms
This directory contains data structures and algorithms for various problems.
It used to contain a solver for set covering, which has moved to the
set_cover folder.
Overview (`ortools/constraint_solver/README.md`)
Constraint Programming (CP) and Routing Solver
This directory contains a Constraint Programming (CP) solver and a Vehicle
Routing solver.
CP solver
Constraint Programming is
a technology issued from AI and used in operations research.
To begin, skim:
- constraint_solver.h:
Declaration of the core objects for the constraint solver. - constraint_solveri.h:
Collection of objects used to extend the Constraint Solver library.
Parameters
- solver_parameters.proto:
This file contains protocol buffers for all parameters of the CP solver. - search_limit.proto:
Holds parameters to limit the search space within the CP solver, which is
important for performance.
Solution
- assignment.proto:
Holds the solution of a CP problem. - demon_profiler.proto:
Holds the timeline and execution profile of constraints and demons
(daemons).
Routing solver
Vehicle Routing is a useful
extension that is implemented on top of the CP solver library.
To begin, skim:
- routing.h:
The vehicle routing library lets one model and solve generic vehicle routing
problems ranging from the Traveling Salesman Problem to more complex problems
such as the Capacitated Vehicle Routing Problem with Time Windows.
Parameters
- routing_parameters.proto:
The Vehicle Routing solver parameters. - routing_enums.proto:
Enums used to define routing parameters.
Solution
- assignment.proto:
Holds the solution of a Routing problem.
Recipes
You can find a set of code recipes in
the documentation directory.
Overview (`ortools/cpp/README.md`)
Introduction
This directory contains some CMake templates files use to build a project which
will depends on OR-Tools.
Overview (`ortools/dotnet/README.md`)
Introduction
This is the documentation page for the .NET wrapper of OR-Tools.
This project aim to explain how you build a .Net native (for win-x64, linux-x64
and osx-x64) nuget package using dotnet and few .csproj.
Table of Content
Requirement
The library is compiled against netcoreapp3.1 and net8.0, so you'll only
need:
- .Net Core 3.1 LTS SDK
- .Net 8.0 LTS SDK
note: We won't/can't rely on VS 2022 since we want a portable cross-platformdotnet/cli pipeline.
Project Layout
Google.OrTools.runtime.linux-x64: Contains the .Net Standard 2.1 native
project for the rid linux-x64.Google.OrTools.runtime.linux-arm64: Contains the .Net Standard 2.1 native
project for the rid linux-arm64.Google.OrTools.runtime.osx-x64: Contains the .Net Standard 2.1 native
project for the rid osx-x64.Google.OrTools.runtime.osx-arm64: Contains the .Net Standard 2.1 native
project for the rid osx-arm64.Google.OrTools.runtime.win-x64: Contains the .Net Standard 2.1 native
project for the rid win-x64.Google.OrToolsIs the .Net Standard 2.1 meta-package which should depends on
all previous available packages and contains the Reference Assembly.
note: While Microsoft use runtime-<rid>.Company.Project for native libraries
naming, it is very difficult to get ownership on it, so you should prefer to useCompany.Project.runtime-<rid> instead since you can have ownership onCompany.* prefix more easily.
Build
Either use the CMake base build or the Makefile based build. The workflow is
typically make dotnet which will build both C# and F# libraries package. The
output will be placed in /dotnet/packages folder. All tests will be
run based on this folder.
Build Process
To Create a native dependent package we will split it in two parts:
- A bunch of
Google.OrTools.runtime.{rid}.nupkgpackages for each
Runtime Identifier (RId)
targeted. - A generic package
Google.OrTools.nupkgdepending on each runtime packages
and containing the managed .Net code.
Actually, You don't need a specific variant of .Net Standard wrapper, simply
omit the library extension and .Net magic will pick the correct native library.
ref: https://www.mono-project.com/docs/advanced/pinvoke/#library-names
note: Microsoft.NetCore.App packages
follow this layout.
We have two use case scenario:
Locally, be able to build a Google.OrTools package which only target the
localOS Platform, i.e. building for only one
Runtime Identifier (RID).
note: This is useful since the C++ build is a complex process for Windows,
Linux and MacOS. i.e. We don't support cross-compilation for the native
library generation.Be able to create a complete cross-platform (ed. platform as multiple rid)
Google.OrTools package.
i.e. First you generate each native Nuget package
(Google.OrTools.runtime.{rid}.nupkg) on each native architecture, then
copy paste these artifacts on one native machine to generate the
meta-packageGoogle.OrTools.
Local Google.OrTools Package
Let's start with scenario 1: Create a Local Google.OrTools package targeting one
Runtime Identifier (RID).
We would like to build a Google.OrTools.nupkg package which only depends
on one Google.OrTools.runtime.{rid}.nupkg in order to work locally.
The pipeline for linux-x64 should be as follow:
note: The pipeline will be similar for osx-x64 and win-x64 architecture,
don't hesitate to look at the CI log.
Building local runtime Google.OrTools Package
disclaimer: We won't cover the C++ ortools library build. So first let's create
the local Google.OrTools.runtime.{rid}.nupkg nuget package.
Here some dev-note concerning this Google.OrTools.runtime.{rid}.csproj.
AssemblyNamemust beGoogle.OrTools.dlli.e. all {rid} projects must
generate an assembly with the same name (i.e. no {rid} in the name).
On the other hand package identifier will contain the {rid}...csproj<RuntimeIdentifier>{rid}</RuntimeIdentifier> <AssemblyName>Google.OrTools</AssemblyName> <PackageId>Google.OrTools.runtime.{rid}</PackageId>- Once you specify a
RuntimeIdentifierthendotnet buildordotnet build -r {rid}
will behave identically (save you from typing it).
note: not the case if you useRuntimeIdentifiers(notice the 's') - It is recommended
to add the tagnativeto the
nuget package tagscsproj<PackageTags>native</PackageTags> - Specify the output target folder for having the assembly output in
runtimes/{rid}/lib/netstandard2.0in the nupkgnote: Every files with an extension different fromcsproj<BuildOutputTargetFolder>runtimes/$(RuntimeIdentifier)/lib</BuildOutputTargetFolder>.dllwill be filter out
by nuget.
note: dotnet/cli automatically add the$(TargetFramework)(i.e.netstandard2.0) to the output path. - Add the native shared library to the nuget package in the repository
runtimes/{rib}/native. e.g. for linux-x64:csproj<Content Include="*.so"> <PackagePath>runtimes/linux-x64/native/%(Filename)%(Extension)</PackagePath> <Pack>true</Pack> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> - Generate the runtime package to a defined directory (i.e. so later in
Google.OrToolspackage we will be able to locate it)csproj<PackageOutputPath>{...}/packages</PackageOutputPath> - Generate the Reference Assembly (but don't include it to this runtime nupkg !,
see below for explanation) using:csproj<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
Then you can generate the package using:
dotnet pack Google.OrTools.runtime.{rid}note: this will automatically trigger the dotnet build.
If everything good the package (located where your PackageOutputPath was
defined) should have this layout:
{...}/packages/Google.OrTools.runtime.{rid}.nupkg:
\- Google.OrTools.runtime.{rid}.nuspec
\- runtimes
\- {rid}
\- native
\- *.so / *.dylib / *.dll
...note: {rid} could be linux-x64 and {framework} could be netstandard2.0
tips: since nuget package are zip archive you can use unzip -l .nupkg
to study their layout.
Building local Google.OrTools Package
So now, let's create the local Google.OrTools.nupkg nuget package which will
depend on our previous runtime package.
Here some dev-note concerning this Google.OrTools.csproj.
- Add the previous package directory:csproj
<RestoreSources>{...}/packages;$(RestoreSources)</RestoreSources> - Add dependency (i.e.
PackageReference) on each runtime package(s) available:Thanks to thecsproj<ItemGroup Condition="Exists('{...}/packages/Google.OrTools.runtime.linux-x64.1.0.0.nupkg')"> <PackageReference Include="Google.OrTools.runtime.linux-x64" Version="1.0.0" /> </ItemGroup>RestoreSourcewe can work locally with our just
builded package without the need to upload it on
nuget.org. - To expose the .Net Surface API the
Google.OrTools.csprojmust contains at
least one
Reference Assembly
of the previously rumtime package.xml <Content Include="../Google.OrTools.runtime.{rid}/bin/$(Configuration)/$(TargetFramework)/{rid}/ref/*.dll"> ref/$(TargetFramework)/%(Filename)%(Extension) true <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content>
Then you can generate the package using:
dotnet pack Google.OrToolsIf everything good the package (located where your PackageOutputPath was
defined) should have this layout:
{...}/packages/Google.OrTools.nupkg:
\- Google.OrTools.nuspec
\- lib
\- {framework}
\- Google.OrTools.dll
...note: {framework} could be netcoreapp3.1 or/and net8.0
Complete Google.OrTools Package
Let's start with scenario 2: Create a Complete Google.OrTools.nupkg package
targeting multiple
Runtime Identifier (RID).
We would like to build a Google.OrTools.nupkg package which depends on severalGoogle.OrTools.runtime.{rid}.nupkg.
The pipeline should be as follow:
note: This pipeline should be run on any architecture, provided you have
generated the three architecture dependent Google.OrTools.runtime.{rid}.nupkg
nuget packages.
Building All runtime Google.OrTools Package
Like in the previous scenario, on each targeted OS Platform you can build the
corresponding Google.OrTools.runtime.{rid}.nupkg package.
Simply run on each platform:
dotnet build <build_dir>/dotnet/Google.OrTools.runtime.{rid}
dotnet pack <build_dir>/dotnet/Google.OrTools.runtime.{rid}note: replace {rid} by the Runtime Identifier associated to the current OS
platform.
Then on one machine used, you copy all other packages in the {...}/packages so
when building Google.OrTools.csproj we can have access to all package...
Building Complete Google.OrTools Package
This is the same step than in the previous scenario, since we "see" all runtime
packages in {...}/packages, the project will depends on each of them.
Once copied all runtime package locally, simply run:
dotnet build <build_dir>/dotnet/Google.OrTools
dotnet pack <build_dir>/dotnet/Google.OrToolsExamples
The Test projects show examples of building applications with net8.0.
The F# example folder shows how to compile against the typical .NET Framework
installed on machine.
Appendices
Few links on the subject...
.Net runtime can deduce library extension so don’t use a platform-specific
library name in the DllImport statement.
Instead, just use the library name itself, without any prefixes or suffixes,
and rely on the runtime to find the appropriate library at runtime.
ref: Mono pinvoke#libraryname
Resources
Target Framework Moniker (TFM)
.Net:Runtime IDentifier (RID)
Reference on .csproj format
- Common MSBuild project properties
- MSBuild well-known item metadata
- Additions to the csproj format for .NET Core
Issues
dotnet seems to use a previous package version ?
You can clear your local cache using: dotnet nuget locals all --clear
Some issue related to this process
PackageReferenceonly supportTargetFrameworkcondition- Nuget needs to support dependencies specific to target runtime #1660
- Improve documentation on creating native packages #238
- Guide for packaging C# library using P/Invoke
Misc
Image has been generated using plantuml:
plantuml -Tpng docs/{file}.dotSo you can find the dot source files in docs.
Overview (`ortools/glop/README.md`)
GLOP
GLOP is Google's Linear Optimization Package. It is a full-featured,
production-ready implementation of the revised simplex method designed for
performance and numerical stability in solving linear programming problems.
It's written in C++ but can also be used from Java, Python, or C# via our
linear solver wrapper.
It contains in particular:
parameters.proto: Contains the definitions for all the
GLOP algorithm parameters and their default values.lp_solver.h: Contains the primary entry point classLPSolver.preprocessor.h: Contains the presolving code for a
Linear Program.revised_simplex.h: Contains the core implementation of
the revised simplex algorithm as described by G.B. Dantzig.status.h: Return type for various solver functions.
Overview (`ortools/graph/README.md`)
Graph and Network Flows
This directory contains data structures and algorithms for graph and network
flow problems.
It contains in particular:
- well-tuned algorithms (for example, shortest paths and
Hamiltonian paths). - hard-to-find algorithms (Hamiltonian paths, push-relabel flow algorithms).
- other, more common algorithms, that are useful to use with graphs from
util/graph.
Generic algorithms for shortest paths:
bounded_dijkstra.h: entry point for shortest paths.
This is the preferred implementation for most needs.bidirectional_dijkstra.h: for large graphs,
this implementation might be faster thanbounded_dijkstra.h.shortest_paths.h: shortest paths that are computed in
parallel (only if there are several sources). Includes
Dijkstra and
Bellman-Ford
algorithms. Although its name is very generic, only use this implementation
if parallelism makes sense.
Specific algorithms for paths:
dag_shortest_path.h: shortest paths on directed
acyclic graphs. If you have such a graph, this implementation is likely to be
the fastest. Unlike most implementations, these algorithms have two interfaces
: a "simple" one (list of edges and weights) and a standard one (taking as
input a graph data structure from//ortools/graph/graph.h).dag_constrained_shortest_path.: shortest
paths on directed acyclic graphs with resource constraints.hamiltonian_path.h: entry point for computing minimum
Hamiltonian paths and cycles
on directed graphs with costs on arcs, using a dynamic-programming algorithm.eulerian_path.h: entry point for computing minimum
Eulerian paths and cycles on
undirected graphs.
Graph decompositions:
connected_components.h: entry point for computing
connected components in an undirected graph. (It does not need an explicit
graph class.)strongly_connected_components.h: entry
point for computing the strongly connected components of a directed graph.cliques.h: entry point for computing maximum cliques and clique
covers in a directed graph, based on the Bron-Kerbosch algorithm.(It does not
need an explicit graph class.)
Flow algorithms:
linear_assignment.h: entry point for solving linear
sum assignment problems (classical assignment problems where the total cost is
the sum of the costs of each arc used) on directed graphs with arc costs,
based on the Goldberg-Kennedy push-relabel algorithm.max_flow.h: entry point for computing maximum flows on
directed graphs with arc capacities, based on the Goldberg-Tarjan push-relabel
algorithm.min_cost_flow.h: entry point for computing minimum-cost
flows on directed graphs with arc capacities, arc costs, and supplies/demands
at nodes, based on the Goldberg-Tarjan push-relabel algorithm.
Class design
BaseGraph is a generic graph interface on which most algorithms can be built
and provides efficient implementations with a fast construction time. Its design
is based on the experience acquired in various graph algorithm implementations.
The main ideas are:
Graph nodes and arcs are represented by integers.
Node or arc annotations (weight, cost, ...) are not part of the graph class,
they can be stored outside in one or more arrays and can be easily retrieved
using a node or arc as an index.
Terminology
An arc (or forward arc) of a graph is directed and going from a tail node
to a head node.
graph TD;
tail -->|arc| head;Node and arc indices are of type NodeIndexType and ArcIndexType
respectively. Those default to int32_t, which allows representing most
practical graphs, but applications that have smaller graphs can improve memory
usage and locality by parameterizing the graph type (e.g. BaseGraph allows representing graphs with up to 65k nodes and arcs).
A node or arc index is valid if it represents a node or arc of the graph. The
validity ranges are always [0, num_nodes()) for nodes and [0, num_arcs())
for forward arcs. For example, the following graph has 4 nodes numbered 0
through 3 and 5 forward arcs numbered 0 through 4:
graph TD;
0-->|0| 1;
2-->|1| 1;
0-->|2| 2;
3-->|3| 3;
1-->|4| 2;Some graph implementations also store reverse arcs and can be used for
undirected graphs or flow-like algorithms. Reverse arcs are elements of[-num_arcs(), 0) and are also considered valid by the implementations that
store them. In the following diagram, reverse arcs are represented with dashed
lines. A reverse arc is created for each forward arc (the forward and
reverse arc in this pair are called opposite of each other). This possibly
results in multiarcs:
graph TD;
0-->|0| 1;
1-.->|-1| 0;
2-->|1| 1;
1-.->|-2| 2;
0-->|2| 2;
2-.->|-3| 0;
3-->|3| 3;
3-.->|-4| 3;
1-->|4| 2;
2-.->|-5| 1;Graph traversal
- The outgoing arcs of a node
vare the forward arcs whose tail isv. - The incoming arcs of
vare the forward arcs whose head isv. - The opposite incoming arcs of
vare the opposite arcs of the incoming
arcs forv.
Forward graph classes allow iteration on outgoing arcs using accessorOutgoingArcs(). Graph classes with both forward and reverse arcs additionally
provide IncomingArcs and OppositeIncomingArcs(), that return iterators for
the two other categories. They also provide OutgoingOrOppositeIncomingArcs(),
which allows iterating both outgoing and opposite incoming arcs at the same
time, which corresponds to iterating forward and reverse arcs leaving the
node.
v |
OutgoingArcs(v) |
IncomingArcs(v) |
OppositeIncomingArcs(v) |
OutgoingOrOppositeIncoming(v) |
|---|---|---|---|---|
| 0 | {0,2} | {} | {} | {0,2} |
| 1 | {4} | {0,1} | {-2,-1} | {-2,-1,4} |
| 2 | {1} | {2,4} | {-5,-3} | {-5,-3,1} |
| 3 | {3} | {3} | {-4} | {-4,3} |
Graph implementations
ListGraph<>: Simplest API. Iterating outgoing arcs requires2 + degree
memory loads.StaticGraph<>: More memory and speed efficient thanListGraph<>, but
requires callingBuild()once after adding all nodes and arcs. Iterating
outgoing arcs requires only2memory loads.ReverseArcListGraph<>adds reverse arcs toListGraph<>. Iterating
neighboring arcs for a node requiresO(degree)memory loads.ReverseArcStaticGraph<>adds reverse arcs toStaticGraph<>. Iterating
neighboring arcs for a node requiresO(1)memory loads.CompleteGraph<>: If you need a fully connected graph. O(1) memory usage
and much faster than explicitly storing nodes and arcs usingStaticGraph<>.CompleteBipartiteGraph<>: If you need a fully connected bipartite graph.
O(1) memory usage and much faster than explicitly storing nodes and arcs
usingStaticGraph<>.FlowGraph<>: A graph specialized for max-flow/min-cost-flow algorithms.
For max-flow or min-cost-flow we need a directed graph where each arc from
tail to head has a reverse arc from head to tail. In practice many input
graphs already have such reverse arc and it can make a big difference just
to reuse them. This is similar toReverseArcStaticGraphbut handles
reverse arcs in a different way. Instead of always creating a new reverse
arc for each arc of the input graph, this detects if a reverse arc is
already present in the input, and does not create a new one when this is the
case. In the best case, this can gain a factor 2 in the final graph size,
note however that the graph construction is slightly slower because of this
detection.
Wrappers
python: This directory contains thepybind11bindings to make
these C++ libraries available in Python.java: the SWIG code that makes the wrapper available in Java and its
unit tests.csharp: the SWIG code that makes the wrapper available in C# and
its unit tests.
Samples
You can find some canonical examples in samples.
Overview (`ortools/java/README.md`)
Introduction
This is the documentation page for the Java wrapper of OR-Tools.
This project aim to explain how you build a Java 1.8 native (for win32-x86-64,
linux-x86-64 and darwin-x86-64) maven multiple package using mvn
and few POM.xml.
Table of Content
Requirement
You'll need a "Java SDK >= 1.8" and "Maven >= 3.6".
Directory Layout
- pom-native.xml.in POM template to build the native
project. - com/google/ortools/Loader.java Helper to
unpack and load the correct native libraries. - pom-local.xml.in POM template to build the "pure" Java
project. - pom-sample.xml.in POM template used to build samples
and examples. - pom-test.xml.in POM template used to build tests.
Build Process
To Create a native dependent package we will split it in two parts:
- A bunch of
com.google.ortools:ortools-{platform}maven packages for each
supported platform targeted and containing the native libraries. - A java maven package
com.google.ortools:ortools-javadepending on the native
package and containing the Java code.
platform names
come from the JNA project (Java Native Access) which will be use to find at
runtime on which platform the code is currently running.
Local Package
The pipeline for linux-x86-64 should be as follow:
note: The pipeline will be similar for darwin-x86-64 and win32-x86-64
architecture, don't hesitate to look at the CI log!
Building local native Package
Thus we have the C++ shared library libortools.so and the SWIG generated JNI
wrappers libjniortools.so.
So first let's create the local com.google.ortools:ortools-{platform}.jar
maven package.
Here some dev-note concerning this POM.xml.
- This package is a native package only containing native libraries.
Then you can generate the package and install it locally using:
mvn package
mvn installnote: this will automatically trigger the mvn compile phase.
If everything good the package (located in/java/ortools-/target/) should have this layout:
{...}/target/ortools-<platform>-1.0.jar:
\- <platform>
\-libortools.so.8.0
\-libjniortools.so
...note: `` could be linux-x86-64, darwin-x86-64 or win32-x86-64.
tips: since maven package are just zip archive you can use unzip -l .jar
to study their layout.
Building local Package
Standard Maven Package
So now, let's create the local com.google.ortools:ortools-java.jar maven
package which will depend on our previous native package.
Here some dev-note concerning this POM.xml.
- Add runtime dependency on each native package(s) available:xml
<dependency> <groupId>com.google.ortools</groupId> <artifactId>ortools-linux-x86-64</artifactId> <version>[8.0,)</version> <type>jar</type> <scope>runtime</scope> </dependency>- Add dependency to jna so we can find at runtime the current ``:
xml<dependency> <groupId>net.java.dev.jna</groupId> <artifactId>jna-platform</artifactId> <version>5.14.0</version> </dependency>
Then you can generate the package using:
mvn package
mvn installIf Maven executes these commands successfully, the package (located in/temp_java/ortools-java/target/) should have this layout:
{...}/target/ortools-java-{build version}.jar:
\- com/
\- google/
\- ortools/
\- Loader$PathConsumer.class
\- Loader$1.class
\- Loader.class
\- constraintsolver/
\- RoutingModel.class
\- RoutingIndexManager.class
\- ...
\- ...
...Dependency-inclusive Maven Package (fat .jar)
We can also create a Maven package that includes all of its own dependencies,
also known as a 'fat .jar'. This is useful for situations in which it is more
convenient, or even necessary, to use a single .jar as a dependency for a Java
OR-Tools project.
One example is when OR-Tools is compiled with third-party solver support (such
as CPLEX or XPress), and one needs to build and execute in an environment that
does not have access to one's local Maven repository (for example, a
remotely-built Docker container).
Building a fat .jar with all dependencies included (including the native
package) allows one to have a single dependency, namelycom.google.ortools:ortools-java.jar, which can be marked as<scope>provided</scope> in one's project pom.xml:
<dependency>
<groupId>com.google.ortools</groupId>
<artifactId>ortools-java</artifactId>
<version>{insert build version here}</version>
<scope>provided</scope>
</dependency>One would then make this fat .jar available in the execution environment's Java
classpath, for example:
java -cp "/path/to/dependency.jar" -jar "/path/to/your/executable/.jar"There are several ways to make the fat .jar dependency available to your Java
program - please consult JDK documentation (this links to
OpenJDK 15 documentation), as well as documentation for any packaging and/or
execution frameworks/tools you may be using.
To package a fat .jar with Maven, run
mvn package -Dfatjar=trueTo then (optionally) install it to your local Maven repository, run
mvn installNote that this will replace any previously downloaded or built 'non-fat-.jar'
ortools-java packages in your local Maven repository
If Maven executes these commands successfully, the package (located in/temp_java/ortools-java/target/) should have this layout:
{...}/target/ortools-java-{build version}.jar:
\- com/
\- google/
\- ortools/
\- Loader$PathConsumer.class
\- Loader$1.class
\- Loader.class
\- constraintsolver/
\- RoutingModel.class
\- RoutingIndexManager.class
\- ...
\- ...
...Testing local Package
We can test everything is working by using any sample project.
First you can build it using:
cmake --build buildnote: ortools-java which is locally installed in the local maven cache
(~/.m2/repository/com/google/ortools/ortools-java/...).
Then you can run it using:
cmake --build build --target testor manually using:
cd <builddir>/java/component/example
mvn compile
mvn exec:javaAppendices
Few links on the subject...
Resources
- POM.xml reference
- Maven Central POM requirement
- Maven Javadoc Plugin
- Maven Source Plugin
- Maven GPG Plugin
- Maven Assembly Plugin
- Java Native Access Project
Misc
Image has been generated using plantuml:
plantuml -Tsvg docs/{file}.dotSo you can find the dot source files in docs.
Overview (`ortools/linear_solver/README.md`)
Linear Programming (LP) and Integer Programming (IP) Solver
Linear optimization problems are common throughout Google, and the Operations
Research team has a few ways to help with them.
These models have the form:
$$\begin{array}{lll}
(P) & \max & cx\
& s.t. & L\leq Ax\leq U\
& & l\leq x\leq u\
& &x_i\in\mathbb{Z}\quad\forall i\in I
\end{array}$$
Where $$A\in\mathbb{R}^{m\times n}$$, $$l,u,c\in\mathbb{R}^n$$,
$$L,U\in\mathbb{R}^m$$, $$n,m\in\mathbb{N}^+$$ and $$I\subseteq{1,\ldots,n}$$.
This module provides:
- The
MPModelRequestProto API (inlinear_solver.proto) for modeling an
optimization problem. - The function
SolveMPModel()(insolve_mp_model.h) for solving an
optimization problem with a solver (Glop, Bop, Sat, SCIP, Gurobi etc.) ModelBuilder(inmodel_builder.h) and similar classes in Python and Java
to help construct anMPModelRequest(e.g. to provide linear expressions).MPSolverwhich is no longer in development.MPSolveris largely
interoperable with theMPModelRequestAPI, although the features supported
are not identical.
To begin, skim
linear_solver.proto:
Specifically, look atMPModelProto. This gives a succinct description of
what problems can be solved.solve_mp_model.h:
This file contains the key functions to run various solvers.
Available solvers
Each *_interface.cc file corresponds to one of the solver accessible
through the wrapper.
Google's BOP (boolean) solver: bop_interface.cc
Google's GLOP (lp) solver: glop_interface.cc
Gurobi (MIP) solver: gurobi_interface.cc
Google's PLDP solver: pdlp_interface.cc
SCIP (MIP) solver: scip_interface.cc
Google's CP-SAT solver: sat_interface.cc
Coin-OR Cbc (MIP) solver: cbc_interface.cc
Coin-OR Clp (LP) solver: clp_interface.cc
CPLEX (MIP) solver: cplex_interface.cc
GLPK (MIP) solver: glpk_interface.cc
Xpress (MIP) solver: xpress_interface.cc
Wrappers
python: the SWIG code that makes the wrapper available in Python,
and its unit tests.java: the SWIG code that makes the wrapper available in Java,
and its unit tests.csharp: the SWIG code that makes the wrapper available in C#,
and its unit tests.
Samples
You can find some canonical examples in samples
Overview (`ortools/lp_data/README.md`)
LP Data
This directory contains a rich collection of C++ libraries for handling Linear
Programming (LP) data structures.
It provides core components for representing, manipulating, and solving linear
programs, with a focus on efficient handling of sparse data and various utility
functions for pre-processing and analysis.
Core Data Structures
This set of libraries provides the fundamental building blocks for representing
and working with linear programming problems.
lp_types.h: Defines common types and constants used throughout
the linear programming solver.lp_data.h: Provides the mainLinearProgramclass for storing
the complete data of a linear program, including the objective function,
constraint matrix, and variable bounds.lp_utils.h: Contains basic utility functions for operations on
fractional numbers and row/column vectors.
Sparse Data Representation
Given that large-scale linear programs are often sparse, this directory offers a
suite of libraries for efficient sparse data handling.
sparse.h: Implements data structures for sparse matrices, based
on well-established references in the field of direct methods for sparse
matrices.sparse_vector.h: Provides classes to represent sparse
vectors efficiently.sparse_column.h&sparse_row.h:
Specializations of sparse vectors for column-oriented and row-oriented matrix
storage schemes.scattered_vector.h: Implements vectors that offer a
sparse interface to what is internally a dense storage, which can be useful
for certain computations.
LP Solvers and Utilities
A collection of tools for preprocessing, analyzing, and manipulating linear
programs.
matrix_scaler.h: Provides theSparseMatrixScalerclass,
which scales aSparseMatrixto improve numerical stability during the
solving process.lp_decomposer.h: Implements a tool to decompose a largeLinearPrograminto several smaller, independent subproblems by identifying
disconnected components in the constraint matrix.permutation.h: Contains utilities for handling row and
column permutations on LP data structures.
Parsers and I/O Utilities
This group of libraries handles reading and writing LP data in various formats.
lp_parser.h: A simple parser for creating a linear program
from a string representation.mps_reader.h: A reader for the industry-standard MPS file
format for mathematical programming problems.sol_reader.h: A reader for .sol files, which are used to
specify solution values for a given model.proto_utils.h: Provides utilities to convertLinearProgramobjects to and from the MPModelProto protobuf format.lp_print_utils.h: Contains utilities to display linear
expressions in a human-readable way, including rational approximations.
Overview (`ortools/math_opt/README.md`)
math_opt
The code in this directory provides a generic way of accessing mathematical
optimization solvers (sometimes called mathematical programming solvers), such
as GLOP, CP-SAT, SCIP and Gurobi. In particular, a single API is provided to
make these solvers largely interoperable.
New code should prefer MathOpt to MPSolver, as defined in
linear_solver.h
when possible.
MathOpt has client libraries in C++, Python, and Java that most users should use
to build and solve their models. A proto API is also provided, but this is not
recommended for most users.
See
parameters.proto
for the list of supported solvers.
Overview (`ortools/packing/README.md`)
Packing
This folder contains utilities and solvers for several packing problems:
knapsack and bin packing, including generalizations. The solvers take as input a
problem description in ProtoBuf.
Knapsack
A typical knapsack problem considers a single container (the knapsack) and many
items. The container has a maximum capacity, items have both a weight and a
value. The goal is to find the subset of items that respects the capacity
constraint and has the maximum value. There can be only one container. More
generally, the knapsack problem models resource allocation.
More information on Wikipedia.
Multi-dimensional knapsack solver:
Bin packing
In an instance of the bin-packing problem, the goal is to use the minimum amount
of bins to fit a series of items of varying size. Vector bin packing (VBP)
considers several dimensions for item size and bin capacity.
More information on
Wikipedia.
Vector bin packing:
- Problem description:
vector_bin_packing.proto - Solver:
arc_flow_solver.h - VBP parser:
vector_bin_packing_parser.h
2D bin packing:
- Problem description:
multiple_dimensions_bin_packing.proto
Overview (`ortools/pdlp/README.md`)
Primal-Dual Hybrid Gradient Solver (PDLP)
This directory contains PDLP, a library for solving linear programming (LP) and
quadratic programming (QP) problems using first-order methods.
The implementation is based on the Primal-Dual Hybrid Gradient (PDHG) algorithm,
which is preprocessed with scaling and optional presolving to improve
performance and numerical stability. See also
Mathematical background for PDLP.
Core C++ libraries:
primal_dual_hybrid_gradient.h: The main
entry pointPrimalDualHybridGradient()for the solver, which takes aQuadraticProgramand solver parameters.quadratic_program.h: Defines theQuadraticProgram
struct to represent the optimization problem, including objective vectors,
constraint matrices, and bounds.quadratic_program_io.h: Provides utilities to read
quadratic programs from various file formats, including MPS and MPModelProto.sharded_quadratic_program.handsharder.h: These provide the infrastructure for sharding
problem data and performing parallel computations.scheduler.h: A thread scheduling interface that supports
multiple backends (e.g. Eigen's thread pools).iteration_stats.handtermination.h: Contain logic for computing convergence and
infeasibility statistics and checking termination criteria.
Configuration and Logging
solvers.proto: Defines thePrimalDualHybridGradientParams
message for configuring the solver, including termination criteria,
algorithmic choices like restart strategies, and linesearch rules.solve_log.proto: Defines messages for logging the
solver's progress and final result, such asIterationStatsandSolveLog.
Wrappers and Samples
python/: Contains thepybind11wrapper to expose the C++ library
to Python, along with its build definitions and tests.samples/: This directory provides example usage of the library.
Overview (`ortools/python/README.md`)
Introduction
This is the documentation page for the Python 3.9+ wrapper of OR-Tools.
This project aim to explain how you build a Python native wheel package usingsetup.py.
Table of Content
Requirement
You'll need "Python >= 3.9" and few python modules ("wheel" and "absl-py").
Directory Layout
- setup.py.in
Setup.pytemplate to build the python native
project.
Build Process
To Create a native dependent package which will contains two parts:
- A bunch of native libraries for the supported platform targeted.
- The Python code depending on it.
note: Since Pypi.org support multiple packages, we will
simply upload one package per supported platform.
Local Package
The pipeline for linux-x86-64 should be as follow:
note: The pipeline will be similar for other architectures, don't hesitate to
look at the CI log!
Building local native Package
Thus we have the C++ shared library libortools.so and the SWIG generated
Python wrappers e.g. pywrapsat.py in the same package.
Here some dev-note concerning this setup.py.
- This package is a native package containing native libraries.
Then you can generate the package and install it locally using:
python3 setup.py bdist_wheel
python3 -m pip install --user --find-links=dist ortoolsIf everything good the package (located in /python/dist) should have
this layout:
{...}/dist/ortools-X.Y.9999-cp3Z-cp3Z-<platform>.whl:
\- ortools
\- __init__.py
\- .libs
\- libortools.so
\- constraint_solver
\- __init__.py
\- pywrapcp.py
\- _pywrapcp.so
\- ...
\- __init__.py
\- pywrap....py
\- _pywrap....so
...note: `` could be manylinux2014_x86_64, macosx_10_9_x86_64 or win-amd64.
tips: since wheel package are just zip archive you can use unzip -l .whl
to study their layout.
Appendices
Few links on the subject...
Resources
- Packaging Python Project
- PEP 513 -- A Platform Tag for Portable Linux Built Distributions
- PEP 571 -- The manylinux2010 Platform Tag
- PEP 600 Future 'manylinux' Platform Tags
Misc
Image has been generated using plantuml:
plantuml -Tsvg docs/{file}.dotSo you can find the dot source files in docs.
Overview (`ortools/routing/README.md`)
Routing
Vehicle Routing is a useful
extension that is implemented on top of the CP solver library.
Routing solver
To begin, skim:
- ../constraint_solver/routing.h: The
vehicle routing library lets one model and solve generic vehicle routing
problems ranging from the Traveling Salesman Problem to more complex
problems such as the Capacitated Vehicle Routing Problem with Time Windows.
Parameters
- ../constraint_solver/routing_parameters.proto:
The Vehicle Routing solver parameters. - ../constraint_solver/routing_enums.proto:
Enums used to define routing parameters.
Solution
- ../constraint_solver/assignment.proto:
Holds the solution of a Routing problem (as a special case of a CS problem).
Parsers
Utilities for file formats are in theparsers subfolder.
Overview (`ortools/sat/README.md`)
CP-SAT
This directory contains a next-gen Constraint Programming (CP) solver with
clause learning. It is built on top of an efficient SAT/max-SAT solver whose
code is also in this directory.
The technology started in 2009. A complete presentation is available
here.
CP-SAT was described during the CPAIOR 2020 masterclass. The recording is
available here.
To begin, skim
cp_model.proto to
understand how optimization problems can be modeled using the solver. You can
then solve a model with the functions in
cp_model_solver.h.
Parameters
- sat_parameters.proto:
The SAT solver parameters. Also contains max-SAT and CP parameters.
Model
The optimization model description and related utilities:
- cp_model.proto:
Proto describing a general Constraint Programming model. - cp_model_utils.h:
Utilities to manipulate and create a cp_model.proto. - cp_model_checker.h:
Tools to validate cp_model.proto and test a solution feasibility. - cp_model_solver.h:
The solver API. - cp_model_presolve.h:
Presolve a cp_model.proto by applying simple rules to simplify a subsequent
solve.
SAT solver
Standalone SAT solver and related files. Note that this is more than a basic
SAT solver as it already includes non-clause constraints. However, these do not
work on the general integer problems that the CP solver handles.
Pure SAT solver:
- sat_base.h: SAT
core classes. - clause.h: SAT clause
propagator with the two-watcher mechanism. Also contains propagators for
binary clauses. - sat_solver.h:
The SatSolver code. - simplification.h:
SAT postsolver and presolver. - symmetry.h:
Dynamic symmetry breaking constraint in SAT. Not used by default.
Extension:
- pb_constraint.h:
Implementation of a Pseudo-Boolean constraint propagator for SAT.
Pseudo-Boolean constraints are simply another name used in the SAT community
for linear constraints on Booleans. - no_cycle.h:
Implementation of a no-cycle constraint on a graph whose arc presences are
controlled by Boolean. This is a SAT propagator, not used in CP. - encoding.h: Basic
algorithm to encode integer variables into a binary representation. This is
not used by the CP solver, just by the max-SAT core based algorithm in
optimization.h.
Input/output:
- drat_writer.h:
Write UNSAT proof in the DRAT format. This allows to check the correctness
of an UNSAT proof with the third party program DRAT-trim. - opb_reader.h:
Parser for the .opb format for Pseudo-Boolean optimization problems. - sat_cnf_reader.h:
Parser for the classic SAT .cnf format. Also parses max-SAT files. - boolean_problem.proto:
Deprecated by cp_model.proto.
CP solver
CP solver built on top of the SAT solver:
- integer.h: The
entry point, which defines the core of the solver.
Basic constraints:
- all_different.h:
Propagation algorithms for the AllDifferent constraint. - integer_expr.h:
Propagation algorithms for integer expression (linear, max, min, div, mod,
...). - table.h: Propagation
algorithms for the table and automaton constraints. - precedences.h:
Propagation algorithms for integer inequalities (integer difference logic
theory). - cp_constraints.h:
Propagation algorithms for other classic CP constraints (XOR, circuit,
non-overlapping rectangles, ...) - linear_programming_constraint.h:
Constraint that solves an LP relaxation of a set of constraints and uses the
dual-ray to explain an eventual infeasibility. Also implements reduced-cost
fixing. - flow_costs.h:
Deprecated. Network flow constraint. We use the generic
linear_programming_constraint instead.
Scheduling constraints:
- intervals.h:
Definition and utility for manipulating "interval" variables (a.k.a. task or
activities). This is the basic CP variable type used in scheduling problems. - disjunctive.h:
Propagation algorithms for the disjunctive scheduling constraint. - cumulative.h,
timetable.h,
timetable_edgefinding.h:
Propagation algorithms for the cumulative scheduling constraint. - cumulative_energy.h:
Propagation algorithms for a more general cumulative constraint.
Packing constraints
- diffn.h: 2D
no_overlap constraints (aka non overlapping rectangles). Note these use 2
intervals, one for each dimension to represent a rectangle. - diffn_util.h:
rectangle utilities.
Linear relaxation
- linear_constraint.h:
Data structure to represent a linear constraint: lb <= sum (ai * xi) <= ub. - linear_constraint_manager.h:
Utility to manage a set of linear constraints (addition, removal,
sorting...) - linear_programming_constraint.h:
Sat propagator that interact with the simplex. It propagates variables
bounds from the SAT solver to the simplex, and extract dual information from
the simplex to be used by the sat solver. - linear_model.h:
Represents a relaxed view of the model as a set of linear constraints. - linear_relaxation.h:
Builds the static (linear model) and the dynamic (cut generators) linear
view of a cp_model. - cuts.h: Data Data
structures and cut generators for the integer constraints. - routing_cuts.h:
Data structures and cut generators for the routing constraints (circuit and
routes). - scheduling_cuts.h:
Data structures and cut generators for the scheduling constraints
(no_overlap and cumulative). - diffn_cuts.h:
Data structures and cut generators for the packing constraints
(no_overlap_2d).
Other
- model.h: Generic
class that implements a basic dependency injection framework for singleton
objects and manages the memory ownership of all the solver classes. - optimization.h:
Algorithms to solve an optimization problem using a satisfiability solver as
an opaque-box. - lp_utils.h:
Utility to scale and convert a MIP model into CP.
Recipes
You can find a set a code recipes in
the documentation directory.
Overview (`ortools/set_cover/README.md`)
Set covering
An instance of set covering is composed of two entities: elements and sets; sets
cover a series of elements. The problem of set covering is about finding the
cheapest combination of sets that cover all the elements.
More information on Wikipedia.
- Solver:
set_cover_heuristics.h. - Instance representation:
set_cover_model.h. - Instance parser:
set_cover_reader.h.
Create an instance:
// If the elements are integers, a subset can be a std::vector<int> (in a pair
// along its cost).
std::vector<std::pair<std::vector<int>, int>> subsets = ...;
SetCoverModel model;
for (const auto [subset, subset_cost] : subsets) {
model.AddEmptySubset(subset_cost)
for (const int element : subset) {
model.AddElementToLastSubset(element);
}
}
SetCoverLedger ledger(&model);Solve it using a MIP solver (guarantees to yield the optimum solution if it has
enough time to run):
SetCoverMip mip(&ledger);
mip.SetTimeLimitInSeconds(10);
mip.NextSolution();
SubsetBoolVector best_choices = ledger.GetSolution();
LOG(INFO) << "Cost: " << ledger.cost();A custom combination of heuristics (10,000 iterations of: clearing 10% of the
variables, running a Chvatal greedy descent, using steepest local search):
Cost best_cost = std::numeric_limits<Cost>::max();
SubsetBoolVector best_choices = ledger.GetSolution();
for (int i = 0; i < 10000; ++i) {
ledger.LoadSolution(best_choices);
ClearRandomSubsets(0.1 * model.num_subsets().value(), &ledger);
GreedySolutionGenerator greedy(&ledger);
CHECK(greedy.NextSolution());
SteepestSearch steepest(&ledger);
CHECK(steepest.NextSolution(10000));
EXPECT_TRUE(ledger.CheckSolution());
if (ledger.cost() < best_cost) {
best_cost = ledger.cost();
best_choices = ledger.GetSolution();
LOG(INFO) << "Better cost: " << best_cost << " at iteration = " << i;
}
}
ledger.LoadSolution(best_choices);
LOG(INFO) << "Best cost: " << ledger.cost();Overview (`tools/docker/README.md`)
CI: Makefile/Docker testing
To test the build on various distro, we are using docker containers and a
Makefile for orchestration.
pros:
- You are independent of third party CI runner config (e.g. github actions
runners). - You can run it locally on any unix system.
- Most CI provide runner with docker and Makefile installed.
cons:
- Only GNU/Linux distro supported.
- Could take few GiB (~50 GiB for all distro and all languages)
- ~500MiB OS + C++/CMake tools,
- ~150 MiB Python, wheel.
Usage
To get the help simply type:
makenote: you can also use from top directory
make --directory=tools/dockerExample
For example to test inside an Alpine container:
make alpine-edge_testDocker layers
Dockerfile is split in several stages.
Legend:
- ``: amd64, arm64, riscv64.
<distro>: alpine-edge, archlinux, centos, debian, fedora, opensuse, ubuntu.<lang>: cpp, dotnet, java, python.
Docker `aarch64` on `x86_64` machine
You can build and run aarch64 docker container on a x86_64 by enabling qemu
support:
docker run --rm --privileged multiarch/qemu-user-static --reset -p yesTo test:
docker run --platform linux/arm64 --rm --init -it ubuntu:latest bash -c "uname -a"ref:
- https://github.com/docker-library/official-images#architectures-other-than-amd64
- https://github.com/multiarch/qemu-user-static#getting-started
Overview (`tools/testing/README.md`)
Binary testing
This folder contains facilities to test executable files.
We offer two APIs:
bintest: A simple scripting language to write simple tests such as
checking execution success or asserting the presence of text or numbers
within bounds,py_bintest: An extension of the unit testing framework that makes it easy
to invoke the binary under test, extract values from its output, and check
them within the unittest framework.
`bintest`
It offers two commands RUN: and CHECK:.
- The
RUN:command executes the binary and asserts it ran successfully.
The passed arguments can use the$(<label>)construct to refer to binary
or data files. These labels are initialized from thenamed_dataattribute
of the BUILD rule. - The
CHECK:command takes one or more matchers and asserts that they all
match the binary output.
If several matchers are passed to a singleCHECK:command, they need to
match in the specified order.
SeveralCHECK:lines can be used after a call toRUN:, they are all
independently checking the same output and must all succeed.
Empty lines or lines starting with # are not taken into account. Other command
will fail with an error message.
An example
The print_args binary is used as an example.
// Snippet from tools/testing/print_args.cc
// This program prints a "Hello world!" message and its command-line arguments,
// one per line.
//
// It has a few options:
// --fail: If present, the program will exit with a non-zero status code.
// --stderr: This argument and the following ones are printed to stderr instead
// of stdout.
// --file=<file>: Prints the content of the file.
//
// Example usage:
// print_args --fail --stderr arg1 arg2
//
// This will print:
// Hello world!
// --fail
// to stdout, and
// --stderr
// arg1
// arg2
// to stderr, and exit with a non-zero status code.
int main(int argc, char** argv) {
const std::string file_flag("--file=");
puts("Hello world!");
int return_code = EXIT_SUCCESS;
FILE* out_file = stdout;
for (int i = 1; i < argc; ++i) {
const std::string arg = argv[i];
std::string output = arg;
if (arg == "--fail") {
return_code = EXIT_FAILURE;
} else if (arg == "--stderr") {
out_file = stderr;
} else if (arg.starts_with(file_flag)) {
std::ifstream stream(arg.substr(file_flag.size()));
std::stringstream buffer;
buffer << stream.rdbuf();
output = buffer.str();
}
fprintf(out_file, "%s\n", output.c_str());
}
return return_code;
}Using the bintest rule we can test its behavior.
bintest(
name = "print_args_bintest",
srcs = ["print_args.bintest"],
named_data = {
"print_args": ":print_args",
"data_file": ":print_args_data.txt",
},
)# Snippet from tools/testing/print_args.bintest
# Running the binary with no argument prints "Hello world!"
RUN: $(print_args)
CHECK: "Hello world!"
# Running the binary with arguments also prints the arguments verbatim.
RUN: $(print_args) --value=0.99999999
CHECK: "Hello world!" "--value=@num(1)"
RUN: $(print_args) a=1 b=54.7 c=12
CHECK: "a=@num(>0)" "b=@num(55~1)" "c=@num(>10, <15)"
# Passing a named variable should be expanded, here we pass the binary path to
# itself.
RUN: $(print_args) --input=$(print_args)
CHECK: "print_args"
# Passing quoted strings
RUN: $(print_args) "Double quoted" 'Single quoted'
CHECK: "Hello world!" "Double quoted" "Single quoted"
# Capturing stderr
RUN: $(print_args) --stderr "written to stderr" 2>&1
CHECK: "written to stderr"
# Expanding side data file
RUN: $(print_args) --file=$(data_file)
CHECK: "This file contains side data."Matchers
Matchers are single or double quoted strings passed to the CHECK: command.
They may contain @num() directives.
Here are a few examples:
| Matcher | Description |
|---|---|
"Hello world!" |
Matches "Hello world!". |
"@num()" |
Matches a real finite value, e.g., -1, 1.2, 1.23e45,To match inf or nan use plain text matchers. |
"@num(1)" |
Matches a number and assert it is 1,By default, comparisons are approximate ±1e-6,For exact match use plain text matchers. |
"@num(1~0.5)" |
Matches a number and assert it is 1±0.5. |
"@num(>1)" |
Matches a number and assert it is >1,Available operators are >=, >, <= and <. |
"@num(>=0, <1)" |
Matches a number and assert it is >=0 and <1. |
"Objective value: @num(100~1)" |
Matches the string "Objective value: " followed by a real value that is between 99 and 101. |
NOTE: This framework is intentionally simple and does not support regex.
For more complex scenarios use py_bintest below.
`py_bintest`
Using the py_bintest rule we can write Python tests that invoke the binary and
collect their output. The framework provides functions to match the output using
the same language as in the simple bintest scripts, and extract values out of
it. The extracted values can then be tested using the standard testing API.
Similarly to bintest above, text of the form $(<label>) in the command line
arguments is expanded using the named_data attribute of the
BUILD rule.
py_bintest(
name = "print_args_test",
srcs = ["print_args_test.py"],
named_data = {
"print_args": ":print_args",
"data_file": ":print_args_data.txt",
},
deps = ["//testing/pybase"], # MOE:replace deps = [requirement("absl-py")],
)Here is a usage example based on the same print_args binary.
/* Detailed source-code truncated for AI context efficiency. */NOTE: Python already has all the infrastructure to test values and so theextract command only accepts num@() as a placeholder. Contrary to bintest
above it will not accept numerical constraints.
Overview (`ortools/constraint_solver/docs/README.md`)
Overview
You can find here the documentation for the two following OR-Tools components.
-
Constraint programming (CP) is the name given to identifying feasible
solutions out of a very large set of candidates, where the problem can be
modeled in terms of arbitrary constraints.note: We strongly recommend using the CP-SAT solver
rather than the original CP solver. -
A specialized library for identifying best vehicle routes given constraints.
This library extension is implemented on top of the CP Solver library.
Overview (`ortools/julia/ORTools.jl/README.md`)
ORTools
This is the
MathOptInterface.jl Julia
wrapper for Google's
MathOpt.
Overview (`ortools/julia/ORToolsBinaries.jl/README.md`)
ORToolsBinaries
This package, upon installation, downloads the latest OR-Tools binaries from
GitHub. This is especially useful on platforms like Windows or ARM64/Aarch64
because no JLL is available for them.
This package otherwise provides the same interface asORTools_jll.jl,
i.e. a global variable libortools that contains the path to the local copy
of OR-Tools.
Overview (`ortools/math_opt/elemental/README.md`)
Elemental
See go/math-opt-elemental and g/math-opt-dev/c/0cgOO6qkoWM.
Overview (`ortools/routing/parsers/README.md`)
Routing
This folder contains utilities for usual file formats useful in routing problems
and utilities directly related to these file formats.
solution_serializer.h contains a generic serializer for routing solutions for
many formats.
| Problem type | File format | Corresponding parser | Data storage | Data sets |
|---|---|---|---|---|
| TSP | TSPLIB | tsplib_parser.h |
TSPLIB95 | |
| TSPTW | TSPTW | tsptw_parser.h |
TSPTW | |
| PDTSP / TSPPD | PDTSP | pdtsp_parser.h |
PDTSP | |
| CVRP | TSPLIB | tsplib_parser.h |
TSPLIB95 | |
| VRPTW | Solomon | solomon_parser.h |
Solomon, Homberger | |
| CARP | CARPLIB | carplib_parser.h |
CARPLIB | |
| NEARP | NEARPLIB | nearplib_parser.h |
NEARPLIB | |
| PDPTW | LiLim | lilim_parser.h |
LiLim | |
| MCND | Dow | dow_parser.h |
capacity_planning.proto |
Canad |
In the future, this folder will contain the whole routing solver.
Overview (`ortools/sat/colab/README.md`)
CpModel Notebook
Below you'll find three examples of Google's CP-SAT solver.
- The first example solves a logic puzzle
- The second solves a simple job shop problem
- The third example solves a two machine scheduling problem with shared
resources
Local instance
Build and run locally:
bazel run -c opt ortools/python:ortools_notebookThis will open a jupyter notebook in your browser.
To use it as a server only, use the command
bazel run -c opt ortools/python:ortools_notebook -- --no-browserAnd paste the resulting url in your favorite environment, like visual studio code.
Hidato Problem {#hidato}
We model the Hidato problem (https://en.wikipedia.org/wiki/Hidato).
/* Detailed source-code truncated for AI context efficiency. */Jobshop example {#jobshop}
This example demonstrates jobshop scheduling
(https://en.wikipedia.org/wiki/Job_shop_scheduling)
/* Detailed source-code truncated for AI context efficiency. */Two Machine Scheduling {#twomachine}
We have a set of jobs to perform (duration, width). We have two parallel
machines that can perform this job. One machine can only perform one job at a
time. At any point in time, the sum of the width of the two active jobs cannot
exceed a max_width.
The objective is to minimize the max end time of all jobs.
/* Detailed source-code truncated for AI context efficiency. */Overview (`ortools/sat/docs/README.md`)
| home | boolean logic | integer arithmetic | channeling constraints | scheduling | Using the CP-SAT solver | Model manipulation | Troubleshooting | Python API |
|---|
Using the CP-SAT solver
https://developers.google.com/optimization/cp/cp_solver
Documentation structure
This document presents modeling recipes for the CP-SAT solver.
Code samples are given in C++, Python, Java, C#, and Go. Each language has
different requirements for the code samples.
Searching for one (optimal) solution of a CP-SAT model
By default, searching for one solution will return the first solution found if
the model has no objective, or the optimal solution if the model has an
objective.
Python code samples
The Python interface to the CP-SAT solver is implemented using two classes.
- The CpModel class proposes modeling methods that creates variables, or
add constraints. This class completely hides the underlying CpModelProto
used to store the model. - The CpSolver class encapsulates the solve API and offers helpers to
access the solution found by the solve.
# Snippet from ortools/sat/samples/simple_sat_program.py
"""Simple solve."""
from ortools.sat.python import cp_model
def simple_sat_program():
"""Minimal CP-SAT example to showcase calling the solver."""
# Creates the model.
model = cp_model.CpModel()
# Creates the variables.
num_vals = 3
x = model.new_int_var(0, num_vals - 1, 'x')
y = model.new_int_var(0, num_vals - 1, 'y')
z = model.new_int_var(0, num_vals - 1, 'z')
# Creates the constraints.
model.add(x != y)
# Creates a solver and solves the model.
solver = cp_model.CpSolver()
status = solver.solve(model)
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
print(f'x = {solver.value(x)}')
print(f'y = {solver.value(y)}')
print(f'z = {solver.value(z)}')
else:
print('No solution found.')
simple_sat_program()C++ code samples
The interface to the C++ CP-SAT solver is implemented through the
CpModelBuilder class described in
ortools/sat/cp_model.h. This class is just a helper to
fill in the cp_model protobuf.
Calling Solve() method will return a CpSolverResponse protobuf that contains the
solve status, the values for each variable in the model if solve was successful,
and some metrics.
// Snippet from ortools/sat/samples/simple_sat_program.cc
#include <stdlib.h>
#include "ortools/base/init_google.h"
#include "ortools/base/logging.h"
#include "absl/base/log_severity.h"
#include "absl/log/globals.h"
#include "ortools/sat/cp_model.h"
#include "ortools/sat/cp_model.pb.h"
#include "ortools/sat/cp_model_solver.h"
#include "ortools/util/sorted_interval_list.h"
namespace operations_research {
namespace sat {
void SimpleSatProgram() {
CpModelBuilder cp_model;
const Domain domain(0, 2);
const IntVar x = cp_model.NewIntVar(domain).WithName("x");
const IntVar y = cp_model.NewIntVar(domain).WithName("y");
const IntVar z = cp_model.NewIntVar(domain).WithName("z");
cp_model.AddNotEqual(x, y);
// Solving part.
const CpSolverResponse response = Solve(cp_model.Build());
if (response.status() == CpSolverStatus::OPTIMAL ||
response.status() == CpSolverStatus::FEASIBLE) {
// Get the value of x in the solution.
LOG(INFO) << "x = " << SolutionIntegerValue(response, x);
LOG(INFO) << "y = " << SolutionIntegerValue(response, y);
LOG(INFO) << "z = " << SolutionIntegerValue(response, z);
} else {
LOG(INFO) << "No solution found.";
}
}
} // namespace sat
} // namespace operations_research
int main(int argc, char* argv[]) {
InitGoogle(argv[0], &argc, &argv, true);
absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo);
operations_research::sat::SimpleSatProgram();
return EXIT_SUCCESS;
}Java code samples
The Java code implements the same interface as the Python code, with a
CpModel and a CpSolver class.
// Snippet from ortools/sat/samples/SimpleSatProgram.java
package com.google.ortools.sat.samples;
import com.google.ortools.Loader;
import com.google.ortools.sat.CpSolverStatus;
import com.google.ortools.sat.CpModel;
import com.google.ortools.sat.CpSolver;
import com.google.ortools.sat.IntVar;
/** Minimal CP-SAT example to showcase calling the solver. */
public final class SimpleSatProgram {
public static void main(String[] args) throws Exception {
Loader.loadNativeLibraries();
// Create the model.
CpModel model = new CpModel();
// Create the variables.
int numVals = 3;
IntVar x = model.newIntVar(0, numVals - 1, "x");
IntVar y = model.newIntVar(0, numVals - 1, "y");
IntVar z = model.newIntVar(0, numVals - 1, "z");
// Create the constraints.
model.addDifferent(x, y);
// Create a solver and solve the model.
CpSolver solver = new CpSolver();
CpSolverStatus status = solver.solve(model);
if (status == CpSolverStatus.OPTIMAL || status == CpSolverStatus.FEASIBLE) {
System.out.println("x = " + solver.value(x));
System.out.println("y = " + solver.value(y));
System.out.println("z = " + solver.value(z));
} else {
System.out.println("No solution found.");
}
}
private SimpleSatProgram() {}
}C\# code samples
The C# code implements the same interface as the Python code, with a
CpModel and a CpSolver class.
// Snippet from ortools/sat/samples/SimpleSatProgram.cs
using System;
using Google.OrTools.Sat;
public class SimpleSatProgram
{
static void Main()
{
// Creates the model.
CpModel model = new CpModel();
// Creates the variables.
int num_vals = 3;
IntVar x = model.NewIntVar(0, num_vals - 1, "x");
IntVar y = model.NewIntVar(0, num_vals - 1, "y");
IntVar z = model.NewIntVar(0, num_vals - 1, "z");
// Creates the constraints.
model.Add(x != y);
// Creates a solver and solves the model.
CpSolver solver = new CpSolver();
CpSolverStatus status = solver.Solve(model);
if (status == CpSolverStatus.Optimal || status == CpSolverStatus.Feasible)
{
Console.WriteLine("x = " + solver.Value(x));
Console.WriteLine("y = " + solver.Value(y));
Console.WriteLine("z = " + solver.Value(z));
}
else
{
Console.WriteLine("No solution found.");
}
}
}Go code samples
The interface to the CP-SAT solver is implemented through the CpModelBuilder
described in the package cpmodel in
ortools/sat/go/cp_model.go. This class is a helper to fill
in the cp_model protobuf.
Also within the cpmodel package is
ortools/sat/go/cp_model.go which provides functions to
solve the model along with helpers to access the solution found by the solver.
// Snippet from ortools/sat/samples/simple_sat_program.go
// The simple_sat_program command is an example of a simple sat program.
package main
import (
"fmt"
log "github.com/golang/glog"
"github.com/google/or-tools/ortools/sat/go/cpmodel"
cmpb "github.com/google/or-tools/ortools/sat/proto/cpmodel"
)
func simpleSatProgram() error {
model := cpmodel.NewCpModelBuilder()
domain := cpmodel.NewDomain(0, 2)
x := model.NewIntVarFromDomain(domain).WithName("x")
y := model.NewIntVarFromDomain(domain).WithName("y")
z := model.NewIntVarFromDomain(domain).WithName("z")
model.AddNotEqual(x, y)
m, err := model.Model()
if err != nil {
return fmt.Errorf("failed to instantiate the CP model: %w", err)
}
response, err := cpmodel.SolveCpModel(m)
if err != nil {
return fmt.Errorf("failed to solve the model: %w", err)
}
switch response.GetStatus() {
case cmpb.CpSolverStatus_OPTIMAL, cmpb.CpSolverStatus_FEASIBLE:
fmt.Printf("x = %d\n", cpmodel.SolutionIntegerValue(response, x))
fmt.Printf("y = %d\n", cpmodel.SolutionIntegerValue(response, y))
fmt.Printf("z = %d\n", cpmodel.SolutionIntegerValue(response, z))
default:
fmt.Println("No solution found.")
}
return nil
}
func main() {
if err := simpleSatProgram(); err != nil {
log.Exitf("simpleSatProgram returned with error: %v", err)
}
}Ortools Julia Docs Index (`ortools/julia/docs/index.md`)
Julia wrappers
This folder contains the Julia wrappers for OR-Tools. They are not useable from
within Google3: they are intended for use by the open-source community only.
Note: the wrapper is being written and highly experimental.
For now, only MathOpt is accessible through
its C API.
TODO(user): how to run this on my machine?
TODO(user): generate the Julia files within CMake and export them manually
with Yggdrasil.
Packages
The Julia wrapper is divided into three Julia packages:
ORTools_jll
contains the precompiled OR-Tools binaries.ORToolsGenerated.jl
contains the generated part of the wrapper. For now, this package
corresponds to Protocol Buffers files.ORTools.jl
is the actual, high-level wrapper (more specifically, the
MathOptInterface code for
use through JuMP). It is not yet written.
Maintenance
When releasing a new version of OR-Tools:
Update the binary dependencies in
ORTools_jll:
create a public PR on GitHub that editsbuild_tarballs.jl. At each
version, you must update two fields:version: to match the version of OR-Tools.GitSource(): to match the Git commit that you want to build.
Update the rest of the file as required. In particular, you must update the
installation of the Protocol Buffers files and the list of exported Protocol
Buffers files any time the new release has new Protocol Buffers files.Update the generated files in
ORToolsGenerated.jl.
This step depends on the first one: the PR you wrote at the first step must
be accepted and merged into the repository. Updating the generated files
amounts to have a local Julia installation, adding the new version ofORTools_jll(within a Julia shell,]add ORTools_jllthe first time, then
update the version with]update), and calling the scriptupdate_package.jl.Synchronize the results of the previous step with OR-Tools' repository: the
updated files go intoortools/julia/ORToolsGenerated/(an updatedProject.toml, new files insrc/gen*).Trigger a release of
ORToolsGenerated.jlusing
JuliaRegistrator. To
this end:Find the new commit you created at the previous step in GitHub.
Add the following comment to register the
ortools/julia/ORToolsGenerated.jlas a Julia package:text@JuliaRegistrator register subdir=ortools/julia/ORToolsGenerated.jlIf registering from a nondefault branch like
julia/dev:text@JuliaRegistrator register subdir=ortools/julia/ORToolsGenerated.jl branch=julia/dev
This operation starts an automated process that will publish a new version
of theORToolsGenerated.jlpackage to the
official Julia package repository. The version
number is taken from the contents ofProject.tomlat this Git commit.In case the C API changes, you must update
ORTools.jlaccordingly: after
the manual update, follow the steps 3 and 4, replacingORToolsGenerated.jl
byORTools.jl. To register the new version, the command thus looks like:text@JuliaRegistrator register subdir=ortools/julia/ORTools.jl branch=julia/dev
There is only one set-up action to perform: install Julia's Registrator bot into
OR-Tools' repository (which only needs read access) using
their one-click procedure.
Package folder structure
This folder corresponds to the user-facing Julia packages. The goal is to have
it exported to the ortools/julia folder in the
GitHub repository, but it is not yet done.
A Julia package is made up of many files, here is a short description of the
ones in each package folder:
Project.toml: description of the project and its dependencies. Update it
when there is a new version.
Official documentation.Manifest.toml: never commit this file.
Official documentation.scripts: only inORToolsGenerated.jl. Julia source code for code
generation (Protocol Buffers and C API).src: Julia source code for the package.ORToolsGenerated.jlhas the
following subfolders:src/genproto: generated Julia source code for the Protocol Buffers
interface.src/genc: generated Julia source code for the C API.
test: automated test suite.
The ORToolsGenerated.jl and ORTools.jl packages do not include any binary
dependency; they are handled through ORTools_jll.
How to sync to Google3 changes?
To update the underlying OR-Tools binary, look atORTools_jll
and the scriptbuild_tarballs.jl
that build OR-Tools for Julia.
If there is an update in the C API, use scripts/TODO(user) to update the
generated code to call the C functions.
If there is an update in the MathOpt proto interface, useORToolsGenerated.jl/scripts/gen_pb.jl
to update the generated Protocol Buffers code. This script is automatically
called byupdate_package.jl.
Design decisions
Storing generated code
The Julia package manager has
a build step
for all packages. However, this step is only called when first installing the
package or manually. Generating the code at the build step definitely possible,
but the generated code would not be up-to-date with the installed release of
OR-Tools, which defeats the purpose of having automatically generated code.
All other Julia packages that rely on generated code in the JuMP ecosystem rely
on stored generated code, at times storing several versions of the generated
code to accommodate various versions of the native dependency.
The package ORToolsGenerated.jl is supposed to correspond to exactly one
version of the native dependency.
Role of `update_package.jl`
Bumping the version of ORToolsGenerated.jl requires two things:
- Updating the version of the package
(following the usual rules for new releases of Julia packages,
which means avoiding skipping versions). - Updating the compatibility with
ORTools_jll.
update_package.jl automates those two steps based on the version ofORTools_jll.
Bazel Docs Ci (`bazel/docs/ci.md`)
CI: Makefile/Docker testing
To test the build on various distro, I'm using docker containers and a Makefile
for orchestration.
pros:
- You are independent of third party CI runner VM images (e.g. github actions/virtual-environments).
- You can run it locally on any host having a linux docker image support.
- Most CI provide runner with docker and Makefile installed.
cons:
- Only GNU/Linux distro supported.
- Could take few GiB (~30 GiB for all distro and all languages)
- ~500MiB OS + C++/CMake tools,
Usage
To get the help simply type:
makenote: you can also use from top directory
make --directory=bazelExample
For example to test inside an Ubuntu container:
make ubuntu_testDocker Layers
Dockerfile is split in several stages.
Cmake Docs Ci (`cmake/docs/ci.md`)
CI: Makefile/Docker/Vagrant testing
To test the build on various distro, I'm using docker containers and a Makefile
for orchestration.
pros:
- You are independent of third party CI runner VM images (e.g. github actions/virtual-environments).
- You can run it locally on any host having a linux docker image support.
- Most CI provide runner with docker and Makefile installed.
cons:
- Only GNU/Linux distro supported.
- Could take few GiB (~30 GiB for all distro and all languages)
- ~500MiB OS + C++/CMake tools,
- ~150 MiB Python,
- ~400 MiB dotnet-sdk,
- ~400 MiB java-jdk.
Usage
To get the help simply type:
makenote: you can also use from top directory
make --directory=cmakeExample
For example to test Python inside an Alpine container:
make alpine_python_testDocker
Docker is a set of platform
as a service products that use OS-level virtualization to deliver software in
packages called containers.
You can find official base image on the Docker registry Docker Hub
Layers
Dockerfile is split in several stages.
Vagrant
Vagrant is a tool for building and managing
virtual machine environments in a single workflow. It is currently used to test
FreeBSD inside a VirtualBox since we don't have any FreeBSD machine.
Vagrant call a base image a box.
Vagrant call a container a vagrant machine.
You can find official box on the Vagrant registry Vagrant Cloud
note: Currently only github MacOS runner provide virtualization support (i.e. VirtualBox).
Basic usage
Once vagrant and VirtualBox are installed, all commands must be run where
the Vagrantfile is located.
Generate a Vagrantfile skeleton, e.g. using the box generic/freebsd12:
vagrant init generic/freebsd12Build and run a new vagrant machine:
vagrant upnote: If you run virtualbox you should see it.
Connect to a vagrant machine:
vagrant ssh
[vagrant@freebsd12 ~]$ ...Execute few commands:
vagrant ssh -c "pwd; ls project ..."Stop and delete a vagrant machine:
vagrant destroy -fCmake Docs Cpp (`cmake/docs/cpp.md`)
| Linux | macOS | Windows |
|---|---|---|
Introduction
For building OR-Tools as a CMake standalone project, you can read the following
instructions.
This project should run on GNU/Linux, MacOS and Windows.
C++ Project Build
Get the source code and change to it.
shgit clone https://github.com/google/or-tools.git cd or-toolsRun CMake to configure the build tree.
shcmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release -DBUILD_DEPS=ONnote: To get the list of available generators (e.g. Visual Studio), use
-GAfterwards, generated files can be used to compile the project.
shcmake --build build --config Release -vTest the build software (optional).
shcmake --build build --target testInstall the built files (optional).
shcmake --build build --target install
Testing
To list tests:
cd build
ctest -NTo only run C++ tests:
cd build
ctest -R "cxx_.*"Technical Notes
Managing RPATH
Since we want to use the
CMAKE_BINARY_DIR
to generate the wrapper package (e.g. python wheel package) as well as be able
to test from the build directory.
We need to enable:
set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)And have a finely tailored rpath for each library.
Cmake Docs Dotnet (`cmake/docs/dotnet.md`)
| Linux | macOS | Windows |
|---|---|---|
Introduction
Try to build a .NetStandard2.0 native (for win-x64, linux-x64 and osx-x64) nuget
multi package using dotnet/cli and the new
.csproj format.
Build the Binary Packages
To build the .Net nuget packages, simply run:
cmake -S. -Bbuild -DBUILD_DOTNET=ON
cmake --build build --target dotnet_package -vnote: Since dotnet_package is in target all, you can also omit the--target option.
Testing
To list tests:
cd build
ctest -NTo only run .NET tests:
cd build
ctest -R "dotnet_.*"Technical Notes
First you should take a look at the
ortools/dotnet/README.md to understand the
layout.
Here I will only focus on the CMake/SWIG tips and tricks.
Build directory layout
Since .Net use a .csproj project file to orchestrate everything we need to
generate them and have the following layout:
<CMAKE_BINARY_DIR>/dotnet
── Directory.Build.props
├── orLogo.png
├── ortools
│ ├── algorithms
│ │ ├── *.cs
│ │ ├── knapsack_solverCSHARP_wrap.cxx
│ │ ├── operations_research_algorithms.cs
│ │ └── operations_research_algorithmsPINVOKE.cs
│ ├── constraint_solver
│ │ ├── *.cs
│ │ ├── operations_research_constraint_solver.cs
│ │ ├── operations_research_constraint_solverPINVOKE.cs
│ │ ├── routingCSHARP_wrap.cxx
│ │ └── routingCSHARP_wrap.h
│ ├── graph
│ │ ├── *.cs
│ │ ├── graphCSHARP_wrap.cxx
│ │ ├── operations_research_graph.cs
│ │ └── operations_research_graphPINVOKE.cs
│ ├── linear_solver
│ │ ├── *.cs
│ │ ├── LinearSolver.pb.cs
│ │ ├── operations_research_linear_solver.cs
│ │ └── operations_research_linear_solverPINVOKE.cs
│ ├── sat
│ │ ├── *.cs
│ │ ├── BooleanProblem.pb.cs
│ │ ├── CpModel.pb.cs
│ │ ├── SatParameters.pb.cs
│ │ ├── operations_research_sat.cs
│ │ ├── operations_research_satPINVOKE.cs
│ │ ├── satCSHARP_wrap.cxx
│ │ └── satCSHARP_wrap.h
│ └── util
│ ├── *.cs
│ ├── operations_research_utilPINVOKE.cs
│ ├── sorted_interval_listCSHARP_wrap.cxx
│ └── sorted_interval_listCSHARP_wrap.h
├── Google.OrTools.runtime.linux-x64
│ └── Google.OrTools.runtime.linux-x64.csproj
├── Google.OrTools
│ └── Google.OrTools.csproj
├── replace.cmake
└── replace_runtime.cmakesrc: tree build/dotnet --prune -I "obj|bin"
Cmake Docs Java (`cmake/docs/java.md`)
| Linux | macOS | Windows |
|---|---|---|
Introduction
First, verify you have the JAVA_HOME environment variable set otherwise CMake
and Maven won't be able to find your Java SDK.
Build the Binary Package
To build the java maven packages, simply run:
cmake -S. -Bbuild -DBUILD_JAVA=ON
cmake --build build --target java_package -vnote: Since java_package is in target all, you can also omit the--target option.
Testing
To list tests:
cd build
ctest -NTo only run Java tests:
cd build
ctest -R "java_.*"Technical Notes
First you should take a look at the
ortools/java/README.md to understand the layout.
Here I will only focus on the CMake/SWIG tips and tricks.
Build directory layout
Since Java use the directory layout and we want to use the
CMAKE_BINARY_DIR
to generate the Java binary package.
We want this layout:
<CMAKE_BINARY_DIR>/java
├── ortools-linux-x86-64
│ ├── pom.xml
│ └── src/main/resources
│ └── linux-x86-64
│ ├── libjniortools.so
│ └── libortools.so
├── ortools-java
│ ├── pom.xml
│ └── src/main/java
│ └── com/google/ortools
│ ├── Loader.java
│ ├── linearsolver
│ │ ├── LinearSolver.java
│ │ └── ...
│ ├── constraintsolver
│ │ ├── ConstraintSolver.java
│ │ └── ...
│ ├── ...
│ └── sat
│ ├── CpModel.java
│ └── ...
├── constraint_solver
│ └── Tsp
│ ├── pom.xml
│ └── src/main/java
│ └── com/google/ortools
│ └── Tsp.javasrc: tree build/java --prune -U -P "*.java|*.xml|*.so*" -I "target"
Managing SWIG generated files
You can use CMAKE_SWIG_DIR to change the output directory for the .java file
e.g.:
set(CMAKE_SWIG_OUTDIR ${CMAKE_CURRENT_BINARY_DIR}/..)And you can use CMAKE_LIBRARY_OUTPUT_DIRECTORY to change the output directory
for the .so file e.g.:
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/..)[optional]You can use SWIG_OUTFILE_DIR to change the output directory for the.cxx file e.g.:
set(SWIG_OUTFILE_DIR ${CMAKE_CURRENT_BINARY_DIR}/..)Then you only need to create a pom.xml file in build/java to be able to use
the build directory to generate the Java package.
Cmake Docs Python (`cmake/docs/python.md`)
| Linux | macOS | Windows |
|---|---|---|
Introduction
To be compliant with
PEP513 a
python package should embbed all its C++ shared libraries.
Creating a Python native package containing all .py and .so (with good
rpath/loaderpath) is not so easy...
Build the Binary Package
To build the Python wheel package, simply run:
cmake -S. -Bbuild -DBUILD_PYTHON=ON
cmake --build build --target python_package -vnote: Since python_package is in target all, you can also omit the--target option.
Testing
To list tests:
cd build
ctest -NTo only run Python tests:
cd build
ctest -R "python_.*"Technical Notes
First you should take a look at the
ortools/python/README.md to understand the
layout.
Here I will only focus on the CMake/SWIG tips and tricks.
Build directory layout
Since Python use the directory name where __init__.py file is located and we
want to use the
CMAKE_BINARY_DIR
to generate the Python binary package.
We want this layout:
<CMAKE_BINARY_DIR>/python
├── setup.py
└── ortools
├── __init__.py
├── algorithms
│ ├── __init__.py
│ ├── _pywrap.so
│ └── pywrap.py
├── graph
│ ├── __init__.py
│ ├── pywrapgraph.py
│ └── _pywrapgraph.so
├── ...
└── .libs
├── libXXX.so.1.0
└── libXXX.so.1.0src: tree build --prune -U -P "*.py|*.so*" -I "build"
note: On UNIX you always need $ORIGIN/../../${PROJECT_NAME}/.libs since_pywrapXXX.so will depend on libortools.so.
note: On APPLE you always need"@loader_path;@loader_path/../../${PROJECT_NAME}/.libs since _pywrapXXX.so
will depend on libortools.dylib.
note: on Windows since we are using static libraries we won't have the .libs
directory...
So we also need to create few __init__.py files to be able to use the build
directory to generate the Python package.
Why on APPLE lib must be .so
Actually, the cpython code responsible for loading native libraries expect .so
on all UNIX platforms.
const char *_PyImport_DynLoadFiletab[] = {
#ifdef __CYGWIN__
".dll",
#else /* !__CYGWIN__ */
"." SOABI ".so",
#ifdef ALT_SOABI
"." ALT_SOABI ".so",
#endif
".abi" PYTHON_ABI_STRING ".so",
".so",
#endif /* __CYGWIN__ */
NULL,
};ref:
https://github.com/python/cpython/blob/main/Python/dynload_shlib.c#L37-L49
i.e. pywrapXXX -> _pywrapXXX.so -> libortools.dylib
Why setup.py has to be generated
To avoid to put hardcoded path to SWIG .so/.dylib generated files, we could
use $<TARGET_FILE_NAME:tgt> to retrieve the file (and also deal with
Mac/Windows suffix, and target dependencies).
In order for setup.py to use
cmake generator expression
(e.g. $<TARGET_FILE_NAME:_pyFoo>). We need to generate it at build time (e.g.
using
add_custom_command()).
note: This will also add automatically a dependency between the command and the
TARGET !
todo(mizux): try to usefile(GENERATE ...)
instead.
Cmake Docs Swig (`cmake/docs/swig.md`)
SWIG Wrapper Generation
Using swig to generate wrapper it's easy thanks
to the modern UseSWIG
module (CMake >= 3.14).
note: SWIG automatically put its target(s) in all, thus cmake --build build
will also call swig and generate _module.so.
Policy
UseSWIG is impacted by two policies:
- CMP0078:UseSWIG
generates standard target names (CMake 3.13+). - CMP0086: UseSWIG
honorsSWIG_MODULE_NAMEvia-moduleflag (CMake 3.14+).
That's why I recommnend to use CMake >= 3.14 with both policies set to new for
SWIG development.
int64_t Management
When working on a k8 (aka x86_64) architecture, you may face issue withint64_t and uint64_t management.
First long long and long int are different types and int64_t is just a
typedef on one of them...
Linux
On Linux we have:
sizeof(long long): 8
sizeof(long int): 8
sizeof(int64_t): 8Gcc
First try to find where and how the compiler define int64_t and uint64_t.
grepc -rn "typedef.*int64_t;" /lib/gcc
/lib/gcc/x86_64-linux-gnu/9/include/stdint-gcc.h:43:typedef __INT64_TYPE__ int64_t;
/lib/gcc/x86_64-linux-gnu/9/include/stdint-gcc.h:55:typedef __UINT64_TYPE__ uint64_t;So we need to find this compiler macro definition
gcc -dM -E -x c /dev/null | grep __INT64
#define __INT64_C(c) c ## L
#define __INT64_MAX__ 0x7fffffffffffffffL
#define __INT64_TYPE__ long int
gcc -dM -E -x c /dev/null | grep __UINT64
#define __UINT64_C(c) c ## UL
#define __UINT64_MAX__ 0xffffffffffffffffUL
#define __UINT64_TYPE__ long unsigned intClang
clang -dM -E -x c++ /dev/null | grep INT64_TYPE
#define __INT64_TYPE__ long int
#define __UINT64_TYPE__ long unsigned intClang, GNU compilers: -dM dumps a list of macros. -E prints results to stdout instead of a file. -x c and -x c++ select the programming language when using a file without a
filename extension, such as /dev/null
MacOS
On Catalina 10.15 we have:
sizeof(long long): 8
sizeof(long int): 8
sizeof(int64_t): 8Clang
clang -dM -E -x c++ /dev/null | grep INT64_TYPE
#define __INT64_TYPE__ long long int
#define __UINT64_TYPE__ long long unsigned intwith: -dM dumps a list of macros. -E prints results to stdout instead of a file. -x c and -x c++ select the programming language when using a file without a
filename extension, such as /dev/null
Windows
Contrary to macOS and Linux, Windows 64bits (x86_64) try hard to keep
compatibility, so we have:
sizeof(long int): 4
sizeof(long long): 8
sizeof(int64_t): 8Visual Studio 2022
Thus, in stdint.h we have:
#if _VCRT_COMPILER_PREPROCESSOR
typedef signed char int8_t;
typedef short int16_t;
typedef int int32_t;
typedef long long int64_t;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;SWIG int64_t management stuff
First, take a look at
Swig stdint.i.
So, when targeting Linux you must use define SWIGWORDSIZE64 (i.e.-DSWIGWORDSIZE64) while on macOS and Windows you must not define it.
Now the bad news, even if you can control the SWIG typedef usingSWIGWORDSIZE64,
SWIG Java
and
SWIG CSHARP
do not take it into account for typemaps...
So you may want to use this for Java:
#if defined(SWIGJAVA)
#if defined(SWIGWORDSIZE64)
%define PRIMITIVE_TYPEMAP(NEW_TYPE, TYPE)
%clear NEW_TYPE;
%clear NEW_TYPE *;
%clear NEW_TYPE &;
%clear const NEW_TYPE &;
%apply TYPE { NEW_TYPE };
%apply TYPE * { NEW_TYPE * };
%apply TYPE & { NEW_TYPE & };
%apply const TYPE & { const NEW_TYPE & };
%enddef // PRIMITIVE_TYPEMAP
PRIMITIVE_TYPEMAP(long int, long long);
PRIMITIVE_TYPEMAP(unsigned long int, long long);
#undef PRIMITIVE_TYPEMAP
#endif // defined(SWIGWORDSIZE64)
#endif // defined(SWIGJAVA)and this for .Net:
#if defined(SWIGCSHARP)
#if defined(SWIGWORDSIZE64)
%define PRIMITIVE_TYPEMAP(NEW_TYPE, TYPE)
%clear NEW_TYPE;
%clear NEW_TYPE *;
%clear NEW_TYPE &;
%clear const NEW_TYPE &;
%apply TYPE { NEW_TYPE };
%apply TYPE * { NEW_TYPE * };
%apply TYPE & { NEW_TYPE & };
%apply const TYPE & { const NEW_TYPE & };
%enddef // PRIMITIVE_TYPEMAP
PRIMITIVE_TYPEMAP(long int, long long);
PRIMITIVE_TYPEMAP(unsigned long int, unsigned long long);
#undef PRIMITIVE_TYPEMAP
#endif // defined(SWIGWORDSIZE64)
#endif // defined(SWIGCSHARP)So int64_t (i.e. long int in this case) will be correctly bind to Java/.Net
primitive type long.
swig_add_library()
You can use OUTPUT_DIR to change the output directory for the .py file e.g.:
swig_add_library(pyFoo
TYPE MODULE
LANGUAGE python
OUTPUT_DIR ${CMAKE_BINARY_DIR}/python/${PROJECT_NAME}/Foo
SOURCES foo.i)Doxygen
Since swig 4.0, swig can now
extract doxygen comments from C++ to
inject it in Python and Java.
Csharp documentation
note: Doxygen to csharp was
planned
but currently is not supported.
Makefiles Docs Ci (`makefiles/docs/ci.md`)
CI: Makefile/Docker testing
To test the build on various distro, I'm using docker containers and a Makefile
for orchestration.
pros:
- You are independent of third party CI runner config (e.g. github actions
runners or Travis-CI VM images). - You can run it locally on your linux system.
- Most CI provide runner with docker and Makefile installed (e.g. tarvis-ci
minimal images.
cons:
- Only GNU/Linux distro supported.
- Could take few GiB (~30 GiB for all distro and all languages)
- ~500MiB OS + C++/CMake tools,
- ~150 MiB Python,
- ~400 MiB dotnet-sdk,
- ~400 MiB java-jdk.
Usage
To get the help simply type:
makenote: you can also use from top directory
make --directory=makefilesExample
For example to test Python inside an Alpine container:
make alpine_python_testDocker layers
Dockerfile is split in several stages.
Ortools Constraint Solver Docs CP (`ortools/constraint_solver/docs/CP.md`)
Using the CP solver
https://developers.google.com/optimization/cp/
Documentation structure
OR-Tools comes with lots of constraint programming samples given in C++, Python,
Java and .Net. Each language have different requirements for the code samples.
Basic example
C++ code samples
/* Detailed source-code truncated for AI context efficiency. */Python code samples
#!/usr/bin/env python3
"""Simple Constraint optimization example."""
from ortools.constraint_solver import pywrapcp
def main():
"""Entry point of the program."""
# Instantiate the solver.
solver = pywrapcp.Solver("CPSimple")
# Create the variables.
num_vals = 3
x = solver.IntVar(0, num_vals - 1, "x")
y = solver.IntVar(0, num_vals - 1, "y")
z = solver.IntVar(0, num_vals - 1, "z")
# Constraint 0: x != y.
solver.Add(x != y)
print("Number of constraints: ", solver.Constraints())
# Solve the problem.
decision_builder = solver.Phase(
[x, y, z], solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE
)
# Print solution on console.
count = 0
solver.NewSearch(decision_builder)
while solver.NextSolution():
count += 1
solution = f"Solution {count}:\n"
for var in [x, y, z]:
solution += f" {var.Name()} = {var.Value()}"
print(solution)
solver.EndSearch()
print(f"Number of solutions found: {count}")
print("Advanced usage:")
print(f"Problem solved in {solver.WallTime()}ms")
print(f"Memory usage: {pywrapcp.Solver.MemoryUsage()}bytes")
if __name__ == "__main__":
main()Java code samples
/* Detailed source-code truncated for AI context efficiency. */.Net code samples
using System;
using Google.OrTools.ConstraintSolver;
/// <summary>
/// This is a simple CP program.
/// </summary>
public class SimpleCpProgram
{
public static void Main(String[] args)
{
// Instantiate the solver.
Solver solver = new Solver("CpSimple");
// Create the variables.
const long numVals = 3;
IntVar x = solver.MakeIntVar(0, numVals - 1, "x");
IntVar y = solver.MakeIntVar(0, numVals - 1, "y");
IntVar z = solver.MakeIntVar(0, numVals - 1, "z");
// Constraint 0: x != y..
solver.Add(solver.MakeAllDifferent(new IntVar[] { x, y }));
Console.WriteLine($"Number of constraints: {solver.Constraints()}");
// Solve the problem.
DecisionBuilder db =
solver.MakePhase(new IntVar[] { x, y, z }, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE);
// Print solution on console.
int count = 0;
solver.NewSearch(db);
while (solver.NextSolution())
{
++count;
Console.WriteLine($"Solution: {count}\n x={x.Value()} y={y.Value()} z={z.Value()}");
}
solver.EndSearch();
Console.WriteLine($"Number of solutions found: {solver.Solutions()}");
Console.WriteLine("Advanced usage:");
Console.WriteLine($"Problem solved in {solver.WallTime()}ms");
Console.WriteLine($"Memory usage: {Solver.MemoryUsage()}bytes");
}
}Ortools Constraint Solver Docs PDP (`ortools/constraint_solver/docs/PDP.md`)
Pickup & Delivery routing Problem recipes for the Vehicle Routing solver.
Introduction
The Vehicle Routing solver can be used to solve various PDP.
Pickup & Delivery Problem
Data Problem:
Solution with the default Policy (ANY):
Samples:
FIFO Policy
Solution with the FIFO Policy:
Samples:
- vrp_pickup_delivery_fifo.cc
- vrp_pickup_delivery_fifo.py
- VrpPickupDeliveryFifo.java
- VrpPickupDeliveryFifo.cs
LIFO Policy
Solution with the LIFO Policy:
Samples:
- vrp_pickup_delivery_lifo.cc
- vrp_pickup_delivery_lifo.py
- VrpPickupDeliveryLifo.java
- VrpPickupDeliveryLifo.cs
Ortools Constraint Solver Docs ROUTING (`ortools/constraint_solver/docs/ROUTING.md`)
Using the Vehicle Routing solver
https://developers.google.com/optimization/routing
Documentation structure
This document presents modeling recipes for the Vehicle routing solver. These
are grouped by type:
OR-Tools comes with lots of vehicle routing samples given in C++, Python, Java
and .Net. Each language have different requirements for the code samples.
Basic example
C++ code samples
/* Detailed source-code truncated for AI context efficiency. */Python code samples
/* Detailed source-code truncated for AI context efficiency. */Java code samples
/* Detailed source-code truncated for AI context efficiency. */.Net code samples
/* Detailed source-code truncated for AI context efficiency. */Misc
Images have been generated using routing_svg.py through bash
script generate_svg.sh.
Ortools Constraint Solver Docs TSP (`ortools/constraint_solver/docs/TSP.md`)
Travelling Salesman Problem recipes for the Vehicle Routing solver.
Introduction
The Vehicle Routing solver can be used to solve a Travelling Salesman Problem
(TSP).
Using Locations
Data Problem:
Solution:
Samples:
Using Distance Matrix
Data Problem:
Solution:
Samples:
Ortools Constraint Solver Docs VRP (`ortools/constraint_solver/docs/VRP.md`)
Vehicle Routing Problem recipes for the Vehicle Routing solver.
Introduction
The Vehicle Routing solver can be used to solve various VRP.
Vehicle Routing Problem
Data Problem:
Solution:
Samples:
Global Span Constraints
Data Problem:
Solution:
Samples:
Capacity Constraints
Data Problem:
Solution:
Samples:
Drop Nodes Constraints
Data Problem:
Solution:
Samples:
Multiple Starts Ends
Data Problem:
Solution:
Samples:
Time Window Constraints
Data Problem:
Solution:
Samples:
Resource Constraints
Data Problem:
Solution:
Samples:
Ortools Sat Docs Boolean Logic (`ortools/sat/docs/boolean_logic.md`)
| home | boolean logic | integer arithmetic | channeling constraints | scheduling | Using the CP-SAT solver | Model manipulation | Troubleshooting | Python API |
|---|
Boolean logic recipes for the CP-SAT solver.
https://developers.google.com/optimization/
Introduction
The CP-SAT solver can express Boolean variables and constraints. A Boolean
variable is an integer variable constrained to be either 0 or 1. A literal
is either a Boolean variable or its negation: 0 negated is 1, and 1 negated is
0. See
https://en.wikipedia.org/wiki/Boolean_satisfiability_problem#Basic_definitions_and_terminology.
Boolean variables and literals
We can create a Boolean variable x and a literal not_x equal to the logical
negation of x.
Python code
# Snippet from ortools/sat/samples/literal_sample_sat.py
"""Code sample to demonstrate Boolean variable and literals."""
from ortools.sat.python import cp_model
def literal_sample_sat():
model = cp_model.CpModel()
x = model.new_bool_var('x')
not_x = ~x
print(x)
print(not_x)
literal_sample_sat()C++ code
// Snippet from ortools/sat/samples/literal_sample_sat.cc
#include <stdlib.h>
#include "ortools/base/init_google.h"
#include "ortools/base/logging.h"
#include "absl/base/log_severity.h"
#include "absl/log/globals.h"
#include "ortools/sat/cp_model.h"
namespace operations_research {
namespace sat {
void LiteralSampleSat() {
CpModelBuilder cp_model;
const BoolVar x = cp_model.NewBoolVar().WithName("x");
const BoolVar not_x = ~x;
LOG(INFO) << "x = " << x << ", not(x) = " << not_x;
}
} // namespace sat
} // namespace operations_research
int main(int argc, char* argv[]) {
InitGoogle(argv[0], &argc, &argv, true);
absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo);
operations_research::sat::LiteralSampleSat();
return EXIT_SUCCESS;
}Java code
// Snippet from ortools/sat/samples/LiteralSampleSat.java
package com.google.ortools.sat.samples;
import com.google.ortools.sat.BoolVar;
import com.google.ortools.sat.CpModel;
import com.google.ortools.sat.Literal;
import com.google.ortools.Loader;
/** Code sample to demonstrate Boolean variable and literals. */
public class LiteralSampleSat {
public static void main(String[] args) throws Exception {
Loader.loadNativeLibraries();
CpModel model = new CpModel();
BoolVar x = model.newBoolVar("x");
Literal notX = x.not();
System.out.println(notX);
}
}C\# code
// Snippet from ortools/sat/samples/LiteralSampleSat.cs
using System;
using Google.OrTools.Sat;
public class LiteralSampleSat
{
static void Main()
{
CpModel model = new CpModel();
BoolVar x = model.NewBoolVar("x");
ILiteral not_x = x.Not();
}
}Go code
// Snippet from ortools/sat/samples/literal_sample_sat.go
// The literal_sample_sat command is a simple example of literals.
package main
import (
log "github.com/golang/glog"
"github.com/google/or-tools/ortools/sat/go/cpmodel"
)
func literalSampleSat() {
model := cpmodel.NewCpModelBuilder()
x := model.NewBoolVar().WithName("x")
notX := x.Not()
log.Infof("x = %d, x.Not() = %d", x.Index(), notX.Index())
}
func main() {
literalSampleSat()
}Boolean constraints
For Boolean variables x and y, the following are elementary Boolean constraints:
- or(x_1, .., x_n)
- and(x_1, .., x_n)
- xor(x_1, .., x_n)
- not(x)
More complicated constraints can be built up by combining these elementary
constraints. For instance, we can add a constraint Or(x, not(y)).
Python code
# Snippet from ortools/sat/samples/bool_or_sample_sat.py
"""Code sample to demonstrates a simple Boolean constraint."""
from ortools.sat.python import cp_model
def bool_or_sample_sat():
model = cp_model.CpModel()
x = model.new_bool_var('x')
y = model.new_bool_var('y')
model.add_bool_or([x, y.negated()])
# The [] is not mandatory.
# ~y is equivalent to y.negated()
model.add_bool_or(x, ~y)
bool_or_sample_sat()C++ code
// Snippet from ortools/sat/samples/bool_or_sample_sat.cc
#include <stdlib.h>
#include "ortools/base/init_google.h"
#include "absl/base/log_severity.h"
#include "absl/log/globals.h"
#include "absl/types/span.h"
#include "ortools/sat/cp_model.h"
namespace operations_research {
namespace sat {
void BoolOrSampleSat() {
CpModelBuilder cp_model;
const BoolVar x = cp_model.NewBoolVar();
const BoolVar y = cp_model.NewBoolVar();
cp_model.AddBoolOr({x, ~y});
// You can also use the ~ operator.
cp_model.AddBoolOr({x, ~y});
}
} // namespace sat
} // namespace operations_research
int main(int argc, char* argv[]) {
InitGoogle(argv[0], &argc, &argv, true);
absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo);
operations_research::sat::BoolOrSampleSat();
return EXIT_SUCCESS;
}Java code
// Snippet from ortools/sat/samples/BoolOrSampleSat.java
package com.google.ortools.sat.samples;
import com.google.ortools.Loader;
import com.google.ortools.sat.BoolVar;
import com.google.ortools.sat.CpModel;
import com.google.ortools.sat.Literal;
/** Code sample to demonstrates a simple Boolean constraint. */
public class BoolOrSampleSat {
public static void main(String[] args) throws Exception {
Loader.loadNativeLibraries();
CpModel model = new CpModel();
BoolVar x = model.newBoolVar("x");
BoolVar y = model.newBoolVar("y");
model.addBoolOr(new Literal[] {x, y.not()});
}
}C\# code
// Snippet from ortools/sat/samples/BoolOrSampleSat.cs
using System;
using Google.OrTools.Sat;
public class BoolOrSampleSat
{
static void Main()
{
CpModel model = new CpModel();
BoolVar x = model.NewBoolVar("x");
BoolVar y = model.NewBoolVar("y");
model.AddBoolOr(new ILiteral[] { x, y.Not() });
}
}Go code
// Snippet from ortools/sat/samples/bool_or_sample_sat.go
// The bool_or_sample_sat command is simple example of the BoolOr constraint.
package main
import (
"github.com/google/or-tools/ortools/sat/go/cpmodel"
)
func boolOrSampleSat() {
model := cpmodel.NewCpModelBuilder()
x := model.NewBoolVar()
y := model.NewBoolVar()
model.AddBoolOr(x, y.Not())
}
func main() {
boolOrSampleSat()
}Reified constraints
The CP-SAT solver supports half-reified constraints, also called
implications, which are of the form:
x implies constraintwhere the constraint must hold if x is true.
Note that this is not an equivalence relation. The constraint can still be true
if x is false.
So we can write b => And(x, not y). That is, if b is true, then x is true and y
is false. Note that in this particular example, there are multiple ways to
express this constraint: you can also write (b => x) and (b => not y), which
then is written as Or(not b, x) and Or(not b, not y).
Python code
# Snippet from ortools/sat/samples/reified_sample_sat.py
"""Simple model with a reified constraint."""
from ortools.sat.python import cp_model
def reified_sample_sat():
"""Showcase creating a reified constraint."""
model = cp_model.CpModel()
x = model.new_bool_var('x')
y = model.new_bool_var('y')
b = model.new_bool_var('b')
# First version using a half-reified bool and.
model.add_bool_and(x, ~y).only_enforce_if(b)
# Second version using implications.
model.add_implication(b, x)
model.add_implication(b, ~y)
# Third version using bool or.
model.add_bool_or(~b, x)
model.add_bool_or(~b, ~y)
reified_sample_sat()C++ code
// Snippet from ortools/sat/samples/reified_sample_sat.cc
#include <stdlib.h>
#include "ortools/base/init_google.h"
#include "absl/base/log_severity.h"
#include "absl/log/globals.h"
#include "absl/types/span.h"
#include "ortools/sat/cp_model.h"
namespace operations_research {
namespace sat {
void ReifiedSampleSat() {
CpModelBuilder cp_model;
const BoolVar x = cp_model.NewBoolVar();
const BoolVar y = cp_model.NewBoolVar();
const BoolVar b = cp_model.NewBoolVar();
// First version using a half-reified bool and.
cp_model.AddBoolAnd({x, ~y}).OnlyEnforceIf(b);
// Second version using implications.
cp_model.AddImplication(b, x);
cp_model.AddImplication(b, ~y);
// Third version using bool or.
cp_model.AddBoolOr({~b, x});
cp_model.AddBoolOr({~b, ~y});
}
} // namespace sat
} // namespace operations_research
int main(int argc, char* argv[]) {
InitGoogle(argv[0], &argc, &argv, true);
absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo);
operations_research::sat::ReifiedSampleSat();
return EXIT_SUCCESS;
}Java code
// Snippet from ortools/sat/samples/ReifiedSampleSat.java
package com.google.ortools.sat.samples;
import com.google.ortools.Loader;
import com.google.ortools.sat.BoolVar;
import com.google.ortools.sat.CpModel;
import com.google.ortools.sat.Literal;
/**
* Reification is the action of associating a Boolean variable to a constraint. This boolean
* enforces or prohibits the constraint according to the value the Boolean variable is fixed to.
*
* <p>Half-reification is defined as a simple implication: If the Boolean variable is true, then the
* constraint holds, instead of an complete equivalence.
*
* <p>The SAT solver offers half-reification. To implement full reification, two half-reified
* constraints must be used.
*/
public class ReifiedSampleSat {
public static void main(String[] args) throws Exception {
Loader.loadNativeLibraries();
CpModel model = new CpModel();
BoolVar x = model.newBoolVar("x");
BoolVar y = model.newBoolVar("y");
BoolVar b = model.newBoolVar("b");
// Version 1: a half-reified boolean and.
model.addBoolAnd(new Literal[] {x, y.not()}).onlyEnforceIf(b);
// Version 2: implications.
model.addImplication(b, x);
model.addImplication(b, y.not());
// Version 3: boolean or.
model.addBoolOr(new Literal[] {b.not(), x});
model.addBoolOr(new Literal[] {b.not(), y.not()});
}
}C\# code
// Snippet from ortools/sat/samples/ReifiedSampleSat.cs
using System;
using Google.OrTools.Sat;
public class ReifiedSampleSat
{
static void Main()
{
CpModel model = new CpModel();
BoolVar x = model.NewBoolVar("x");
BoolVar y = model.NewBoolVar("y");
BoolVar b = model.NewBoolVar("b");
// First version using a half-reified bool and.
model.AddBoolAnd(new ILiteral[] { x, y.Not() }).OnlyEnforceIf(b);
// Second version using implications.
model.AddImplication(b, x);
model.AddImplication(b, y.Not());
// Third version using bool or.
model.AddBoolOr(new ILiteral[] { b.Not(), x });
model.AddBoolOr(new ILiteral[] { b.Not(), y.Not() });
}
}Go code
// Snippet from ortools/sat/samples/reified_sample_sat.go
// The reified_sample_sat command is a simple example of implication constraints.
package main
import (
"github.com/google/or-tools/ortools/sat/go/cpmodel"
)
func reifiedSampleSat() {
model := cpmodel.NewCpModelBuilder()
x := model.NewBoolVar()
y := model.NewBoolVar()
b := model.NewBoolVar()
// First version using a half-reified bool and.
model.AddBoolAnd(x, y.Not()).OnlyEnforceIf(b)
// Second version using implications.
model.AddImplication(b, x)
model.AddImplication(b, y.Not())
// Third version using bool or.
model.AddBoolOr(b.Not(), x)
model.AddBoolOr(b.Not(), y.Not())
}
func main() {
reifiedSampleSat()
}Product of two Boolean Variables
A useful construct is the product p of two Boolean variables x and y.
p == x * yThis is equivalent to the logical relation
p <=> x and yThis is encoded using one bool_or constraint and two implications. The following
code samples output this truth table:
x = 0 y = 0 p = 0
x = 1 y = 0 p = 0
x = 0 y = 1 p = 0
x = 1 y = 1 p = 1Python code
# Snippet from ortools/sat/samples/boolean_product_sample_sat.py
"""Code sample that encodes the product of two Boolean variables."""
from ortools.sat.python import cp_model
def boolean_product_sample_sat():
"""Encoding of the product of two Boolean variables.
p == x * y, which is the same as p <=> x and y
"""
model = cp_model.CpModel()
x = model.new_bool_var('x')
y = model.new_bool_var('y')
p = model.new_bool_var('p')
# x and y implies p, rewrite as not(x and y) or p.
model.add_bool_or(~x, ~y, p)
# p implies x and y, expanded into two implications.
model.add_implication(p, x)
model.add_implication(p, y)
# Create a solver and solve.
solver = cp_model.CpSolver()
solution_printer = cp_model.VarArraySolutionPrinter([x, y, p])
solver.parameters.enumerate_all_solutions = True
solver.solve(model, solution_printer)
boolean_product_sample_sat()Go code
/* Detailed source-code truncated for AI context efficiency. */Ortools Sat Docs Channeling (`ortools/sat/docs/channeling.md`)
| home | boolean logic | integer arithmetic | channeling constraints | scheduling | Using the CP-SAT solver | Model manipulation | Troubleshooting | Python API |
|---|
Channeling constraints
https://developers.google.com/optimization/
A channeling constraint links variables inside a model. They're used when you
want to express a complicated relationship between variables, such as "if this
variable satisfies a condition, force another variable to a particular value".
Channeling is usually implemented using half-reified linear constraints: one
constraint implies another (a → b), but not necessarily the other way
around (a ← b).
If-Then-Else expressions
Let's say you want to implement the following: "If x is less than 5, set y
to 0. Otherwise, set y to 10-x". You can do this creating an intermediate
boolean variable b that is true if x is greater than or equal to 5, and
false otherwise:
b implies y == 10 - x
not(b) implies y == 0
These are implemented using the OnlyEnforceIf method as shown below.
Python code
/* Detailed source-code truncated for AI context efficiency. */C++ code
/* Detailed source-code truncated for AI context efficiency. */Java code
/* Detailed source-code truncated for AI context efficiency. */C\# code
/* Detailed source-code truncated for AI context efficiency. */Go code
/* Detailed source-code truncated for AI context efficiency. */This displays the following:
x=0 y=0 b=0
x=1 y=0 b=0
x=2 y=0 b=0
x=3 y=0 b=0
x=4 y=0 b=0
x=5 y=5 b=1
x=6 y=4 b=1
x=7 y=3 b=1
x=8 y=2 b=1
x=9 y=1 b=1
x=10 y=0 b=1Computing the index of the first Boolean variable set to true
A common request is to compute the index of the first Boolean variable set to
true. It can be encoded using a min_equality constraint. The index will be set
to the number of Boolean variables if they are all false.
Python code
/* Detailed source-code truncated for AI context efficiency. */This displays the following:
00000 -> 5
00001 -> 4
00010 -> 3
00011 -> 3
00100 -> 2
00101 -> 2
00110 -> 2
00111 -> 2
01000 -> 1
01001 -> 1
01010 -> 1
01011 -> 1
01100 -> 1
01101 -> 1
01110 -> 1
01111 -> 1
10000 -> 0
10001 -> 0
10010 -> 0
10011 -> 0
10100 -> 0
10101 -> 0
10110 -> 0
10111 -> 0
11000 -> 0
11001 -> 0
11010 -> 0
11011 -> 0
11100 -> 0
11101 -> 0
11110 -> 0
11111 -> 0A bin-packing problem
As another example of a channeling constraint, consider a bin packing problem in
which one part of the model computes the load of each bin, while another
maximizes the number of bins under a given threshold. To implement this, you can
channel the load of each bin into a set of boolean variables, each indicating
whether it's under the threshold.
To make this more concrete, let's say you have 10 bins of capacity 100, and
items to pack into the bins. You would like to maximize the number of bins that
can accept one emergency load of size 20.
To do this, you need to maximize the number of bins that have a load less than
80. In the code below, channeling is used to link the load and slack
variables together:
Python code
/* Detailed source-code truncated for AI context efficiency. */C++ code
/* Detailed source-code truncated for AI context efficiency. */Java code
/* Detailed source-code truncated for AI context efficiency. */C\# code
/* Detailed source-code truncated for AI context efficiency. */Go code
/* Detailed source-code truncated for AI context efficiency. */Ortools Sat Docs Integer Arithmetic (`ortools/sat/docs/integer_arithmetic.md`)
| home | boolean logic | integer arithmetic | channeling constraints | scheduling | Using the CP-SAT solver | Model manipulation | Troubleshooting | Python API |
|---|
Integer arithmetic recipes for the CP-SAT solver.
https://developers.google.com/optimization/
Introduction
The CP-SAT solver can express integer variables and constraints.
Integer variables
Integer variables can take on 64-bit signed integer values. When creating them,
a domain must be provided.
Interval domain
To create a single contiguous integer domain, just call the NewIntVar method
with the lower and upper bounds. For instance, to create a variable that can
take on any value between 0 and 10, inclusive:
- C++:
IntVar x = model.NewIntVar({0, 10}).WithName("x"); - Python:
x = model.NewIntVar(0, 10, 'x') - Java:
IntVar x = model.newIntVar(0, 10, "x"); - C#:
IntVar x = model.NewIntVar(0, 10, "x"); - Go:
x := model.NewIntVar(0, 10).WithName("x")
Non-contiguous domain
An instance of the Domain class is needed to create variables with
non-contiguous domains. Here, the variable can be any of 1, 3, 4, or 6:
- C++:
model.NewIntVar(Domain::FromValues({1, 3, 4, 6}); - Python:
model.NewIntVarFromDomain(cp_model.Domain.FromValues([1, 3, 4, 6]), 'x') - Java:
model.newIntVarFromDomain(Domain.fromValues(new long[] {1, 3, 4, 6}), "x"); - C#:
model.NewIntVarFromDomain(Domain.FromValues(new long[] {1, 3, 4, 6}), "x"); - Go:
model.NewIntVarFromDomain(cpmodel.FromValues([]int64{1, 3, 4, 6})
Variables can also be created using a list of intervals. Below, the variable
created is constrained to be 1, 2, 4, 5, or 6:
- C++:
model.NewIntVar(Domain::FromIntervals({{1, 2}, {4, 6}}); - Python:
model.NewIntVarFromDomain(cp_model.Domain.FromIntervals([[1, 2], [4, 6]]), 'x') - Java:
model.newIntVarFromDomain(Domain.fromIntervals(new long[][] {{1, 2}, {4, 6}}), "x"); - C#:
model.NewIntVarFromDomain(Domain.FromIntervals(new long[][] { new long[] {1, 2}, new long[] {4, 6} }), "x"); - Go:
model.NewIntVarFromDomain(cpmodel.FromIntervals( []cpmodel.ClosedInterval{{1, 2}, {4, 6}}))
Boolean variables
To create a Boolean variable, use the NewBoolVar method. Note that Boolean
variables are typed differently than integer variables, and that this type is
not uniform across languages.
- C++:
BoolVar x = model.NewBoolVar().WithName("x"); - Python:
x = model.NewBoolVar('x') - Java:
Literal x = model.newBoolVar("x"); - C#:
ILiteral x = model.NewBoolVar("x"); - Go:
x := model.NewBoolVar().WithName("x")
Other methods
To exclude a single value, use ranges combined with int64min and int64max
values, e.g., [[int64min, -3], [-1, int64max]], or use the Complement
method.
To create a variable with a single value domain, use the NewConstant() API (ornewConstant() in Java).
Linear constraints
C++, Java, and Go linear constraints and linear expressions
C++, Java, and Go APIs do not use arithmetic operators (+, *, -,
<=...). Linear constraints are created using a method of the model factory, such
as cp_model.AddEquality(x, 3) in C++, cp_model.addGreaterOrEqual(x, 10) in
Java, or model.AddLessThan(x, cpmodel.NewConstant(5)) in Go.
Furthermore, helper methods can be used to create sums and scalar products likeLinearExpr::Sum({x, y, z}) in C++, LinearExpr.weightedSum(new IntVar[] {x, y, z}, new long[] {1, 2, 3}) in Java, and cpmodel.NewLinearExpr().AddSum(x, y, z) in Go.
Python and C\# linear constraints and linear expressions
Python and C# CP-SAT APIs support general linear arithmetic (+, *, -,
==, >=, >, <, <=, !=). You need to use the Add method of the cp_model, as incp_model.Add(x + y != 3).
Generic linear constraint
in all languages, the cp_model factory offers a generic method to constrain
a linear expression to be in a domain. This is used in the step function
examples below.
Limitations
Everything must be linear. Multiplying two variables is not supported with
this API; instead,model.AddMultiplicationEquality()must be used.In C++, there is a typing issue when using an array of Boolean variables in
a sum or a scalar product. Use theLinearExpr.BooleanSum()method instead.The Python construct
sum()is supported, butmin(),max()or anynumpyconstructs likenp.unique()are not.
Rabbits and Pheasants examples
Let's solve a simple children's puzzle: the Rabbits and Pheasants problem.
In a field of rabbits and pheasants, there are 20 heads and 56 legs. How many
rabbits and pheasants are there?
Python code
# Snippet from ortools/sat/samples/rabbits_and_pheasants_sat.py
"""Rabbits and Pheasants quizz."""
from ortools.sat.python import cp_model
def rabbits_and_pheasants_sat():
"""Solves the rabbits + pheasants problem."""
model = cp_model.CpModel()
r = model.new_int_var(0, 100, 'r')
p = model.new_int_var(0, 100, 'p')
# 20 heads.
model.add(r + p == 20)
# 56 legs.
model.add(4 * r + 2 * p == 56)
# Solves and prints out the solution.
solver = cp_model.CpSolver()
status = solver.solve(model)
if status == cp_model.OPTIMAL:
print(f'{solver.value(r)} rabbits and {solver.value(p)} pheasants')
rabbits_and_pheasants_sat()C++ code
// Snippet from ortools/sat/samples/rabbits_and_pheasants_sat.cc
#include <stdlib.h>
#include "ortools/base/init_google.h"
#include "ortools/base/logging.h"
#include "absl/base/log_severity.h"
#include "absl/log/globals.h"
#include "ortools/sat/cp_model.h"
#include "ortools/sat/cp_model.pb.h"
#include "ortools/sat/cp_model_solver.h"
#include "ortools/util/sorted_interval_list.h"
namespace operations_research {
namespace sat {
void RabbitsAndPheasantsSat() {
CpModelBuilder cp_model;
const Domain all_animals(0, 20);
const IntVar rabbits = cp_model.NewIntVar(all_animals).WithName("rabbits");
const IntVar pheasants =
cp_model.NewIntVar(all_animals).WithName("pheasants");
cp_model.AddEquality(rabbits + pheasants, 20);
cp_model.AddEquality(4 * rabbits + 2 * pheasants, 56);
const CpSolverResponse response = Solve(cp_model.Build());
if (response.status() == CpSolverStatus::OPTIMAL) {
// Get the value of x in the solution.
LOG(INFO) << SolutionIntegerValue(response, rabbits) << " rabbits, and "
<< SolutionIntegerValue(response, pheasants) << " pheasants";
}
}
} // namespace sat
} // namespace operations_research
int main(int argc, char* argv[]) {
InitGoogle(argv[0], &argc, &argv, true);
absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo);
operations_research::sat::RabbitsAndPheasantsSat();
return EXIT_SUCCESS;
}Java code
// Snippet from ortools/sat/samples/RabbitsAndPheasantsSat.java
package com.google.ortools.sat.samples;
import com.google.ortools.Loader;
import com.google.ortools.sat.CpSolverStatus;
import com.google.ortools.sat.CpModel;
import com.google.ortools.sat.CpSolver;
import com.google.ortools.sat.IntVar;
import com.google.ortools.sat.LinearExpr;
/**
* In a field of rabbits and pheasants, there are 20 heads and 56 legs. How many rabbits and
* pheasants are there?
*/
public class RabbitsAndPheasantsSat {
public static void main(String[] args) throws Exception {
Loader.loadNativeLibraries();
// Creates the model.
CpModel model = new CpModel();
// Creates the variables.
IntVar r = model.newIntVar(0, 100, "r");
IntVar p = model.newIntVar(0, 100, "p");
// 20 heads.
model.addEquality(LinearExpr.newBuilder().add(r).add(p), 20);
// 56 legs.
model.addEquality(LinearExpr.newBuilder().addTerm(r, 4).addTerm(p, 2), 56);
// Creates a solver and solves the model.
CpSolver solver = new CpSolver();
CpSolverStatus status = solver.solve(model);
if (status == CpSolverStatus.OPTIMAL) {
System.out.println(solver.value(r) + " rabbits, and " + solver.value(p) + " pheasants");
}
}
}C\# code
// Snippet from ortools/sat/samples/RabbitsAndPheasantsSat.cs
using System;
using Google.OrTools.Sat;
public class RabbitsAndPheasantsSat
{
static void Main()
{
// Creates the model.
CpModel model = new CpModel();
// Creates the variables.
IntVar r = model.NewIntVar(0, 100, "r");
IntVar p = model.NewIntVar(0, 100, "p");
// 20 heads.
model.Add(r + p == 20);
// 56 legs.
model.Add(4 * r + 2 * p == 56);
// Creates a solver and solves the model.
CpSolver solver = new CpSolver();
CpSolverStatus status = solver.Solve(model);
if (status == CpSolverStatus.Optimal)
{
Console.WriteLine(solver.Value(r) + " rabbits, and " + solver.Value(p) + " pheasants");
}
}
}Go code
/* Detailed source-code truncated for AI context efficiency. */Earliness-Tardiness cost function.
Let's encode a useful convex piecewise linear function that often appears in
scheduling. You want to encourage a delivery to happen during a time window. If
you deliver early, you pay a linear penalty on waiting time. If you deliver
late, you pay a linear penalty on the delay.
Because the function is convex, you can define all affine functions, and take
the max of them to define the piecewise linear function.
The following samples output:
x=0 expr=40
x=1 expr=32
x=2 expr=24
x=3 expr=16
x=4 expr=8
x=5 expr=0
x=6 expr=0
x=7 expr=0
x=8 expr=0
x=9 expr=0
x=10 expr=0
x=11 expr=0
x=12 expr=0
x=13 expr=0
x=14 expr=0
x=15 expr=0
x=16 expr=12
x=17 expr=24
x=18 expr=36
x=19 expr=48
x=20 expr=60Python code
/* Detailed source-code truncated for AI context efficiency. */C++ code
/* Detailed source-code truncated for AI context efficiency. */Java code
/* Detailed source-code truncated for AI context efficiency. */C\# code
/* Detailed source-code truncated for AI context efficiency. */Go code
/* Detailed source-code truncated for AI context efficiency. */Step function.
Let's encode a step function. We will use one Boolean variable per step value,
and filter the admissible domain of the input variable with this variable.
The following samples output:
x=0 expr=2
x=1 expr=2
x=3 expr=2
x=4 expr=2
x=5 expr=0
x=6 expr=0
x=7 expr=3
x=8 expr=0
x=9 expr=0
x=10 expr=0
x=11 expr=2
x=12 expr=2
x=13 expr=2
x=14 expr=2
x=15 expr=2
x=16 expr=2
x=17 expr=2
x=18 expr=2
x=19 expr=2
x=20 expr=2Python code
/* Detailed source-code truncated for AI context efficiency. */C++ code
/* Detailed source-code truncated for AI context efficiency. */Java code
/* Detailed source-code truncated for AI context efficiency. */C\# code
/* Detailed source-code truncated for AI context efficiency. */Go code
/* Detailed source-code truncated for AI context efficiency. */Product of a Boolean variable and an integer variable
This sample implements an helper function that will take two variables (Boolean
and integer), and will return a new integer variable that is constrained to be
equal to the product of the two variables.
The following samples output:
x=1 b=0 p=0
x=2 b=0 p=0
x=3 b=0 p=0
x=5 b=0 p=0
x=6 b=0 p=0
x=7 b=0 p=0
x=9 b=0 p=0
x=10 b=0 p=0
x=1 b=1 p=1
x=2 b=1 p=2
x=3 b=1 p=3
x=5 b=1 p=5
x=6 b=1 p=6
x=7 b=1 p=7
x=9 b=1 p=9
x=10 b=1 p=10Python code
/* Detailed source-code truncated for AI context efficiency. */Scanning the domain of variables.
In this example, we will implement the all_different_except_0 constraint. This
constraint is useful as it expresses that 2 active assignment should be
different, but we do not care when they are inactive (represented by being
assigned a zero value).
To implement this constraint, we will collect all values in the initial domain
of all variables and attach Boolean variables for each of them. This requires
reading back the values from the model.
Python code
/* Detailed source-code truncated for AI context efficiency. */Ortools Sat Docs Model (`ortools/sat/docs/model.md`)
| home | boolean logic | integer arithmetic | channeling constraints | scheduling | Using the CP-SAT solver | Model manipulation | Troubleshooting | Python API |
|---|
Model manipulation
https://developers.google.com/optimization/
Introduction
In all languages, the CpModel class is a thin wrapper around a
protocol buffer object
cp_model.proto.
Some functionalities require using the cp_model protobuf directly. To write code
that manipulates this protobuf, one must understand how modeling objects
(variables, constraints) are mapped onto the protobuf.
The fundamental idea is that the cp_model protobuf object contains a list of
variables and a list of constraints. Constraints are built with variables and
reference them using their indices in the list of variables.
In all languages, the IntVar class has a method to query their index in the
model list of variables:
- C++:
var.index() - Python:
var.Index() - Java:
var.getIndex() - C#:
var.Indexorvar.GetIndex() - Go:
var.Index()
The implementation of Boolean literals differs across languages.
- C++: The
BoolVarclass is a separate class from theIntVarclass. ABoolVarobject can implicitly be cast to anIntVarobject. ABoolVar
object has two important methods:index()andNot().Not()returns
anotherBoolVarwith a different index:b.Not().index() = -b.index() - 1. - Python: There is no
BoolVarclass. A Boolean variable is defined as anIntVarwith a Boolean domain (0 or 1). TheNot()method returns a
different class. Both theIntVarclass and the negation class implementIndex()andNot(). - Java: There is no
BoolVarclass. A Boolean variable is defined as anIntVarwith a Boolean domain (0 or 1). Boolean variables and their
negation implement theLiteralinterface. This interface definesgetIndex()andnot()methods. - C#: Boolean variables are defined as an
IntVarwith a Boolean domain
(0 or 1). Boolean variables and their negations implement theILiteral
interface. This interface definesGetIndex()andNot()methods. - Go: The
BoolVarclass is a separate class from theIntVarclass. ABoolVarobject has two important methods:Not()andIndex().Not()
returned anotherBoolVarwith a different index:b.Not.Index() = -b.Index() - 1
Solution hinting
A solution is a partial assignment of variables to values that the search will
try to stick to. It is meant to guide the search with external knowledge towards
good solutions.
The CpModelProto message has a solution_hint field. This field is aPartialVariableAssignment message that contains two parallel vectors (variable
indices and hint values).
Adding solution hinting to a model implies filling these two fields. This is
done by the addHint, AddHint, or SetHint methods.
Some remarks:
- A solution hint is not a hard constraint. The solver will try to use it in
search for a while, and then gradually revert to its default search
technique. - It's OK to have an infeasible solution hint. It can still be helpful in some
repair procedures, where one wants to identify a solution close to a
previously violated state. - Solution hints don't need to use all variables: partial solution hints are
fine.
Python code
# Snippet from ortools/sat/samples/solution_hinting_sample_sat.py
from ortools.sat.python import cp_model
def solution_hinting_sample_sat():
"""Showcases solution hinting."""
# Creates the model.
model = cp_model.CpModel()
# Creates the variables.
num_vals = 3
x = model.new_int_var(0, num_vals - 1, 'x')
y = model.new_int_var(0, num_vals - 1, 'y')
z = model.new_int_var(0, num_vals - 1, 'z')
# Creates the constraints.
model.add(x != y)
model.maximize(x + 2 * y + 3 * z)
# Solution hinting: x <- 1, y <- 2
model.add_hint(x, 1)
model.add_hint(y, 2)
# Creates a solver and solves.
solver = cp_model.CpSolver()
solution_printer = cp_model.VarArrayAndObjectiveSolutionPrinter([x, y, z])
status = solver.solve(model, solution_printer)
print(f'Status = {solver.status_name(status)}')
print(f'Number of solutions found: {solution_printer.solution_count}')
solution_hinting_sample_sat()C++ code
/* Detailed source-code truncated for AI context efficiency. */Java code
/* Detailed source-code truncated for AI context efficiency. */C\# code
/* Detailed source-code truncated for AI context efficiency. */Go code
/* Detailed source-code truncated for AI context efficiency. */Model copy
The CpModel classes supports deep copy from a previous model. This is useful
to solve variations of a base model. The trick is to recover the copies of the
variables in the original model to be able to manipulate the new model. This is
illustrated in the following examples.
Python code
The deep copy python mechanism relies on thecopy Python Standard Library.
# Snippet from ortools/sat/samples/clone_model_sample_sat.py
import copy
from ortools.sat.python import cp_model
def clone_model_sample_sat():
"""Showcases cloning a model."""
# Creates the model.
model = cp_model.CpModel()
# Creates the variables.
num_vals = 3
x = model.new_int_var(0, num_vals - 1, 'x')
y = model.new_int_var(0, num_vals - 1, 'y')
z = model.new_int_var(0, num_vals - 1, 'z')
# Creates the constraints.
model.add(x != y)
model.maximize(x + 2 * y + 3 * z)
# Creates a solver and solves.
solver = cp_model.CpSolver()
status = solver.solve(model)
if status == cp_model.OPTIMAL:
print(
'Optimal value of the original model: {}'.format(solver.objective_value)
)
# Creates a dictionary holding the model and the variables you want to use.
to_clone = {
'model': model,
'x': x,
'y': y,
'z': z,
}
# Deep copy the dictionary.
clone = copy.deepcopy(to_clone)
# Retrieve the cloned model and variables.
cloned_model: cp_model.CpModel = clone['model']
cloned_x = clone['x']
cloned_y = clone['y']
cloned_model.add(cloned_x + cloned_y <= 1)
status = solver.solve(cloned_model)
if status == cp_model.OPTIMAL:
print(
'Optimal value of the modified model: {}'.format(solver.objective_value)
)
clone_model_sample_sat()C++ code
/* Detailed source-code truncated for AI context efficiency. */Java code
/* Detailed source-code truncated for AI context efficiency. */Ortools Sat Docs Scheduling (`ortools/sat/docs/scheduling.md`)
| home | boolean logic | integer arithmetic | channeling constraints | scheduling | Using the CP-SAT solver | Model manipulation | Troubleshooting | Python API |
|---|
Scheduling recipes for the CP-SAT solver.
https://developers.google.com/optimization/
Introduction
Scheduling in Operations Research involves problems of tasks, resources and
times. In general, scheduling problems have the following features: fixed or
variable durations, alternate ways of performing the same task, mutual
exclusivity between tasks, and temporal relations between tasks.
Interval variables
Intervals are constraints containing three constant of affine expressions
(start, size, and end). Creating an interval constraint will enforce that `start
- size == end`.
The more general API uses three expressions to define the interval. If the size
is fixed, a simpler API uses the start expression and the fixed size.
Creating these intervals is illustrated in the following code snippets.
Python code
# Snippet from ortools/sat/samples/interval_sample_sat.py
"""Code sample to demonstrates how to build an interval."""
from ortools.sat.python import cp_model
def interval_sample_sat():
"""Showcases how to build interval variables."""
model = cp_model.CpModel()
horizon = 100
# An interval can be created from three affine expressions.
start_var = model.new_int_var(0, horizon, 'start')
duration = 10 # Python cp/sat code accept integer variables or constants.
end_var = model.new_int_var(0, horizon, 'end')
interval_var = model.new_interval_var(
start_var, duration, end_var + 2, 'interval'
)
print(f'interval = {repr(interval_var)}')
# If the size is fixed, a simpler version uses the start expression and the
# size.
fixed_size_interval_var = model.new_fixed_size_interval_var(
start_var, 10, 'fixed_size_interval_var'
)
print(f'fixed_size_interval_var = {repr(fixed_size_interval_var)}')
# A fixed interval can be created using the same API.
fixed_interval = model.new_fixed_size_interval_var(5, 10, 'fixed_interval')
print(f'fixed_interval = {repr(fixed_interval)}')
interval_sample_sat()C++ code
/* Detailed source-code truncated for AI context efficiency. */Java code
// Snippet from ortools/sat/samples/IntervalSampleSat.java
package com.google.ortools.sat.samples;
import com.google.ortools.Loader;
import com.google.ortools.sat.CpModel;
import com.google.ortools.sat.IntVar;
import com.google.ortools.sat.IntervalVar;
import com.google.ortools.sat.LinearExpr;
/** Code sample to demonstrates how to build an interval. */
public class IntervalSampleSat {
public static void main(String[] args) throws Exception {
Loader.loadNativeLibraries();
CpModel model = new CpModel();
int horizon = 100;
// An interval can be created from three affine expressions.
IntVar startVar = model.newIntVar(0, horizon, "start");
IntVar endVar = model.newIntVar(0, horizon, "end");
IntervalVar intervalVar =
model.newIntervalVar(
startVar,
LinearExpr.constant(10),
LinearExpr.newBuilder().add(endVar).add(2),
"interval");
System.out.println(intervalVar);
// If the size is fixed, a simpler version uses the start expression and the size.
IntervalVar fixedSizeIntervalVar =
model.newFixedSizeIntervalVar(startVar, 10, "fixed_size_interval_var");
System.out.println(fixedSizeIntervalVar);
// A fixed interval can be created using another method.
IntervalVar fixedInterval = model.newFixedInterval(5, 10, "fixed_interval");
System.out.println(fixedInterval);
}
}C\# code
// Snippet from ortools/sat/samples/IntervalSampleSat.cs
using System;
using Google.OrTools.Sat;
public class IntervalSampleSat
{
static void Main()
{
CpModel model = new CpModel();
int horizon = 100;
// C# code supports constant of affine expressions.
IntVar start_var = model.NewIntVar(0, horizon, "start");
IntVar end_var = model.NewIntVar(0, horizon, "end");
IntervalVar interval = model.NewIntervalVar(start_var, 10, end_var + 2, "interval");
Console.WriteLine(interval);
// If the size is fixed, a simpler version uses the start expression, the size and the
// literal.
IntervalVar fixedSizeIntervalVar = model.NewFixedSizeIntervalVar(start_var, 10, "fixed_size_interval_var");
Console.WriteLine(fixedSizeIntervalVar);
// A fixed interval can be created using the same API.
IntervalVar fixedInterval = model.NewFixedSizeIntervalVar(5, 10, "fixed_interval");
Console.WriteLine(fixedInterval);
}
}Go code
/* Detailed source-code truncated for AI context efficiency. */Optional intervals
An interval can be marked as optional. The presence of this interval is
controlled by a literal. The no overlap and cumulative constraints
understand these presence literals, and correctly ignore inactive intervals.
Python code
# Snippet from ortools/sat/samples/optional_interval_sample_sat.py
"""Code sample to demonstrates how to build an optional interval."""
from ortools.sat.python import cp_model
def optional_interval_sample_sat():
"""Showcases how to build optional interval variables."""
model = cp_model.CpModel()
horizon = 100
# An interval can be created from three affine expressions.
start_var = model.new_int_var(0, horizon, 'start')
duration = 10 # Python cp/sat code accept integer variables or constants.
end_var = model.new_int_var(0, horizon, 'end')
presence_var = model.new_bool_var('presence')
interval_var = model.new_optional_interval_var(
start_var, duration, end_var + 2, presence_var, 'interval'
)
print(f'interval = {repr(interval_var)}')
# If the size is fixed, a simpler version uses the start expression and the
# size.
fixed_size_interval_var = model.new_optional_fixed_size_interval_var(
start_var, 10, presence_var, 'fixed_size_interval_var'
)
print(f'fixed_size_interval_var = {repr(fixed_size_interval_var)}')
# A fixed interval can be created using the same API.
fixed_interval = model.new_optional_fixed_size_interval_var(
5, 10, presence_var, 'fixed_interval'
)
print(f'fixed_interval = {repr(fixed_interval)}')
optional_interval_sample_sat()C++ code
/* Detailed source-code truncated for AI context efficiency. */Java code
/* Detailed source-code truncated for AI context efficiency. */C\# code
// Snippet from ortools/sat/samples/OptionalIntervalSampleSat.cs
using System;
using Google.OrTools.Sat;
public class OptionalIntervalSampleSat
{
static void Main()
{
CpModel model = new CpModel();
int horizon = 100;
// C# code supports constant of affine expressions.
IntVar start_var = model.NewIntVar(0, horizon, "start");
IntVar end_var = model.NewIntVar(0, horizon, "end");
BoolVar presence_var = model.NewBoolVar("presence");
IntervalVar interval = model.NewOptionalIntervalVar(start_var, 10, end_var + 2, presence_var, "interval");
Console.WriteLine(interval);
// If the size is fixed, a simpler version uses the start expression, the size and the
// literal.
IntervalVar fixedSizeIntervalVar =
model.NewOptionalFixedSizeIntervalVar(start_var, 10, presence_var, "fixed_size_interval_var");
Console.WriteLine(fixedSizeIntervalVar);
// A fixed interval can be created using the same API.
IntervalVar fixedInterval = model.NewOptionalFixedSizeIntervalVar(5, 10, presence_var, "fixed_interval");
Console.WriteLine(fixedInterval);
}
}Go code
// Snippet from ortools/sat/samples/optional_interval_sample_sat.go
// The optional_interval_sample_sat command is an example of an Interval variable that is
// marked as optional.
package main
import (
"fmt"
log "github.com/golang/glog"
"github.com/google/or-tools/ortools/sat/go/cpmodel"
)
const horizon = 100
func optionalIntervalSampleSat() error {
model := cpmodel.NewCpModelBuilder()
domain := cpmodel.NewDomain(0, horizon)
x := model.NewIntVarFromDomain(domain).WithName("x")
y := model.NewIntVar(2, 4).WithName("y")
z := model.NewIntVarFromDomain(domain).WithName("z")
presenceVar := model.NewBoolVar().WithName("presence")
// An optional interval can be created from three affine expressions and a BoolVar.
intervalVar := model.NewOptionalIntervalVar(x, y, cpmodel.NewConstant(2).Add(z), presenceVar).WithName("interval")
// If the size is fixed, a simpler version uses the start expression and the size.
fixedSizeIntervalVar := model.NewOptionalFixedSizeIntervalVar(x, 10, presenceVar).WithName("fixedSizeInterval")
m, err := model.Model()
if err != nil {
return fmt.Errorf("failed to instantiate the CP model: %w", err)
}
fmt.Printf("%v\n", m.GetConstraints()[intervalVar.Index()])
fmt.Printf("%v\n", m.GetConstraints()[fixedSizeIntervalVar.Index()])
return nil
}
func main() {
if err := optionalIntervalSampleSat(); err != nil {
log.Exitf("optionalIntervalSampleSat returned with error: %v", err)
}
}Time relations between intervals
Temporal relations between intervals can be expressions using linear
inequalities involving the start and end expressions of the intervals.
As seen above, the factory methods on the model used to build intervals accept
1-var affine expression (a * var + b, a, b integer constants) as arguments to
the start, size, and end parameters.
Once the interval is build, these same expressions can be queries usingStartExpr(), SizeExpr() and EndExpr() in C++ and C#, start_expr(), size_expr(), and end_expr() in python, and getStartExpr(), getSizeExpr(), and getEndExpr() in Java.
If one or both intervals are optional, then these inequalities must be reified
by the presence literals of the optional intervals used.
Python code
/* Detailed source-code truncated for AI context efficiency. */NoOverlap constraint
A no_overlap constraint simply states that all intervals are disjoint. It is
built with a list of interval variables. Fixed intervals are useful for
excluding part of the timeline.
In the following examples, you want to schedule 3 tasks on 3 weeks excluding
weekends, making the final day as early as possible.
Python code
/* Detailed source-code truncated for AI context efficiency. */C++ code
/* Detailed source-code truncated for AI context efficiency. */Java code
/* Detailed source-code truncated for AI context efficiency. */C\# code
/* Detailed source-code truncated for AI context efficiency. */Go code
/* Detailed source-code truncated for AI context efficiency. */Cumulative constraint with min and max capacity profile.
A cumulative constraint takes a list of intervals, and a list of demands, and a
capacity. It enforces that at any time point, the sum of demands of tasks active
at that time point is less than a given capacity.
Modeling a non constant max profile can be done using fixed (interval, demand)
to occupy the capacity between the actual profile and it max capacity.
Modeling a non zero min profile can be done using fixed (interval, demand) on
the complementary cumulative constraint.
Python code
/* Detailed source-code truncated for AI context efficiency. */Alternative resources for one interval
Ranking tasks in a disjunctive resource
To rank intervals in a no_overlap constraint, you will count the number of
performed intervals that precede each interval.
This is slightly complicated if some interval variables are optional. To
implement it, you will create a matrix of precedences boolean variables.precedences[i][j] is set to true if and only if interval i is performed,
interval j is performed, and if the start of i is before the start of j.
Furthermore, precedences[i][i] is set to be equal to presences[i]. This way,
you can define the rank of an interval i as sum over j(precedences[j][i]) - 1. If the interval is not performed, the rank computed as -1, if the interval
is performed, its presence variable negates the -1, and the formula counts the
number of other intervals that precede it.
Python code
/* Detailed source-code truncated for AI context efficiency. */C++ code
/* Detailed source-code truncated for AI context efficiency. */Java code
/* Detailed source-code truncated for AI context efficiency. */C\# code
/* Detailed source-code truncated for AI context efficiency. */Go code
/* Detailed source-code truncated for AI context efficiency. */Ranking tasks in a disjunctive resource with a circuit constraint.
To rank intervals in a no_overlap constraint, you will use a circuit constraint
to perform the transitive reduction from precedences to successors.
This is slightly complicated if some interval variables are optional, and you
need to take into account the case where no task is performed.
Python code
/* Detailed source-code truncated for AI context efficiency. */Intervals spanning over breaks in the calendar
Sometimes, a task can be interrupted by a break (overnight, lunch break). In
that context, although the processing time of the task is the same, the duration
can vary.
To implement this feature, you will have the duration of the task be a function
of the start of the task. This is implemented using channeling constraints.
The following code displays:
start=8 duration=3 across=0
start=9 duration=3 across=0
start=10 duration=3 across=0
start=11 duration=4 across=1
start=12 duration=4 across=1
start=14 duration=3 across=0
start=15 duration=3 across=0Python code
/* Detailed source-code truncated for AI context efficiency. */Detecting if two intervals overlap.
You want a Boolean variable to be true if and only if two intervals overlap. To
enforce this, you will create 3 Boolean variables, link two of them to the
relative positions of the two intervals, and define the third one using the
other two Boolean variables.
There are two ways of linking the three Boolean variables. The first version
uses one clause and two implications. Propagation will be faster using this
version. The second version uses a sum(..) == 1 equation. It is more compact,
but assumes the length of the two intervals is > 0.
Note that you need to create the intervals to enforce start + size == end, but
you do not actually use them in this code sample.
The following code displays
start_a=0 start_b=0 a_overlaps_b=1
start_a=0 start_b=1 a_overlaps_b=1
start_a=0 start_b=2 a_overlaps_b=1
start_a=0 start_b=3 a_overlaps_b=0
start_a=0 start_b=4 a_overlaps_b=0
start_a=0 start_b=5 a_overlaps_b=0
start_a=1 start_b=0 a_overlaps_b=1
start_a=1 start_b=1 a_overlaps_b=1
start_a=1 start_b=2 a_overlaps_b=1
start_a=1 start_b=3 a_overlaps_b=1
start_a=1 start_b=4 a_overlaps_b=0
start_a=1 start_b=5 a_overlaps_b=0
...Python code
/* Detailed source-code truncated for AI context efficiency. */Transitions in a no_overlap constraint
In some scheduling problems, switching between certain type of tasks on a
machine implies some penalty, and/or some delay. Implementing these
functionalities implies knowing which are the direct successors of each task.
The circuit constraint is used to perform the transitive reduction from
precedences to successors. Once this is done, it is straightforward to use the
successor literals to implement the penalties or the delays.
Python code
/* Detailed source-code truncated for AI context efficiency. */Managing sequences in a no_overlap constraint
In some scheduling problems, there can be constraints on sequence of tasks. For
instance, tasks of a given type may have limit on the min or max number of
contiguous tasks of the same type.
The circuit constraint is used to constraint the length of any sequence of tasks
of the same type. It uses counters attached to each task, and transition
literals to maintain these counters.
Python code
/* Detailed source-code truncated for AI context efficiency. */Convex hull of a set of intervals
Ortools Sat Docs Solver (`ortools/sat/docs/solver.md`)
| home | boolean logic | integer arithmetic | channeling constraints | scheduling | Using the CP-SAT solver | Model manipulation | Troubleshooting | Python API |
|---|
Solving a CP-SAT model
https://developers.google.com/optimization/
Changing the parameters of the solver
The SatParameters protobuf encapsulates the set of parameters of a CP-SAT
solver. The most useful one is the time limit.
Specifying the time limit in Python
# Snippet from ortools/sat/samples/solve_with_time_limit_sample_sat.py
"""Solves a problem with a time limit."""
from ortools.sat.python import cp_model
def solve_with_time_limit_sample_sat():
"""Minimal CP-SAT example to showcase calling the solver."""
# Creates the model.
model = cp_model.CpModel()
# Creates the variables.
num_vals = 3
x = model.new_int_var(0, num_vals - 1, 'x')
y = model.new_int_var(0, num_vals - 1, 'y')
z = model.new_int_var(0, num_vals - 1, 'z')
# Adds an all-different constraint.
model.add(x != y)
# Creates a solver and solves the model.
solver = cp_model.CpSolver()
# Sets a time limit of 10 seconds.
solver.parameters.max_time_in_seconds = 10.0
status = solver.solve(model)
if status == cp_model.OPTIMAL:
print(f'x = {solver.value(x)}')
print(f'y = {solver.value(y)}')
print(f'z = {solver.value(z)}')
solve_with_time_limit_sample_sat()Specifying the time limit in C++
/* Detailed source-code truncated for AI context efficiency. */Specifying the time limit in Java
// Snippet from ortools/sat/samples/SolveWithTimeLimitSampleSat.java
package com.google.ortools.sat.samples;
import com.google.ortools.Loader;
import com.google.ortools.sat.CpSolverStatus;
import com.google.ortools.sat.CpModel;
import com.google.ortools.sat.CpSolver;
import com.google.ortools.sat.IntVar;
/** Solves a problem with a time limit. */
public final class SolveWithTimeLimitSampleSat {
public static void main(String[] args) {
Loader.loadNativeLibraries();
// Create the model.
CpModel model = new CpModel();
// Create the variables.
int numVals = 3;
IntVar x = model.newIntVar(0, numVals - 1, "x");
IntVar y = model.newIntVar(0, numVals - 1, "y");
IntVar z = model.newIntVar(0, numVals - 1, "z");
// Create the constraint.
model.addDifferent(x, y);
// Create a solver and solve the model.
CpSolver solver = new CpSolver();
solver.getParameters().setMaxTimeInSeconds(10.0);
CpSolverStatus status = solver.solve(model);
if (status == CpSolverStatus.OPTIMAL) {
System.out.println("x = " + solver.value(x));
System.out.println("y = " + solver.value(y));
System.out.println("z = " + solver.value(z));
}
}
private SolveWithTimeLimitSampleSat() {}
}Specifying the time limit in C\#.
Parameters must be passed as string to the solver.
// Snippet from ortools/sat/samples/SolveWithTimeLimitSampleSat.cs
using System;
using Google.OrTools.Sat;
public class SolveWithTimeLimitSampleSat
{
static void Main()
{
// Creates the model.
CpModel model = new CpModel();
// Creates the variables.
int num_vals = 3;
IntVar x = model.NewIntVar(0, num_vals - 1, "x");
IntVar y = model.NewIntVar(0, num_vals - 1, "y");
IntVar z = model.NewIntVar(0, num_vals - 1, "z");
// Adds a different constraint.
model.Add(x != y);
// Creates a solver and solves the model.
CpSolver solver = new CpSolver();
// Adds a time limit. Parameters are stored as strings in the solver.
solver.StringParameters = "max_time_in_seconds:10.0";
CpSolverStatus status = solver.Solve(model);
if (status == CpSolverStatus.Optimal)
{
Console.WriteLine("x = " + solver.Value(x));
Console.WriteLine("y = " + solver.Value(y));
Console.WriteLine("z = " + solver.Value(z));
}
}
}Specifying the time limit in Go
/* Detailed source-code truncated for AI context efficiency. */Printing intermediate solutions
In an optimization model, you can print intermediate solutions. For all
languages except Go, you need to register a callback on the solver that will be
called at each solution. For Go, callbacks are not implemented, but you can
still get the intermediate solutions in the response.
The exact implementation depends on the target language.
Python code
/* Detailed source-code truncated for AI context efficiency. */C++ code
/* Detailed source-code truncated for AI context efficiency. */Java code
/* Detailed source-code truncated for AI context efficiency. */C\# code
/* Detailed source-code truncated for AI context efficiency. */Go code
/* Detailed source-code truncated for AI context efficiency. */Searching for all solutions in a satisfiability model
In an non-optimization model, you can search for all solutions. For all
languages except Go, you need to register a callback on the solver that will be
called at each solution. For Go, callbacks are not implemented, but you can
still get the intermediate solutions in the response.
Note that it does not work in parallel (i. e. parameter num_workers > 1).
It also does not work if the model contains an objective.
The method will return the following:
- FEASIBLE if some solutions have been found
- INFEASIBLE if the solver has proved there are no solution
- OPTIMAL if all solutions have been found
The exact implementation depends on the target language.
Python code
To search for all solutions, use the Solve() method after setting the correct
parameter.
# Snippet from ortools/sat/samples/search_for_all_solutions_sample_sat.py
from ortools.sat.python import cp_model
class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):
"""Print intermediate solutions."""
def __init__(self, variables: list[cp_model.IntVar]):
cp_model.CpSolverSolutionCallback.__init__(self)
self.__variables = variables
self.__solution_count = 0
def on_solution_callback(self) -> None:
self.__solution_count += 1
for v in self.__variables:
print(f'{v}={self.value(v)}', end=' ')
print()
@property
def solution_count(self) -> int:
return self.__solution_count
def search_for_all_solutions_sample_sat():
"""Showcases calling the solver to search for all solutions."""
# Creates the model.
model = cp_model.CpModel()
# Creates the variables.
num_vals = 3
x = model.new_int_var(0, num_vals - 1, 'x')
y = model.new_int_var(0, num_vals - 1, 'y')
z = model.new_int_var(0, num_vals - 1, 'z')
# Create the constraints.
model.add(x != y)
# Create a solver and solve.
solver = cp_model.CpSolver()
solution_printer = VarArraySolutionPrinter([x, y, z])
# Enumerate all solutions.
solver.parameters.enumerate_all_solutions = True
# Solve.
status = solver.solve(model, solution_printer)
print(f'Status = {solver.status_name(status)}')
print(f'Number of solutions found: {solution_printer.solution_count}')
search_for_all_solutions_sample_sat()C++ code
To search for all solutions, a parameter of the SAT solver must be changed.
/* Detailed source-code truncated for AI context efficiency. */Java code
As in Python, CpSolver.solve() must be called after setting the correct
parameter.
/* Detailed source-code truncated for AI context efficiency. */C\# code
As in Python, CpSolver.Solve() must be called after setting the correct string
parameter.
/* Detailed source-code truncated for AI context efficiency. */Go code
To search for all solutions, a parameter of the SAT solver must be changed.
/* Detailed source-code truncated for AI context efficiency. */Stopping search early
Sometimes, you might decide that the current solution is good enough. In this
section, we will enumerate all possible combinations of three variables, and
stop after the fifth combination found.
Python code
You can stop the search by calling StopSearch() inside of
CpSolverSolutionCallback.OnSolutionCallback().
/* Detailed source-code truncated for AI context efficiency. */C++ code
Stopping search is done by registering an atomic bool on the model-owned time
limit, and setting that bool to true.
/* Detailed source-code truncated for AI context efficiency. */Java code
Stopping search is performed by calling stopSearch() inside of
CpSolverSolutionCallback.onSolutionCallback().
/* Detailed source-code truncated for AI context efficiency. */C\# code
Stopping search is performed by calling StopSearch() inside of
CpSolverSolutionCallback.OnSolutionCallback().
/* Detailed source-code truncated for AI context efficiency. */Ortools Sat Docs Troubleshooting (`ortools/sat/docs/troubleshooting.md`)
| home | boolean logic | integer arithmetic | channeling constraints | scheduling | Using the CP-SAT solver | Model manipulation | Troubleshooting | Python API |
|---|
Troubleshooting
Enable logging
CP-SAT supports extensive logging of its internals.
To enable it, just set the parameter log_search_progress to true.
A good explanation of the log can be found in the
cpsat-primer.
Exporting the model
As seen in the model section, the model is stored as a protocol
buffer object. This protocol buffer can be exported using the commandmodel.ExportToFile(filename) and model.exportToFile(filename) in java.
if filename ends with .txt or .textproto, the model will be written using
the protocol buffer text format. Otherwise, the binary format will be used.
Note that the binary format is not human readable, but takes less space.
Improving performance with multiple workers
CP-SAT is built with parallelism in mind. While you can achieve a good solution
with a single worker, you'll get the best results when using multiple workers.
There are several tiers of behavior:
- [8 workers] This is the minimum number of workers needed to trigger
parallel search. It blends workers with different linear relaxations (none,
default, maximal), core-based search if applicable, a quick_restart
subsolver, a feasibility_jump first solution subsolver, and dedicated Large
Neighborhood Search subsolvers to find improved solutions. - [16 workers] Bumping to 16 workers adds a continuous probing subsolver,
more first solution subsolvers (random search and feasibility_jump), two
dual subsolvers dedicated to improving the lower bound of the objective
(when minimizing), and more LNS subsolvers. - [24 workers] Adds more dual subsolvers, no_lp and max_lp variations of
previous subsolvers, more feasibility_jump first solution subsolvers, and
more LNS subsolvers. - [32 workers and more] Adds more LNS workers, and more first solution
subsolvers.
Solve status
Solving a problem yields the following possible status (CpSolverStatus):
- [UNKNOWN] The status of the model is still unknown. A search limit has
been reached before any of the statuses below could be determined. - [MODEL_INVALID] The given CpModelProto didn't pass the validation step.
You can get a detailed error by callingValidateCpModel(model_proto)in
C++, ormodel.Validate()in other languages. - [FEASIBLE] A feasible solution has been found. But the search was
stopped before we could prove optimality or before we enumerated all
solutions of a feasibility problem (if asked). - [INFEASIBLE] The problem has been proven infeasible.
- [OPTIMAL] An optimal feasible solution has been found. More generally,
this status represents a success. So we also return OPTIMAL if we find a
solution for a pure feasibility problem or if a gap limit has been specified
and we return a solution within this limit. In the case where we need to
return all the feasible solution, this status will only be returned if
solve() enumerated all of them; If we stopped earlier, we will return
FEASIBLE.
Debugging infeasible models
Oftentimes, solving a model yields an infeasibility status. While this can be a
valid answer, it can happen because of either a data issue, or a model issue.
Here are some ways to diagnose the source of the infeasibility.
- reduce the model size, if possible
- remove all constraints, which making sure that the resulting model is still
infeasible - enlarge the domains of variables
- inject a known feasible solution and try to find where it breaks
- check the soundness of your data (e.g., do you have negative capacities, or
unreasonable ranges?)
Using assumptions to explain infeasibility
CP-SAT implements a mechanism of assumptions to find the root cause of an
infeasible problem. To use it, one must add enforcement literals to constraints
and add these literals to the set of assumptions.
Note that this set is minimized but not guaranteed to be minimal. To find a
minimal unsatisfiable set (MUS), you must minimize the (weighted) sum of these
assumption literals instead of using the assumptions mechanism.
Finally, be aware that solving with assumptions is not compatible with
parallelism. Therefore, the number of workers must be set to 1.
Python code sample
# Snippet from ortools/sat/samples/assumptions_sample_sat.py
from ortools.sat.python import cp_model
def main() -> None:
"""Showcases assumptions."""
# Creates the model.
model = cp_model.CpModel()
# Creates the variables.
x = model.new_int_var(0, 10, 'x')
y = model.new_int_var(0, 10, 'y')
z = model.new_int_var(0, 10, 'z')
a = model.new_bool_var('a')
b = model.new_bool_var('b')
c = model.new_bool_var('c')
# Creates the constraints.
model.add(x > y).only_enforce_if(a)
model.add(y > z).only_enforce_if(b)
model.add(z > x).only_enforce_if(c)
# Add assumptions
model.add_assumptions([a, b, c])
# Creates a solver and solves.
solver = cp_model.CpSolver()
status = solver.solve(model)
# Print solution.
print(f'Status = {solver.status_name(status)}')
if status == cp_model.INFEASIBLE:
print(
'sufficient_assumptions_for_infeasibility = '
f'{solver.sufficient_assumptions_for_infeasibility()}'
)
if __name__ == '__main__':
main()C++ code samples
/* Detailed source-code truncated for AI context efficiency. */Java code samples
// Snippet from ortools/sat/samples/AssumptionsSampleSat.java
package com.google.ortools.sat.samples;
import com.google.ortools.Loader;
import com.google.ortools.sat.CpSolverStatus;
import com.google.ortools.sat.CpModel;
import com.google.ortools.sat.CpSolver;
import com.google.ortools.sat.IntVar;
import com.google.ortools.sat.Literal;
/** Minimal CP-SAT example to showcase assumptions. */
public class AssumptionsSampleSat {
public static void main(String[] args) {
Loader.loadNativeLibraries();
// Create the model.
CpModel model = new CpModel();
// Create the variables.
IntVar x = model.newIntVar(0, 10, "x");
IntVar y = model.newIntVar(0, 10, "y");
IntVar z = model.newIntVar(0, 10, "z");
Literal a = model.newBoolVar("a");
Literal b = model.newBoolVar("b");
Literal c = model.newBoolVar("c");
// Creates the constraints.
model.addGreaterThan(x, y).onlyEnforceIf(a);
model.addGreaterThan(y, z).onlyEnforceIf(b);
model.addGreaterThan(z, x).onlyEnforceIf(c);
// Add assumptions
model.addAssumptions(new Literal[] {a, b, c});
// Create a solver and solve the model.
CpSolver solver = new CpSolver();
CpSolverStatus status = solver.solve(model);
// Print solution.
// Check that the problem is infeasible.
if (status == CpSolverStatus.INFEASIBLE) {
System.out.println(solver.sufficientAssumptionsForInfeasibility());
}
}
private AssumptionsSampleSat() {}
}C\# code samples
// Snippet from ortools/sat/samples/AssumptionsSampleSat.cs
using System;
using Google.OrTools.Sat;
public class AssumptionsSampleSat
{
static void Main()
{
// Creates the model.
CpModel model = new CpModel();
// Creates the variables.
IntVar x = model.NewIntVar(0, 10, "x");
IntVar y = model.NewIntVar(0, 10, "y");
IntVar z = model.NewIntVar(0, 10, "z");
ILiteral a = model.NewBoolVar("a");
ILiteral b = model.NewBoolVar("b");
ILiteral c = model.NewBoolVar("c");
// Creates the constraints.
model.Add(x > y).OnlyEnforceIf(a);
model.Add(y > z).OnlyEnforceIf(b);
model.Add(z > x).OnlyEnforceIf(c);
// Add assumptions
model.AddAssumptions(new ILiteral[] { a, b, c });
// Creates a solver and solves the model.
CpSolver solver = new CpSolver();
CpSolverStatus status = solver.Solve(model);
Console.WriteLine(solver.SufficientAssumptionsForInfeasibility());
}
}--- METRICS ---
- Files Extracted: 56
- Estimated Token Budget: ~57988 tokens
- Canonical Reference: https://codewiki.google/github.com/google/or-tools