Construction Start Opt-In

This example shows that construction-start behavior is component-local and opt-in. The model contains two independent generators in the same Path:

  • delayed_gen includes VariableConstructionStart("output", energy, 2).
  • ordinary_gen has deployment behavior but no construction-start behavior.

Both generators are optimized together. Only delayed_gen reports construction starts before its deployment years.

Model

using EnergyPathway
using HiGHS
using JuMP: objective_value, set_silent

function add_supply_case!(snap, cname, demand; construction_duration=nothing)
    carrier = EnergyCarrier("$(cname)_power", sim(snap))
    grid = Node("$(cname)_grid", carrier)

    load = Component("$(cname)_load", Demand(carrier, demand))
    connect!(snap, load, grid)

    behaviors = Any[
        VariableCapacity("output", energy),
        VariableDeployment("output", energy),
    ]
    if !isnothing(construction_duration)
        push!(behaviors, VariableConstructionStart("output", energy, construction_duration))
    end
    append!(
        behaviors,
        [
            VariableRetirement("output", energy),
            Lifetime(30),
            SingleCost(:capex, :deployment, "output", energy, 1.0, nothing),
        ],
    )

    generator = Component(cname, DispatchableSource(carrier), behaviors)
    connect!(snap, generator, grid)

    return snap
end

function build_pathway()
    opt = PathOpt(; mesh=TimeMesh(fill(1 // 1, 2)), endyear=2030)
    path = Path(HiGHS.Optimizer, opt)
    set_silent(model(path))

    for (year, delayed_demand, ordinary_demand) in [
        (2020, 4.0, 6.0),
        (2030, 7.0, 9.0),
    ]
        snap = addsnapshot!(path, year)
        add_supply_case!(snap, "delayed_gen", delayed_demand; construction_duration=2)
        add_supply_case!(snap, "ordinary_gen", ordinary_demand)
    end

    optimize!(path, cost(path))

    return extract(path), objective_value(model(path))
end

display_value(x) = round(x; digits=6)

function print_summary(path, objective)
    println("Objective: ", display_value(objective))
    println("First model year: ", firstyear(path))
    println()

    println("Component with VariableConstructionStart:")
    println("  delayed_gen capacity in 2020: ", display_value(capacity(path, "delayed_gen", 2020)))
    println("  delayed_gen capacity in 2030: ", display_value(capacity(path, "delayed_gen", 2030)))
    println("  delayed_gen deployment in 2030: ", display_value(deployment(path, "delayed_gen", 2030)))
    println("  delayed_gen construction start in 2018: ", display_value(constructionstart(path, "delayed_gen", 2018)))
    println("  delayed_gen construction start in 2028: ", display_value(constructionstart(path, "delayed_gen", 2028)))
    println()

    println("Component without construction-start behavior:")
    println("  ordinary_gen capacity in 2020: ", display_value(capacity(path, "ordinary_gen", 2020)))
    println("  ordinary_gen capacity in 2030: ", display_value(capacity(path, "ordinary_gen", 2030)))
    println("  ordinary_gen deployment in 2030: ", display_value(deployment(path, "ordinary_gen", 2030)))
    println("  ordinary_gen construction start in 2018: ", display_value(constructionstart(path, "ordinary_gen", 2018)))
    println("  ordinary_gen construction start in 2028: ", display_value(constructionstart(path, "ordinary_gen", 2028)))
end

result, objective = build_pathway()
print_summary(result, objective)
Objective: 13.68348
First model year: 2018

Component with VariableConstructionStart:
  delayed_gen capacity in 2020: 4.0
  delayed_gen capacity in 2030: 7.0
  delayed_gen deployment in 2030: 3.0
  delayed_gen construction start in 2018: 4.0
  delayed_gen construction start in 2028: 3.0

Component without construction-start behavior:
  ordinary_gen capacity in 2020: 6.0
  ordinary_gen capacity in 2030: 9.0
  ordinary_gen deployment in 2030: 3.0
  ordinary_gen construction start in 2018: 0.0
  ordinary_gen construction start in 2028: 0.0

firstyear(result) is 2018 because the construction-start behavior on delayed_gen creates a start event two years before the first 2020 deployment. The ordinary generator is unchanged: it deploys capacity in snapshot years, and constructionstart(result, "ordinary_gen", year) returns zero.