2024-04-30 11:10:39 +02:00
|
|
|
[home](README.md) | [boolean logic](boolean_logic.md) | [integer arithmetic](integer_arithmetic.md) | [channeling constraints](channeling.md) | [scheduling](scheduling.md) | [Using the CP-SAT solver](solver.md) | [Model manipulation](model.md) | [Troubleshooting](troubleshooting.md) | [Python API](https://or-tools.github.io/docs/pdoc/ortools/sat/python/cp_model.html)
|
2025-07-18 08:11:03 +00:00
|
|
|
----------------- | --------------------------------- | ------------------------------------------- | --------------------------------------- | --------------------------- | ------------------------------------ | ------------------------------ | ------------------------------------- | -----------------------------------------------------------------------------------
|
2018-05-22 15:07:11 +02:00
|
|
|
# Integer arithmetic recipes for the CP-SAT solver.
|
|
|
|
|
|
2021-09-03 15:25:40 +02:00
|
|
|
https://developers.google.com/optimization/
|
2018-05-22 15:07:11 +02:00
|
|
|
|
|
|
|
|
## Introduction
|
2018-05-22 17:13:52 +02:00
|
|
|
|
2018-05-24 14:30:14 +02:00
|
|
|
The CP-SAT solver can express integer variables and constraints.
|
2018-06-05 15:56:55 +02:00
|
|
|
|
|
|
|
|
## Integer variables
|
|
|
|
|
|
2019-05-07 16:41:00 +02:00
|
|
|
Integer variables can take on 64-bit signed integer values. When creating them,
|
|
|
|
|
a domain must be provided.
|
2019-05-07 08:31:53 +02:00
|
|
|
|
|
|
|
|
### Interval domain
|
|
|
|
|
|
|
|
|
|
To create a single contiguous integer domain, just call the `NewIntVar` method
|
2019-05-07 16:41:00 +02:00
|
|
|
with the lower and upper bounds. For instance, to create a variable that can
|
|
|
|
|
take on any value between 0 and 10, inclusive:
|
2019-05-07 08:31:53 +02:00
|
|
|
|
|
|
|
|
- **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");`
|
2023-07-01 06:10:55 +02:00
|
|
|
- **Go**: `x := model.NewIntVar(0, 10).WithName("x")`
|
2019-05-07 08:31:53 +02:00
|
|
|
|
2019-05-07 16:41:00 +02:00
|
|
|
### Non-contiguous domain
|
2019-05-07 08:31:53 +02:00
|
|
|
|
2023-07-01 06:10:55 +02:00
|
|
|
An instance of the Domain class is needed to create variables with
|
2019-05-07 16:41:00 +02:00
|
|
|
non-contiguous domains. Here, the variable can be any of 1, 3, 4, or 6:
|
2019-05-07 08:31:53 +02:00
|
|
|
|
|
|
|
|
- **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");`
|
2024-03-21 11:34:50 +01:00
|
|
|
- **Go**: `model.NewIntVarFromDomain(cpmodel.FromValues([]int64{1, 3, 4, 6})`
|
2019-05-07 08:31:53 +02:00
|
|
|
|
2019-05-07 16:41:00 +02:00
|
|
|
Variables can also be created using a list of intervals. Below, the variable
|
|
|
|
|
created is constrained to be 1, 2, 4, 5, or 6:
|
2019-05-07 08:31:53 +02:00
|
|
|
|
|
|
|
|
- **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
|
2019-05-07 16:41:00 +02:00
|
|
|
long[] {1, 2}, new long[] {4, 6} }), "x");`
|
2024-01-08 10:52:30 +01:00
|
|
|
- **Go**: `model.NewIntVarFromDomain(cpmodel.FromIntervals(
|
|
|
|
|
[]cpmodel.ClosedInterval{{1, 2}, {4, 6}}))`
|
2019-05-07 08:31:53 +02:00
|
|
|
|
|
|
|
|
### Boolean variables
|
|
|
|
|
|
2025-07-18 08:11:03 +00:00
|
|
|
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.
|
2019-05-07 08:31:53 +02:00
|
|
|
|
|
|
|
|
- **C++**: `BoolVar x = model.NewBoolVar().WithName("x");`
|
|
|
|
|
- **Python**: `x = model.NewBoolVar('x')`
|
|
|
|
|
- **Java**: `Literal x = model.newBoolVar("x");`
|
|
|
|
|
- **C#**: `ILiteral x = model.NewBoolVar("x");`
|
2024-01-08 10:52:30 +01:00
|
|
|
- **Go**: `x := model.NewBoolVar().WithName("x")`
|
2019-05-07 08:31:53 +02:00
|
|
|
|
|
|
|
|
### Other methods
|
|
|
|
|
|
2019-05-07 16:41:00 +02:00
|
|
|
To exclude a single value, use ranges combined with int64min and int64max
|
2022-01-04 19:35:22 +01:00
|
|
|
values, e.g., `[[int64min, -3], [-1, int64max]]`, or use the `Complement`
|
|
|
|
|
method.
|
2019-05-07 08:31:53 +02:00
|
|
|
|
|
|
|
|
To create a variable with a single value domain, use the `NewConstant()` API (or
|
|
|
|
|
`newConstant()` in Java).
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2018-06-05 15:56:55 +02:00
|
|
|
## Linear constraints
|
|
|
|
|
|
2024-01-08 10:52:30 +01:00
|
|
|
### C++, Java, and Go linear constraints and linear expressions
|
2019-05-07 08:31:53 +02:00
|
|
|
|
2024-01-08 10:52:30 +01:00
|
|
|
**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.
|
2018-06-05 15:56:55 +02:00
|
|
|
|
2019-05-07 08:31:53 +02:00
|
|
|
Furthermore, helper methods can be used to create sums and scalar products like
|
2024-01-08 10:52:30 +01:00
|
|
|
`LinearExpr::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.
|
2018-06-05 15:56:55 +02:00
|
|
|
|
2019-05-07 08:31:53 +02:00
|
|
|
### Python and C\# linear constraints and linear expressions
|
2019-05-03 16:30:50 +02:00
|
|
|
|
2019-05-07 16:41:00 +02:00
|
|
|
**Python** and **C\#** CP-SAT APIs support general linear arithmetic (+, \*, -,
|
|
|
|
|
==, >=, >, <, <=, !=). You need to use the Add method of the cp_model, as in
|
2019-05-07 08:31:53 +02:00
|
|
|
`cp_model.Add(x + y != 3)`.
|
2019-05-03 16:30:50 +02:00
|
|
|
|
2019-05-07 08:31:53 +02:00
|
|
|
### Generic linear constraint
|
2018-06-05 15:56:55 +02:00
|
|
|
|
2022-01-04 19:35:22 +01:00
|
|
|
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.
|
2019-05-03 16:30:50 +02:00
|
|
|
|
2019-05-07 08:31:53 +02:00
|
|
|
### Limitations
|
2019-05-03 16:30:50 +02:00
|
|
|
|
2025-07-18 08:11:03 +00:00
|
|
|
- Everything must be linear. Multiplying two variables is not supported with
|
|
|
|
|
this API; instead, `model.AddMultiplicationEquality()` must be used.
|
2018-06-05 15:56:55 +02:00
|
|
|
|
2019-05-07 16:41:00 +02:00
|
|
|
- In C++, there is a typing issue when using an array of Boolean variables in
|
2019-05-07 08:31:53 +02:00
|
|
|
a sum or a scalar product. Use the `LinearExpr.BooleanSum()` method instead.
|
2018-06-05 15:56:55 +02:00
|
|
|
|
2025-07-18 08:11:03 +00:00
|
|
|
- The Python construct `sum()` is supported, but `min()`, `max()` or any
|
|
|
|
|
`numpy` constructs like `np.unique()` are not.
|
2018-06-05 15:56:55 +02:00
|
|
|
|
|
|
|
|
## Rabbits and Pheasants examples
|
|
|
|
|
|
2018-06-26 21:24:10 +02:00
|
|
|
Let's solve a simple children's puzzle: the Rabbits and Pheasants problem.
|
2018-06-05 15:56:55 +02:00
|
|
|
|
2019-03-25 11:26:14 +01:00
|
|
|
In a field of rabbits and pheasants, there are 20 heads and 56 legs. How many
|
2018-06-26 21:24:10 +02:00
|
|
|
rabbits and pheasants are there?
|
2018-06-05 15:56:55 +02:00
|
|
|
|
|
|
|
|
### Python code
|
|
|
|
|
|
2018-07-17 10:31:58 -07:00
|
|
|
```python
|
2025-08-06 10:54:53 +02:00
|
|
|
# Snippet from ortools/sat/samples/rabbits_and_pheasants_sat.py
|
2023-07-01 06:10:55 +02:00
|
|
|
"""Rabbits and Pheasants quizz."""
|
2018-07-16 18:40:14 -07:00
|
|
|
|
2018-06-05 15:56:55 +02:00
|
|
|
from ortools.sat.python import cp_model
|
|
|
|
|
|
2018-07-16 18:40:14 -07:00
|
|
|
|
2023-11-17 11:56:36 +01:00
|
|
|
def rabbits_and_pheasants_sat():
|
2025-08-06 10:54:53 +02:00
|
|
|
"""Solves the rabbits + pheasants problem."""
|
|
|
|
|
model = cp_model.CpModel()
|
2018-06-05 15:56:55 +02:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
r = model.new_int_var(0, 100, 'r')
|
|
|
|
|
p = model.new_int_var(0, 100, 'p')
|
2018-06-05 15:56:55 +02:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# 20 heads.
|
|
|
|
|
model.add(r + p == 20)
|
|
|
|
|
# 56 legs.
|
|
|
|
|
model.add(4 * r + 2 * p == 56)
|
2018-06-05 15:56:55 +02:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Solves and prints out the solution.
|
|
|
|
|
solver = cp_model.CpSolver()
|
|
|
|
|
status = solver.solve(model)
|
2018-06-05 15:56:55 +02:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
if status == cp_model.OPTIMAL:
|
|
|
|
|
print(f'{solver.value(r)} rabbits and {solver.value(p)} pheasants')
|
2018-07-16 18:40:14 -07:00
|
|
|
|
|
|
|
|
|
2023-11-17 11:56:36 +01:00
|
|
|
rabbits_and_pheasants_sat()
|
2018-06-05 15:56:55 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### C++ code
|
|
|
|
|
|
2018-07-17 10:31:58 -07:00
|
|
|
```cpp
|
2025-09-16 16:25:04 +02:00
|
|
|
// Snippet from ortools/sat/samples/rabbits_and_pheasants_sat.cc
|
2022-02-15 18:00:11 +01:00
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
2025-05-19 11:57:15 +02:00
|
|
|
#include "ortools/base/init_google.h"
|
2022-02-15 18:00:11 +01:00
|
|
|
#include "ortools/base/logging.h"
|
2025-08-06 10:54:53 +02:00
|
|
|
#include "absl/base/log_severity.h"
|
|
|
|
|
#include "absl/log/globals.h"
|
2018-11-05 16:24:47 +01:00
|
|
|
#include "ortools/sat/cp_model.h"
|
2022-02-15 18:00:11 +01:00
|
|
|
#include "ortools/sat/cp_model.pb.h"
|
|
|
|
|
#include "ortools/sat/cp_model_solver.h"
|
|
|
|
|
#include "ortools/util/sorted_interval_list.h"
|
2018-06-05 15:56:55 +02:00
|
|
|
|
|
|
|
|
namespace operations_research {
|
|
|
|
|
namespace sat {
|
|
|
|
|
|
2018-11-28 10:37:45 +01:00
|
|
|
void RabbitsAndPheasantsSat() {
|
2018-11-05 16:24:47 +01:00
|
|
|
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");
|
|
|
|
|
|
2021-12-09 15:29:49 +01:00
|
|
|
cp_model.AddEquality(rabbits + pheasants, 20);
|
|
|
|
|
cp_model.AddEquality(4 * rabbits + 2 * pheasants, 56);
|
2018-11-05 16:24:47 +01:00
|
|
|
|
2019-04-05 14:58:33 +02:00
|
|
|
const CpSolverResponse response = Solve(cp_model.Build());
|
2018-06-05 15:56:55 +02:00
|
|
|
|
2020-11-03 14:01:06 +01:00
|
|
|
if (response.status() == CpSolverStatus::OPTIMAL) {
|
2018-06-05 15:56:55 +02:00
|
|
|
// Get the value of x in the solution.
|
2018-11-05 16:24:47 +01:00
|
|
|
LOG(INFO) << SolutionIntegerValue(response, rabbits) << " rabbits, and "
|
|
|
|
|
<< SolutionIntegerValue(response, pheasants) << " pheasants";
|
2018-06-05 15:56:55 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-16 18:40:14 -07:00
|
|
|
} // namespace sat
|
|
|
|
|
} // namespace operations_research
|
|
|
|
|
|
2025-05-19 11:57:15 +02:00
|
|
|
int main(int argc, char* argv[]) {
|
|
|
|
|
InitGoogle(argv[0], &argc, &argv, true);
|
|
|
|
|
absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo);
|
2018-11-28 10:37:45 +01:00
|
|
|
operations_research::sat::RabbitsAndPheasantsSat();
|
2018-07-16 18:40:14 -07:00
|
|
|
return EXIT_SUCCESS;
|
|
|
|
|
}
|
2018-06-05 15:56:55 +02:00
|
|
|
```
|
|
|
|
|
|
2018-08-01 17:16:44 -07:00
|
|
|
### Java code
|
|
|
|
|
|
|
|
|
|
```java
|
2025-08-06 10:54:53 +02:00
|
|
|
// Snippet from ortools/sat/samples/RabbitsAndPheasantsSat.java
|
2020-06-25 18:31:47 +02:00
|
|
|
package com.google.ortools.sat.samples;
|
|
|
|
|
|
2020-11-03 18:40:54 +01:00
|
|
|
import com.google.ortools.Loader;
|
2025-08-06 10:54:53 +02:00
|
|
|
import com.google.ortools.sat.CpSolverStatus;
|
2018-08-03 16:42:45 -07:00
|
|
|
import com.google.ortools.sat.CpModel;
|
|
|
|
|
import com.google.ortools.sat.CpSolver;
|
|
|
|
|
import com.google.ortools.sat.IntVar;
|
2019-05-06 22:21:53 +02:00
|
|
|
import com.google.ortools.sat.LinearExpr;
|
2018-08-01 17:16:44 -07:00
|
|
|
|
2018-08-28 11:19:49 +02:00
|
|
|
/**
|
|
|
|
|
* In a field of rabbits and pheasants, there are 20 heads and 56 legs. How many rabbits and
|
|
|
|
|
* pheasants are there?
|
|
|
|
|
*/
|
2018-11-28 10:37:45 +01:00
|
|
|
public class RabbitsAndPheasantsSat {
|
2018-08-03 16:42:45 -07:00
|
|
|
public static void main(String[] args) throws Exception {
|
2020-11-03 18:40:54 +01:00
|
|
|
Loader.loadNativeLibraries();
|
2018-08-01 17:16:44 -07:00
|
|
|
// 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.
|
2022-01-04 16:30:04 +01:00
|
|
|
model.addEquality(LinearExpr.newBuilder().add(r).add(p), 20);
|
2018-08-01 17:16:44 -07:00
|
|
|
// 56 legs.
|
2022-01-04 16:30:04 +01:00
|
|
|
model.addEquality(LinearExpr.newBuilder().addTerm(r, 4).addTerm(p, 2), 56);
|
2018-08-01 17:16:44 -07:00
|
|
|
|
|
|
|
|
// Creates a solver and solves the model.
|
|
|
|
|
CpSolver solver = new CpSolver();
|
|
|
|
|
CpSolverStatus status = solver.solve(model);
|
|
|
|
|
|
2020-11-16 17:50:35 +01:00
|
|
|
if (status == CpSolverStatus.OPTIMAL) {
|
2018-08-03 16:42:45 -07:00
|
|
|
System.out.println(solver.value(r) + " rabbits, and " + solver.value(p) + " pheasants");
|
2018-08-01 17:16:44 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2018-06-05 15:56:55 +02:00
|
|
|
### C\# code
|
|
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
```csharp
|
2025-09-16 16:25:04 +02:00
|
|
|
// Snippet from ortools/sat/samples/RabbitsAndPheasantsSat.cs
|
2018-06-05 15:56:55 +02:00
|
|
|
using System;
|
|
|
|
|
using Google.OrTools.Sat;
|
|
|
|
|
|
2020-11-03 14:01:06 +01:00
|
|
|
public class RabbitsAndPheasantsSat
|
|
|
|
|
{
|
|
|
|
|
static void Main()
|
2018-06-05 15:56:55 +02:00
|
|
|
{
|
2020-11-03 14:01:06 +01:00
|
|
|
// 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");
|
|
|
|
|
}
|
2018-06-05 15:56:55 +02:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-16 16:25:04 +02:00
|
|
|
|
2018-06-05 15:56:55 +02:00
|
|
|
```
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2024-01-08 10:52:30 +01:00
|
|
|
### Go code
|
|
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
```go
|
2025-09-16 16:25:04 +02:00
|
|
|
// Snippet from ortools/sat/samples/rabbits_and_pheasants_sat.go
|
2024-01-08 10:52:30 +01:00
|
|
|
// The rabbits_and_pheasants_sat command is an example of a simple sat program that
|
|
|
|
|
// solves the rabbits and pheasants problem.
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
|
2024-09-30 17:28:08 +02:00
|
|
|
log "github.com/golang/glog"
|
|
|
|
|
"github.com/google/or-tools/ortools/sat/go/cpmodel"
|
2025-08-06 10:54:53 +02:00
|
|
|
|
2024-09-30 17:28:08 +02:00
|
|
|
cmpb "github.com/google/or-tools/ortools/sat/proto/cpmodel"
|
2024-01-08 10:52:30 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const numAnimals = 20
|
|
|
|
|
|
|
|
|
|
func rabbitsAndPheasants() error {
|
|
|
|
|
model := cpmodel.NewCpModelBuilder()
|
|
|
|
|
|
|
|
|
|
allAnimals := cpmodel.NewDomain(0, numAnimals)
|
|
|
|
|
rabbits := model.NewIntVarFromDomain(allAnimals).WithName("rabbits")
|
|
|
|
|
pheasants := model.NewIntVarFromDomain(allAnimals).WithName("pheasants")
|
|
|
|
|
|
|
|
|
|
model.AddEquality(cpmodel.NewLinearExpr().AddSum(rabbits, pheasants), cpmodel.NewConstant(numAnimals))
|
|
|
|
|
model.AddEquality(cpmodel.NewLinearExpr().AddTerm(rabbits, 4).AddTerm(pheasants, 2), cpmodel.NewConstant(56))
|
|
|
|
|
|
|
|
|
|
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("There are %d rabbits and %d pheasants.\n",
|
|
|
|
|
cpmodel.SolutionIntegerValue(response, rabbits),
|
|
|
|
|
cpmodel.SolutionIntegerValue(response, pheasants))
|
|
|
|
|
default:
|
|
|
|
|
fmt.Println("No solution found.")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
if err := rabbitsAndPheasants(); err != nil {
|
2024-09-30 17:28:08 +02:00
|
|
|
log.Exitf("rabbitsAndPheasants returned with error: %v", err)
|
2024-01-08 10:52:30 +01:00
|
|
|
}
|
|
|
|
|
}
|
2025-08-06 10:54:53 +02:00
|
|
|
|
2024-01-08 10:52:30 +01:00
|
|
|
```
|
|
|
|
|
|
2019-03-25 11:26:14 +01:00
|
|
|
## 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:
|
|
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
```
|
|
|
|
|
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=60
|
|
|
|
|
```
|
2019-03-25 11:26:14 +01:00
|
|
|
|
|
|
|
|
### Python code
|
|
|
|
|
|
|
|
|
|
```python
|
2025-08-06 10:54:53 +02:00
|
|
|
# Snippet from ortools/sat/samples/earliness_tardiness_cost_sample_sat.py
|
2023-07-01 06:10:55 +02:00
|
|
|
"""Encodes a convex piecewise linear function."""
|
|
|
|
|
|
2019-03-25 11:26:14 +01:00
|
|
|
|
|
|
|
|
from ortools.sat.python import cp_model
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):
|
2025-08-06 10:54:53 +02:00
|
|
|
"""Print intermediate solutions."""
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
def __init__(self, variables: list[cp_model.IntVar]):
|
|
|
|
|
cp_model.CpSolverSolutionCallback.__init__(self)
|
|
|
|
|
self.__variables = variables
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
def on_solution_callback(self) -> None:
|
|
|
|
|
for v in self.__variables:
|
|
|
|
|
print(f'{v}={self.value(v)}', end=' ')
|
|
|
|
|
print()
|
2019-03-25 11:26:14 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def earliness_tardiness_cost_sample_sat():
|
2025-08-06 10:54:53 +02:00
|
|
|
"""Encode the piecewise linear expression."""
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
earliness_date = 5 # ed.
|
|
|
|
|
earliness_cost = 8
|
|
|
|
|
lateness_date = 15 # ld.
|
|
|
|
|
lateness_cost = 12
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Model.
|
|
|
|
|
model = cp_model.CpModel()
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Declare our primary variable.
|
|
|
|
|
x = model.new_int_var(0, 20, 'x')
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Create the expression variable and implement the piecewise linear function.
|
|
|
|
|
#
|
|
|
|
|
# \ /
|
|
|
|
|
# \______/
|
|
|
|
|
# ed ld
|
|
|
|
|
#
|
|
|
|
|
large_constant = 1000
|
|
|
|
|
expr = model.new_int_var(0, large_constant, 'expr')
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# First segment.
|
|
|
|
|
s1 = model.new_int_var(-large_constant, large_constant, 's1')
|
|
|
|
|
model.add(s1 == earliness_cost * (earliness_date - x))
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Second segment.
|
|
|
|
|
s2 = 0
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Third segment.
|
|
|
|
|
s3 = model.new_int_var(-large_constant, large_constant, 's3')
|
|
|
|
|
model.add(s3 == lateness_cost * (x - lateness_date))
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Link together expr and x through s1, s2, and s3.
|
|
|
|
|
model.add_max_equality(expr, [s1, s2, s3])
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Search for x values in increasing order.
|
|
|
|
|
model.add_decision_strategy(
|
|
|
|
|
[x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE
|
|
|
|
|
)
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Create a solver and solve with a fixed search.
|
|
|
|
|
solver = cp_model.CpSolver()
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Force the solver to follow the decision strategy exactly.
|
|
|
|
|
solver.parameters.search_branching = cp_model.FIXED_SEARCH
|
|
|
|
|
# Enumerate all solutions.
|
|
|
|
|
solver.parameters.enumerate_all_solutions = True
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Search and print out all solutions.
|
|
|
|
|
solution_printer = VarArraySolutionPrinter([x, expr])
|
|
|
|
|
solver.solve(model, solution_printer)
|
2019-03-25 11:26:14 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
earliness_tardiness_cost_sample_sat()
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### C++ code
|
|
|
|
|
|
|
|
|
|
```cpp
|
2025-08-06 10:54:53 +02:00
|
|
|
// Snippet from ortools/sat/samples/earliness_tardiness_cost_sample_sat.cc
|
2022-02-15 18:00:11 +01:00
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
2021-03-04 18:26:01 +01:00
|
|
|
#include <cstdint>
|
|
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
#include "ortools/base/init_google.h"
|
|
|
|
|
#include "ortools/base/logging.h"
|
2025-05-19 11:57:15 +02:00
|
|
|
#include "absl/base/log_severity.h"
|
|
|
|
|
#include "absl/log/globals.h"
|
2022-02-15 18:00:11 +01:00
|
|
|
#include "absl/types/span.h"
|
2019-03-25 11:26:14 +01:00
|
|
|
#include "ortools/sat/cp_model.h"
|
2022-02-15 18:00:11 +01:00
|
|
|
#include "ortools/sat/cp_model.pb.h"
|
|
|
|
|
#include "ortools/sat/cp_model_solver.h"
|
2019-03-25 11:26:14 +01:00
|
|
|
#include "ortools/sat/model.h"
|
|
|
|
|
#include "ortools/sat/sat_parameters.pb.h"
|
|
|
|
|
|
|
|
|
|
namespace operations_research {
|
|
|
|
|
namespace sat {
|
|
|
|
|
|
|
|
|
|
void EarlinessTardinessCostSampleSat() {
|
2021-03-04 18:26:01 +01:00
|
|
|
const int64_t kEarlinessDate = 5;
|
|
|
|
|
const int64_t kEarlinessCost = 8;
|
|
|
|
|
const int64_t kLatenessDate = 15;
|
|
|
|
|
const int64_t kLatenessCost = 12;
|
2019-03-25 11:26:14 +01:00
|
|
|
|
|
|
|
|
// Create the CP-SAT model.
|
|
|
|
|
CpModelBuilder cp_model;
|
|
|
|
|
|
|
|
|
|
// Declare our primary variable.
|
|
|
|
|
const IntVar x = cp_model.NewIntVar({0, 20});
|
|
|
|
|
|
|
|
|
|
// Create the expression variable and implement the piecewise linear function.
|
|
|
|
|
//
|
|
|
|
|
// \ /
|
|
|
|
|
// \______/
|
|
|
|
|
// ed ld
|
|
|
|
|
//
|
2021-03-04 18:26:01 +01:00
|
|
|
const int64_t kLargeConstant = 1000;
|
2019-03-25 11:26:14 +01:00
|
|
|
const IntVar expr = cp_model.NewIntVar({0, kLargeConstant});
|
|
|
|
|
|
2021-12-09 15:29:49 +01:00
|
|
|
// Link together expr and x through the 3 segments.
|
|
|
|
|
cp_model.AddMaxEquality(expr, {(kEarlinessDate - x) * kEarlinessCost, 0,
|
|
|
|
|
(x - kLatenessDate) * kLatenessCost});
|
2019-03-25 11:26:14 +01:00
|
|
|
|
|
|
|
|
// Search for x values in increasing order.
|
|
|
|
|
cp_model.AddDecisionStrategy({x}, DecisionStrategyProto::CHOOSE_FIRST,
|
|
|
|
|
DecisionStrategyProto::SELECT_MIN_VALUE);
|
|
|
|
|
|
|
|
|
|
// Create a solver and solve with a fixed search.
|
|
|
|
|
Model model;
|
|
|
|
|
SatParameters parameters;
|
|
|
|
|
parameters.set_search_branching(SatParameters::FIXED_SEARCH);
|
|
|
|
|
parameters.set_enumerate_all_solutions(true);
|
|
|
|
|
model.Add(NewSatParameters(parameters));
|
|
|
|
|
model.Add(NewFeasibleSolutionObserver([&](const CpSolverResponse& r) {
|
|
|
|
|
LOG(INFO) << "x=" << SolutionIntegerValue(r, x) << " expr"
|
|
|
|
|
<< SolutionIntegerValue(r, expr);
|
|
|
|
|
}));
|
2019-05-16 16:50:11 +02:00
|
|
|
SolveCpModel(cp_model.Build(), &model);
|
2019-03-25 11:26:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace sat
|
|
|
|
|
} // namespace operations_research
|
|
|
|
|
|
2025-05-19 11:57:15 +02:00
|
|
|
int main(int argc, char* argv[]) {
|
|
|
|
|
InitGoogle(argv[0], &argc, &argv, true);
|
|
|
|
|
absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo);
|
2019-03-25 11:26:14 +01:00
|
|
|
operations_research::sat::EarlinessTardinessCostSampleSat();
|
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Java code
|
|
|
|
|
|
|
|
|
|
```java
|
2025-08-06 10:54:53 +02:00
|
|
|
// Snippet from ortools/sat/samples/EarlinessTardinessCostSampleSat.java
|
2020-06-25 18:31:47 +02:00
|
|
|
package com.google.ortools.sat.samples;
|
|
|
|
|
|
2020-11-03 18:40:54 +01:00
|
|
|
import com.google.ortools.Loader;
|
2025-08-06 10:54:53 +02:00
|
|
|
import com.google.ortools.sat.CpSolverStatus;
|
|
|
|
|
import com.google.ortools.sat.DecisionStrategyProto;
|
|
|
|
|
import com.google.ortools.sat.SatParameters;
|
2019-03-25 11:26:14 +01:00
|
|
|
import com.google.ortools.sat.CpModel;
|
|
|
|
|
import com.google.ortools.sat.CpSolver;
|
|
|
|
|
import com.google.ortools.sat.CpSolverSolutionCallback;
|
|
|
|
|
import com.google.ortools.sat.IntVar;
|
2019-05-06 22:21:53 +02:00
|
|
|
import com.google.ortools.sat.LinearExpr;
|
2019-03-25 11:26:14 +01:00
|
|
|
|
|
|
|
|
/** Encode the piecewise linear expression. */
|
|
|
|
|
public class EarlinessTardinessCostSampleSat {
|
|
|
|
|
public static void main(String[] args) throws Exception {
|
2020-11-03 18:40:54 +01:00
|
|
|
Loader.loadNativeLibraries();
|
2019-03-25 11:26:14 +01:00
|
|
|
long earlinessDate = 5;
|
|
|
|
|
long earlinessCost = 8;
|
|
|
|
|
long latenessDate = 15;
|
|
|
|
|
long latenessCost = 12;
|
|
|
|
|
|
|
|
|
|
// Create the CP-SAT model.
|
|
|
|
|
CpModel model = new CpModel();
|
|
|
|
|
|
|
|
|
|
// Declare our primary variable.
|
|
|
|
|
IntVar x = model.newIntVar(0, 20, "x");
|
|
|
|
|
|
|
|
|
|
// Create the expression variable and implement the piecewise linear function.
|
|
|
|
|
//
|
|
|
|
|
// \ /
|
|
|
|
|
// \______/
|
|
|
|
|
// ed ld
|
|
|
|
|
//
|
|
|
|
|
long largeConstant = 1000;
|
|
|
|
|
IntVar expr = model.newIntVar(0, largeConstant, "expr");
|
|
|
|
|
|
2022-01-03 09:43:59 +01:00
|
|
|
// Link together expr and the 3 segment.
|
|
|
|
|
// First segment: y == earlinessCost * (earlinessDate - x).
|
|
|
|
|
// Second segment: y = 0
|
|
|
|
|
// Third segment: y == latenessCost * (x - latenessDate).
|
2025-08-06 10:54:53 +02:00
|
|
|
model.addMaxEquality(
|
|
|
|
|
expr,
|
|
|
|
|
new LinearExpr[] {
|
|
|
|
|
LinearExpr.newBuilder()
|
|
|
|
|
.addTerm(x, -earlinessCost)
|
|
|
|
|
.add(earlinessCost * earlinessDate)
|
|
|
|
|
.build(),
|
|
|
|
|
LinearExpr.constant(0),
|
|
|
|
|
LinearExpr.newBuilder().addTerm(x, latenessCost).add(-latenessCost * latenessDate).build()
|
|
|
|
|
});
|
2019-03-25 11:26:14 +01:00
|
|
|
|
|
|
|
|
// Search for x values in increasing order.
|
2025-08-06 10:54:53 +02:00
|
|
|
model.addDecisionStrategy(
|
|
|
|
|
new IntVar[] {x},
|
2019-03-25 11:26:14 +01:00
|
|
|
DecisionStrategyProto.VariableSelectionStrategy.CHOOSE_FIRST,
|
|
|
|
|
DecisionStrategyProto.DomainReductionStrategy.SELECT_MIN_VALUE);
|
|
|
|
|
|
|
|
|
|
// Create the solver.
|
|
|
|
|
CpSolver solver = new CpSolver();
|
|
|
|
|
|
|
|
|
|
// Force the solver to follow the decision strategy exactly.
|
|
|
|
|
solver.getParameters().setSearchBranching(SatParameters.SearchBranching.FIXED_SEARCH);
|
2021-05-03 12:11:39 +02:00
|
|
|
// Tell the solver to enumerate all solutions.
|
|
|
|
|
solver.getParameters().setEnumerateAllSolutions(true);
|
2019-03-25 11:26:14 +01:00
|
|
|
|
|
|
|
|
// Solve the problem with the printer callback.
|
2025-08-06 10:54:53 +02:00
|
|
|
CpSolverStatus unusedStatus =
|
|
|
|
|
solver.solve(
|
|
|
|
|
model,
|
|
|
|
|
new CpSolverSolutionCallback() {
|
|
|
|
|
public CpSolverSolutionCallback init(IntVar[] variables) {
|
|
|
|
|
variableArray = variables;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onSolutionCallback() {
|
|
|
|
|
for (IntVar v : variableArray) {
|
|
|
|
|
System.out.printf("%s=%d ", v.getName(), value(v));
|
|
|
|
|
}
|
|
|
|
|
System.out.println();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IntVar[] variableArray;
|
|
|
|
|
}.init(new IntVar[] {x, expr}));
|
2019-03-25 11:26:14 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### C\# code
|
|
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
```csharp
|
|
|
|
|
// Snippet from ortools/sat/samples/EarlinessTardinessCostSampleSat.cs
|
2019-03-25 11:26:14 +01:00
|
|
|
using System;
|
|
|
|
|
using Google.OrTools.Sat;
|
2019-05-08 13:43:05 +02:00
|
|
|
using Google.OrTools.Util;
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2020-11-03 14:01:06 +01:00
|
|
|
public class VarArraySolutionPrinter : CpSolverSolutionCallback
|
|
|
|
|
{
|
|
|
|
|
public VarArraySolutionPrinter(IntVar[] variables)
|
|
|
|
|
{
|
|
|
|
|
variables_ = variables;
|
|
|
|
|
}
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2020-11-03 14:01:06 +01:00
|
|
|
public override void OnSolutionCallback()
|
2019-03-25 11:26:14 +01:00
|
|
|
{
|
2020-11-03 14:01:06 +01:00
|
|
|
{
|
|
|
|
|
foreach (IntVar v in variables_)
|
|
|
|
|
{
|
2022-01-10 19:29:40 +01:00
|
|
|
Console.Write(String.Format("{0}={1} ", v.ToString(), Value(v)));
|
2020-11-03 14:01:06 +01:00
|
|
|
}
|
|
|
|
|
Console.WriteLine();
|
|
|
|
|
}
|
2019-03-25 11:26:14 +01:00
|
|
|
}
|
|
|
|
|
|
2020-11-03 14:01:06 +01:00
|
|
|
private IntVar[] variables_;
|
2019-03-25 11:26:14 +01:00
|
|
|
}
|
|
|
|
|
|
2020-11-03 14:01:06 +01:00
|
|
|
public class EarlinessTardinessCostSampleSat
|
|
|
|
|
{
|
|
|
|
|
static void Main()
|
|
|
|
|
{
|
|
|
|
|
long earliness_date = 5;
|
|
|
|
|
long earliness_cost = 8;
|
|
|
|
|
long lateness_date = 15;
|
|
|
|
|
long lateness_cost = 12;
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2020-11-03 14:01:06 +01:00
|
|
|
// Create the CP-SAT model.
|
|
|
|
|
CpModel model = new CpModel();
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2020-11-03 14:01:06 +01:00
|
|
|
// Declare our primary variable.
|
|
|
|
|
IntVar x = model.NewIntVar(0, 20, "x");
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2020-11-03 14:01:06 +01:00
|
|
|
// Create the expression variable and implement the piecewise linear
|
|
|
|
|
// function.
|
|
|
|
|
//
|
|
|
|
|
// \ /
|
|
|
|
|
// \______/
|
|
|
|
|
// ed ld
|
|
|
|
|
//
|
|
|
|
|
long large_constant = 1000;
|
|
|
|
|
IntVar expr = model.NewIntVar(0, large_constant, "expr");
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2020-11-03 14:01:06 +01:00
|
|
|
// Link together expr and x through s1, s2, and s3.
|
2022-01-10 19:29:40 +01:00
|
|
|
model.AddMaxEquality(expr, new LinearExpr[] { earliness_cost * (earliness_date - x), model.NewConstant(0),
|
|
|
|
|
lateness_cost * (x - lateness_date) });
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2020-11-03 14:01:06 +01:00
|
|
|
// Search for x values in increasing order.
|
|
|
|
|
model.AddDecisionStrategy(new IntVar[] { x }, DecisionStrategyProto.Types.VariableSelectionStrategy.ChooseFirst,
|
|
|
|
|
DecisionStrategyProto.Types.DomainReductionStrategy.SelectMinValue);
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2020-11-03 14:01:06 +01:00
|
|
|
// Create the solver.
|
|
|
|
|
CpSolver solver = new CpSolver();
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2020-11-03 14:01:06 +01:00
|
|
|
// Force solver to follow the decision strategy exactly.
|
2021-05-03 12:11:39 +02:00
|
|
|
// Tell the solver to search for all solutions.
|
|
|
|
|
solver.StringParameters = "search_branching:FIXED_SEARCH, enumerate_all_solutions:true";
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2020-11-03 14:01:06 +01:00
|
|
|
VarArraySolutionPrinter cb = new VarArraySolutionPrinter(new IntVar[] { x, expr });
|
2021-05-03 12:11:39 +02:00
|
|
|
solver.Solve(model, cb);
|
2020-11-03 14:01:06 +01:00
|
|
|
}
|
2019-03-25 11:26:14 +01:00
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2024-01-08 10:52:30 +01:00
|
|
|
### Go code
|
|
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
```go
|
|
|
|
|
// Snippet from ortools/sat/samples/earliness_tardiness_cost_sample_sat.go
|
2024-01-08 10:52:30 +01:00
|
|
|
// The earliness_tardiness_cost_sample_sat command is an example of an implementation of a convex
|
|
|
|
|
// piecewise linear function.
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
|
2024-09-30 17:28:08 +02:00
|
|
|
log "github.com/golang/glog"
|
2025-08-06 10:54:53 +02:00
|
|
|
"google.golang.org/protobuf/proto"
|
2024-09-30 17:28:08 +02:00
|
|
|
"github.com/google/or-tools/ortools/sat/go/cpmodel"
|
2025-08-06 10:54:53 +02:00
|
|
|
|
2024-09-30 17:28:08 +02:00
|
|
|
cmpb "github.com/google/or-tools/ortools/sat/proto/cpmodel"
|
|
|
|
|
sppb "github.com/google/or-tools/ortools/sat/proto/satparameters"
|
2024-01-08 10:52:30 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
earlinessDate = 5
|
|
|
|
|
earlinessCost = 8
|
|
|
|
|
latenessDate = 15
|
|
|
|
|
latenessCost = 12
|
|
|
|
|
largeConstant = 1000
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func earlinessTardinessCostSampleSat() error {
|
|
|
|
|
// Create the CP-SAT model.
|
|
|
|
|
model := cpmodel.NewCpModelBuilder()
|
|
|
|
|
|
|
|
|
|
// Declare our primary variable.
|
|
|
|
|
x := model.NewIntVar(0, 20)
|
|
|
|
|
|
|
|
|
|
// Create the expression variable and implement the piecewise linear function.
|
|
|
|
|
//
|
|
|
|
|
// \ /
|
|
|
|
|
// \______/
|
|
|
|
|
// ed ld
|
|
|
|
|
//
|
|
|
|
|
expr := model.NewIntVar(0, largeConstant)
|
|
|
|
|
|
|
|
|
|
// Link together expr and x through the 3 segments.
|
|
|
|
|
firstSegment := cpmodel.NewConstant(earlinessDate*earlinessCost).AddTerm(x, -earlinessCost)
|
|
|
|
|
secondSegment := cpmodel.NewConstant(0)
|
|
|
|
|
thirdSegment := cpmodel.NewConstant(-latenessDate*latenessCost).AddTerm(x, latenessCost)
|
|
|
|
|
model.AddMaxEquality(expr, firstSegment, secondSegment, thirdSegment)
|
|
|
|
|
|
|
|
|
|
// Search for x values in increasing order.
|
|
|
|
|
model.AddDecisionStrategy([]cpmodel.IntVar{x}, cmpb.DecisionStrategyProto_CHOOSE_FIRST, cmpb.DecisionStrategyProto_SELECT_MIN_VALUE)
|
|
|
|
|
|
|
|
|
|
// Create a solver and solve with a fixed search.
|
|
|
|
|
m, err := model.Model()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to instantiate the CP model: %w", err)
|
|
|
|
|
}
|
2025-08-06 10:54:53 +02:00
|
|
|
params := &sppb.SatParameters{
|
2024-01-08 10:52:30 +01:00
|
|
|
FillAdditionalSolutionsInResponse: proto.Bool(true),
|
|
|
|
|
EnumerateAllSolutions: proto.Bool(true),
|
|
|
|
|
SolutionPoolSize: proto.Int32(21),
|
|
|
|
|
SearchBranching: sppb.SatParameters_FIXED_SEARCH.Enum(),
|
2024-09-30 17:28:08 +02:00
|
|
|
}
|
2024-01-08 10:52:30 +01:00
|
|
|
response, err := cpmodel.SolveCpModelWithParameters(m, params)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to solve the model: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Printf("Status: %v\n", response.GetStatus())
|
|
|
|
|
|
|
|
|
|
for _, additionalSolution := range response.GetAdditionalSolutions() {
|
|
|
|
|
vs := additionalSolution.GetValues()
|
|
|
|
|
fmt.Printf("x= %v expr= %v\n", vs[x.Index()], vs[expr.Index()])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
if err := earlinessTardinessCostSampleSat(); err != nil {
|
2024-09-30 17:28:08 +02:00
|
|
|
log.Exitf("earlinessTardinessCostSampleSat returned with error: %v", err)
|
2024-01-08 10:52:30 +01:00
|
|
|
}
|
|
|
|
|
}
|
2025-08-06 10:54:53 +02:00
|
|
|
|
2024-01-08 10:52:30 +01:00
|
|
|
```
|
|
|
|
|
|
2019-03-25 11:26:14 +01:00
|
|
|
## 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:
|
|
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
```
|
|
|
|
|
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=2
|
|
|
|
|
```
|
2019-03-25 11:26:14 +01:00
|
|
|
|
|
|
|
|
### Python code
|
|
|
|
|
|
|
|
|
|
```python
|
2025-08-06 10:54:53 +02:00
|
|
|
# Snippet from ortools/sat/samples/step_function_sample_sat.py
|
2023-07-01 06:10:55 +02:00
|
|
|
"""Implements a step function."""
|
2019-03-25 11:26:14 +01:00
|
|
|
|
|
|
|
|
from ortools.sat.python import cp_model
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):
|
2025-08-06 10:54:53 +02:00
|
|
|
"""Print intermediate solutions."""
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
def __init__(self, variables: list[cp_model.IntVar]):
|
|
|
|
|
cp_model.CpSolverSolutionCallback.__init__(self)
|
|
|
|
|
self.__variables = variables
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
def on_solution_callback(self) -> None:
|
|
|
|
|
for v in self.__variables:
|
|
|
|
|
print(f'{v}={self.value(v)}', end=' ')
|
|
|
|
|
print()
|
2019-03-25 11:26:14 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def step_function_sample_sat():
|
2025-08-06 10:54:53 +02:00
|
|
|
"""Encode the step function."""
|
|
|
|
|
|
|
|
|
|
# Model.
|
|
|
|
|
model = cp_model.CpModel()
|
|
|
|
|
|
|
|
|
|
# Declare our primary variable.
|
|
|
|
|
x = model.new_int_var(0, 20, 'x')
|
|
|
|
|
|
|
|
|
|
# Create the expression variable and implement the step function
|
|
|
|
|
# Note it is not defined for x == 2.
|
|
|
|
|
#
|
|
|
|
|
# - 3
|
|
|
|
|
# -- -- --------- 2
|
|
|
|
|
# 1
|
|
|
|
|
# -- --- 0
|
|
|
|
|
# 0 ================ 20
|
|
|
|
|
#
|
|
|
|
|
expr = model.new_int_var(0, 3, 'expr')
|
|
|
|
|
|
|
|
|
|
# expr == 0 on [5, 6] U [8, 10]
|
|
|
|
|
b0 = model.new_bool_var('b0')
|
|
|
|
|
model.add_linear_expression_in_domain(
|
|
|
|
|
x, cp_model.Domain.from_intervals([(5, 6), (8, 10)])
|
|
|
|
|
).only_enforce_if(b0)
|
|
|
|
|
model.add(expr == 0).only_enforce_if(b0)
|
|
|
|
|
|
|
|
|
|
# expr == 2 on [0, 1] U [3, 4] U [11, 20]
|
|
|
|
|
b2 = model.new_bool_var('b2')
|
|
|
|
|
model.add_linear_expression_in_domain(
|
|
|
|
|
x, cp_model.Domain.from_intervals([(0, 1), (3, 4), (11, 20)])
|
|
|
|
|
).only_enforce_if(b2)
|
|
|
|
|
model.add(expr == 2).only_enforce_if(b2)
|
|
|
|
|
|
|
|
|
|
# expr == 3 when x == 7
|
|
|
|
|
b3 = model.new_bool_var('b3')
|
|
|
|
|
model.add(x == 7).only_enforce_if(b3)
|
|
|
|
|
model.add(expr == 3).only_enforce_if(b3)
|
|
|
|
|
|
|
|
|
|
# At least one bi is true. (we could use an exactly one constraint).
|
|
|
|
|
model.add_bool_or(b0, b2, b3)
|
|
|
|
|
|
|
|
|
|
# Search for x values in increasing order.
|
|
|
|
|
model.add_decision_strategy(
|
|
|
|
|
[x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Create a solver and solve with a fixed search.
|
|
|
|
|
solver = cp_model.CpSolver()
|
|
|
|
|
|
|
|
|
|
# Force the solver to follow the decision strategy exactly.
|
|
|
|
|
solver.parameters.search_branching = cp_model.FIXED_SEARCH
|
|
|
|
|
# Enumerate all solutions.
|
|
|
|
|
solver.parameters.enumerate_all_solutions = True
|
|
|
|
|
|
|
|
|
|
# Search and print out all solutions.
|
|
|
|
|
solution_printer = VarArraySolutionPrinter([x, expr])
|
|
|
|
|
solver.solve(model, solution_printer)
|
2019-03-25 11:26:14 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
step_function_sample_sat()
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### C++ code
|
|
|
|
|
|
|
|
|
|
```cpp
|
2025-08-06 10:54:53 +02:00
|
|
|
// Snippet from ortools/sat/samples/step_function_sample_sat.cc
|
2022-02-15 18:00:11 +01:00
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
#include "ortools/base/init_google.h"
|
|
|
|
|
#include "ortools/base/logging.h"
|
2025-05-19 11:57:15 +02:00
|
|
|
#include "absl/base/log_severity.h"
|
|
|
|
|
#include "absl/log/globals.h"
|
2022-10-05 15:56:33 +02:00
|
|
|
#include "absl/types/span.h"
|
2019-03-25 11:26:14 +01:00
|
|
|
#include "ortools/sat/cp_model.h"
|
2022-02-15 18:00:11 +01:00
|
|
|
#include "ortools/sat/cp_model.pb.h"
|
|
|
|
|
#include "ortools/sat/cp_model_solver.h"
|
2019-03-25 11:26:14 +01:00
|
|
|
#include "ortools/sat/model.h"
|
|
|
|
|
#include "ortools/sat/sat_parameters.pb.h"
|
2022-02-15 18:00:11 +01:00
|
|
|
#include "ortools/util/sorted_interval_list.h"
|
2019-03-25 11:26:14 +01:00
|
|
|
|
|
|
|
|
namespace operations_research {
|
|
|
|
|
namespace sat {
|
|
|
|
|
|
|
|
|
|
void StepFunctionSampleSat() {
|
|
|
|
|
// Create the CP-SAT model.
|
|
|
|
|
CpModelBuilder cp_model;
|
|
|
|
|
|
|
|
|
|
// Declare our primary variable.
|
|
|
|
|
const IntVar x = cp_model.NewIntVar({0, 20});
|
|
|
|
|
|
|
|
|
|
// Create the expression variable and implement the step function
|
|
|
|
|
// Note it is not defined for var == 2.
|
|
|
|
|
//
|
|
|
|
|
// - 3
|
|
|
|
|
// -- -- --------- 2
|
|
|
|
|
// 1
|
|
|
|
|
// -- --- 0
|
|
|
|
|
// 0 ================ 20
|
|
|
|
|
//
|
|
|
|
|
IntVar expr = cp_model.NewIntVar({0, 3});
|
|
|
|
|
|
|
|
|
|
// expr == 0 on [5, 6] U [8, 10]
|
|
|
|
|
BoolVar b0 = cp_model.NewBoolVar();
|
|
|
|
|
cp_model.AddLinearConstraint(x, Domain::FromValues({5, 6, 8, 9, 10}))
|
|
|
|
|
.OnlyEnforceIf(b0);
|
|
|
|
|
cp_model.AddEquality(expr, 0).OnlyEnforceIf(b0);
|
|
|
|
|
|
|
|
|
|
// expr == 2 on [0, 1] U [3, 4] U [11, 20]
|
|
|
|
|
BoolVar b2 = cp_model.NewBoolVar();
|
|
|
|
|
cp_model
|
|
|
|
|
.AddLinearConstraint(x, Domain::FromIntervals({{0, 1}, {3, 4}, {11, 20}}))
|
|
|
|
|
.OnlyEnforceIf(b2);
|
|
|
|
|
cp_model.AddEquality(expr, 2).OnlyEnforceIf(b2);
|
|
|
|
|
|
|
|
|
|
// expr == 3 when x = 7
|
|
|
|
|
BoolVar b3 = cp_model.NewBoolVar();
|
|
|
|
|
cp_model.AddEquality(x, 7).OnlyEnforceIf(b3);
|
|
|
|
|
cp_model.AddEquality(expr, 3).OnlyEnforceIf(b3);
|
|
|
|
|
|
2022-01-01 19:26:39 +01:00
|
|
|
// At least one bi is true. (we could use an exactly one constraint).
|
2019-03-25 11:26:14 +01:00
|
|
|
cp_model.AddBoolOr({b0, b2, b3});
|
|
|
|
|
|
|
|
|
|
// Search for x values in increasing order.
|
|
|
|
|
cp_model.AddDecisionStrategy({x}, DecisionStrategyProto::CHOOSE_FIRST,
|
|
|
|
|
DecisionStrategyProto::SELECT_MIN_VALUE);
|
|
|
|
|
|
|
|
|
|
// Create a solver and solve with a fixed search.
|
|
|
|
|
Model model;
|
|
|
|
|
SatParameters parameters;
|
|
|
|
|
parameters.set_search_branching(SatParameters::FIXED_SEARCH);
|
|
|
|
|
parameters.set_enumerate_all_solutions(true);
|
|
|
|
|
model.Add(NewSatParameters(parameters));
|
|
|
|
|
model.Add(NewFeasibleSolutionObserver([&](const CpSolverResponse& r) {
|
|
|
|
|
LOG(INFO) << "x=" << SolutionIntegerValue(r, x) << " expr"
|
|
|
|
|
<< SolutionIntegerValue(r, expr);
|
|
|
|
|
}));
|
2019-05-16 16:50:11 +02:00
|
|
|
SolveCpModel(cp_model.Build(), &model);
|
2019-03-25 11:26:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace sat
|
|
|
|
|
} // namespace operations_research
|
|
|
|
|
|
2025-05-19 11:57:15 +02:00
|
|
|
int main(int argc, char* argv[]) {
|
|
|
|
|
InitGoogle(argv[0], &argc, &argv, true);
|
|
|
|
|
absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo);
|
2019-03-25 11:26:14 +01:00
|
|
|
operations_research::sat::StepFunctionSampleSat();
|
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Java code
|
|
|
|
|
|
|
|
|
|
```java
|
2025-08-06 10:54:53 +02:00
|
|
|
// Snippet from ortools/sat/samples/StepFunctionSampleSat.java
|
2020-06-25 18:31:47 +02:00
|
|
|
package com.google.ortools.sat.samples;
|
|
|
|
|
|
2020-11-03 18:40:54 +01:00
|
|
|
import com.google.ortools.Loader;
|
2025-08-06 10:54:53 +02:00
|
|
|
import com.google.ortools.sat.CpSolverStatus;
|
|
|
|
|
import com.google.ortools.sat.DecisionStrategyProto;
|
|
|
|
|
import com.google.ortools.sat.SatParameters;
|
2019-03-25 11:26:14 +01:00
|
|
|
import com.google.ortools.sat.CpModel;
|
|
|
|
|
import com.google.ortools.sat.CpSolver;
|
|
|
|
|
import com.google.ortools.sat.CpSolverSolutionCallback;
|
|
|
|
|
import com.google.ortools.sat.IntVar;
|
|
|
|
|
import com.google.ortools.sat.Literal;
|
2019-05-06 22:21:53 +02:00
|
|
|
import com.google.ortools.util.Domain;
|
2019-03-25 11:26:14 +01:00
|
|
|
|
|
|
|
|
/** Link integer constraints together. */
|
|
|
|
|
public class StepFunctionSampleSat {
|
|
|
|
|
public static void main(String[] args) throws Exception {
|
2020-11-03 18:40:54 +01:00
|
|
|
Loader.loadNativeLibraries();
|
2019-03-25 11:26:14 +01:00
|
|
|
// Create the CP-SAT model.
|
|
|
|
|
CpModel model = new CpModel();
|
|
|
|
|
|
|
|
|
|
// Declare our primary variable.
|
|
|
|
|
IntVar x = model.newIntVar(0, 20, "x");
|
|
|
|
|
|
|
|
|
|
// Create the expression variable and implement the step function
|
|
|
|
|
// Note it is not defined for var == 2.
|
|
|
|
|
//
|
|
|
|
|
// - 3
|
|
|
|
|
// -- -- --------- 2
|
|
|
|
|
// 1
|
|
|
|
|
// -- --- 0
|
|
|
|
|
// 0 ================ 20
|
|
|
|
|
//
|
|
|
|
|
IntVar expr = model.newIntVar(0, 3, "expr");
|
|
|
|
|
|
|
|
|
|
// expr == 0 on [5, 6] U [8, 10]
|
|
|
|
|
Literal b0 = model.newBoolVar("b0");
|
2025-08-06 10:54:53 +02:00
|
|
|
model
|
|
|
|
|
.addLinearExpressionInDomain(x, Domain.fromValues(new long[] {5, 6, 8, 9, 10}))
|
2019-05-06 10:12:44 +02:00
|
|
|
.onlyEnforceIf(b0);
|
2019-03-25 11:26:14 +01:00
|
|
|
model.addEquality(expr, 0).onlyEnforceIf(b0);
|
|
|
|
|
|
|
|
|
|
// expr == 2 on [0, 1] U [3, 4] U [11, 20]
|
|
|
|
|
Literal b2 = model.newBoolVar("b2");
|
2019-05-06 10:12:44 +02:00
|
|
|
model
|
2019-05-06 22:21:53 +02:00
|
|
|
.addLinearExpressionInDomain(
|
|
|
|
|
x, Domain.fromIntervals(new long[][] {{0, 1}, {3, 4}, {11, 20}}))
|
2019-05-06 10:12:44 +02:00
|
|
|
.onlyEnforceIf(b2);
|
2019-03-25 11:26:14 +01:00
|
|
|
model.addEquality(expr, 2).onlyEnforceIf(b2);
|
|
|
|
|
|
|
|
|
|
// expr == 3 when x = 7
|
|
|
|
|
Literal b3 = model.newBoolVar("b3");
|
|
|
|
|
model.addEquality(x, 7).onlyEnforceIf(b3);
|
|
|
|
|
model.addEquality(expr, 3).onlyEnforceIf(b3);
|
|
|
|
|
|
|
|
|
|
// At least one bi is true. (we could use a sum == 1).
|
|
|
|
|
model.addBoolOr(new Literal[] {b0, b2, b3});
|
|
|
|
|
|
|
|
|
|
// Search for x values in increasing order.
|
2025-08-06 10:54:53 +02:00
|
|
|
model.addDecisionStrategy(
|
|
|
|
|
new IntVar[] {x},
|
2019-03-25 11:26:14 +01:00
|
|
|
DecisionStrategyProto.VariableSelectionStrategy.CHOOSE_FIRST,
|
|
|
|
|
DecisionStrategyProto.DomainReductionStrategy.SELECT_MIN_VALUE);
|
|
|
|
|
|
|
|
|
|
// Create the solver.
|
|
|
|
|
CpSolver solver = new CpSolver();
|
|
|
|
|
|
|
|
|
|
// Force the solver to follow the decision strategy exactly.
|
|
|
|
|
solver.getParameters().setSearchBranching(SatParameters.SearchBranching.FIXED_SEARCH);
|
2021-05-03 12:11:39 +02:00
|
|
|
// Tell the solver to enumerate all solutions.
|
|
|
|
|
solver.getParameters().setEnumerateAllSolutions(true);
|
2019-03-25 11:26:14 +01:00
|
|
|
|
|
|
|
|
// Solve the problem with the printer callback.
|
2025-08-06 10:54:53 +02:00
|
|
|
CpSolverStatus unusedStatus =
|
|
|
|
|
solver.solve(
|
|
|
|
|
model,
|
|
|
|
|
new CpSolverSolutionCallback() {
|
|
|
|
|
public CpSolverSolutionCallback init(IntVar[] variables) {
|
|
|
|
|
variableArray = variables;
|
|
|
|
|
return this;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onSolutionCallback() {
|
|
|
|
|
for (IntVar v : variableArray) {
|
|
|
|
|
System.out.printf("%s=%d ", v.getName(), value(v));
|
|
|
|
|
}
|
|
|
|
|
System.out.println();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IntVar[] variableArray;
|
|
|
|
|
}.init(new IntVar[] {x, expr}));
|
2019-03-25 11:26:14 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### C\# code
|
|
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
```csharp
|
|
|
|
|
// Snippet from ortools/sat/samples/StepFunctionSampleSat.cs
|
2019-03-25 11:26:14 +01:00
|
|
|
using System;
|
|
|
|
|
using Google.OrTools.Sat;
|
2019-05-08 13:43:05 +02:00
|
|
|
using Google.OrTools.Util;
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2020-11-03 14:01:06 +01:00
|
|
|
public class VarArraySolutionPrinter : CpSolverSolutionCallback
|
|
|
|
|
{
|
|
|
|
|
public VarArraySolutionPrinter(IntVar[] variables)
|
|
|
|
|
{
|
|
|
|
|
variables_ = variables;
|
|
|
|
|
}
|
2019-03-25 11:26:14 +01:00
|
|
|
|
2020-11-03 14:01:06 +01:00
|
|
|
public override void OnSolutionCallback()
|
2019-03-25 11:26:14 +01:00
|
|
|
{
|
2020-11-03 14:01:06 +01:00
|
|
|
{
|
|
|
|
|
foreach (IntVar v in variables_)
|
|
|
|
|
{
|
2022-01-10 19:29:40 +01:00
|
|
|
Console.Write(String.Format("{0}={1} ", v.ToString(), Value(v)));
|
2020-11-03 14:01:06 +01:00
|
|
|
}
|
|
|
|
|
Console.WriteLine();
|
|
|
|
|
}
|
2019-03-25 11:26:14 +01:00
|
|
|
}
|
|
|
|
|
|
2020-11-03 14:01:06 +01:00
|
|
|
private IntVar[] variables_;
|
2019-03-25 11:26:14 +01:00
|
|
|
}
|
|
|
|
|
|
2020-11-03 14:01:06 +01:00
|
|
|
public class StepFunctionSampleSat
|
|
|
|
|
{
|
|
|
|
|
static void Main()
|
|
|
|
|
{
|
|
|
|
|
// Create the CP-SAT model.
|
|
|
|
|
CpModel model = new CpModel();
|
|
|
|
|
|
|
|
|
|
// Declare our primary variable.
|
|
|
|
|
IntVar x = model.NewIntVar(0, 20, "x");
|
|
|
|
|
|
|
|
|
|
// Create the expression variable and implement the step function
|
|
|
|
|
// Note it is not defined for var == 2.
|
|
|
|
|
//
|
|
|
|
|
// - 3
|
|
|
|
|
// -- -- --------- 2
|
|
|
|
|
// 1
|
|
|
|
|
// -- --- 0
|
|
|
|
|
// 0 ================ 20
|
|
|
|
|
//
|
|
|
|
|
IntVar expr = model.NewIntVar(0, 3, "expr");
|
|
|
|
|
|
|
|
|
|
// expr == 0 on [5, 6] U [8, 10]
|
|
|
|
|
ILiteral b0 = model.NewBoolVar("b0");
|
|
|
|
|
model.AddLinearExpressionInDomain(x, Domain.FromValues(new long[] { 5, 6, 8, 9, 10 })).OnlyEnforceIf(b0);
|
|
|
|
|
model.Add(expr == 0).OnlyEnforceIf(b0);
|
|
|
|
|
|
|
|
|
|
// expr == 2 on [0, 1] U [3, 4] U [11, 20]
|
|
|
|
|
ILiteral b2 = model.NewBoolVar("b2");
|
|
|
|
|
model
|
|
|
|
|
.AddLinearExpressionInDomain(
|
|
|
|
|
x,
|
|
|
|
|
Domain.FromIntervals(new long[][] { new long[] { 0, 1 }, new long[] { 3, 4 }, new long[] { 11, 20 } }))
|
|
|
|
|
.OnlyEnforceIf(b2);
|
|
|
|
|
model.Add(expr == 2).OnlyEnforceIf(b2);
|
|
|
|
|
|
|
|
|
|
// expr == 3 when x == 7
|
|
|
|
|
ILiteral b3 = model.NewBoolVar("b3");
|
|
|
|
|
model.Add(x == 7).OnlyEnforceIf(b3);
|
|
|
|
|
model.Add(expr == 3).OnlyEnforceIf(b3);
|
|
|
|
|
|
|
|
|
|
// At least one bi is true. (we could use a sum == 1).
|
|
|
|
|
model.AddBoolOr(new ILiteral[] { b0, b2, b3 });
|
|
|
|
|
|
|
|
|
|
// Search for x values in increasing order.
|
|
|
|
|
model.AddDecisionStrategy(new IntVar[] { x }, DecisionStrategyProto.Types.VariableSelectionStrategy.ChooseFirst,
|
|
|
|
|
DecisionStrategyProto.Types.DomainReductionStrategy.SelectMinValue);
|
|
|
|
|
|
|
|
|
|
// Create the solver.
|
|
|
|
|
CpSolver solver = new CpSolver();
|
|
|
|
|
|
|
|
|
|
// Force solver to follow the decision strategy exactly.
|
2021-05-03 12:11:39 +02:00
|
|
|
// Tells the solver to enumerate all solutions.
|
|
|
|
|
solver.StringParameters = "search_branching:FIXED_SEARCH, enumerate_all_solutions:true";
|
2020-11-03 14:01:06 +01:00
|
|
|
|
|
|
|
|
VarArraySolutionPrinter cb = new VarArraySolutionPrinter(new IntVar[] { x, expr });
|
2021-05-03 12:11:39 +02:00
|
|
|
solver.Solve(model, cb);
|
2020-11-03 14:01:06 +01:00
|
|
|
}
|
2019-03-25 11:26:14 +01:00
|
|
|
}
|
|
|
|
|
```
|
2024-01-08 10:52:30 +01:00
|
|
|
|
|
|
|
|
### Go code
|
|
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
```go
|
|
|
|
|
// Snippet from ortools/sat/samples/step_function_sample_sat.go
|
2024-01-08 10:52:30 +01:00
|
|
|
// The step_function_sample_sat command is an example of an implementation of a step function.
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
|
2024-09-30 17:28:08 +02:00
|
|
|
log "github.com/golang/glog"
|
2025-08-06 10:54:53 +02:00
|
|
|
"google.golang.org/protobuf/proto"
|
2024-09-30 17:28:08 +02:00
|
|
|
"github.com/google/or-tools/ortools/sat/go/cpmodel"
|
2025-08-06 10:54:53 +02:00
|
|
|
|
2024-09-30 17:28:08 +02:00
|
|
|
cmpb "github.com/google/or-tools/ortools/sat/proto/cpmodel"
|
|
|
|
|
sppb "github.com/google/or-tools/ortools/sat/proto/satparameters"
|
2024-01-08 10:52:30 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func stepFunctionSampleSat() error {
|
|
|
|
|
// Create the CP-SAT model.
|
|
|
|
|
model := cpmodel.NewCpModelBuilder()
|
|
|
|
|
|
|
|
|
|
// Declare our primary variable.
|
|
|
|
|
x := model.NewIntVar(0, 20)
|
|
|
|
|
|
|
|
|
|
// Create the expression variable and implement the step function
|
|
|
|
|
// Note it is not defined for var == 2.
|
|
|
|
|
//
|
|
|
|
|
// - 3
|
|
|
|
|
// -- -- --------- 2
|
|
|
|
|
// 1
|
|
|
|
|
// -- --- 0
|
|
|
|
|
// 1 2
|
|
|
|
|
// 012345678901234567890
|
|
|
|
|
//
|
|
|
|
|
expr := model.NewIntVar(0, 3)
|
|
|
|
|
|
|
|
|
|
// expr == 0 on [5, 6] U [8, 10]
|
|
|
|
|
b0 := model.NewBoolVar()
|
2024-03-21 11:34:50 +01:00
|
|
|
d0 := cpmodel.FromValues([]int64{5, 6, 8, 9, 10})
|
2024-01-08 10:52:30 +01:00
|
|
|
model.AddLinearConstraintForDomain(x, d0).OnlyEnforceIf(b0)
|
|
|
|
|
model.AddEquality(expr, cpmodel.NewConstant(0)).OnlyEnforceIf(b0)
|
|
|
|
|
|
|
|
|
|
// expr == 2 on [0, 1] U [3, 4] U [11, 20]
|
|
|
|
|
b2 := model.NewBoolVar()
|
2024-04-02 16:17:01 +02:00
|
|
|
d2 := cpmodel.FromIntervals([]cpmodel.ClosedInterval{{Start: 0, End: 1}, {Start: 3, End: 4}, {Start: 11, End: 20}})
|
2024-01-08 10:52:30 +01:00
|
|
|
model.AddLinearConstraintForDomain(x, d2).OnlyEnforceIf(b2)
|
|
|
|
|
model.AddEquality(expr, cpmodel.NewConstant(2)).OnlyEnforceIf(b2)
|
|
|
|
|
|
|
|
|
|
// expr = 3 when x = 7
|
|
|
|
|
b3 := model.NewBoolVar()
|
|
|
|
|
model.AddEquality(x, cpmodel.NewConstant(7)).OnlyEnforceIf(b3)
|
|
|
|
|
model.AddEquality(expr, cpmodel.NewConstant(3)).OnlyEnforceIf(b3)
|
|
|
|
|
|
|
|
|
|
// At least one Boolean variable is true.
|
|
|
|
|
model.AddBoolOr(b0, b2, b3)
|
|
|
|
|
|
|
|
|
|
// Search for x values in increasing order.
|
|
|
|
|
model.AddDecisionStrategy([]cpmodel.IntVar{x}, cmpb.DecisionStrategyProto_CHOOSE_FIRST, cmpb.DecisionStrategyProto_SELECT_MIN_VALUE)
|
|
|
|
|
|
|
|
|
|
// Create a solver and solve with fixed search.
|
|
|
|
|
m, err := model.Model()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to instantiate the CP model: %w", err)
|
|
|
|
|
}
|
2025-08-06 10:54:53 +02:00
|
|
|
params := &sppb.SatParameters{
|
2024-01-08 10:52:30 +01:00
|
|
|
FillAdditionalSolutionsInResponse: proto.Bool(true),
|
|
|
|
|
EnumerateAllSolutions: proto.Bool(true),
|
|
|
|
|
SolutionPoolSize: proto.Int32(21),
|
|
|
|
|
SearchBranching: sppb.SatParameters_FIXED_SEARCH.Enum(),
|
2024-09-30 17:28:08 +02:00
|
|
|
}
|
2024-01-08 10:52:30 +01:00
|
|
|
response, err := cpmodel.SolveCpModelWithParameters(m, params)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to solve the model: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Printf("Status: %v\n", response.GetStatus())
|
|
|
|
|
|
|
|
|
|
for _, additionalSolution := range response.GetAdditionalSolutions() {
|
|
|
|
|
vs := additionalSolution.GetValues()
|
|
|
|
|
fmt.Printf("x= %v expr= %v\n", vs[x.Index()], vs[expr.Index()])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
if err := stepFunctionSampleSat(); err != nil {
|
2024-09-30 17:28:08 +02:00
|
|
|
log.Exitf("stepFunctionSampleSat returned with error: %v", err)
|
2024-01-08 10:52:30 +01:00
|
|
|
}
|
|
|
|
|
}
|
2025-08-06 10:54:53 +02:00
|
|
|
|
2024-01-08 10:52:30 +01:00
|
|
|
```
|
2024-04-16 12:26:45 +02:00
|
|
|
|
|
|
|
|
## 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=10
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Python code
|
|
|
|
|
|
|
|
|
|
```python
|
2025-08-06 10:54:53 +02:00
|
|
|
# Snippet from ortools/sat/samples/bool_and_int_var_product_sample_sat.py
|
2024-04-16 12:26:45 +02:00
|
|
|
"""Code sample that encodes the product of a Boolean and an integer variable."""
|
|
|
|
|
|
|
|
|
|
from ortools.sat.python import cp_model
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback):
|
2025-08-06 10:54:53 +02:00
|
|
|
"""Print intermediate solutions."""
|
2024-04-16 12:26:45 +02:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
def __init__(self, variables: list[cp_model.IntVar]):
|
|
|
|
|
cp_model.CpSolverSolutionCallback.__init__(self)
|
|
|
|
|
self.__variables = variables
|
2024-04-16 12:26:45 +02:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
def on_solution_callback(self) -> None:
|
|
|
|
|
for v in self.__variables:
|
|
|
|
|
print(f'{v}={self.value(v)}', end=' ')
|
|
|
|
|
print()
|
2024-04-16 12:26:45 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_product_var(
|
|
|
|
|
model: cp_model.CpModel, b: cp_model.IntVar, x: cp_model.IntVar, name: str
|
|
|
|
|
) -> cp_model.IntVar:
|
2025-08-06 10:54:53 +02:00
|
|
|
"""Builds the product of a Boolean variable and an integer variable."""
|
|
|
|
|
p = model.new_int_var_from_domain(
|
|
|
|
|
cp_model.Domain.from_flat_intervals(x.proto.domain).union_with(
|
|
|
|
|
cp_model.Domain(0, 0)
|
|
|
|
|
),
|
|
|
|
|
name,
|
|
|
|
|
)
|
|
|
|
|
model.add(p == x).only_enforce_if(b)
|
|
|
|
|
model.add(p == 0).only_enforce_if(~b)
|
|
|
|
|
return p
|
2024-04-16 12:26:45 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def bool_and_int_var_product_sample_sat():
|
2025-08-06 10:54:53 +02:00
|
|
|
"""Encoding of the product of two Boolean variables.
|
|
|
|
|
|
|
|
|
|
p == x * y, which is the same as p <=> x and y
|
|
|
|
|
"""
|
|
|
|
|
model = cp_model.CpModel()
|
|
|
|
|
b = model.new_bool_var('b')
|
|
|
|
|
x = model.new_int_var_from_domain(
|
|
|
|
|
cp_model.Domain.from_values([1, 2, 3, 5, 6, 7, 9, 10]), 'x'
|
|
|
|
|
)
|
|
|
|
|
p = build_product_var(model, b, x, 'p')
|
|
|
|
|
|
|
|
|
|
# Search for x and b values in increasing order.
|
|
|
|
|
model.add_decision_strategy(
|
|
|
|
|
[b, x], cp_model.CHOOSE_FIRST, cp_model.SELECT_MIN_VALUE
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Create a solver and solve.
|
|
|
|
|
solver = cp_model.CpSolver()
|
|
|
|
|
solution_printer = VarArraySolutionPrinter([x, b, p])
|
|
|
|
|
solver.parameters.enumerate_all_solutions = True
|
|
|
|
|
solver.parameters.search_branching = cp_model.FIXED_SEARCH
|
|
|
|
|
solver.solve(model, solution_printer)
|
2024-04-16 12:26:45 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
bool_and_int_var_product_sample_sat()
|
|
|
|
|
```
|
2024-07-12 13:56:11 +02:00
|
|
|
|
|
|
|
|
## 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
|
|
|
|
|
|
|
|
|
|
```python
|
2025-08-06 10:54:53 +02:00
|
|
|
# Snippet from ortools/sat/samples/all_different_except_zero_sample_sat.py
|
2024-07-12 13:56:11 +02:00
|
|
|
"""Implements AllDifferentExcept0 using atomic constraints."""
|
|
|
|
|
|
|
|
|
|
import collections
|
|
|
|
|
|
|
|
|
|
from ortools.sat.python import cp_model
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def all_different_except_0():
|
2025-08-06 10:54:53 +02:00
|
|
|
"""Encode the AllDifferentExcept0 constraint."""
|
2024-07-12 13:56:11 +02:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Model.
|
|
|
|
|
model = cp_model.CpModel()
|
2024-07-12 13:56:11 +02:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Declare our primary variable.
|
|
|
|
|
x = [model.new_int_var(0, 10, f'x{i}') for i in range(5)]
|
2024-07-12 13:56:11 +02:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Expand the AllDifferentExcept0 constraint.
|
|
|
|
|
variables_per_value = collections.defaultdict(list)
|
|
|
|
|
all_values = set()
|
2024-07-12 13:56:11 +02:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
for var in x:
|
|
|
|
|
all_encoding_literals = []
|
|
|
|
|
# Domains of variables are represented by flat intervals.
|
|
|
|
|
for i in range(0, len(var.proto.domain), 2):
|
|
|
|
|
start = var.proto.domain[i]
|
|
|
|
|
end = var.proto.domain[i + 1]
|
|
|
|
|
for value in range(start, end + 1): # Intervals are inclusive.
|
|
|
|
|
# Create the literal attached to var == value.
|
|
|
|
|
bool_var = model.new_bool_var(f'{var} == {value}')
|
|
|
|
|
model.add(var == value).only_enforce_if(bool_var)
|
2024-07-12 13:56:11 +02:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Collect all encoding literals for a given variable.
|
|
|
|
|
all_encoding_literals.append(bool_var)
|
2024-07-12 13:56:11 +02:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Collect all encoding literals for a given value.
|
|
|
|
|
variables_per_value[value].append(bool_var)
|
2024-07-12 13:56:11 +02:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Collect all different values.
|
|
|
|
|
all_values.add(value)
|
2024-07-12 13:56:11 +02:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# One variable must have exactly one value.
|
|
|
|
|
model.add_exactly_one(all_encoding_literals)
|
2024-07-12 13:56:11 +02:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Add the all_different constraints.
|
|
|
|
|
for value, literals in variables_per_value.items():
|
|
|
|
|
if value == 0:
|
|
|
|
|
continue
|
|
|
|
|
model.add_at_most_one(literals)
|
2024-07-12 13:56:11 +02:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
model.add(x[0] == 0)
|
|
|
|
|
model.add(x[1] == 0)
|
2024-07-12 13:56:11 +02:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
model.maximize(sum(x))
|
2024-07-12 13:56:11 +02:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Create a solver and solve.
|
|
|
|
|
solver = cp_model.CpSolver()
|
|
|
|
|
status = solver.solve(model)
|
2024-07-12 13:56:11 +02:00
|
|
|
|
2025-08-06 10:54:53 +02:00
|
|
|
# Checks and prints the output.
|
|
|
|
|
if status == cp_model.OPTIMAL:
|
|
|
|
|
print(f'Optimal solution: {solver.objective_value}, expected: 27.0')
|
|
|
|
|
elif status == cp_model.FEASIBLE:
|
|
|
|
|
print(f'Feasible solution: {solver.objective_value}, optimal 27.0')
|
|
|
|
|
elif status == cp_model.INFEASIBLE:
|
|
|
|
|
print('The model is infeasible')
|
|
|
|
|
else:
|
|
|
|
|
print('Something went wrong. Please check the status and the log')
|
2024-07-12 13:56:11 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
all_different_except_0()
|
|
|
|
|
```
|