This model is an example of a production problem. In production planning problems, choices must be made regarding the what resources to use to produce what products. These problems are common across a broad range of industries.

In this example we’ll model and solve a multi-period production planning problem: In this case the application is to optimize the operation of a farm over 5 years. The farmer has a certain number of farmable acres and cows. The cows can be sold or used to produce milk, which can also be sold for profit. The food for the cows is grown on the farmable acres. The aim is to create an optimal operation plan for the next 5 years to maximize the total profit. This includes figuring out what is the optimal cultivation of the acres, the optimal operations related to the cows, and the operations for the whole farm.

Note: you can download the model, implemented in Python, here. More information on this type of model can be found in the fifth edition of *Model Building in Mathematical Programming*, by H. Paul Williams.

*H. Paul Williams, Model Building in Mathematical Programming, fifth edition (Page 262-263, 358-359)*

A farmer with a 200 acre farm wishes to create a five-year production plan.

Currently, he has a herd of 120 cows comprising 20 heifers (young female cows) and 100 adult dairy cows. Each heifer requires 2/3rds of an acre and each dairy cow requires one acre to support it.

A dairy cow produces 1.1 calves per year on average. Half of these calves will be bullocks that are sold shortly after birth for an average of $30 each. The remaining calves, heifers, can either be sold for $40 or raised until age two when they will be come dairy cows. For the current year, all heifers identified for sale have already been sold.

The general practice is to sell all dairy cows at the age of 12 for an average of $120 each. However, each year an average of 5% of heifers and 2% of dairy cows die. At present, the farmer’s herd of 120 cows is evenly distributed with 10 cows per age from newborn to 11 years old.

The milk from a dairy cow can be sold for $370 annually. The farmer can currently house up to 130 cows, but this capacity limit can be increased for $200 per additional cow.

Each dairy cow requires 0.6 tons of grain and 0.7 tons of sugar beet per year. Both of these can be grown on the farm. Each acre can yield 1.5 tons of sugar beet. However, only 80 acres are suitable for growing grain, and those acres have different levels of productivity as follows:

Land Group | Acres | Grain Production |
---|---|---|

Group 1 | 20 acres | 1.1 tons per acre |

Group 2 | 30 acres | 0.9 tons per acre |

Group 3 | 20 acres | 0.8 tons per acre |

Group 4 | 10 acres | 0.65 tons per acre |

Sugar beet can be bought for $70 a ton and sold for $58 a ton. Grain can be bought for $90 a ton and sold for $75 a ton.

The annual labor requirements for cows as well as grain and sugar beet production are as follows:

Labor Required | |
---|---|

Each Heifer | 10 hours per year |

Each milk-producing cow | 42 hours per year |

Each acre put to grain | 4 hours per year |

Each acre put to sugar beat | 14 hours per year |

Other annual costs are as follows:

Cost | |
---|---|

Each Heifer | $50 per year |

Each milk-producing cow | $100 per year |

Each acre put to grain | $15 per year |

Each acre put to sugar beat | $10 per year |

Labor currently costs the farmer $4000 per year and that cost provides 5500 hours of labor. Additional labor can be paid for at the rate of $1.20 per hour.

Any capital expenditure can be financed with a 10-year loan at 15% interest annually. The interest and principle are paid back in 10 equal annual payments. In no year can the cash flow be negative.

The farmer does not want to reduce the total number of dairy cows at the end of the five-year period by more than 50%, nor does he want to increase their number by more than 75%.

What plan should the farmer follow over the next five years in order to maximize profit?

