|
|
@ -135,6 +135,7 @@ function _from_json(json; repair = true)::UnitCommitmentScenario
|
|
|
|
lines = TransmissionLine[]
|
|
|
|
lines = TransmissionLine[]
|
|
|
|
loads = PriceSensitiveLoad[]
|
|
|
|
loads = PriceSensitiveLoad[]
|
|
|
|
reserves = Reserve[]
|
|
|
|
reserves = Reserve[]
|
|
|
|
|
|
|
|
profiled_units = ProfiledUnit[]
|
|
|
|
|
|
|
|
|
|
|
|
function scalar(x; default = nothing)
|
|
|
|
function scalar(x; default = nothing)
|
|
|
|
x !== nothing || return default
|
|
|
|
x !== nothing || return default
|
|
|
@ -182,6 +183,7 @@ function _from_json(json; repair = true)::UnitCommitmentScenario
|
|
|
|
timeseries(dict["Load (MW)"]),
|
|
|
|
timeseries(dict["Load (MW)"]),
|
|
|
|
Unit[],
|
|
|
|
Unit[],
|
|
|
|
PriceSensitiveLoad[],
|
|
|
|
PriceSensitiveLoad[],
|
|
|
|
|
|
|
|
ProfiledUnit[],
|
|
|
|
)
|
|
|
|
)
|
|
|
|
name_to_bus[bus_name] = bus
|
|
|
|
name_to_bus[bus_name] = bus
|
|
|
|
push!(buses, bus)
|
|
|
|
push!(buses, bus)
|
|
|
@ -207,90 +209,119 @@ function _from_json(json; repair = true)::UnitCommitmentScenario
|
|
|
|
|
|
|
|
|
|
|
|
# Read units
|
|
|
|
# Read units
|
|
|
|
for (unit_name, dict) in json["Generators"]
|
|
|
|
for (unit_name, dict) in json["Generators"]
|
|
|
|
|
|
|
|
# Read and validate unit type
|
|
|
|
|
|
|
|
unit_type = scalar(dict["Type"], default = nothing)
|
|
|
|
|
|
|
|
unit_type !== nothing || error("unit $unit_name has no type specified")
|
|
|
|
bus = name_to_bus[dict["Bus"]]
|
|
|
|
bus = name_to_bus[dict["Bus"]]
|
|
|
|
|
|
|
|
|
|
|
|
# Read production cost curve
|
|
|
|
if lowercase(unit_type) === "thermal"
|
|
|
|
K = length(dict["Production cost curve (MW)"])
|
|
|
|
# Read production cost curve
|
|
|
|
curve_mw = hcat(
|
|
|
|
K = length(dict["Production cost curve (MW)"])
|
|
|
|
[timeseries(dict["Production cost curve (MW)"][k]) for k in 1:K]...,
|
|
|
|
curve_mw = hcat(
|
|
|
|
)
|
|
|
|
[
|
|
|
|
curve_cost = hcat(
|
|
|
|
timeseries(dict["Production cost curve (MW)"][k]) for
|
|
|
|
[timeseries(dict["Production cost curve (\$)"][k]) for k in 1:K]...,
|
|
|
|
k in 1:K
|
|
|
|
)
|
|
|
|
]...,
|
|
|
|
min_power = curve_mw[:, 1]
|
|
|
|
|
|
|
|
max_power = curve_mw[:, K]
|
|
|
|
|
|
|
|
min_power_cost = curve_cost[:, 1]
|
|
|
|
|
|
|
|
segments = CostSegment[]
|
|
|
|
|
|
|
|
for k in 2:K
|
|
|
|
|
|
|
|
amount = curve_mw[:, k] - curve_mw[:, k-1]
|
|
|
|
|
|
|
|
cost = (curve_cost[:, k] - curve_cost[:, k-1]) ./ amount
|
|
|
|
|
|
|
|
replace!(cost, NaN => 0.0)
|
|
|
|
|
|
|
|
push!(segments, CostSegment(amount, cost))
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Read startup costs
|
|
|
|
|
|
|
|
startup_delays = scalar(dict["Startup delays (h)"], default = [1])
|
|
|
|
|
|
|
|
startup_costs = scalar(dict["Startup costs (\$)"], default = [0.0])
|
|
|
|
|
|
|
|
startup_categories = StartupCategory[]
|
|
|
|
|
|
|
|
for k in 1:length(startup_delays)
|
|
|
|
|
|
|
|
push!(
|
|
|
|
|
|
|
|
startup_categories,
|
|
|
|
|
|
|
|
StartupCategory(
|
|
|
|
|
|
|
|
startup_delays[k] .* time_multiplier,
|
|
|
|
|
|
|
|
startup_costs[k],
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
)
|
|
|
|
)
|
|
|
|
end
|
|
|
|
curve_cost = hcat(
|
|
|
|
|
|
|
|
[
|
|
|
|
|
|
|
|
timeseries(dict["Production cost curve (\$)"][k]) for
|
|
|
|
|
|
|
|
k in 1:K
|
|
|
|
|
|
|
|
]...,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
min_power = curve_mw[:, 1]
|
|
|
|
|
|
|
|
max_power = curve_mw[:, K]
|
|
|
|
|
|
|
|
min_power_cost = curve_cost[:, 1]
|
|
|
|
|
|
|
|
segments = CostSegment[]
|
|
|
|
|
|
|
|
for k in 2:K
|
|
|
|
|
|
|
|
amount = curve_mw[:, k] - curve_mw[:, k-1]
|
|
|
|
|
|
|
|
cost = (curve_cost[:, k] - curve_cost[:, k-1]) ./ amount
|
|
|
|
|
|
|
|
replace!(cost, NaN => 0.0)
|
|
|
|
|
|
|
|
push!(segments, CostSegment(amount, cost))
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
# Read reserve eligibility
|
|
|
|
# Read startup costs
|
|
|
|
unit_reserves = Reserve[]
|
|
|
|
startup_delays = scalar(dict["Startup delays (h)"], default = [1])
|
|
|
|
if "Reserve eligibility" in keys(dict)
|
|
|
|
startup_costs = scalar(dict["Startup costs (\$)"], default = [0.0])
|
|
|
|
unit_reserves =
|
|
|
|
startup_categories = StartupCategory[]
|
|
|
|
[name_to_reserve[n] for n in dict["Reserve eligibility"]]
|
|
|
|
for k in 1:length(startup_delays)
|
|
|
|
end
|
|
|
|
push!(
|
|
|
|
|
|
|
|
startup_categories,
|
|
|
|
|
|
|
|
StartupCategory(
|
|
|
|
|
|
|
|
startup_delays[k] .* time_multiplier,
|
|
|
|
|
|
|
|
startup_costs[k],
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
# Read and validate initial conditions
|
|
|
|
# Read reserve eligibility
|
|
|
|
initial_power = scalar(dict["Initial power (MW)"], default = nothing)
|
|
|
|
unit_reserves = Reserve[]
|
|
|
|
initial_status = scalar(dict["Initial status (h)"], default = nothing)
|
|
|
|
if "Reserve eligibility" in keys(dict)
|
|
|
|
if initial_power === nothing
|
|
|
|
unit_reserves =
|
|
|
|
initial_status === nothing ||
|
|
|
|
[name_to_reserve[n] for n in dict["Reserve eligibility"]]
|
|
|
|
error("unit $unit_name has initial status but no initial power")
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
initial_status !== nothing ||
|
|
|
|
|
|
|
|
error("unit $unit_name has initial power but no initial status")
|
|
|
|
|
|
|
|
initial_status != 0 ||
|
|
|
|
|
|
|
|
error("unit $unit_name has invalid initial status")
|
|
|
|
|
|
|
|
if initial_status < 0 && initial_power > 1e-3
|
|
|
|
|
|
|
|
error("unit $unit_name has invalid initial power")
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
initial_status *= time_multiplier
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
unit = Unit(
|
|
|
|
# Read and validate initial conditions
|
|
|
|
unit_name,
|
|
|
|
initial_power =
|
|
|
|
bus,
|
|
|
|
scalar(dict["Initial power (MW)"], default = nothing)
|
|
|
|
max_power,
|
|
|
|
initial_status =
|
|
|
|
min_power,
|
|
|
|
scalar(dict["Initial status (h)"], default = nothing)
|
|
|
|
timeseries(dict["Must run?"], default = [false for t in 1:T]),
|
|
|
|
if initial_power === nothing
|
|
|
|
min_power_cost,
|
|
|
|
initial_status === nothing || error(
|
|
|
|
segments,
|
|
|
|
"unit $unit_name has initial status but no initial power",
|
|
|
|
scalar(dict["Minimum uptime (h)"], default = 1) * time_multiplier,
|
|
|
|
)
|
|
|
|
scalar(dict["Minimum downtime (h)"], default = 1) * time_multiplier,
|
|
|
|
else
|
|
|
|
scalar(dict["Ramp up limit (MW)"], default = 1e6),
|
|
|
|
initial_status !== nothing || error(
|
|
|
|
scalar(dict["Ramp down limit (MW)"], default = 1e6),
|
|
|
|
"unit $unit_name has initial power but no initial status",
|
|
|
|
scalar(dict["Startup limit (MW)"], default = 1e6),
|
|
|
|
)
|
|
|
|
scalar(dict["Shutdown limit (MW)"], default = 1e6),
|
|
|
|
initial_status != 0 ||
|
|
|
|
initial_status,
|
|
|
|
error("unit $unit_name has invalid initial status")
|
|
|
|
initial_power,
|
|
|
|
if initial_status < 0 && initial_power > 1e-3
|
|
|
|
startup_categories,
|
|
|
|
error("unit $unit_name has invalid initial power")
|
|
|
|
unit_reserves,
|
|
|
|
end
|
|
|
|
)
|
|
|
|
initial_status *= time_multiplier
|
|
|
|
push!(bus.units, unit)
|
|
|
|
end
|
|
|
|
for r in unit_reserves
|
|
|
|
|
|
|
|
push!(r.units, unit)
|
|
|
|
unit = Unit(
|
|
|
|
|
|
|
|
unit_name,
|
|
|
|
|
|
|
|
bus,
|
|
|
|
|
|
|
|
max_power,
|
|
|
|
|
|
|
|
min_power,
|
|
|
|
|
|
|
|
timeseries(dict["Must run?"], default = [false for t in 1:T]),
|
|
|
|
|
|
|
|
min_power_cost,
|
|
|
|
|
|
|
|
segments,
|
|
|
|
|
|
|
|
scalar(dict["Minimum uptime (h)"], default = 1) *
|
|
|
|
|
|
|
|
time_multiplier,
|
|
|
|
|
|
|
|
scalar(dict["Minimum downtime (h)"], default = 1) *
|
|
|
|
|
|
|
|
time_multiplier,
|
|
|
|
|
|
|
|
scalar(dict["Ramp up limit (MW)"], default = 1e6),
|
|
|
|
|
|
|
|
scalar(dict["Ramp down limit (MW)"], default = 1e6),
|
|
|
|
|
|
|
|
scalar(dict["Startup limit (MW)"], default = 1e6),
|
|
|
|
|
|
|
|
scalar(dict["Shutdown limit (MW)"], default = 1e6),
|
|
|
|
|
|
|
|
initial_status,
|
|
|
|
|
|
|
|
initial_power,
|
|
|
|
|
|
|
|
startup_categories,
|
|
|
|
|
|
|
|
unit_reserves,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
push!(bus.units, unit)
|
|
|
|
|
|
|
|
for r in unit_reserves
|
|
|
|
|
|
|
|
push!(r.units, unit)
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
name_to_unit[unit_name] = unit
|
|
|
|
|
|
|
|
push!(units, unit)
|
|
|
|
|
|
|
|
elseif lowercase(unit_type) === "profiled"
|
|
|
|
|
|
|
|
bus = name_to_bus[dict["Bus"]]
|
|
|
|
|
|
|
|
pu = ProfiledUnit(
|
|
|
|
|
|
|
|
unit_name,
|
|
|
|
|
|
|
|
bus,
|
|
|
|
|
|
|
|
timeseries(dict["Maximum power (MW)"]),
|
|
|
|
|
|
|
|
timeseries(dict["Cost (\$/MW)"]),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
push!(bus.profiled_units, pu)
|
|
|
|
|
|
|
|
push!(profiled_units, pu)
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
error("unit $unit_name has an invalid type")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
name_to_unit[unit_name] = unit
|
|
|
|
|
|
|
|
push!(units, unit)
|
|
|
|
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
# Read transmission lines
|
|
|
|
# Read transmission lines
|
|
|
@ -371,6 +402,8 @@ function _from_json(json; repair = true)::UnitCommitmentScenario
|
|
|
|
time = T,
|
|
|
|
time = T,
|
|
|
|
units_by_name = Dict(g.name => g for g in units),
|
|
|
|
units_by_name = Dict(g.name => g for g in units),
|
|
|
|
units = units,
|
|
|
|
units = units,
|
|
|
|
|
|
|
|
profiled_units_by_name = Dict(pu.name => pu for pu in profiled_units),
|
|
|
|
|
|
|
|
profiled_units = profiled_units,
|
|
|
|
isf = spzeros(Float64, length(lines), length(buses) - 1),
|
|
|
|
isf = spzeros(Float64, length(lines), length(buses) - 1),
|
|
|
|
lodf = spzeros(Float64, length(lines), length(lines)),
|
|
|
|
lodf = spzeros(Float64, length(lines), length(lines)),
|
|
|
|
)
|
|
|
|
)
|
|
|
|