Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ authors = ["Sienna-Platform"]
[deps]
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
InfraStore = "6aee0376-896a-4a59-96ff-12f221c99746"
InfrastructureOptimizationModels = "bed98974-b02a-5e2f-9ee0-a103f5c45069"
InfrastructureSystems = "2cd47ed4-ca9b-11e9-27f2-ab636a7671f1"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
Expand All @@ -25,10 +26,11 @@ TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"
PowerFlows = "94fada2c-fd9a-4e89-8d82-81405f5cb4f6"

[sources]
InfrastructureSystems = {rev = "IS4", url = "https://github.com/Sienna-Platform/InfrastructureSystems.jl"}
PowerSystems = {rev = "psy6", url = "https://github.com/Sienna-Platform/PowerSystems.jl"}
InfrastructureOptimizationModels = {rev = "main", url = "https://github.com/Sienna-Platform/InfrastructureOptimizationModels.jl"}
InfraStore = {path = "../../infrastore/julia/InfraStore.jl"}
InfrastructureOptimizationModels = {path = "../InfrastructureOptimizationModels.jl"}
InfrastructureSystems = {path = "../IS3.jl"}
PowerNetworkMatrices = {rev = "psy6", url = "https://github.com/Sienna-Platform/PowerNetworkMatrices.jl"}
PowerSystems = {rev = "feat/rust-time-series-store", url = "https://github.com/Sienna-Platform/PowerSystems.jl"}

