# Introduction to Linear Programming with Python – Part 6

## Mocking conditional statements using binary constraints

In part 5, I mentioned that in some cases it is possible to construct conditional statements using binary constraints.

We will explore not only conditional statements using binary constraints, but combining them with logical operators, ‘and’ and ‘or’.

First we’ll work through some theory, then a real world example as an extension of part 5’s example at the end.

#### Conditional statement

To start simply, if we have the binary constraint x_{1} and we want:

```
if x1 == 1:
y1 == 1
elif x1 == 0:
y1 == 0
```

We can achieve this easily using the following constraint:

```
y1 == x1
```

However, if we wanted the opposite:

```
if x1 == 1:
y1 == 0
elif x1 == 0:
y1 == 1
```

Given that they most both be 1 or 0, we just need the following constraint:

```
x1 + y1 == 1
```

#### Logical ‘AND’ operator

Now for something a little more complex, we can coerce a particular binary constraint to be 1 based on the states of 2 other binary constraints.

If we have binary constraints x_{1} and x_{2} and we want to achieve the following:

```
if x1 == 1 and x2 == 0:
y1 == 1
else:
y1 == 0
```

So that $y_1$ is only 1 in the case that x_{1} is 1 and x_{2} is 0. We can use the following 3 constraints to achieve this:

```
[
y1 >= x1 - x2,
y1 <= x1,
y1 <= (1 - x2)
]
```

We’ll take a moment to deconstruct this. In our preferred case that x_{1} = 1 and x_{2} = 0, the three statments resolve to:

- y
_{1}≥ 1 - y
_{1}≤ 1 - y
_{1}≤ 1

The only value of $y_1$ that fulfils each of these is 1.

In any other case, however, y_{1} will be zero. Let’s take another example, say x_{1} = 0 and x_{2} = 1. This resolves to:

- y
_{1}≥ -1 - y
_{1}≤ 0 - y
_{1}≤ 0

Given that y_{1} is a binary variable and must be 0 or 1, the only value of y_{1} that can fulfil each of these is 0.

You can construct 3 constraints so that y_{1} is equal to 1, only in the case you’re interested in out of the 4 following options:

- x
_{1}= 1 and x_{2}= 1 - x
_{1}= 1 and x_{2}= 0 - x
_{1}= 0 and x_{2}= 1 - x
_{1}= 0 and x_{2}= 0

I have created a function for exactly this purpose to cover all cases:

```
def make_io_and_constraint(y1, x1, x2, target_x1, target_x2):
"""
Returns a list of constraints for a linear programming model
that will constrain y1 to 1 when
x1 = target_x1 and x2 = target_x2;
where target_x1 and target_x2 are 1 or 0
"""
binary = [0,1]
assert target_x1 in binary
assert target_x2 in binary
if IOx1 == 1 and IOx2 == 1:
return [
y1 >= x1 + x2 - 1,
y1 <= x1,
y1 <= x2
]
elif IOx1 == 1 and IOx2 == 0:
return [
y1 >= x1 - x2,
y1 <= x1,
y1 <= (1 - x2)
]
elif IOx1 == 0 and IOx2 == 1:
return [
y1 >= x2 - x1,
y1 <= (1 - x1),
y1 <= x2
]
else:
return [
y1 >= - (x1 + x2 -1),
y1 <= (1 - x1),
y1 <= (1 - x2)
]
```

#### Logical ‘OR’ operator

This is all well and good for the ‘and’ logical operator. What about the ‘or’ logical operator.

If we would like the following:

```
if x1 == 1 or x2 == 1:
y1 == 1
else:
y1 == 0
```

We can use the following linear constraints:

```
y1 <= x1 + x2
y1 * 2 >= x1 + x2
```

So that:

- if x
_{1}is 1 and x_{2}is 1:- y
_{1}≤ 2 - 2y
_{1}≥ 2 - y
_{1}must equal 1

- y
- if x
_{1}is 1 and x_{2}is 0:- y
_{1}≤ 1 - 2y
_{1}≥ 1 - y
_{1}must equal 1

- y
- if x
_{1}is 0 and x_{2}is 1:- y
_{1}≤ 1 - 2y
_{1}≥ 1 - y
_{1}must equal 1

- y
- if x
_{1}is 0 and x_{2}is 0:- y
_{1}≤ 0 - 2y
_{1}≥ 0 - y
_{1}must equal 0

- y

Again, we’ll consider the alternative option:

```
if x1 == 0 or x2 == 0:
y1 == 1
else:
y1 == 0
```

We can use the following linear constraints:

```
y1 * 2 <= 2 - (x1 + x2)
y1 >= 1 - (x1 + x2)
```

### An Example – Scheduling Example Extended

In our last example, we explored the scheduling of 2 factories.

Both factories had 2 costs:

- Fixed Costs – Costs incurred while the factory is running
- Variable Costs – Cost per unit of production

We’re going to introduce a third cost – Start up cost.