Let \( T \) be a set of time periods, where \( t_0 \in T \) is the first period and \( t_e \in T \) the last period. Let \( L\) be a set of land groups and \(K\) be a set of cow ages, where \(k_0\) is the first cow age and \(k_e\) is the last cow age.
For each time period \(t \in T \) we have the continous variables:
\[
\begin{array}{rl}
SB_t & \text{how much sugar beet is grown (in tons)} \\
GRBUY_t & \text{how much grain is bought (in tons)} \\
GRSELL_t & \text{how much grain is sold (in tons)} \\
SBBUY_t & \text{how much sugar beet is bought (in tons)} \\
SBSELL_t & \text{how much sugar beet is sold (in tons)} \\
EXLAB_t & \text{how much extra labour is recruited} \\
EXCAP_t & \text{how much capital outlay there is} \\
HFSELL_t & \text{how many heifers are sold at birth} \\
PROF_t & \text{how much profit there is} \\
NEWCOW_t & \text{how many cows of age 0 we have} \\
\end{array}
\]

For each time period \(t \in T \) and each land group \(l \in L\) we have the followingcontinuous variables:
\[
\begin{array}{rl}
GR_{tl} & \text{how much grain is grown on that landgroup} \\
\end{array}
\]

Also, we have given for each land group \(l \in L\) the parameters:
\[
\begin{array}{rl}
GrainYld_{l} & \text{the grain production factor } \\
GrainArea_{l} & \text{the number of acres available} \\
\end{array}
\]

For each time period \(t \in T \) and each cow age \(k\in K\) we have the continous variables:
\[
\begin{array}{rl}
COWS_{tk} & \text{how many cows there are of that age} \\
\end{array}
\]

We define all the continous variables described above:
\begin{multline}
SB_t, GRBUY_t, GRSELL_t, SBBUY_t, SBSELL_t, EXLAB_t, EXCAP_t, HFSELL_t, PROF_t, NEWCOW_t \geq 0 \quad \forall t \in T
\end{multline}
\begin{equation}
GR_{tl} \geq 0 \quad \forall t \in T, \forall l \in L
\end{equation}
\begin{equation}
GR_{tk} \geq 0 \quad \forall t \in T, \forall k \in K
\end{equation}

Next we insert the constraints:

There is only a housing capacity for 130 cows per year. It is possible that there are more than 130 cows, but this comes with additional costs related to renting houses, which is covered with the EXCAP variables:
\begin{equation}
NEWCOW_t + \sum_{k \in K \setminus k_e} COWS_{tk} - \sum_{d \in T : d \leq t} EXCAP_d \geq 130 \quad \forall t \in T
\end{equation}

There needs to be enough grain produced to feed all cows in the current year. Grain cannot be bought in the model unlike sugar beet.
\begin{equation}
\sum_{k \in K \setminus \{k_0,k_e\}} 0.6COWS_{tk} \leq \sum_{l \in L} GR_{tl} + GRBUY_t - GRSELL_t \quad \forall t \in T
\end{equation}

After selling, buying and producing sugar beet, there needs to be enough sugar beet in storage to feed all the cows:
\begin{equation}
\sum_{k \in K \setminus \{k_0,k_e\}} 0.7COWS_{tk} \leq SB_t + SBBUY_t - SBSELL_t \quad \forall t \in T
\end{equation}

The grain produced on each land group cannot exceed the specified production capacity of each land group:
\begin{equation}
GR_{tl} \leq GrainYld_{l} \cdot GrainArea_{l} \quad \forall t \in T, \forall l \in L
\end{equation}

Each cow needs to certrain number of acres to support it. The amount depends on the age of the cow. There are at most 200 acres available:
\begin{equation}
\frac{2}{3}SB_t + \frac{2}{3}NEWCOW_t + \frac{2}{3}COWS_{tk_0} + \sum_{k \in K \setminus \{k_0,k_e\}} COWS_{tk}+ \sum_{l \in L} \frac{1}{GrainYld_{l}}GR_{tl} \leq 200 \quad \forall t \in T
\end{equation}

