PROC OPTMODEL Statements and Output

The first several PROC OPTMODEL statements are straightforward declarations of index sets, parameters, and variables:

proc optmodel;
   set AGES;
   num init_num_cows {AGES};
   num acres_needed {AGES};
   num annual_loss {AGES};
   num bullock_yield {AGES};
   num heifer_yield {AGES};
   num milk_revenue {AGES};
   num grain_req {AGES};
   num sugar_beet_req {AGES};
   num cow_labour_req {AGES};
   num cow_other_costs {AGES};
   read data cow_data into AGES=[age]
      init_num_cows acres_needed annual_loss bullock_yield heifer_yield
      milk_revenue grain_req sugar_beet_req cow_labour_req=labour_req
      cow_other_costs=other_costs;

   num num_years = &num_years;
   set YEARS = 1..num_years;
   set YEARS0 = {0} union YEARS;

   var NumCows {AGES union {&dairy_cow_selling_age}, YEARS0} >= 0;
   for {age in AGES} fix NumCows[age,0] = init_num_cows[age];
   fix NumCows[&dairy_cow_selling_age,0] = 0;

   var NumBullocksSold {YEARS} >= 0;
   var NumHeifersSold {YEARS} >= 0;

   set <str> GROUPS;
   num acres {GROUPS};
   num grain_yield {GROUPS};
   var GrainAcres {GROUPS, YEARS} >= 0;

In Chapter 6, the READ DATA statement was used to populate the .ub suffix for a one-dimensional variable. The following READ DATA statement populates .ub for the two-dimensional variable GrainAcres:

   read data grain_data into GROUPS=[group]
      {year in YEARS} <GrainAcres[group,year].ub=acres>
      grain_yield=yield;
   var GrainBought {YEARS} >= 0;
   var GrainSold {YEARS} >= 0;

   var SugarBeetAcres {YEARS} >= 0;
   var SugarBeetBought {YEARS} >= 0;
   var SugarBeetSold {YEARS} >= 0;

   var NumExcessLabourHours {YEARS} >= 0;
   var CapitalOutlay {YEARS} >= 0;

The following NUM statement uses the FINANCE function to calculate the yearly loan payment per capital outlay:

   num yearly_loan_payment =
      -finance('pmt', &annual_interest_rate, &num_loan_years,
         &capital_outlay_unit);
   print yearly_loan_payment;

The resulting value of yearly_loan_payment (shown in Figure 8.1) differs from the value given in Williams (1999), perhaps because of rounding in intermediate calculations by Williams. The formula

\[  \Argument{yearly\_ loan\_ payment} = \frac{\Argument{annual\_ interest\_ rate} \cdot \Argument{capital\_ outlay\_ unit}}{1 - (1 + \Argument{annual\_ interest\_ rate})^{-\Argument{num\_ loan\_ years}}}  \]

yields the same value as the FINANCE function. For the given input data, it turns out that the optimal solution has no capital outlay and agrees with the solution reported in Williams (1999).

Figure 8.1: yearly_loan_payment Parameter

The OPTMODEL Procedure

yearly_loan_payment
39.85


The following IMPVAR statements declare Revenue, Cost, and Profit as linear functions of the decision variables:

   impvar Revenue {year in YEARS} =
      &bullock_revenue * NumBullocksSold[year]
    + &heifer_revenue * NumHeifersSold[year]
    + &dairy_cow_selling_revenue * NumCows[&dairy_cow_selling_age,year]
    + sum {age in AGES} milk_revenue[age] * NumCows[age,year]
    + &grain_revenue * GrainSold[year]
    + &sugar_beet_revenue * SugarBeetSold[year]
   ;
   impvar Cost {year in YEARS} =
      &grain_cost * GrainBought[year]
    + &sugar_beet_cost * SugarBeetBought[year]
    + &nominal_labour_cost
    + &excess_labour_cost * NumExcessLabourHours[year]
    + sum {age in AGES} cow_other_costs[age] * NumCows[age,year]
    + sum {group in GROUPS} &grain_other_costs * GrainAcres[group,year]
    + &sugar_beet_other_costs * SugarBeetAcres[year]
    + sum {y in YEARS: y <= year} yearly_loan_payment * CapitalOutlay[y]
   ;
   impvar Profit {year in YEARS} = Revenue[year] - Cost[year];

