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
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).
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 |
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 |