MixedFarm.Rmd
In Mediterranean mixed‐farm systems, land and labor are shared between arable crops and livestock enterprises. The objective of this analysis is to determine the profit‐maximizing allocation of 15 ha of land, 350 h of labor and 400 kg of available nitrogen among four winter crops and a temporary pasture for sheep. Each crop carries standard agronomic area bounds (e.g. oat between 2 ha and 8 ha), and pasture must lie between 2 ha and 4 ha. Margins (€/ha) and resource requirements (land, labor, nitrogen) are summarized in Table 1.
Table 1. Resource requirements, margins and area bounds for each activity.
Activity | Land (ha) | Labor (h) | Nitrogen (kg) | Margin (€/ha) | Area bounds (ha) |
---|---|---|---|---|---|
Oat | 1 | 20 | 80 | 400 | 2 – 8 |
Barley | 1 | 25 | 100 | 450 | 1 – 6 |
Lupin | 1 | 15 | 0 | 350 | 2 – 5 |
Fava | 1 | 30 | 30 | 500 | 1 – 3 |
Pasture | 1 | 10 | 0 | 360 | 2 – 4 |
We formulate a linear program to maximize total gross margin subject
to resource and area‐bound constraints. The following sections describe
the model construction, solution and sensitivity analysis using the
ram
package.
We first declare the available resources and the minimum/maximum area
constraints for each crop and pasture. The function
define_resources()
accepts three parallel vectors: resource
names, availabilities, and constraint directions ("≤"
or
"≥"
).
# Step 1: Define Resource Constraints (flexible min/max for each crop)
resources <- define_resources(
resources = c(
# total availables
"land", "labor", "nitrogen",
# per‐crop bounds
"oat_min", "oat_max",
"barley_min","barley_max",
"lupin_min", "lupin_max",
"fava_min", "fava_max",
"pasture_min","pasture_max"
),
availability = c(
# totals
15, 350, 400,
# bounds
2, 8, # oat
1, 6, # barley
2, 5, # lupin
1, 3, # fava
2, 4 # pasture
),
direction = c(
# totals all <=
"<=", "<=", "<=",
# minimums >=, maximums <=
">=", "<=", # oat
">=", "<=", # barley
">=", "<=", # lupin
">=", "<=", # fava
">=", "<=" # pasture
)
)
print(resources)
#> resource availability direction
#> 1 land 15 <=
#> 2 labor 350 <=
#> 3 nitrogen 400 <=
#> 4 oat_min 2 >=
#> 5 oat_max 8 <=
#> 6 barley_min 1 >=
#> 7 barley_max 6 <=
#> 8 lupin_min 2 >=
#> 9 lupin_max 5 <=
#> 10 fava_min 1 >=
#> 11 fava_max 3 <=
#> 12 pasture_min 2 >=
#> 13 pasture_max 4 <=
Next, we assemble the technical matrix, whose rows correspond to the resources declared above, and whose columns correspond to the five activities. Each entry is the per‐unit requirement (or, for area‐bounds rows, an indicator). The vector objective lists the margin for each activity.
# Step 2: Define Activities
activity_requirements_matrix <- matrix(
c(
# land (ha)
1, 1, 1, 1, 1,
# labor (h)
20, 25, 15, 30, 10,
# nitrogen (kg)
80, 100, 0, 30, 0,
# bounds indicator rows (1 for that crop, 0 else)
# oat_min
1, 0, 0, 0, 0,
# oat_max
1, 0, 0, 0, 0,
# barley_min
0, 1, 0, 0, 0,
# barley_max
0, 1, 0, 0, 0,
# lupin_min
0, 0, 1, 0, 0,
# lupin_max
0, 0, 1, 0, 0,
# fava_min
0, 0, 0, 1, 0,
# fava_max
0, 0, 0, 1, 0,
# pasture_min
0, 0, 0, 0, 1,
# pasture_max
0, 0, 0, 0, 1
),
nrow = nrow(resources),
byrow = TRUE,
dimnames = list(
resources$resource,
c("oat","barley","lupin","fava","pasture")
)
)
objective <- c(oat=400, barley=450, lupin=350, fava=500, pasture=360)
activities <- define_activities(
activities = colnames(activity_requirements_matrix),
activity_requirements_matrix = activity_requirements_matrix,
objective = objective
)
print(activities)
#> activity land labor nitrogen oat_min oat_max barley_min barley_max
#> oat oat 1 20 80 1 1 0 0
#> barley barley 1 25 100 0 0 1 1
#> lupin lupin 1 15 0 0 0 0 0
#> fava fava 1 30 30 0 0 0 0
#> pasture pasture 1 10 0 0 0 0 0
#> lupin_min lupin_max fava_min fava_max pasture_min pasture_max objective
#> oat 0 0 0 0 0 0 400
#> barley 0 0 0 0 0 0 450
#> lupin 1 1 0 0 0 0 350
#> fava 0 0 1 1 0 0 500
#> pasture 0 0 0 0 1 1 360
The model is constructed via create_ram_model()
and
solved in the maximization sense with solve_ram()
.
# Step 3: Build and Solve the Model
model <- create_ram_model(resources, activities)
solution <- solve_ram(model, direction = "max")
The optimal area (ha) for each activity is extracted from
solution$optimal_activities
and presented in Table 2.
Table 2. Optimal area allocation (ha) under maximized gross margin.
dat <- data.frame(
Activity = names(solution$optimal_activities),
Area_ha = round(as.numeric(solution$optimal_activities), 1)
)
DT::datatable(
dat,
caption = htmltools::tags$caption(
style = "caption-side: top; text-align: left;",
"Table 2. Optimal area allocation (ha) under maximized gross margin."
),
rownames = FALSE,
options = list(
autoWidth = TRUE,
columnDefs = list(
list(width = '3cm', targets = 0), # first column (activity)
list(width = '2cm', targets = 1) # second column (level)
)
)
) |>
DT::formatRound("Area_ha", 2)
The maximum total gross margin achieved is:
cat("Maximum gross margin (EUR):", solution$objective_value, "\n")
#> Maximum gross margin (EUR): 5990
plot_ram(solution)
To explore the economic value of relaxing each constraint, we compute
the shadow prices via sensitivity_ram()
. A non‐zero shadow
price for a "≤"
constraint indicates the marginal gain per
extra unit of that resource.
sensitivity_ram(solution)
#> resource direction availability shadow_price lower_bound upper_bound
#> 1 land <= 15 0 0 0
#> 2 labor <= 350 NA NA NA
#> 3 nitrogen <= 400 NA NA NA
#> 4 oat_min >= 2 NA NA NA
#> 5 oat_max <= 8 NA NA NA
#> 6 barley_min >= 1 NA NA NA
#> 7 barley_max <= 6 NA NA NA
#> 8 lupin_min >= 2 NA NA NA
#> 9 lupin_max <= 5 NA NA NA
#> 10 fava_min >= 1 NA NA NA
#> 11 fava_max <= 3 NA NA NA
#> 12 pasture_min >= 2 NA NA NA
#> 13 pasture_max <= 4 NA NA NA
# Compute sensitivity
sens <- sensitivity_ram(solution)
# Render as a clean datatable
DT::datatable(
sens,
caption = htmltools::tags$caption(
style = "caption-side: top; text-align: left;",
"Table 3. Sensitivity analysis of constraints (shadow prices and allowable ranges)."
),
rownames = FALSE,
colnames = c(
"Resource", "Direction", "Availability",
"Shadow Price", "Lower Bound", "Upper Bound"
),
options = list(
pageLength = nrow(sens),
dom = 't', # only the table, no extra controls
columnDefs = list(
list(className = 'dt-center', targets = 0:5)
)
)
) |>
DT::formatRound(columns = c("availability", "shadow_price", "lower_bound", "upper_bound"), digits = 2)
The sensitivity analysis in Table 3 highlights which constraints are active at the optimum and how marginal changes in resource availability would affect the maximum gross margin. A shadow price of zero for land indicates that, under the current parameterisation, land is not the binding constraint—there is slack capacity. The absence of numeric shadow prices (NA) for labor, nitrogen, and the per‐crop bounds signifies that these constraints either are non‐binding or lie outside the solver’s range reporting.
Notably, all minimum area bounds for the four winter crops and the temporary pasture are respected exactly (the solution values coincide with their minimum or maximum limits), confirming that these structural requirements shape the optimal plan. Practitioners may use these insights to decide whether relaxing or tightening particular bounds (e.g., allowing more pasture area or increasing labor availability) could unlock additional profitability. Moreover, this framework can readily incorporate further enterprises (e.g., olive groves, poultry) or environmental targets (e.g., nitrate leaching limits) by augmenting the resources and activities matrices accordingly.