The following objective declaration accounts for loan repayments beyond the planning horizon, as described in Williams (1999):


   max TotalProfit =
      sum {year in YEARS} (Profit[year]
         - yearly_loan_payment * (num_years - 1 + year) * CapitalOutlay[year]);

The following model declaration statements are straightforward:


   con Num_acres_con {year in YEARS}:
      sum {age in AGES} acres_needed[age] * NumCows[age,year]
    + sum {group in GROUPS} GrainAcres[group,year]
    + SugarBeetAcres[year]
   <= &num_acres;

   con Aging {age in AGES diff {&dairy_cow_selling_age},
              year in YEARS0 diff {num_years}}:
      NumCows[age+1,year+1] = (1 - annual_loss[age]) * NumCows[age,year];

   con NumBullocksSold_def {year in YEARS}:
      NumBullocksSold[year]
    = sum {age in AGES} bullock_yield[age] * NumCows[age,year];

   con NumHeifersSold_def {year in YEARS}:
      NumCows[0,year]
    = sum {age in AGES} heifer_yield[age] * NumCows[age,year]
    - NumHeifersSold[year];

   con Max_num_cows_def {year in YEARS}:
      sum {age in AGES} NumCows[age,year]
   <= &max_num_cows + sum {y in YEARS: y <= year} CapitalOutlay[y];

   impvar GrainGrown {group in GROUPS, year in YEARS} =
      grain_yield[group] * GrainAcres[group,year];
   con Grain_req_def {year in YEARS}:
      sum {age in AGES} grain_req[age] * NumCows[age,year]
   <= sum {group in GROUPS} GrainGrown[group,year]
    + GrainBought[year] - GrainSold[year];

   impvar SugarBeetGrown {year in YEARS} =
      &sugar_beet_yield * SugarBeetAcres[year];
   con Sugar_beet_req_def {year in YEARS}:
      sum {age in AGES} sugar_beet_req[age] * NumCows[age,year]
   <= SugarBeetGrown[year] + SugarBeetBought[year] - SugarBeetSold[year];

   con Labour_req_def {year in YEARS}:
      sum {age in AGES} cow_labour_req[age] * NumCows[age,year]
    + sum {group in GROUPS} &grain_labour_req * GrainAcres[group,year]
    + &sugar_beet_labour_req * SugarBeetAcres[year]
   <= &nominal_labour_hours + NumExcessLabourHours[year];

   con Cash_flow {year in YEARS}:
      Profit[year] >= 0;

The following CON statement declares a range constraint:

   con Final_dairy_cows_range:
      1 - &max_decrease_ratio
   <= (sum {age in AGES: age >= 2} NumCows[age,num_years])
    / (sum {age in AGES: age >= 2} init_num_cows[age])
   <= 1 + &max_increase_ratio;

   solve;

   print NumCows NumBullocksSold NumHeifersSold CapitalOutlay
      NumExcessLabourHours Revenue Cost Profit;
   print GrainAcres;
   print GrainGrown;
   print GrainBought GrainSold SugarBeetAcres SugarBeetGrown SugarBeetBought
      SugarBeetSold;
   print Num_acres_con.body Max_num_cows_def.body Final_dairy_cows_range.body;

The following CREATE DATA statement writes dense two-dimensional data to a data set:

   create data sol_data1 from [age]=AGES
      {year in YEARS} <col('NumCows_year'||year)=NumCows[age,year].sol>;

You could instead use the following CREATE DATA statement to interchange the roles of age and year:

   create data sol_data1 from [year]=YEARS
      {age in AGES} <col('NumCows_age'||age)=NumCows[age,year].sol>;

The following CREATE DATA statement writes sparse two-dimensional data to a data set:

   create data sol_data2 from [group year] GrainAcres GrainGrown;

