Alternative Objectives & near-optimal alternatives
The objective passed to optimize! can be any scalar expression built from the snapshot.
This example first minimises total cost, then maximises gas capacity while keeping total cost within 1% of the optimum. This technique, sometimes referred to as Modelling to Generate Alternatives (MGA), is used to discover near-optimal alternatives. If the alternative objective's value is very different from its value in the cost-optimal solution, it indicates a choice; otherwise, similar values indicate a no-regrets decision.
using Nosy
using HiGHS
import JuMP: @constraint, set_silent # Avoid `using JuMP`; both JuMP and Nosy export optimize!.
function build_pv_gas_system()
s = Sim(Model(HiGHS.Optimizer); mesh=TimeMesh())
set_silent(model(s))
elec_carrier = EnergyCarrier("power", s)
# Synthetic data for load
hours = 1:8760
day_angle = 2pi .* ((hours .- 1) .% 24) ./ 24
season_angle = 2pi .* (hours .- 1) ./ 8760
load_profile = 3000 .+ 1500 .* sin.(day_angle .- pi / 2) .+
120 .* sin.(season_angle .- pi / 2)
# Synthetic data for PV
cf_pv = [
x < 1e-6 ? 0.0 : x for x in [
max(0, cos((h % 24 - 12) / 12 * pi) * (0.6 + 0.4 * sin(2pi * (h / 24) / 365)))
for h in 1:8760
]
]
# Snapshot initialisation
snapshot = Snapshot(s)
grid = Node("grid", elec_carrier, rule=:curtailed)
# Component: electricity consumption.
consumption = Component("consumption", Demand(elec_carrier, load_profile))
connect!(snapshot, consumption, grid)
# Component: PV.
pv = Component(
"PV",
ProfileSource(elec_carrier, cf_pv),
[
VariableCapacity("output", energy),
FixedCost(:capex, "output", energy, 50_000.0),
],
)
connect!(snapshot, pv, grid)
# Component: gas turbine.
gasplant = Component(
"gasplant",
DispatchableSource(elec_carrier),
[
VariableCapacity("output", energy),
FixedCost(:capex, "output", energy, 60_000.0),
VariableCost(:fuel, "output", energy, 50.0),
],
)
connect!(snapshot, gasplant, grid)
return snapshot
end
# First run: minimise total system cost.
cost_snapshot = build_pv_gas_system()
optimize!(cost_snapshot, cost(cost_snapshot))
cost_result = extract(cost_snapshot)
c0 = cost(cost_result) # Minimum system cost
# Second run: maximise gas capacity under a 1% system-cost slack.
snapshot = build_pv_gas_system()
s = sim(snapshot)
@constraint(model(s), cost(snapshot) <= c0 * 1.01)
optimize!(snapshot, -capacity(snapshot, "gasplant"))
result = extract(snapshot)The first run is the cost-optimal reference solution. In the second run:
- gas plant capacity is maximised by minimising
-capacity(snapshot, "gasplant") - total system cost is constrained to stay below
c0 * 1.01using JuMP
Expected results:
julia> table(cost_result, capacity)
1×3 DataFrame
Row │ PV consumption gasplant
│ Float64 Float64 Float64
─────┼────────────────────────────────
1 │ 6151.55 0.0 3508.23
julia> cost(result) / c0
1.0100000000000011
julia> table(result, capacity)
1×3 DataFrame
Row │ PV consumption gasplant
│ Float64 Float64 Float64
─────┼────────────────────────────────
1 │ 6151.55 0.0 3733.8The second solution spends the full 1% cost slack to increase gas capacity. The optimisation objective passed to optimize! is flexible: it can be any snapshot-derived scalar expression, including a JuMP expression or a number.