Each cow and each acre requires a certain amount of worker time to maintain it. The farm is currently able to provide a fixed number of worker hours in the year. Any additional work that needs to be done, can be bought externally at additional cost:
\begin{multline}
0.1NEWCOW_t + 0.1COWS_{tk_0} + \sum_{k \in K \setminus \{k_0,k_e\}} 0.42COWS_{tk} + \\ \sum_{l \in L}
\frac{0.04}{GrainYld_l}GR_{tl} + \frac{0.14}{1.5} SB_t \leq 55 + EXLAB_t \quad \forall t \in T
\end{multline}

Each year a certain percentage of the cows die, depending on each age:
\begin{equation}
COWS_{t+1,k_0} = 0.95 NEWCOW_t \quad \forall t \in T \setminus \{t_e\}
\end{equation}
\begin{equation}
COWS_{t+1,k_0+1} = 0.95 COWS_{t,k_0} \quad \forall t \in T \setminus \{t_e\}
\end{equation}
\begin{equation}
COWS_{t+1,k+1} = 0.98 COWS_{t,k} \quad \forall t \in T \setminus \{t_e\}, k \in K \setminus \{k_0,k_e\}
\end{equation}

To keep the amount of cows correct, we must capture that cows can come into/out of the model through buying them, selling them or through birth:
\begin{equation}
NEWCOW_{t} - \sum_{k \in K \setminus \{k_0,k_e\}} 0.55COWS_{tk} + HFSELL_t = 0 \quad \forall t \in T
\end{equation}

At the end of the five year period, the farmer wants to have at least 50 and at most 175 diary cows:
\begin{equation}
50 \leq \sum_{k \in K \setminus \{k_0,k_e\}} 0.55COWS_{t_ek} \leq 175
\end{equation}

These constraints make sure that the variable PROF takes the value of the profit in this year. There are currently costs of $4000 for the labor. The profit is influenced by the selling of heifers, selling of 12-year-old-cows, selling of milk, selling of grain, selling of sugar beet, buying of grain, buying of sugar beet, labour, heifer costs, dairy cow costs, grain costs, sugar beet costs, as well as capital costs:
\begin{multline}
\sum_{k \in K \setminus \{k_0,k_e\}} 16.5COWS_{tk} + 40HFSELL_t + 120COWS_{tk_e} \\
+ \sum_{k \in K \setminus \{k_0,k_e\}} 370COWS_{tk} + 75 GRSELL_t \\
+ 58SBSELL_t - 90 GRBUY_t - 70 SBBUY_t - 120EXLAB_t \\
- 50 NEWCOW_t - 50COWS_{tk_0} - \sum_{k \in K \setminus \{k_0,k_e\}} 100COWS_{tk} \\
- \sum_{l \in L} \frac{15}{GrainYld_l}GR_{tl} - \frac{20}{3} SB_t - \sum_{d \in T : d \leq t} 39.71EXCAP_d - PROF_t = 4000 \quad \forall t \in T
\end{multline}

In the first year there are 9.5 cows, respectively, given for age 1 and 2. And there is given 9.8 respectively for the ages 3 till 12. Note that we are solving a LP model for computational easier calculating, this can lead to fractional values for variables, which are in reality integers. The implementation of the action needs to take this into account and round them:
\begin{equation}
COWS_{t_0k_0} = 9.5
\end{equation}
\begin{equation}
COWS_{t_0k_0+1} = 9.5
\end{equation}
\begin{equation}
COWS_{t_0k} = 9.8 \quad \forall k \in K \setminus \{k_0, k_0+1\}
\end{equation}

The total profit consists of the calculated profit minus the costs for additional work and housing, which needs to be bought. This is modeled as:
\begin{equation}
\max \sum_{t \in T} PROF_t - 158.85EXCAP_t - 39.71\cdot t \cdot EXCAP_t
\end{equation}

First, we import the Gurobi Python Module and initalize the data structures:

fromgurobipyimport*#tested with Python 3.5.2 & Gurobi 7.0.1GrainYld = [1.1, 0.9, 0.8, 0.65] GrainArea = [20.0, 30.0, 20.0, 10.0] years = range(1,5+1) land_groups = range(1,4+1) cow_ages = range(1,12+1) s_cow_ages = range(2,11+1) max_accommodation = 130 grain_per_cow = 0.6 sugar_beet_per_cow = 0.7 acre_per_heifer = 2/3.0 max_acre = 200 labour_per_heifer = 10/100.0 labour_per_cow = 42/100.0 labour_per_acre_to_grain = 4/100.0 labour_per_acre_to_sb = 14/100.0 total_available_work = 5500/100.0 survivial_rate_cow = 0.98 survivial_rate_heifer = 0.95 initial_heifers = 9.5 initial_cows = 9.8 new_heifers_per_cow = 1.1/2 min_end_total = 50 max_end_total = 175 num_bulldogs_per_cow = 1.1/2 price_selling_bulldogs = 30 price_selling_heifers = 40 price_selling_12year_old_cows = 120 price_selling_milk = 370 price_selling_grain = 75 price_selling_sugar_beet = 58 price_buying_grain = 90 price_buying_sugar_beet = 70 price_extra_labour = 120 heifer_costs = 50 cow_costs = 100 acre_grain_labour_cost = 15 acre_sb_labour_cost = 10 annual_loan_repayment = 39.71 available_labour_cost = 4000 capital_expenditures = 158.85

Next, we create a model and the variables. For each year we have continous variables, which tell us how much sugar beet is grown (in tons), how much grain is bought (in tons), how much grain is sold (in tons), how much sugar beet is bought (in tons), how much sugar beet is sold (in tons), how much extra labour is recruited, how much capital outlay there is, how many heifers are sold at birth, how much profit there is, and how many cows are age 0.

For each year and each land group there is a continous variable which tells us how much grain is grown on that land group. For each year and each cow_age, there is a continuous which tells us how many cows exists in the current year of that age.

model = Model('Farming') SB = model.addVars(years, vtype=GRB.CONTINUOUS, name="SB") GRBUY = model.addVars(years, vtype=GRB.CONTINUOUS, name="GRBUY") GRSELL = model.addVars(years, vtype=GRB.CONTINUOUS, name="GRSELL") SBBUY = model.addVars(years, vtype=GRB.CONTINUOUS, name="SBBUY") SBSELL = model.addVars(years, vtype=GRB.CONTINUOUS, name="SBSELL") EXLAB = model.addVars(years, vtype=GRB.CONTINUOUS, name="EXLAB") EXCAP = model.addVars(years, vtype=GRB.CONTINUOUS, name="EXCAP") HFSELL = model.addVars(years, vtype=GRB.CONTINUOUS, name="HFSELL") NEWCOW = model.addVars(years, vtype=GRB.CONTINUOUS, name="NEWCOW") PROF = model.addVars(years, vtype=GRB.CONTINUOUS, name="PROF") GR = model.addVars(years, land_groups, vtype=GRB.CONTINUOUS, name="GR") COWS = model.addVars(years, cow_ages, vtype=GRB.CONTINUOUS, name="COWS")

Next we insert the constraints:

There are only a housing capacities for 130 cows per year. It is possible that there are more than 130 cows, but they come with additional costs related to renting houses, which is covered with the EXCAP variables.

# Accommodation for the cows model.addConstrs((NEWCOW[year] + COWS[year,1] + quicksum(COWS[year,i] for i in s_cow_ages) - quicksum(EXCAP[i] for i in years if i <= year) <= max_accommodation for year in years), name="Accommodation")

Enough grain needs to be produced to feed all cows in the current year. In the model, Grain cannot be bought, unlike sugar beet.

# Grain consumption model.addConstrs((quicksum(grain_per_cow*COWS[year, i] for i in s_cow_ages) <= GR.sum(year, '*') + GRBUY[year] - GRSELL[year] for year in years), name="Grain_consumption")