The final CREATE DATA statement writes one-dimensional data to a data set, as in previous examples:

   create data sol_data3 from [year]
      NumBullocksSold NumHeifersSold CapitalOutlay NumExcessLabourHours
      Revenue Cost Profit GrainBought GrainSold
      SugarBeetAcres SugarBeetGrown SugarBeetBought SugarBeetSold;
quit;

Figure 8.2 shows the output from the linear programming solver.

Figure 8.2: Output from Linear Programming Solver

Problem Summary
Objective Sense Maximization
Objective Function TotalProfit
Objective Type Linear
   
Number of Variables 143
Bounded Above 0
Bounded Below 110
Bounded Below and Above 20
Free 0
Fixed 13
   
Number of Constraints 101
Linear LE (<=) 25
Linear EQ (=) 70
Linear GE (>=) 5
Linear Range 1
   
Constraint Coefficients 780

Performance Information
Execution Mode Single-Machine
Number of Threads 1

Solution Summary
Solver LP
Algorithm Dual Simplex
Objective Function TotalProfit
Solution Status Optimal
Objective Value 121719.17286
   
Primal Infeasibility 5.684342E-14
Dual Infeasibility 0
Bound Infeasibility 0
   
Iterations 47
Presolve Time 0.00
Solution Time 0.00

NumCows
  0 1 2 3 4 5
0 10.0000 22.8000 11.5844 0.0000 0.0000 0.0000
1 10.0000 9.5000 21.6600 11.0052 0.0000 0.0000
2 10.0000 9.5000 9.0250 20.5770 10.4549 0.0000
3 10.0000 9.8000 9.3100 8.8445 20.1655 10.2458
4 10.0000 9.8000 9.6040 9.1238 8.6676 19.7622
5 10.0000 9.8000 9.6040 9.4119 8.9413 8.4943
6 10.0000 9.8000 9.6040 9.4119 9.2237 8.7625
7 10.0000 9.8000 9.6040 9.4119 9.2237 9.0392
8 10.0000 9.8000 9.6040 9.4119 9.2237 9.0392
9 10.0000 9.8000 9.6040 9.4119 9.2237 9.0392
10 10.0000 9.8000 9.6040 9.4119 9.2237 9.0392
11 10.0000 9.8000 9.6040 9.4119 9.2237 9.0392
12 0.0000 9.8000 9.6040 9.4119 9.2237 9.0392

[1] NumBullocksSold NumHeifersSold CapitalOutlay NumExcessLabourHours Revenue Cost Profit
1 53.735 30.935 0 0 41495 19588 21906
2 52.342 40.757 0 0 41153 19265 21889
3 57.436 57.436 0 0 45212 19396 25816
4 56.964 56.964 0 0 45860 19034 26826
5 50.853 50.853 0 0 42717 17434 25283

GrainAcres
  1 2 3 4 5
group1 20.0000 20.0000 20.0000 20.0000 20.0000
group2 0.0000 0.0000 3.1342 0.0000 0.0000
group3 0.0000 0.0000 0.0000 0.0000 0.0000
group4 0.0000 0.0000 0.0000 0.0000 0.0000

GrainGrown
  1 2 3 4 5
group1 22.0000 22.0000 22.0000 22.0000 22.0000
group2 0.0000 0.0000 2.8207 0.0000 0.0000
group3 0.0000 0.0000 0.0000 0.0000 0.0000
group4 0.0000 0.0000 0.0000 0.0000 0.0000

[1] GrainBought GrainSold SugarBeetAcres SugarBeetGrown SugarBeetBought SugarBeetSold
1 36.620 0 60.767 91.150 0 22.760
2 35.100 0 62.670 94.005 0 27.388
3 37.837 0 65.100 97.650 0 24.550
4 40.143 0 76.429 114.643 0 42.143
5 33.476 0 87.539 131.309 0 66.586

[1] Num_acres_con.BODY Max_num_cows_def.BODY
1 200 130.000
2 200 128.411
3 200 115.434
4 200 103.571
5 200 92.461

Final_dairy_cows_range.BODY
0.92461