This will be a cost incurred by turning on the machines at one of the factories.

In this example, our start-up costs will be:

- Factory A – €20,000
- Factory B – €400,000

Let’s start by reminding ourselves of the input data.

```
import pandas as pd
import pulp
factories = pd.DataFrame.from_csv('csv/factory_variables.csv', index_col=['Month', 'Factory'])
factories
```

```
demand = pd.DataFrame.from_csv('csv/monthly_demand.csv', index_col=['Month'])
demand
```

We’ll begin by defining our decision variables, we have an additional binary variable for switching on the factory.

```
# Production
production = pulp.LpVariable.dicts("production",
((month, factory) for month, factory in factories.index),
lowBound=0,
cat='Integer')
# Factory Status, On or Off
factory_status = pulp.LpVariable.dicts("factory_status",
((month, factory) for month, factory in factories.index),
cat='Binary')
# Factory switch on or off
switch_on = pulp.LpVariable.dicts("switch_on",
((month, factory) for month, factory in factories.index),
cat='Binary')
```

We instantiate our model and define our objective function, including start up costs

```
# Instantiate the model
model = pulp.LpProblem("Cost minimising scheduling problem", pulp.LpMinimize)
# Select index on factory A or B
factory_A_index = [tpl for tpl in factories.index if tpl[1] == 'A']
factory_B_index = [tpl for tpl in factories.index if tpl[1] == 'B']
# Define objective function
model += pulp.lpSum(
[production[m, f] * factories.loc[(m, f), 'Variable_Costs'] for m, f in factories.index]
+ [factory_status[m, f] * factories.loc[(m, f), 'Fixed_Costs'] for m, f in factories.index]
+ [switch_on[m, f] * 20000 for m, f in factory_A_index]
+ [switch_on[m, f] * 400000 for m, f in factory_B_index]
)
```

Now we begin to build up our constraints as in Part 5

```
# Production in any month must be equal to demand
months = demand.index
for month in months:
model += production[(month, 'A')] + production[(month, 'B')] == demand.loc[month, 'Demand']
# Production in any month must be between minimum and maximum capacity, or zero.
for month, factory in factories.index:
min_production = factories.loc[(month, factory), 'Min_Capacity']
max_production = factories.loc[(month, factory), 'Max_Capacity']
model += production[(month, factory)] >= min_production * factory_status[month, factory]
model += production[(month, factory)] <= max_production * factory_status[month, factory]
# Factory B is off in May
model += factory_status[5, 'B'] == 0
model += production[5, 'B'] == 0
```

But now we want to add in our constraints for switching on.

A factory switches on if:

- It is off in the previous month (m-1)
- AND it on in the current month (m).

As we don’t know if the factory is on before month 0, we’ll assume that the factory has switched on if it is on in month 1.

```
for month, factory in factories.index:
# In month 1, if the factory ison, we assume it turned on
if month == 1:
model += switch_on[month, factory] == factory_status[month, factory]
# In other months, if the factory is on in the current month AND off in the previous month, switch on = 1
else:
model += switch_on[month, factory] >= factory_status[month, factory] - factory_status[month-1, factory]
model += switch_on[month, factory] <= 1 - factory_status[month-1, factory]
model += switch_on[month, factory] <= factory_status[month, factory]
```

We’ll then solve our model

```
model.solve()
pulp.LpStatus[model.status]
```

```
output = []
for month, factory in production:
var_output = {
'Month': month,
'Factory': factory,
'Production': production[(month, factory)].varValue,
'Factory Status': factory_status[(month, factory)].varValue,
'Switch On': switch_on[(month, factory)].varValue
}
output.append(var_output)
output_df = pd.DataFrame.from_records(output).sort_values(['Month', 'Factory'])
output_df.set_index(['Month', 'Factory'], inplace=True)
output_df
```

Interestingly, we see that it now makes economic sense to keep factory B on after it turns off in month 5 up until month 12.

Previously, we had the case that it was not economic to run factory B in month 10, but as there is now a significant cost to switching off and back on, the factory runs through month 10 at its lowest capacity (20,000 units).

For those interested in using my function defined above (make_io_and_constraint). Instead of:

```
model += switch_on[month, factory] >= factory_status[month, factory] - factory_status[month-1, factory]
model += switch_on[month, factory] <= 1 - factory_status[month-1, factory]
model += switch_on[month, factory] <= factory_status[month, factory]
```

You could write:

```
for constraint in make_io_and_constraint(switch_on[month, factory],
factory_status[month, factory],
factory_status[month-1, factory], 0, 1):
model += constriant
```

Introduction

Part 1 – Introduction to Linear Programming

Part 2 – Introduction to PuLP

Part 3 – Real world examples – Resourcing Problem

Part 4 – Real world examples – Blending Problem

Part 5 – Using PuLP with pandas and binary constraints to solve a scheduling problem

Part 6 – Mocking conditional statements using binary constraints