Short note. A compact reference, correct as far as it goes. It will be expanded into a full guide with a worked example and an interactive demonstration.
Problem
Real decisions come with rules in plain English: “open at most one of these three warehouses,” “if you hire this contractor, you can’t also hire that one,” “at least two of these five upgrades must happen.” Each maps to a different linear pattern over binaries.
When to use it
Whenever a requirement is naturally phrased as a boolean relationship between decisions rather than a numeric bound.
Formulation
Given binaries y_1, ..., y_n:
OR (at least one true): sum(y_i) >= 1
AND (all true): y_i = 1 for all i # rarely needed as a "constraint"
AT MOST ONE: sum(y_i) <= 1
EXACTLY ONE: sum(y_i) = 1
NOT BOTH (y_1, y_2): y_1 + y_2 <= 1
IMPLICATION (y_1 => y_2): y_1 <= y_2
XOR (exactly one of two): y_1 + y_2 = 1
These compose — most “business rule” constraints in a real MILP are just combinations of these primitives layered together.
Code sketch
import pulp
prob = pulp.LpProblem("facility_rules", pulp.LpMinimize)
open_a = pulp.LpVariable("open_a", cat="Binary")
open_b = pulp.LpVariable("open_b", cat="Binary")
open_c = pulp.LpVariable("open_c", cat="Binary")
# at most one of A, B, C
prob += open_a + open_b + open_c <= 1
# if A opens, B must not open (mutually exclusive sites)
prob += open_a + open_b <= 1
Pitfalls
- It’s tempting to encode complex nested logic (“(A or B) and not C”) in one giant constraint. Don’t — decompose into the primitives above; it stays readable and it’s what the presolver expects.
- Watch for redundant constraints once you have several rules — an “at most one” combined with an “exactly one” on overlapping sets can silently make the model infeasible. Validate with a small feasible instance before scaling up.