After selling, buying and producing sugar beet, there needs to be enough sugar beet in the storage to feed all the cows.

# Sugar beet consumption model.addConstrs((quicksum(sugar_beet_per_cow*COWS[year, i] for i in s_cow_ages) <= SB[year] + SBBUY[year] - SBSELL[year] for year in years), name="Sugar_beet_consumption")

The grain produced on each land group cannot exceed the specified production capacity of each land group.

# Grain growing capacity model.addConstrs((GR[year, land_group] <= GrainYld[land_group-1]*GrainArea[land_group-1] for year in years for land_group in land_groups), name="Grain_growing_capacity")

Each cow needs a certain number of acres to support it. The amount depends on the age of the cow. There are at most 200 acres available.

# Acreage limitation model.addConstrs((acre_per_heifer*SB[year] + acre_per_heifer*NEWCOW[year] + acre_per_heifer*COWS[year,1] + quicksum((1/GrainYld[land_group-1])*GR[year, land_group] for land_group in land_groups)+ quicksum(COWS[year, i] for i in s_cow_ages) <= max_acre for year in years), name="Acreage_limitation")

Each cow and each acre requires a certain amount of worker time to maintain them. The farm is currently able to provide a fixed number of worker hours in a year. Any additional work that needs to be done can be bought externally at additional cost.

# Labour model.addConstrs((labour_per_heifer*NEWCOW[year] + labour_per_heifer*COWS[year,1] + quicksum(labour_per_cow*COWS[year, i] for i in s_cow_ages) + quicksum((labour_per_acre_to_grain/GrainYld[land_group-1])*GR[year,land_group] for land_group in land_groups) + labour_per_acre_to_sb*acre_per_heifer*SB[year] <= total_available_work + EXLAB[year] for year in years), name="Labour")

Each year a certain percentage of the cows die, depending on their age.

# Continuity model.addConstrs((COWS[year+1,1] == survivial_rate_heifer*NEWCOW[year] for year in years if year < max(years)), "ContinuityA") model.addConstrs((COWS[year+1,2] == survivial_rate_heifer*COWS[year,1] for year in years if year < max(years)), "ContinuityB") model.addConstrs((COWS[year+1,s_cow_age+1] == survivial_rate_cow*COWS[year,s_cow_age] for year in years for s_cow_age in s_cow_ages if year < max(years)), name="Continuity")

To keep the number of cows correct, cows can come into/out of the model through buying them, selling them or through birth.

# Continuity C model.addConstrs((NEWCOW[year] - quicksum(new_heifers_per_cow*COWS[year,i] for i in s_cow_ages) + HFSELL[year] == 0 for year in years), name="ContinuityC")

At the end of the five years, the farmer wants at least 50 and at most 175 diary cows.

# End Total model.addConstr(min_end_total <= quicksum(COWS[max(years), i] for i in s_cow_ages) <= max_end_total, "END")

The following constraints make sure that the variable PROF takes the value of the profit in this year. The costs for labor current run $4000. The profit is influenced by the selling of heifers, selling of 12-year-old-cows, selling of milk, selling of grain, selling of sugar beet, buying of grain, buying of sugar beet, labor costs, heifer costs, dairy cow costs, grain costs, sugar beet costs, and capital costs.

# Profit Constraint model.addConstrs((quicksum(price_selling_bulldogs*num_bulldogs_per_cow*COWS[year, i] for i in s_cow_ages) + price_selling_heifers*HFSELL[year] + price_selling_12year_old_cows*COWS[year, 12] + quicksum(price_selling_milk*COWS[year, i] for i in s_cow_ages) + price_selling_grain*GRSELL[year] + price_selling_sugar_beet*SBSELL[year] - price_buying_grain*GRBUY[year] - price_buying_sugar_beet*SBBUY[year] - price_extra_labour*EXLAB[year] - heifer_costs*NEWCOW[year] - heifer_costs*COWS[year,1] - quicksum(cow_costs*COWS[year, i] for i in s_cow_ages) - quicksum((acre_grain_labour_cost/GrainYld[i-1])*GR[year, i] for i in land_groups) - (acre_sb_labour_cost*acre_per_heifer)*SB[year] - quicksum(annual_loan_repayment*EXCAP[i] for i in years if i <= year) - PROF[year] == available_labour_cost for year in years), "PROFIT")

In the first year, there are 9.5 one year old cows and 9.5 two year old cows. In addition, there are 9.8 cows for each age from three to 12. Note that we are solving a LP model to make the model computationally easier to calculate. This can lead to fractional values for variables, which are in reality integers. The implementation needs to take this into account.

model.addConstrs((initial_heifers == COWS[1, cow_age] for cow_age in cow_ages if cow_age < 3), name="Initial_condition_heifers") model.addConstrs((initial_cows == COWS[1, cow_age] for cow_age in cow_ages if cow_age >= 3), name="Initial_condition_cows")

The total profit consists of the calculated profit minus the costs for additional labor and housing that need to be bought:

# Profit model.setObjective(quicksum(PROF[t] - capital_expenditures*EXCAP[t] - annual_loan_repayment*t*EXCAP[t] for t in years), GRB.MAXIMIZE)

Next, we start the optimization and Gurobi tries to find the optimal solution.

model.optimize() for v in model.getVars(): if v.X != 0: print("%s %f" % (v.Varname, v.X))

Note: If you want to write your solution to a file, rather than print it to the terminal, you can use the model.write() command. An example implementation is:

model.write("farm-planning-output.sol")

Changed value of parameter UpdateMode to 1 Prev: 0 Min: 0 Max: 1 Default: 0 Optimize a model with 116 rows, 130 columns and 733 nonzeros Coefficient statistics: Matrix range [4e-02, 3e+02] Objective range [1e+00, 4e+02] Bounds range [0e+00, 0e+00] RHS range [6e+00, 4e+03] Presolve removed 84 rows and 66 columns Presolve time: 0.00s Presolved: 32 rows, 64 columns, 252 nonzeros Iteration Objective Primal Inf. Dual Inf. Time 0 1.7920000e+33 5.000000e+30 1.792000e+03 0s 21 1.2171917e+05 0.000000e+00 0.000000e+00 0s Solved in 21 iterations and 0.00 seconds Optimal objective 1.217191729e+05

The optimal plan results in a total profit of $121 719 over the five-year period the model covers. The detailed plan for each year is as follows.

Note: In order to create the final action plan, the optimal values shown below have been rounded so they are integers.

Year | Decisions |
---|---|

Year One: Profit $21 906 | Grow 22 tons of grain on group 1 land Grow 91 tons of sugar beet Buy 37 tons of grain Sell 23 tons of sugar beet Sell 31 heifers (leaving) 23 |

Year Two: Profit $21 888 | Grow 22 tons of grain on group 1 land Grow 94 tons of sugar beet Buy 35 tons of grain Sell 27 tons of sugar beet Sell 41 heifers (leaving) 12 |

Year Three: Profit $25 816 | Grow 22 tons of grain on group 1 land Grow 3 tons of grain on group 2 land Grow 98 tons of sugar beet Buy 38 tons of grain Sell 25 tons of sugar beet Sell 57 heifers (leaving none) |

Year Four: Profit $26 826 | Grow 22 tons of grain on group 1 land Grow 115 tons of sugar beet Buy 40 tons of grain Sell 42 tons of sugar beet Sell 57 heifers (leaving none) |

Year Five: Profit $25 283 | Grow 22 tons of grain on group 1 land Grow 131 tons of sugar beet Buy 33 tons of grain Sell 67 tons of sugar beet Sell 51 heifers (leaving none) |

The maximum number of cows limit is only reached in year one. No investment should be made to expand housing.