Skip to content

Commit

Permalink
Added contributors and chores.
Browse files Browse the repository at this point in the history
  • Loading branch information
d-krupke committed Aug 21, 2024
1 parent 5b4312e commit 7da70f7
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 86 deletions.
12 changes: 10 additions & 2 deletions 00_intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 --->

Expand Down Expand Up @@ -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]`.
Expand Down
86 changes: 48 additions & 38 deletions 04B_advanced_modelling.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,18 +300,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
Expand All @@ -332,11 +334,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)]
Expand All @@ -352,20 +354,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
Expand All @@ -380,18 +384,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
Expand All @@ -414,21 +418,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:

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.
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.

```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)
Expand Down
7 changes: 6 additions & 1 deletion CITATION.cff
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"
96 changes: 57 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 --->

Expand Down Expand Up @@ -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]`.
Expand Down Expand Up @@ -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
Expand All @@ -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)]
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down
Loading

0 comments on commit 7da70f7

Please sign in to comment.