diff --git a/psy_data/data_10bus_ac_dc_pu.jl b/psy_data/data_10bus_ac_dc_pu.jl index 9f3f12a..0d1a50f 100644 --- a/psy_data/data_10bus_ac_dc_pu.jl +++ b/psy_data/data_10bus_ac_dc_pu.jl @@ -172,6 +172,7 @@ branches10_ac(nodes10) = [ ), ] +# base_current (A) = S_base / V_base = 100 MVA / 500 kV (the DC bus base voltage) = 200 A. branches10_dc(nodes10_dc) = [ TModelHVDCLine( name = "nodeC_DC-nodeC2_DC", @@ -183,6 +184,7 @@ branches10_dc(nodes10_dc) = [ c = 0.0, active_power_limits_from=(min=-10.0, max=10.0), active_power_limits_to=(min=-10.0, max=10.0), + base_current = 200.0, ), TModelHVDCLine( name = "nodeD_DC-nodeD2_DC", @@ -194,6 +196,7 @@ branches10_dc(nodes10_dc) = [ c = 0.0, active_power_limits_from=(min=-10.0, max=10.0), active_power_limits_to=(min=-10.0, max=10.0), + base_current = 200.0, ), TModelHVDCLine( name = "nodeC_DC-nodeD2_DC", @@ -205,6 +208,7 @@ branches10_dc(nodes10_dc) = [ c = 0.0, active_power_limits_from=(min=-10.0, max=10.0), active_power_limits_to=(min=-10.0, max=10.0), + base_current = 200.0, ), ] diff --git a/psy_data/data_5bus_pu.jl b/psy_data/data_5bus_pu.jl index dd2d926..473615e 100644 --- a/psy_data/data_5bus_pu.jl +++ b/psy_data/data_5bus_pu.jl @@ -1017,64 +1017,71 @@ interruptible(nodes5) = [InterruptiblePowerLoad( # Natural Units: First vector: Power in MW, Second Vector: Slopes in $/MWh ORDC_cost = CostCurve(PiecewiseIncrementalCurve(0.0, [0.0, 20.0, 40.0, 60.0, 80.0], [150.0, 27.5, 24.5, 0.5])) +# Keyword form: `OnlineReserve`'s positional order differs from the old `ConstantReserve`. constant_reserve5() = - ConstantReserve{ReserveUp}( - "ReserveUp", - true, - 300, - 1.0, - 3600.0, - 1.0, - 1.0, - 0.0, + OnlineReserve{ReserveUp}(; + name = "ReserveUp", + available = true, + time_frame = 300, + requirement = 1.0, + sustained_time = 3600.0, + max_output_fraction = 1.0, + max_participation_factor = 1.0, + deployed_fraction = 0.0, ) +# The ORDC carries its demand curve on `variable`; non-spinning is `OfflineReserve` (no direction). reserve5(thermal_generators5) = [ - VariableReserve{ReserveUp}( + OnlineReserve{ReserveUp}( "Reserve1", true, 0.6, maximum([gen.active_power_limits[:max] for gen in thermal_generators5]) .* 0.001, ), - VariableReserve{ReserveDown}( + OnlineReserve{ReserveDown}( "Reserve2", true, 0.3, maximum([gen.active_power_limits[:max] for gen in thermal_generators5]) .* 0.005, ), - VariableReserve{ReserveUp}( + OnlineReserve{ReserveUp}( "Reserve11", true, 0.8, maximum([gen.active_power_limits[:max] for gen in thermal_generators5]) .* 0.001, ), - ReserveDemandCurve{ReserveUp}(nothing, "ORDC1", true, 0.6), - VariableReserveNonSpinning("NonSpinningReserve", true, 0.5, maximum([gen.active_power_limits[:max] for gen in thermal_generators5]) .* 0.001), + OnlineReserve{ReserveUp}(; + name = "ORDC1", + available = true, + time_frame = 0.6, + variable = ORDC_cost, + ), + OfflineReserve("NonSpinningReserve", true, 0.5, maximum([gen.active_power_limits[:max] for gen in thermal_generators5]) .* 0.001), ] reserve5_re(renewable_generators5) = [ - VariableReserve{ReserveUp}("Reserve3", true, 30, 100), - VariableReserve{ReserveDown}("Reserve4", true, 5, 50), - ReserveDemandCurve{ReserveUp}(nothing, "ORDC1", true, 0.6), + OnlineReserve{ReserveUp}("Reserve3", true, 30, 100), + OnlineReserve{ReserveDown}("Reserve4", true, 5, 50), + OnlineReserve{ReserveUp}(; name = "ORDC1", available = true, time_frame = 0.6, variable = ORDC_cost), ] reserve5_hy(hydro_generators5) = [ - VariableReserve{ReserveUp}("Reserve5", true, 30, 100), - VariableReserve{ReserveDown}("Reserve6", true, 5, 50), - ReserveDemandCurve{ReserveUp}(nothing, "ORDC1", true, 0.6), + OnlineReserve{ReserveUp}("Reserve5", true, 30, 100), + OnlineReserve{ReserveDown}("Reserve6", true, 5, 50), + OnlineReserve{ReserveUp}(; name = "ORDC1", available = true, time_frame = 0.6, variable = ORDC_cost), ] reserve5_il(interruptible_loads) = [ - VariableReserve{ReserveUp}("Reserve7", true, 30, 100), - VariableReserve{ReserveDown}("Reserve8", true, 5, 50), - ReserveDemandCurve{ReserveUp}(nothing, "ORDC1", true, 0.6), + OnlineReserve{ReserveUp}("Reserve7", true, 30, 100), + OnlineReserve{ReserveDown}("Reserve8", true, 5, 50), + OnlineReserve{ReserveUp}(; name = "ORDC1", available = true, time_frame = 0.6, variable = ORDC_cost), ] reserve5_phes(phes5) = [ - VariableReserve{ReserveUp}("Reserve9", true, 30, 100), - VariableReserve{ReserveDown}("Reserve10", true, 5, 50), - ReserveDemandCurve{ReserveUp}(nothing, "ORDC1", true, 0.6), + OnlineReserve{ReserveUp}("Reserve9", true, 30, 100), + OnlineReserve{ReserveDown}("Reserve10", true, 5, 50), + OnlineReserve{ReserveUp}(; name = "ORDC1", available = true, time_frame = 0.6, variable = ORDC_cost), ] # TODO: add a sensible cost for hybrid devices diff --git a/psy_data/generation_cost_function_data.jl b/psy_data/generation_cost_function_data.jl index e3d9944..37f2248 100644 --- a/psy_data/generation_cost_function_data.jl +++ b/psy_data/generation_cost_function_data.jl @@ -338,13 +338,11 @@ end ## Single Bid MarketBid Cost Function function thermal_generators_market_bid(node) - market_bid1 = MarketBidCost( - 30.0, - (hot = 1.5, warm = 1.5, cold = 1.5), - 0.75, - CostCurve(PiecewiseIncrementalCurve(0.0, [10.0, 30.0, 50.0, 100.0], [63.37, 98.155, 102.276])), - nothing, - Vector{Service}(), + market_bid1 = MarketBidCost(; + no_load_cost = LinearCurve(30.0), + start_up = (hot = 1.5, warm = 1.5, cold = 1.5), + shut_down = LinearCurve(0.75), + incremental_offer_curves = CostCurve(PiecewiseIncrementalCurve(0.0, [10.0, 30.0, 50.0, 100.0], [63.37, 98.155, 102.276])), ) gen1 = ThermalStandard( @@ -365,13 +363,11 @@ function thermal_generators_market_bid(node) base_power=100.0, ) - market_bid2 = MarketBidCost( - 50.0, - (hot = 1.5, warm = 1.5, cold = 1.5), - 0.75, - CostCurve(PiecewiseIncrementalCurve(0.0, [10.0, 30.0, 50.0, 100.0], [66.37, 88.155, 109.276])), - nothing, - Vector{Service}(), + market_bid2 = MarketBidCost(; + no_load_cost = LinearCurve(50.0), + start_up = (hot = 1.5, warm = 1.5, cold = 1.5), + shut_down = LinearCurve(0.75), + incremental_offer_curves = CostCurve(PiecewiseIncrementalCurve(0.0, [10.0, 30.0, 50.0, 100.0], [66.37, 88.155, 109.276])), ) gen2 = ThermalStandard( @@ -395,6 +391,42 @@ function thermal_generators_market_bid(node) return [gen1, gen2] end +# Constant-valued forecast matching the market-bid windows (2 windows x 5 steps, hourly). +_mbts_constant_forecast(ini_time, val) = Dict( + ini_time => fill(val, 5), + ini_time + Hour(1) => fill(val, 5), +) + +# Attach the series backing a `MarketBidTimeSeriesCost` (fields reference them by key, +# so `gen` must already be in the system) and set it as the operation cost. +function _set_ts_market_bid_cost!(sys, gen, incremental_bid, ini_time, no_load) + _det(name, val) = PSY.Deterministic(; + name = name, + data = _mbts_constant_forecast(ini_time, val), + resolution = Hour(1), + ) + pwl_key = PSY.add_time_series!(sys, gen, incremental_bid) + init_key = PSY.add_time_series!(sys, gen, _det("initial_input_incremental", 0.0)) + dec_key = PSY.add_time_series!( + sys, gen, _det("variable_cost_decremental", PiecewiseStepData([0.0, 0.0], [0.0])), + ) + dec_init_key = PSY.add_time_series!(sys, gen, _det("initial_input_decremental", 0.0)) + nl_key = PSY.add_time_series!(sys, gen, _det("no_load_cost", no_load)) + su_key = PSY.add_time_series!(sys, gen, _det("start_up", (1.5, 1.5, 1.5))) + sd_key = PSY.add_time_series!(sys, gen, _det("shut_down", 0.75)) + PSY.set_operation_cost!( + gen, + PSY.MarketBidTimeSeriesCost(; + no_load_cost = PSY.TimeSeriesLinearCurve(nl_key), + start_up = PSY.IS.TupleTimeSeries{PSY.StartUpStages}(su_key), + shut_down = PSY.TimeSeriesLinearCurve(sd_key), + incremental_offer_curves = PSY.make_market_bid_ts_curve(pwl_key, init_key), + decremental_offer_curves = PSY.make_market_bid_ts_curve(dec_key, dec_init_key), + ), + ) + return +end + function thermal_generators_market_bid_ts(sys, node) ini_time = DateTime("1/1/2024 0:00:00", "d/m/y H:M:S") market_bid_gen1_data = Dict( @@ -441,14 +473,8 @@ function thermal_generators_market_bid_ts(sys, node) resolution = Hour(1), ) - market_bid1 = MarketBidCost( - 30.0, - (hot = 1.5, warm = 1.5, cold = 1.5), - 0.75, - nothing, - nothing, - Vector{Service}(), - ) + # Placeholder, replaced by the TS-backed cost after `add_component!`. + market_bid1 = MarketBidCost(nothing) gen1 = ThermalStandard( name="Test Unit1", @@ -468,14 +494,7 @@ function thermal_generators_market_bid_ts(sys, node) base_power=100.0, ) - market_bid2 = MarketBidCost( - 50.0, - (hot = 1.5, warm = 1.5, cold = 1.5), - 0.75, - nothing, - nothing, - Vector{Service}(), - ) + market_bid2 = MarketBidCost(nothing) gen2 = ThermalStandard( name="Test Unit2", @@ -498,8 +517,8 @@ function thermal_generators_market_bid_ts(sys, node) PSY.add_component!(sys, gen1) PSY.add_component!(sys, gen2) - PSY.set_variable_cost!(sys, gen1, market_bid_gen1, PSY.UnitSystem.NATURAL_UNITS) - PSY.set_variable_cost!(sys, gen2, market_bid_gen2, PSY.UnitSystem.NATURAL_UNITS) + _set_ts_market_bid_cost!(sys, gen1, market_bid_gen1, ini_time, 30.0) + _set_ts_market_bid_cost!(sys, gen2, market_bid_gen2, ini_time, 50.0) return [gen1, gen2] end