[extensions]
PowerFlowsExt = "PowerFlows"
Expand All @@ -39,9 +41,9 @@ DocStringExtensions = "~0.8, ~0.9"
InfrastructureSystems = "3"
InteractiveUtils = "1.11.0"
JuMP = "^1.28"
PowerSystems = "^5.10"
PowerFlows = "0.21, 0.22"
PowerNetworkMatrices = "^0.24"
PowerSystems = "^5.10"
PrettyTables = "3"
ProgressMeter = "1.11.0"
TimerOutputs = "~0.5"
Expand Down
92 changes: 53 additions & 39 deletions src/common_models/add_parameters.jl
Original file line number Diff line number Diff line change
Expand Up @@ -162,35 +162,43 @@ function _add_time_series_parameters!(
device_names = String[]
devices_with_time_series = D[]
initial_values = Dict{String, AbstractArray}()
# device name -> ts_uuid cache so the second loop below doesn't re-query IS.
device_ts_uuids = Dict{String, String}()
# device name -> data hash cache so the second loop below doesn't re-query IS.
device_ts_hashes = Dict{String, String}()
model_interval = get_interval(get_settings(container))
is_ts_interval = _to_is_interval(model_interval)
model_resolution = get_resolution(get_settings(container))
is_ts_resolution = _to_is_resolution(model_resolution)

@debug "adding" T D ts_name ts_type _group = IOM.LOG_GROUP_OPTIMIZATION_CONTAINER

# One catalog query resolves the content hash of every device's stored array.
# Devices whose time series share an array share a hash, so the parameter rows
# dedupe on it (the role the per-array time series UUID used to play).
ts_hashes_by_id = IS.get_time_series_hashes(
devices,
ts_type,
ts_name;
resolution = is_ts_resolution,
interval = is_ts_interval,
)
for device::D in devices
if !PSY.has_time_series(device, ts_type, ts_name)
@debug "Time series $(ts_type):$(ts_name) for $D, $(PSY.get_name(device)) not found. Skipping parameter addition for this device."
continue
end
device_name = PSY.get_name(device)
ts_hash = get(ts_hashes_by_id, IS.get_id(device), nothing)
if ts_hash === nothing
error(
"Time series $(ts_type):$(ts_name) for $D, $device_name does not " *
"match resolution=$model_resolution interval=$model_interval.",
)
end
push!(device_names, device_name)
push!(devices_with_time_series, device)
ts_uuid = string(
IS.get_time_series_uuid(
ts_type,
device,
ts_name;
resolution = is_ts_resolution,
interval = is_ts_interval,
),
)
device_ts_uuids[device_name] = ts_uuid
if !(ts_uuid in keys(initial_values))
initial_values[ts_uuid] =
device_ts_hashes[device_name] = ts_hash
if !(ts_hash in keys(initial_values))
initial_values[ts_hash] =
IOM.get_time_series_initial_values!(
container,
ts_type,
Expand All @@ -199,7 +207,7 @@ function _add_time_series_parameters!(
interval = model_interval,
resolution = model_resolution,
)
_check_branch_rating_ts(initial_values[ts_uuid], param, device, model)
_check_branch_rating_ts(initial_values[ts_hash], param, device, model)
end
end

Expand Down Expand Up @@ -227,7 +235,7 @@ function _add_time_series_parameters!(
jump_model = get_jump_model(container)
param_instance = T()
parent_param = IOM.get_parameter_array_data(param_container)
for (i, (ts_uuid, raw_ts_vals)) in enumerate(initial_values)
for (i, (ts_hash, raw_ts_vals)) in enumerate(initial_values)
ts_vals = IOM.unwrap_for_param.((param_instance,), raw_ts_vals, (additional_axes,))
@assert all(_size_wrapper.(ts_vals) .== (length.(additional_axes),))

Expand All @@ -244,7 +252,7 @@ function _add_time_series_parameters!(
IOM.add_component_name!(
IOM.get_attributes(param_container),
device_name,
device_ts_uuids[device_name],
device_ts_hashes[device_name],
)
end
return
Expand Down Expand Up @@ -280,7 +288,7 @@ function _add_time_series_parameters!(
ts_name = _get_time_series_name(T, first(devices), model)
model_interval = get_interval(get_settings(container))
ts_interval = model_interval
device_name_axis, ts_uuid_axis =
device_name_axis, ts_hash_axis =
get_branch_argument_parameter_axes(
net_reduction_data,
devices,
Expand All @@ -292,17 +300,17 @@ function _add_time_series_parameters!(
@info "No devices with time series $ts_name found for $D devices. Skipping parameter addition."
return
end
# name -> ts_uuid cache built from the axis pair so the per-branch loop below
# doesn't re-query IS.get_time_series_uuid for each branch.
branch_ts_uuids = Dict{String, String}(zip(device_name_axis, ts_uuid_axis))
# name -> data hash cache built from the axis pair so the per-branch loop below
# doesn't re-query the store catalog for each branch.
branch_ts_hashes = Dict{String, String}(zip(device_name_axis, ts_hash_axis))
additional_axes = ()
param_container = add_param_container!(
container,
T,
D,
ts_type,
ts_name,
ts_uuid_axis,
ts_hash_axis,
device_name_axis,
additional_axes,
time_steps,
Expand All @@ -312,10 +320,10 @@ function _add_time_series_parameters!(
jump_model = get_jump_model(container)
parent_param = IOM.get_parameter_array_data(param_container)
parent_mult = IOM.get_multiplier_array_data(param_container)
# The param array's first axis is `ts_uuid_axis` (UUID-keyed) while the multiplier
# array's first axis is `device_name_axis` (name-keyed); we need two separate row
# lookups so parallel branches sharing a UUID still write their multiplier to the
# correct (per-branch-name) row.
# The param array's first axis is `ts_hash_axis` (content-hash-keyed) while the
# multiplier array's first axis is `device_name_axis` (name-keyed); we need two
# separate row lookups so parallel branches sharing an array still write their
# multiplier to the correct (per-branch-name) row.
param_lookup = get_parameter_array(param_container).lookup[1]
mult_lookup = get_multiplier_array(param_container).lookup[1]
for (name, (arc, reduction)) in PNM.get_name_to_arc_map(net_reduction_data, D)
Expand All @@ -325,8 +333,8 @@ function _add_time_series_parameters!(
if device_with_time_series === nothing
continue
end
ts_uuid = branch_ts_uuids[name]
i_param = param_lookup[ts_uuid]
ts_hash = branch_ts_hashes[name]
i_param = param_lookup[ts_hash]
i_mult = mult_lookup[name]

has_entry, tracker_container = search_for_reduced_branch_parameter!(
Expand Down Expand Up @@ -382,7 +390,7 @@ function _add_time_series_parameters!(
IOM.add_component_name!(
IOM.get_attributes(param_container),
name,
ts_uuid,
ts_hash,
)
end
return
Expand Down Expand Up @@ -418,7 +426,8 @@ function _get_time_series_name(
)
op_cost = PSY.get_operation_cost(device)
IS.@assert_op op_cost isa TS_OFFER_CURVE_COST_TYPES
return IS.get_name(IS.get_time_series_key(PSY.get_start_up(op_cost)))
# The TS-backed startup field is the time-series key itself.
return IS.get_name(PSY.get_start_up(op_cost))
end

function _get_time_series_name(
Expand Down Expand Up @@ -816,21 +825,26 @@ function _add_parameters!(
name = PSY.get_name(service)
model_interval = get_interval(get_settings(container))
ts_interval = model_interval
ts_uuid = string(
IS.get_time_series_uuid(
ts_type,
service,
ts_name;
interval = _to_is_interval(ts_interval),
),
ts_hashes = IS.get_time_series_hashes(
(service,),
ts_type,
ts_name;
interval = _to_is_interval(ts_interval),
)
ts_hash = get(ts_hashes, IS.get_id(service), nothing)
if ts_hash === nothing
error(
"Time series $(ts_type):$(ts_name) for $U, $name does not match " *
"interval=$ts_interval.",
)
end
@debug "adding" T U _group = IOM.LOG_GROUP_OPTIMIZATION_CONTAINER
additional_axes = calc_additional_axes(container, T, [service], model)
parameter_container = add_param_container!(container, T,
U,
ts_type,
ts_name,
[ts_uuid],
[ts_hash],
[name],
additional_axes,
time_steps;
Expand All @@ -847,7 +861,7 @@ function _add_parameters!(
for t in time_steps
IOM._set_parameter_at!(parent_param, jump_model, ts_vector[t], 1, t)
end
IOM.add_component_name!(IOM.get_attributes(parameter_container), name, ts_uuid)
IOM.add_component_name!(IOM.get_attributes(parameter_container), name, ts_hash)
return
end

Expand Down
18 changes: 15 additions & 3 deletions src/common_models/market_bid_overrides.jl
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,19 @@ function IOM.validate_occ_component(
device::PSY.ThermalMultiStart,
)
startup = PSY.get_start_up(PSY.get_operation_cost(device))
# TupleTimeSeries{PSY.StartUpStages} guarantees NTuple{3, Float64} values at construction
startup isa IS.TupleTimeSeries && return
# A TS-backed startup is a bare time-series key referencing NTuple{3, Float64}
# stages; its element type is checked when the parameter is populated.
if startup isa IS.TimeSeriesKey
if eltype(typeof(startup)) != NTuple{3, Float64}
# TODO: should this be a helper in IOM?
throw(
ArgumentError(
"Expected element type NTuple{3, Float64} but got $(typeof(startup))",
),
)
end
return
end
_validate_eltype(
Union{Float64, NTuple{3, Float64}, PSY.StartUpStages},
device,
Expand All @@ -99,7 +110,8 @@ function IOM.validate_occ_component(
startup = PSY.get_start_up(PSY.get_operation_cost(device))
apply_maybe_across_time_series(device, startup) do x
# x may be Float64 (TGC), PSY.StartUpStages (static MBC), or NTuple{3, Float64}
# (TupleTimeSeries elements). `values` normalizes both NamedTuple and Tuple.
# (elements of a TS-backed startup key). `values` normalizes both NamedTuple
# and Tuple.
if any(!iszero, x isa Number ? (x,) : values(x))
@warn "Nonzero startup cost detected for renewable generation or storage device $(get_name(device))."
end
Expand Down
4 changes: 2 additions & 2 deletions src/common_models/market_bid_plumbing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -617,8 +617,8 @@ IOM.transform_single_time_series!(
interval::Dates.Period;
kwargs...,
) = PSY.transform_single_time_series!(sys, horizon, interval; kwargs...)
# sys.data.internal UUID, not sys's wrapper UUID — IOM uses this as a filename identifier.
IOM.get_system_uuid(sys::PSY.System) = IS.get_uuid(sys.data.internal)
# The system's own UUID (`System.metadata.uuid`) — IOM uses this as a filename identifier.
IOM.get_system_uuid(sys::PSY.System) = PSY.get_uuid(sys)
# PSY.get_components restricts T <: PSY.Component; IOM passes IS.InfrastructureSystemsComponent.
# Bridge directly to IS.get_components to preserve the looser typing.
IOM.get_subsystem_components(
Expand Down
31 changes: 18 additions & 13 deletions src/network_models/network_reductions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -219,30 +219,35 @@ function get_branch_argument_parameter_axes(
) where {T <: IS.InfrastructureSystemsComponent, V <: IS.TimeSeriesData}
is_interval = IOM._to_is_interval(interval)
name_axis = Vector{String}()
ts_uuid_axis = Vector{String}()
ts_hash_axis = Vector{String}()
arc_map = get(PNM.get_name_to_arc_maps(net_reduction_data), T, nothing)
isnothing(arc_map) && return name_axis, ts_uuid_axis
isnothing(arc_map) && return name_axis, ts_hash_axis
devices_with_time_series = IS.InfrastructureSystemsComponent[]
for (name, (arc, reduction)) in arc_map
reduction_entry =
PNM.get_all_branch_maps_by_type(net_reduction_data)[reduction][T][arc]
device_with_time_series =
get_branch_with_time_series(reduction_entry, V, ts_name)
if !isnothing(device_with_time_series)
push!(name_axis, name)
push!(
ts_uuid_axis,
string(
IS.get_time_series_uuid(
V,
device_with_time_series,
ts_name;
interval = is_interval,
),
),
push!(devices_with_time_series, device_with_time_series)
end
end
# One catalog query resolves the content hash of every branch's stored array;
# branches sharing an array share a hash, which is what keys the parameter rows.
hashes = IS.get_time_series_hashes(devices_with_time_series, V, ts_name;
interval = is_interval)
for (name, device) in zip(name_axis, devices_with_time_series)
ts_hash = get(hashes, IS.get_id(device), nothing)
if ts_hash === nothing
error(
"Time series $V:$ts_name for branch $name does not match " *
"interval=$interval.",
)
end
push!(ts_hash_axis, ts_hash)
end
return name_axis, ts_uuid_axis
return name_axis, ts_hash_axis
end

function get_branch_argument_variable_axis(
Expand Down
14 changes: 10 additions & 4 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ DataFramesMeta = "1313f7d8-7da2-5740-9ea0-a2ca25f37964"
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b"
InfraStore = "6aee0376-896a-4a59-96ff-12f221c99746"
InfrastructureOptimizationModels = "bed98974-b02a-5e2f-9ee0-a103f5c45069"
InfrastructureSystems = "2cd47ed4-ca9b-11e9-27f2-ab636a7671f1"
Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9"
Expand Down Expand Up @@ -37,14 +38,19 @@ TimeSeries = "9e3dc215-6440-5c97-bce1-76c03772f85e"
TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

# InfraStore is not registered yet, so it cannot resolve from a registry. Pkg
# only honors the [sources] section of the root project, so this environment
# needs its own entry (and InfraStore must be a direct dependency for the source
# to apply). Drop the dep + entry once InfraStore is registered.
[sources]
InfrastructureOptimizationModels = {rev = "main", url = "https://github.com/Sienna-Platform/InfrastructureOptimizationModels.jl"}
InfrastructureSystems = {rev = "IS4", url = "https://github.com/Sienna-Platform/InfrastructureSystems.jl"}
PowerSystems = {rev = "psy6", url = "https://github.com/Sienna-Platform/PowerSystems.jl"}
# psy6: disabled pending transformer refactor
# PowerFlows = {rev = "psy6", url = "https://github.com/Sienna-Platform/PowerFlows.jl"}
InfraStore = {path = "../../../infrastore/julia/InfraStore.jl"}
InfrastructureSystems = {path = "../../IS3.jl"}
PowerSystems = {url = "https://github.com/Sienna-Platform/PowerSystems.jl", rev = "feat/rust-time-series-store"}
InfrastructureOptimizationModels = {path = "../../InfrastructureOptimizationModels.jl"}
PowerNetworkMatrices = {rev = "psy6", url = "https://github.com/Sienna-Platform/PowerNetworkMatrices.jl"}
PowerSystemCaseBuilder = {rev = "psy6", url = "https://github.com/Sienna-Platform/PowerSystemCaseBuilder.jl"}
PowerSystemCaseBuilder = {rev = "feat/rust-time-series-store", url = "https://github.com/Sienna-Platform/PowerSystemCaseBuilder.jl"}

[compat]
HiGHS = "1"
Expand Down
4 changes: 2 additions & 2 deletions test/test_mbc_parameter_population.jl
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ function _build_mbtsc_thermal_system(;

new_cost = PSY.MarketBidTimeSeriesCost(;
no_load_cost = PSY.TimeSeriesLinearCurve(nl_key),
start_up = IS.TupleTimeSeries{PSY.StartUpStages}(su_key),
start_up = su_key,
shut_down = PSY.TimeSeriesLinearCurve(sd_key),
incremental_offer_curves = PSY.make_market_bid_ts_curve(pwl_incr_key, ii_incr_key),
decremental_offer_curves = PSY.make_market_bid_ts_curve(pwl_decr_key, ii_decr_key),
Expand All @@ -97,7 +97,7 @@ function _pp_build_container(sys::PSY.System, time_steps::UnitRange{Int})
return container
end

@testset "StartupCostParameter populated from TupleTimeSeries" begin
@testset "StartupCostParameter populated from tuple-valued time series" begin
# Drift by 10 per hour so each timestep is distinct: (100,150,200), (110,160,210), ...
sys, gen = _build_mbtsc_thermal_system(;
name = _PP_THERMAL_NAME,
Expand Down
2 changes: 1 addition & 1 deletion test/test_utils/add_market_bid_cost.jl
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ function extend_mbc!(

new_cost = MarketBidTimeSeriesCost(;
no_load_cost = TimeSeriesLinearCurve(nl_key),
start_up = IS.TupleTimeSeries{PSY.StartUpStages}(su_key),
start_up = su_key,
shut_down = TimeSeriesLinearCurve(sd_key),
incremental_offer_curves = ts_curves["incremental"],
decremental_offer_curves = ts_curves["decremental"],
Expand Down
2 changes: 1 addition & 1 deletion test/test_utils/mbc_math_helpers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ function stub_ts_market_bid_cost(;
)
return PSY.MarketBidTimeSeriesCost(;
no_load_cost = PSY.TimeSeriesLinearCurve(_stub_forecast_key("no_load")),
start_up = IS.TupleTimeSeries{PSY.StartUpStages}(_stub_forecast_key("start_up")),
start_up = _stub_forecast_key("start_up"),
shut_down = PSY.TimeSeriesLinearCurve(_stub_forecast_key("shut_down")),
incremental_offer_curves = stub_ts_offer_curve(;
curve_name = "variable_cost incremental",
Expand Down
Loading
Loading