Skip to content

Single Stage Model Tutorial Doc #69

Closed

Description

@yasirroni

The example code in https://nrel-sienna.github.io/StorageSystemsSimulations.jl/stable/tutorials/single_stage_model/ is not feasible due to high reserve requirements.

We need to either remove the

set_service_model!(template_uc, ServiceModel(VariableReserve{ReserveUp}, RangeReserve))
set_service_model!(template_uc, ServiceModel(VariableReserve{ReserveDown}, RangeReserve))

Or add below snippets to make it feasible,

res_up = get_component(VariableReserve{ReserveUp}, sys, "Reserve3")
set_requirement!(res_up, 2.0)
res_down = get_component(VariableReserve{ReserveDown}, sys, "Reserve4")
set_requirement!(res_down, 1.0)

Then the complete code became,

using PowerSystems
using PowerSimulations
using StorageSystemsSimulations
using PowerSystemCaseBuilder
using HiGHS # solver

sys = build_system(
    PSITestSystems,
    "c_sys5_bat_ems";
    add_single_time_series=true,
    add_reserves=true,
)
orcd = get_component(ReserveDemandCurve, sys, "ORDC1")
set_available!(orcd, false)

res_up = get_component(VariableReserve{ReserveUp}, sys, "Reserve3")
set_requirement!(res_up, 2.0)
res_down = get_component(VariableReserve{ReserveDown}, sys, "Reserve4")
set_requirement!(res_down, 1.0)

batt = get_component(EnergyReservoirStorage, sys, "Bat2")

operation_cost = get_operation_cost(batt)

template_uc = ProblemTemplate(PTDFPowerModel)
set_device_model!(template_uc, ThermalStandard, ThermalStandardUnitCommitment)
set_device_model!(template_uc, RenewableDispatch, RenewableFullDispatch)
set_device_model!(template_uc, PowerLoad, StaticPowerLoad)
set_device_model!(template_uc, Line, StaticBranch)

storage_model = DeviceModel(
    EnergyReservoirStorage,
    StorageDispatchWithReserves;
    attributes=Dict(
        "reservation" => true,
        "energy_target" => false,
        "cycling_limits" => false,
        "regularization" => true,
    ),
)
set_device_model!(template_uc, storage_model)

set_service_model!(template_uc, ServiceModel(VariableReserve{ReserveUp}, RangeReserve))
set_service_model!(template_uc, ServiceModel(VariableReserve{ReserveDown}, RangeReserve))

solver = optimizer_with_attributes(HiGHS.Optimizer, "mip_rel_gap" => 0.01)
decision_model = DecisionModel(template_uc, sys; optimizer = solver, name = "UC")

using Dates
horizon = Hour(get_forecast_horizon(sys))  # 24 hours
interval = Hour(get_forecast_interval(sys))  # 24 hours

build!(decision_model; output_dir=mktempdir())
solve!(decision_model)

