Let’s look at ways we can build constraint programs (CP) in a structured way. As a case study, we will model the curriculum requirements of the Computer Science program.
2 Data
Let’s start with data. The environment is modeled as a database. Let’s build a database to model the curriculum.
data = []for i inrange(4):for s in ['Fall', 'Winter']: data.append(f"Y{i+1}{s}")semesters = Series(data, name='semesters')semesters
0 Y1 Fall
1 Y1 Winter
2 Y2 Fall
3 Y2 Winter
4 Y3 Fall
5 Y3 Winter
6 Y4 Fall
7 Y4 Winter
Name: semesters, dtype: object
model = CpModel()unknown = make_unknowns(model)C1 = make_semester_atmost_five(model, unknown)C1.head()
semesters
Y1 Fall ((((((((((((((((((((((((((((((((((((((((((CSCI...
Y1 Winter ((((((((((((((((((((((((((((((((((((((((((CSCI...
Y2 Fall ((((((((((((((((((((((((((((((((((((((((((CSCI...
Y2 Winter ((((((((((((((((((((((((((((((((((((((((((CSCI...
Y3 Fall ((((((((((((((((((((((((((((((((((((((((((CSCI...
dtype: object
def make_min_selection(model:CpModel, unknown:DataFrame, min:int):vars= unknown.values.reshape(-1) c =sum(vars) >min model.Add(c)return c
5 Dependent Variables
We will declare a number of dependent variables. These values are derived from data and unkowns (and maybe other dependent variables). Since the values of unknowns are non-deterministic, derived qualities are also variables.
They can be general integer variables.
Let’s create a set of dependent integer variables, taken_in, which indicates the semester that the courses are taken in. The taken_in[c] is from 1 to \(n\) if the course [c] is taken. Otherwise taken_in[c] = 0.
def make_taken_in(model:CpModel, unknown:DataFrame)->Series:def fn(row:pd.Series)->cp_model.IntVar: var = model.NewIntVar(0, len(row)+1, 'taken_in') model.add_map_domain(var, row, offset=1)return var taken_in = unknown.apply(fn, axis=1)return taken_in
model = CpModel()unknowns = make_unknowns(model)taken_in = make_taken_in(model, unknown)taken_in.head()
Let’s also define the set of dependent variables, taken, which are booleans indicating of the course is taken.
def make_taken(model:CpModel, unknown:DataFrame)->Series:def fn(row:pd.Series)->cp_model.IntVar: var = model.NewBoolVar('taken') model.AddMaxEquality(var, row)return var taken = unknown.apply(fn, axis=1)return taken
model = CpModel()unknown = make_unknowns(model)taken = make_taken(model, unknown)taken.head()
courses
CSCI 1030U taken
CSCI 1060U taken
CSCI 1061U taken
CSCI 1062U taken
CSCI 1063U taken
dtype: object
6 Solution
We can solve the unknowns (hopefully) and the derived variables using a Solver. The solution will be rendered by views.