Farm Planning Example

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)


Problem Description

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?

Model Formulation

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}

Implementation with comments

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

from gurobipy import *
#tested with Python 3.5.2 & Gurobi 7.0.1
GrainYld = [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")

Gurobi Output and Analysis

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

Analyzing the Output

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.