Activity

  1. yasirroni commented on Dec 3, 2025

    @yasirroni
    ContributorAuthor

    Related to #57

  2. yasirroni commented on Dec 3, 2025

    @yasirroni
    ContributorAuthor

    Will you guys accept if I make a PR that changes the reserve requirement?

    res_up = get_component(VariableReserve{ReserveUp}, sys, "Reserve3")
    set_requirement!(res_up, 2.0)
    res_down = get_component(VariableReserve{ReserveDown}, sys, "Reserve4")
    set_requirement!(res_down, 1.0)
  3. yasirroni commented on Dec 4, 2025

    @yasirroni
    ContributorAuthor

    I checked the "energy_target" parameter, why it is not respected based on the tutorial? Without the battery, the problem can be solved (no slack). Below I attached the code.

    using PowerSystems
    using PowerSimulations
    using StorageSystemsSimulations
    using PowerSystemCaseBuilder
    using HiGHS # solver
    
    sys = build_system(
        PSITestSystems,
        "c_sys5_bat_ems";
        add_single_time_series=true,
        add_reserves=true,
    )
    orcd = get_component(ReserveDemandCurve, sys, "ORDC1")
    set_available!(orcd, false)
    
    res_up = get_component(VariableReserve{ReserveUp}, sys, "Reserve3")
    set_requirement!(res_up, 2.0)
    res_down = get_component(VariableReserve{ReserveDown}, sys, "Reserve4")
    set_requirement!(res_down, 1.0)
    
    batt = get_component(EnergyReservoirStorage, sys, "Bat2")
    
    operation_cost = get_operation_cost(batt)
    
    template_uc = ProblemTemplate(PTDFPowerModel)
    set_device_model!(template_uc, ThermalStandard, ThermalStandardUnitCommitment)
    set_device_model!(template_uc, RenewableDispatch, RenewableFullDispatch)
    set_device_model!(template_uc, PowerLoad, StaticPowerLoad)
    set_device_model!(template_uc, Line, StaticBranch)
    
    storage_model = DeviceModel(
        EnergyReservoirStorage,
        StorageDispatchWithReserves;
        attributes=Dict(
            "reservation" => true,
            "energy_target" => true,
            "cycling_limits" => false,
            "regularization" => true,
        ),
    )
    set_device_model!(template_uc, storage_model)
    
    set_service_model!(template_uc, ServiceModel(VariableReserve{ReserveUp}, RangeReserve))
    set_service_model!(template_uc, ServiceModel(VariableReserve{ReserveDown}, RangeReserve))
    
    solver = optimizer_with_attributes(HiGHS.Optimizer, "mip_rel_gap" => 0.01)
    decision_model = DecisionModel(template_uc, sys; optimizer = solver, name = "UC")
    
    using Dates
    horizon = Hour(get_forecast_horizon(sys))  # 24 hours
    interval = Hour(get_forecast_interval(sys))  # 24 hours
    
    build!(decision_model; output_dir=mktempdir())
    solve!(decision_model)
    
    function get_battery_results(res)
        res_variables = read_variables(res)
        
        power_out = res_variables["ActivePowerOutVariable__EnergyReservoirStorage"]
        power_in = res_variables["ActivePowerInVariable__EnergyReservoirStorage"]
        energy = res_variables["EnergyVariable__EnergyReservoirStorage"]
        
        combined_df = DataFrame(
            DateTime = power_out.DateTime,
            Bat2_Discharge = power_out.Bat2,
            Bat2_Charge = power_in.Bat2,
            Bat2_Net = power_out.Bat2 .- power_in.Bat2,
            Bat2_Energy = energy.Bat2
        )
        return combined_df
    end
    
    res = OptimizationProblemResults(decision_model)
    combined_df = get_battery_results(res)
    println(combined_df)
    
    println(read_variables(res)["StorageEnergyShortageVariable__EnergyReservoirStorage"])
    println(read_variables(res)["StorageEnergySurplusVariable__EnergyReservoirStorage"])
  4. yasirroni commented on Jan 20, 2026

    @yasirroni
    ContributorAuthor

    The energy_target is not respected due to cheap energy_shortage_cost. Unfortunately, this component is not seen when we print the storage. See that operation_cost didn't show constants cost,

    storage = first(get_components(EnergyReservoirStorage, sys))
    # EnergyReservoirStorage: Bat2:
    #    name: Bat2
    #    available: true
    #    bus: ACBus: nodeA
    #    prime_mover_type: PrimeMovers.BA = 1
    #    storage_technology_type: StorageTech.OTHER_CHEM = 9
    #    storage_capacity: 7.0
    #    storage_level_limits: (min = 0.014285714285714287, max = 1.0)
    #    initial_storage_capacity_level: 0.7142857142857143
    #    rating: 7.0
    #    active_power: 2.0
    #    input_active_power_limits: (min = 0.0, max = 2.0)
    #    output_active_power_limits: (min = 0.0, max = 2.0)
    #    efficiency: (in = 0.8, out = 0.9)
    #    reactive_power: 0.0
    #    reactive_power_limits: (min = -2.0, max = 2.0)
    #    base_power: 100.0
    #    operation_cost: StorageCost composed of charge_variable_cost: CostCurve{LinearCurve}, discharge_variable_cost: CostCurve{LinearCurve}
    #    conversion_factor: 1.0
    #    storage_target: 0.2
    #    cycle_limits: 10000
    #    services: 0-element Vector{Service}
    #    dynamic_injector: nothing
    #    ext: Dict{String, Any}()
    #    InfrastructureSystems.SystemUnitsSettings:
    #       base_value: 100.0
    #       unit_system: UnitSystem.SYSTEM_BASE = 0
    #    has_supplemental_attributes: false
    #    has_time_series: false

    But it is visible with get_operation_cost,

    get_operation_cost(storage)
    # StorageCost: 
    #   charge_variable_cost: CostCurve:
    #     value_curve: LinearCurve (a type of InputOutputCurve) where function is: f(x) = 0.0 x + 0.0
    #     power_units: UnitSystem.NATURAL_UNITS = 2
    #     vom_cost: LinearCurve (a type of InputOutputCurve) where function is: f(x) = 0.0 x + 0.0
    #   discharge_variable_cost: CostCurve:
    #     value_curve: LinearCurve (a type of InputOutputCurve) where function is: f(x) = 0.0 x + 0.0
    #     power_units: UnitSystem.NATURAL_UNITS = 2
    #     vom_cost: LinearCurve (a type of InputOutputCurve) where function is: f(x) = 0.0 x + 0.0
    #   fixed: 0.0
    #   start_up: 0.0
    #   shut_down: 0.0
    #   energy_shortage_cost: 50.0
    #   energy_surplus_cost: 40.0

    operation_cost should print: StorageCost composed of charge_variable_cost: CostCurve{LinearCurve}, discharge_variable_cost: CostCurve{LinearCurve}, fixed: 0.0, start_up: 0.0, shut_down: 0.0, energy_shortage_cost: 50.0, energy_surplus_cost: 40.0. or at least StorageCost composed of charge_variable_cost: CostCurve{LinearCurve}, discharge_variable_cost: CostCurve{LinearCurve}, energy_shortage_cost: 50.0, energy_surplus_cost: 40.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions