I am having a real life situation where I am trying to solve a school carpooling problem. I thought it would be good to try and use or-tools and learn in the process.
The school is divided into 2 locations, some children go to location 1 and some to location 2 (for the entire week)
So far, I have defined the model:
from ortools.sat.python import cp_model
def main():
# Data.
num_parents = 12
num_children = 20
num_days = 5
num_locations = 2
all_parents = range(num_parents)
all_children = range(num_children)
all_days = range(num_days)
all_locations = range(num_locations)
# Creates the model.
model = cp_model.CpModel()
# Creates ride variables.
# rides[(n, d, c, l)]: parent 'n' takes child 'c' on day 'd' to location l.
rides = {}
for n in all_parents:
for d in all_days:
for c in all_children:
for l in all_locations:
rides[(n, d, c, l)] = model.NewBoolVar('ride_n%id%ic%il%i' % (n, d, c, l))
Now I am trying to define the constraints. Here they are:
Could someone please give me some examples how to model these constraints ? Also, I haven't yet thought of how to model the parent-child relationship in constraint #4 or the child-location relationship in constraints #5
Interesting problem...
Below is a solution written in pulp
which is another framework for linear programs. It is pretty simple framework, doesn't have some of the bells & whistles of or-tools
, but most of those (IMHO) are just syntactic sugar for the basics.
Anyhow, a couple of comments on the problem/code...
drive
variable to zero (with a constraint) for particular days that are captured in another parameter, indexed by parent.from pulp import LpProblem, LpVariable, LpConstraint, LpMinimize, LpBinary, lpSum, LpStatus
from collections import defaultdict
# DATA
parents = [f'p{n}' for n in range(12)]
kids = [f'k{n}' for n in range(20)]
days = list(range(15))
schools = ['Middle School', 'High School']
# some wrangling to make parameters
school_assignments = dict()
for k in kids[:12]:
school_assignments[k] = 'Middle School'
for k in kids[12:]:
school_assignments[k] = 'High School'
# ... model parameters ...
# car sizes
car_size = dict()
car_size.update({p:3 for p in parents[:4]})
car_size.update({p:4 for p in parents[4:9]})
car_size.update({p:6 for p in parents[9:]}) # <- the mini van-ers!
max_passengers = max(car_size.values())
# parent-kid pairs
kids_parents = (
('k0', 'p0'),
('k1', 'p1'),
('k2', 'p1'),
('k3', 'p1'),
('k4', 'p2'),
('k5', 'p2'),
('k6', 'p3'),
('k7', 'p4'),
('k8', 'p4'),
('k9', 'p5'),
('k10', 'p6'),
('k11', 'p7'),
('k12', 'p8'),
('k13', 'p8'),
('k14', 'p9'),
('k15', 'p10'),
('k16', 'p10'),
('k17', 'p10'),
('k18', 'p10'),
('k19', 'p11'))
# convert the tuples to dict... could have done this from start, but oh well
parent_dict = defaultdict(set)
for k, p in kids_parents:
parent_dict[p].add(k)
# print(parent_dict)
# make the model...
prob = LpProblem('carpool', sense=LpMinimize)
# VARIABLES
# pre-bake the index set, it is just a convenience
pksd = {(p, k, s, d) for p in parents for k in kids for s in schools for d in days}
carry = LpVariable.dicts('carry', indices=pksd, cat=LpBinary)
psd = {(p, s, d) for p in parents for s in schools for d in days}
drive = LpVariable.dicts('drive', indices=psd, cat=LpBinary)
max_trips = LpVariable('max_trips')
# OBJ: minimize the max number of trips across individuals
prob += max_trips # this just assigns the single variable as the objective and tries to minimize this single variable
# CONSTRAINTS
# 0. capture the max trips per parent to support the obj
for p in parents:
prob += max_trips >= sum(drive[p, s, d] for s in schools for d in days)
# 1. limit to car capacity... see the first linking constraint below
# 2. only one location per parent-trip
for d in days:
for p in parents:
prob += sum(drive[p, s, d] for s in schools) <= 1
# linking constraints
for d in days:
for p in parents:
for s in schools:
# don't exceed car capacity
prob += sum(carry[p, k, s, d] for k in kids) <= car_size[p] * drive[p, s, d]
# carry at least one of your own if you make the trip...
prob += sum(carry[p, k, s, d] for k in parent_dict[p]) >= drive[p, s, d]
# 3. Skipped... N/A
# 4. Carry one of your own (see linking constraint)
# 5. Every kid must make it to (or from) their school every day
for k in kids:
for d in days:
prob += sum(carry[p, k, school_assignments[k], d] for p in parents) >= 1
# print(prob)
soln = prob.solve()
assert (LpStatus[prob.status]) == 'Optimal' # must check that the solve went OK
# for p, k, s, d in sorted(pksd):
# if carry[p, k, s, d].varValue:
# print(f'{p} carrys {k} to {s} on {d}')
print(f'the max trips per driver is: {max_trips.varValue}')
for p, s, d in sorted(psd, key=lambda x: (x[2], x[0], x[1])):
if drive[p, s, d].varValue:
carried_kids = {k for k in kids if carry[p, k, s, d].varValue}
print(f'on day {d} parent {p} carries {carried_kids} to {s}')
the max trips per driver is: 7.0
on day 0 parent p0 carries {'k18', 'k0', 'k13'} to High School
on day 0 parent p11 carries {'k12', 'k19', 'k15', 'k17', 'k16', 'k14'} to High School
on day 0 parent p3 carries {'k6', 'k3', 'k1'} to Middle School
on day 0 parent p5 carries {'k9', 'k5', 'k0', 'k7'} to Middle School
on day 0 parent p9 carries {'k11', 'k4', 'k10', 'k14', 'k8', 'k2'} to Middle School
on day 1 parent p0 carries {'k0', 'k7', 'k2'} to Middle School
on day 1 parent p1 carries {'k6', 'k1', 'k8'} to Middle School
on day 1 parent p10 carries {'k12', 'k18', 'k15', 'k17', 'k13', 'k14'} to High School
on day 1 parent p3 carries {'k6', 'k16', 'k19'} to High School
on day 1 parent p6 carries {'k9', 'k3', 'k10', 'k5'} to Middle School
on day 1 parent p8 carries {'k11', 'k4', 'k13'} to Middle School
on day 2 parent p11 carries {'k19', 'k3', 'k2'} to Middle School
on day 2 parent p2 carries {'k4', 'k9', 'k10'} to Middle School
on day 2 parent p3 carries {'k6', 'k0', 'k8'} to Middle School
on day 2 parent p4 carries {'k11', 'k5', 'k1', 'k7'} to Middle School
on day 2 parent p8 carries {'k18', 'k16', 'k12', 'k19'} to High School
on day 2 parent p9 carries {'k12', 'k15', 'k17', 'k13', 'k14'} to High School
on day 3 parent p0 carries {'k17', 'k0', 'k19'} to High School
on day 3 parent p4 carries {'k4', 'k7', 'k1', 'k2'} to Middle School
on day 3 parent p5 carries {'k9', 'k3', 'k10', 'k0'} to Middle School
on day 3 parent p7 carries {'k6', 'k5', 'k11', 'k8'} to Middle School
on day 3 parent p9 carries {'k12', 'k18', 'k15', 'k13', 'k16', 'k14'} to High School
on day 4 parent p1 carries {'k10', 'k7', 'k2'} to Middle School
on day 4 parent p10 carries {'k12', 'k19', 'k18', 'k15', 'k17', 'k14'} to High School
on day 4 parent p11 carries {'k9', 'k1', 'k4', 'k5', 'k6', 'k19'} to Middle School
on day 4 parent p4 carries {'k11', 'k3', 'k0', 'k8'} to Middle School
on day 4 parent p8 carries {'k16', 'k12', 'k13'} to High School
on day 5 parent p0 carries {'k15', 'k12', 'k0'} to High School
on day 5 parent p1 carries {'k5', 'k0', 'k2'} to Middle School
on day 5 parent p10 carries {'k19', 'k18', 'k17', 'k13', 'k16', 'k14'} to High School
on day 5 parent p3 carries {'k6', 'k4', 'k8'} to Middle School
on day 5 parent p4 carries {'k3', 'k7'} to Middle School
on day 5 parent p6 carries {'k11', 'k9', 'k10', 'k1'} to Middle School
on day 6 parent p0 carries {'k12', 'k0', 'k14'} to High School
on day 6 parent p1 carries {'k3', 'k5'} to Middle School
on day 6 parent p10 carries {'k18', 'k15', 'k17', 'k13', 'k16', 'k19'} to High School
on day 6 parent p5 carries {'k4', 'k9'} to Middle School
on day 6 parent p6 carries {'k10', 'k0', 'k1', 'k2'} to Middle School
on day 6 parent p7 carries {'k6', 'k11', 'k8', 'k7'} to Middle School
on day 7 parent p10 carries {'k12', 'k19', 'k18', 'k17', 'k13', 'k14'} to High School
on day 7 parent p2 carries {'k4', 'k15', 'k16'} to High School
on day 7 parent p4 carries {'k6', 'k5', 'k1', 'k8'} to Middle School
on day 7 parent p6 carries {'k9', 'k10', 'k0', 'k2'} to Middle School
on day 7 parent p7 carries {'k11', 'k4', 'k3', 'k7'} to Middle School
on day 8 parent p11 carries {'k12', 'k18', 'k15', 'k17', 'k16', 'k19'} to High School
on day 8 parent p2 carries {'k4', 'k10', 'k7'} to Middle School
on day 8 parent p3 carries {'k6', 'k14', 'k13'} to High School
on day 8 parent p4 carries {'k8', 'k0', 'k1', 'k2'} to Middle School
on day 8 parent p7 carries {'k11', 'k9', 'k3'} to Middle School
on day 8 parent p8 carries {'k6', 'k5', 'k12'} to Middle School
on day 9 parent p0 carries {'k11', 'k0', 'k8'} to Middle School
on day 9 parent p10 carries {'k18', 'k3', 'k1', 'k2'} to Middle School
on day 9 parent p2 carries {'k5', 'k10', 'k7'} to Middle School
on day 9 parent p3 carries {'k6', 'k4', 'k9'} to Middle School
on day 9 parent p8 carries {'k18', 'k15', 'k12'} to High School
on day 9 parent p9 carries {'k17', 'k13', 'k16', 'k14', 'k19'} to High School
on day 10 parent p11 carries {'k12', 'k18', 'k17', 'k13', 'k16', 'k19'} to High School
on day 10 parent p2 carries {'k15', 'k5', 'k14'} to High School
on day 10 parent p5 carries {'k4', 'k3', 'k9', 'k2'} to Middle School
on day 10 parent p6 carries {'k5', 'k10', 'k0', 'k8'} to Middle School
on day 10 parent p7 carries {'k6', 'k11', 'k1', 'k7'} to Middle School
on day 11 parent p1 carries {'k7', 'k2'} to Middle School
on day 11 parent p2 carries {'k18', 'k15', 'k5'} to High School
on day 11 parent p3 carries {'k6'} to Middle School
on day 11 parent p5 carries {'k4', 'k5', 'k9', 'k8'} to Middle School
on day 11 parent p6 carries {'k10', 'k0', 'k1'} to Middle School
on day 11 parent p7 carries {'k11', 'k3'} to Middle School
on day 11 parent p9 carries {'k12', 'k19', 'k17', 'k13', 'k16', 'k14'} to High School
on day 12 parent p11 carries {'k19', 'k18', 'k15', 'k17', 'k16', 'k14'} to High School
on day 12 parent p2 carries {'k4', 'k10', 'k0'} to Middle School
on day 12 parent p5 carries {'k6', 'k9', 'k5', 'k8'} to Middle School
on day 12 parent p8 carries {'k12', 'k14', 'k13'} to High School
on day 12 parent p9 carries {'k1', 'k11', 'k2', 'k3', 'k14', 'k7'} to Middle School
on day 13 parent p1 carries {'k6', 'k4', 'k1'} to Middle School
on day 13 parent p11 carries {'k19', 'k5', 'k0', 'k2', 'k10', 'k7'} to Middle School
on day 13 parent p7 carries {'k11', 'k9', 'k3', 'k8'} to Middle School
on day 13 parent p8 carries {'k15', 'k13', 'k16'} to High School
on day 13 parent p9 carries {'k12', 'k18', 'k17', 'k14', 'k19'} to High School
on day 14 parent p1 carries {'k18', 'k3', 'k17'} to High School
on day 14 parent p10 carries {'k12', 'k19', 'k15', 'k13', 'k16', 'k14'} to High School
on day 14 parent p4 carries {'k6', 'k4', 'k11', 'k8'} to Middle School
on day 14 parent p5 carries {'k9', 'k0', 'k7', 'k2'} to Middle School
on day 14 parent p6 carries {'k3', 'k10', 'k1', 'k5'} to Middle School
[Finished in 5.1s]