When you should use Constraint Solvers instead of Machine Learning

A practical application for a constraint solver. Studying other technologies can save you several days of data cleansing and model training.

A Rubik’s cube could be modeled as a constraint satisfaction problem [1], Photo by NeONBRAND on Unsplash

Machine Learning and Deep Learning are ongoing buzzwords in the industry. Branding ahead of functionalities led to Deep Learning being overused in many artificial intelligence applications.

This post will provide a quick grasp at constraint satisfaction, a powerful yet underused approach which can tackle a large number of problems in AI and other areas of computer science, from logistics and scheduling to temporal reasoning and graph problems.

Solving a real-world problem

Let’s consider a factual and highly topical problem.

A pandemic is rising. Hospitals must organize quickly to treat ill people.

The world needs an algorithm which matches infected people and hospitals together given multiple criteria such as the severity of illness, patient age and location, hospital capacity and equipment, etc.

Fig. 1: Map of the pandemic, reduced to 3 parameters: location of patients and hospitals, severit of patients

Many would say that a neural network would be the perfect fit for it: different configurations from a broad range of parameters that need to be reduced to a unique solution.

However, there are downsides which would undermine such an approach:

The model needs to be trained, hence the need for historical data on previous cases,

A lot of time would be wasted cleaning and consolidating the dataset,

A variety of architectures would need to be tested with lengthy training sessions.

On the other hand, if formulated in terms of a boolean satisfiability problem, the situation wouldn’t have any of the aforementioned downsides while still giving a sub-optimal solution in nondeterministic polynomial time (NP-complete problem), and without the need for any historical data.

Disclaimer: the purpose of this post is to deliver a quick look at CSPs. Theory and problem formulation will be much overlooked. For a more rigorous approach, please refer to [2][3][4].

Abstracting the problem

This post will provide a gentle introduction to constraint programming, aiming to resolve this case study. This map of the pandemic (1) illustrates the output of our algorithm which will match infected people with hospitals.There are several frameworks for constraint solving. Google Optimization Tools (a.k.a., OR-Tools) is an open-source software suite for solving combinatorial optimization problems. Our problem will be modeled using this framework in Python.

from ortools.sat.python import cp_model

Parameters

For now, let’s simplify the problem to 4 parameters (1):

Location of infected people

Severity of infected people

Location of hospitals

Number of beds in each hospital

Let’s define those parameters in python:

# Number of hospitals

n_hospitals = 3

# Number of infected people

n_patients = 200

# Number of beds in every hospital

n_beds_in_hospitals = [30,50,20]

# Location of infected people -- random integer tuple (x,y)

patients_loc = [(randint(0, 100), randint(0, 100)) for _ in range(n_patients)]

# Location of hospitals -- random integer tuple (x,y)

hospitals_loc = [(randint(0, 100), randint(0, 100)) for _ in range(n_hospitals)]

# Illness severity -- 1 = mild -> 5 = severe

patients_severity = [randint(1, 5) for _ in range(n_patients)]

Variables

A constraint satisfaction problem consists of a set of variables that must be assigned values in such a way that a set of constraints is satisfied.

Let I be the set of hospitals

Let Jᵢ be the set of beds in hospital i

Let K be the set of patients.

Let define as our indexed family of variables :

If in the hospital i, the bed j is taken by the person k then xᵢⱼₖ = 1. In order to associate each bed of an hospital to an ill person, the goal is to find a set of variables that satisfies all of our constraints.

We can add those variables to our model:

model = cp_model.CpModel()

x = {}

for i in range(n_hospitals):

for j in range(n_beds_in_hospitals[i]):

for k in range(n_patients):

x[(i,j,k)] = model.NewBoolVar("x(%d,%d,%d)" % (i,j,k))

Hard constraints

Hard constraints define a goal for our model. They are essential, if they are not resolved then the problem can’t be tackled:

There must be at most a single person in every bed,

a single person in every bed, There must be at most a single bed assigned to every person.

Let’s focus on the first hard constraint. For each bed j in every hospital i:

Either there is a unique patient k,

Either the bed is empty.

Hence, it can be expressed in the following way:

Our solver is a combinatorial optimization solver, it can process integer constraints only. Hence, must be turned into an integer equation:

This inequality can then be added to our model.