-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
130 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,8 @@ | |
|
||
<!-- STOP_SKIP_FOR_README --> | ||
|
||
_By [Dominik Krupke](https://krupke.cc), TU Braunschweig_ | ||
_By [Dominik Krupke](https://krupke.cc), TU Braunschweig, with contributions | ||
from Leon Lan, Michael Perk, and others._ | ||
|
||
<!-- Introduction Paragraph ---> | ||
|
||
|
@@ -149,14 +150,21 @@ to coding; rather, it originates from an earlier usage of the word "program", | |
which denoted a plan of action or a schedule. If this distinction is new to you, | ||
it is a strong indication that you would benefit from reading this article. | ||
|
||
> **About the Main Author:** [Dr. Dominik Krupke](https://krupke.cc) is a | ||
> **About the Lead Author:** [Dr. Dominik Krupke](https://krupke.cc) is a | ||
> postdoctoral researcher with the | ||
> [Algorithms Division](https://www.ibr.cs.tu-bs.de/alg) at TU Braunschweig. He | ||
> specializes in practical solutions to NP-hard problems. Initially focused on | ||
> theoretical computer science, he now applies his expertise to solve what was | ||
> once deemed impossible, frequently with the help of CP-SAT. This primer on | ||
> CP-SAT, first developed as course material for his students, has been extended | ||
> in his spare time to cater to a wider audience. | ||
> | ||
> **Contributors:** This primer has been enriched by the contributions of | ||
> several individuals. Notably, Leon Lan played a key role in restructuring the | ||
> content and offering critical feedback, while Michael Perk significantly | ||
> enhanced the section on the reservoir constraint. I also extend my gratitude | ||
> to all other contributors who identified and corrected errors, improved the | ||
> text, and offered valuable insights. | ||
> **Found a mistake?** Please open an issue or a pull request. You can also just | ||
> write me a quick mail to `[email protected]`. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,18 @@ | ||
# YAML 1.2 | ||
--- | ||
cff-version: "1.1.0" | ||
message: "You are free to use material from this primer. If so, please cite it." | ||
message: "You are free to use material from this primer. If so, please cite or refert to it." | ||
title: "The CP-SAT Primer: Using and Understanding Google OR-Tools' CP-SAT Solver" | ||
abstract: "The CP-SAT Primer: Using and Understanding Google OR-Tools' CP-SAT Solver" | ||
authors: | ||
- affiliation: "TU Braunschweig" | ||
family-names: Krupke | ||
given-names: Dominik | ||
orcid: "https://orcid.org/0000-0003-1573-3496" | ||
- family-names: Lan | ||
given-names: Leon | ||
- family-names: Perk | ||
given-names: Michael | ||
- family-names: and others | ||
license: CC-BY-4.0 | ||
repository-code: "https://github.com/d-krupke/cpsat-primer" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,8 @@ | |
# The CP-SAT Primer: Using and Understanding Google OR-Tools' CP-SAT Solver | ||
|
||
|
||
_By [Dominik Krupke](https://krupke.cc), TU Braunschweig_ | ||
_By [Dominik Krupke](https://krupke.cc), TU Braunschweig, with contributions | ||
from Leon Lan, Michael Perk, and others._ | ||
|
||
<!-- Introduction Paragraph ---> | ||
|
||
|
@@ -148,14 +149,21 @@ to coding; rather, it originates from an earlier usage of the word "program", | |
which denoted a plan of action or a schedule. If this distinction is new to you, | ||
it is a strong indication that you would benefit from reading this article. | ||
|
||
> **About the Main Author:** [Dr. Dominik Krupke](https://krupke.cc) is a | ||
> **About the Lead Author:** [Dr. Dominik Krupke](https://krupke.cc) is a | ||
> postdoctoral researcher with the | ||
> [Algorithms Division](https://www.ibr.cs.tu-bs.de/alg) at TU Braunschweig. He | ||
> specializes in practical solutions to NP-hard problems. Initially focused on | ||
> theoretical computer science, he now applies his expertise to solve what was | ||
> once deemed impossible, frequently with the help of CP-SAT. This primer on | ||
> CP-SAT, first developed as course material for his students, has been extended | ||
> in his spare time to cater to a wider audience. | ||
> | ||
> **Contributors:** This primer has been enriched by the contributions of | ||
> several individuals. Notably, Leon Lan played a key role in restructuring the | ||
> content and offering critical feedback, while Michael Perk significantly | ||
> enhanced the section on the reservoir constraint. I also extend my gratitude | ||
> to all other contributors who identified and corrected errors, improved the | ||
> text, and offered valuable insights. | ||
> **Found a mistake?** Please open an issue or a pull request. You can also just | ||
> write me a quick mail to `[email protected]`. | ||
|
@@ -1812,18 +1820,20 @@ infeasible as it does not end in a final state. | |
|
||
Sometimes, we need to keep the balance between inflows and outflows of a | ||
reservoir. The name giving example is a water reservoir, where we need to keep | ||
the water level between a minimum and a maximum level. | ||
The reservoir constraint takes a list of time variables, | ||
a list of integer level changes, and the minimum and maximum level of the reservoir. | ||
If the affine expression `times[i]` is assigned a value `t`, then the current | ||
level changes by `level_changes[i]`. Note that at the moment, variable level changes are not supported, which means | ||
level changes are constant at time `t`. The constraint ensures that the level stays between the minimum and maximum | ||
level at all time, i.e. `sum(level_changes[i] if times[i] <= t) in [min_level, max_level]`. | ||
|
||
There are many other examples apart from water reservoirs, where you need to balance demands and supplies, | ||
such as maintaining a certain stock level in a warehouse, or | ||
ensuring a certain staffing level in a clinic. The `add_reservoir_constraint` | ||
constraint in CP-SAT allows you to model such problems easily. | ||
the water level between a minimum and a maximum level. The reservoir constraint | ||
takes a list of time variables, a list of integer level changes, and the minimum | ||
and maximum level of the reservoir. If the affine expression `times[i]` is | ||
assigned a value `t`, then the current level changes by `level_changes[i]`. Note | ||
that at the moment, variable level changes are not supported, which means level | ||
changes are constant at time `t`. The constraint ensures that the level stays | ||
between the minimum and maximum level at all time, i.e. | ||
`sum(level_changes[i] if times[i] <= t) in [min_level, max_level]`. | ||
|
||
There are many other examples apart from water reservoirs, where you need to | ||
balance demands and supplies, such as maintaining a certain stock level in a | ||
warehouse, or ensuring a certain staffing level in a clinic. The | ||
`add_reservoir_constraint` constraint in CP-SAT allows you to model such | ||
problems easily. | ||
|
||
In the following example, `times[i]` represents the time at which the change | ||
`level_changes[i]` will be applied, thus both lists needs to be of the same | ||
|
@@ -1844,11 +1854,11 @@ model.add_reservoir_constraint( | |
|
||
Additionally, the `add_reservoir_constraint_with_active` constraint allows you | ||
to model a reservoir with _optional_ changes. Here, we additionally have a list | ||
of Boolean variables `actives`, where `actives[i]` indicates if the | ||
change `level_changes[i]` takes place, i.e. if | ||
of Boolean variables `actives`, where `actives[i]` indicates if the change | ||
`level_changes[i]` takes place, i.e. if | ||
`sum(level_changes[i] * actives[i] if times[i] <= t) in [min_level, max_level]` | ||
If a change is not active, it is as if it does not exist, and the reservoir level remains the same, independent of the | ||
time and change values. | ||
If a change is not active, it is as if it does not exist, and the reservoir | ||
level remains the same, independent of the time and change values. | ||
|
||
```python | ||
times = [model.new_int_var(0, 10, f"time_{i}") for i in range(10)] | ||
|
@@ -1864,20 +1874,22 @@ model.add_reservoir_constraint_with_active( | |
) | ||
``` | ||
|
||
To illustrate the usage of the reservoir constraint, we look at an example for scheduling nurses in a clinic. | ||
For the full example, take a look at the | ||
To illustrate the usage of the reservoir constraint, we look at an example for | ||
scheduling nurses in a clinic. For the full example, take a look at the | ||
[notebook](https://github.com/d-krupke/cpsat-primer/blob/main/examples/add_reservoir.ipynb). | ||
|
||
The clinic needs to ensure that there are always enough nurses available without over-staffing too much. | ||
For a 12-hour work day, we model the demands for nurses as integers for each hour of the day. | ||
The clinic needs to ensure that there are always enough nurses available without | ||
over-staffing too much. For a 12-hour work day, we model the demands for nurses | ||
as integers for each hour of the day. | ||
|
||
```python | ||
# a positive number means we need more nurses, a negative number means we need fewer nurses. | ||
demand_change_at_t = [3, 0, 0, 0, 2, 0, 0, 0, -1, 0, -1, 0, -3] | ||
demand_change_times = list(range(len(demand_change_at_t))) # [0, 1, ..., 12] | ||
``` | ||
|
||
We have a list of nurses, each with an individual availability as well as a maximum shift length. | ||
We have a list of nurses, each with an individual availability as well as a | ||
maximum shift length. | ||
|
||
```python | ||
max_shift_length = 5 | ||
|
@@ -1892,18 +1904,18 @@ nurse_availabilities = 2 * [ | |
(5, 12), | ||
(7, 12), | ||
(0, 12), | ||
(4, 12) | ||
(4, 12), | ||
] | ||
``` | ||
|
||
We now initialize all relevant variables of the model. Each nurse is assigned a start and end time of their shift | ||
as well as a Boolean variable indicating if they are working at all. | ||
We now initialize all relevant variables of the model. Each nurse is assigned a | ||
start and end time of their shift as well as a Boolean variable indicating if | ||
they are working at all. | ||
|
||
```python | ||
# boolean variable to indicate if a nurse is scheduled | ||
nurse_scheduled = [ | ||
model.new_bool_var(f"nurse_{i}_scheduled") | ||
for i in range(len(nurse_availabilities)) | ||
model.new_bool_var(f"nurse_{i}_scheduled") for i in range(len(nurse_availabilities)) | ||
] | ||
|
||
# model the begin and end of each shift | ||
|
@@ -1926,21 +1938,27 @@ for begin, end in zip(shifts_begin, shifts_end): | |
model.add(end - begin <= max_shift_length) # make sure, the shifts are not too long | ||
``` | ||
|
||
Our reservoir level is the number of nurses scheduled at any time minus the demand for nurses up until that point. | ||
We can now add the reservoir constraint to ensure that we have enough nurses available at all times while not having | ||
too many nurses scheduled (i.e., the reservoir level is between 0 and 2). We have three types of changes in the | ||
reservoir: | ||
Our reservoir level is the number of nurses scheduled at any time minus the | ||
demand for nurses up until that point. We can now add the reservoir constraint | ||
to ensure that we have enough nurses available at all times while not having too | ||
many nurses scheduled (i.e., the reservoir level is between 0 and 2). We have | ||
three types of changes in the reservoir: | ||
|
||
1. The demand for nurses changes at the beginning of each hour. For these we use fixed integer times and activate all | ||
changes. Note that the demand changes are negated, as an increase in demand lowers the reservoir level. | ||
2. If a nurse begins a shift, we increase the reservoir level by 1. We use the `shifts_begin` variables as times and | ||
change the reservoir level only if the nurse is scheduled. | ||
3. Once a nurse ends a shift, we decrease the reservoir level by 1. We use the `shifts_end` variables as times and | ||
change the reservoir level only if the nurse is scheduled. | ||
1. The demand for nurses changes at the beginning of each hour. For these we use | ||
fixed integer times and activate all changes. Note that the demand changes | ||
are negated, as an increase in demand lowers the reservoir level. | ||
2. If a nurse begins a shift, we increase the reservoir level by 1. We use the | ||
`shifts_begin` variables as times and change the reservoir level only if the | ||
nurse is scheduled. | ||
3. Once a nurse ends a shift, we decrease the reservoir level by 1. We use the | ||
`shifts_end` variables as times and change the reservoir level only if the | ||
nurse is scheduled. | ||
|
||
```python | ||
times = demand_change_times | ||
demands = [-demand for demand in demand_change_at_t] # an increase in demand lowers the reservoir | ||
demands = [ | ||
-demand for demand in demand_change_at_t | ||
] # an increase in demand lowers the reservoir | ||
actives = [1] * len(demand_change_times) | ||
|
||
times += list(shifts_begin) | ||
|
Oops, something went wrong.