diff --git a/src/system_data.jl b/src/system_data.jl index a9219bec..23d53a4b 100644 --- a/src/system_data.jl +++ b/src/system_data.jl @@ -574,7 +574,11 @@ function _transform_single_time_series!( end try - add_metadata!(data.time_series_manager.metadata_store, components, all_metadata) + begin_time_series_update(data.time_series_manager) do + for (component, metadata) in zip(components, all_metadata) + add_metadata!(data.time_series_manager.metadata_store, component, metadata) + end + end catch # This shouldn't be needed, but just in case there is a bug, remove all # DeterministicSingleTimeSeries to keep our guarantee. diff --git a/src/time_series_manager.jl b/src/time_series_manager.jl index b28ab47f..8d8fc55d 100644 --- a/src/time_series_manager.jl +++ b/src/time_series_manager.jl @@ -2,10 +2,15 @@ # This strikes a balance in SQLite efficiency vs loading many time arrays into memory. const ADD_TIME_SERIES_BATCH_SIZE = 100 +mutable struct BulkUpdateTSCache + forecast_params::Union{Nothing, ForecastParameters} +end + mutable struct TimeSeriesManager <: InfrastructureSystemsType data_store::TimeSeriesStorage metadata_store::TimeSeriesMetadataStore read_only::Bool + bulk_update_cache::Union{Nothing, BulkUpdateTSCache} end function TimeSeriesManager(; @@ -31,107 +36,122 @@ function TimeSeriesManager(; compression = compression, ) end - return TimeSeriesManager(data_store, metadata_store, read_only) + return TimeSeriesManager(data_store, metadata_store, read_only, nothing) end -function bulk_add_time_series!( - mgr::TimeSeriesManager, - associations; - batch_size = ADD_TIME_SERIES_BATCH_SIZE, -) - ts_keys = TimeSeriesKey[] - batch = TimeSeriesAssociation[] - sizehint!(batch, batch_size) - open_store!(mgr.data_store, "r+") do - for association in associations - push!(batch, association) - if length(batch) >= batch_size - append!(ts_keys, add_time_series!(mgr, batch)) - empty!(batch) - end - end +_get_forecast_params(ts::Forecast) = make_time_series_parameters(ts) +_get_forecast_params(::StaticTimeSeries) = nothing +_get_forecast_params(::TimeSeriesManager, ::StaticTimeSeries) = nothing - if !isempty(batch) - append!(ts_keys, add_time_series!(mgr, batch)) +function _get_forecast_params(mgr::TimeSeriesManager, forecast::Forecast) + if isnothing(mgr.bulk_update_cache) + return get_forecast_parameters(mgr.metadata_store) + end + + if isnothing(mgr.bulk_update_cache.forecast_params) + mgr.bulk_update_cache.forecast_params = get_forecast_parameters(mgr.metadata_store) + if isnothing(mgr.bulk_update_cache.forecast_params) + mgr.bulk_update_cache.forecast_params = _get_forecast_params(forecast) end end - return ts_keys + return mgr.bulk_update_cache.forecast_params end -function add_time_series!(mgr::TimeSeriesManager, batch::Vector{TimeSeriesAssociation}) - _throw_if_read_only(mgr) - forecast_params = get_forecast_parameters(mgr.metadata_store) - sts_params = StaticTimeSeriesParameters() - num_metadata = length(batch) - all_metadata = Vector{TimeSeriesMetadata}(undef, num_metadata) - owners = Vector{TimeSeriesOwners}(undef, num_metadata) - ts_keys = Vector{TimeSeriesKey}(undef, num_metadata) - time_series_uuids = Dict{Base.UUID, TimeSeriesData}() - metadata_identifiers = Set{Tuple}() - TimerOutputs.@timeit_debug SYSTEM_TIMERS "add_time_series! in bulk" begin - for (i, item) in enumerate(batch) - throw_if_does_not_support_time_series(item.owner) - check_time_series_data(item.time_series) - metadata_type = time_series_data_to_metadata(typeof(item.time_series)) - metadata = metadata_type(item.time_series; item.features...) - identifier = make_unique_owner_metadata_identifer(item.owner, metadata) - if identifier in metadata_identifiers - throw(ArgumentError("$identifier is present multiple times")) +""" +Begin an update of time series. Use this function when adding many time series arrays +in order to improve performance. + +Using this function to remove time series is currently not supported. +""" +function begin_time_series_update( + func::Function, + mgr::TimeSeriesManager, +) + open_store!(mgr.data_store, "r+") do + original_ts_uuids = Set(list_existing_time_series_uuids(mgr.metadata_store)) + mgr.bulk_update_cache = BulkUpdateTSCache(nothing) + try + SQLite.transaction(mgr.metadata_store.db) do + func() end - push!(metadata_identifiers, identifier) - if isnothing(forecast_params) - forecast_params = _get_forecast_params(item.time_series) + catch + # If an error occurs, we can easily remove new time series data to ensure + # that the metadata database is consistent with the data. + # We currently can't restore time series data that was deleted. + new_ts_uuids = setdiff( + Set(list_existing_time_series_uuids(mgr.metadata_store)), + original_ts_uuids, + ) + for uuid in new_ts_uuids + remove_time_series!(mgr.data_store, uuid) end - check_params_compatibility(sts_params, forecast_params, item.time_series) - all_metadata[i] = metadata - owners[i] = item.owner - ts_keys[i] = make_time_series_key(metadata) - time_series_uuids[get_uuid(item.time_series)] = item.time_series + rethrow() + finally + mgr.bulk_update_cache = nothing end + end +end - uuids = keys(time_series_uuids) - existing_ts_uuids = if isempty(uuids) - Base.UUID[] - else - list_existing_time_series_uuids(mgr.metadata_store, uuids) - end - new_ts_uuids = setdiff(keys(time_series_uuids), existing_ts_uuids) - - existing_metadata = list_existing_metadata(mgr.metadata_store, owners, all_metadata) - if !isempty(existing_metadata) - throw( - ArgumentError( - "Time series data with duplicate attributes are already stored: " * - "$(existing_metadata)", - ), +function bulk_add_time_series!( + mgr::TimeSeriesManager, + associations; + kwargs..., +) + # TODO: deprecate this function if team agrees + ts_keys = TimeSeriesKey[] + begin_time_series_update(mgr) do + for association in associations + key = add_time_series!( + mgr, + association.owner, + association.time_series; association.features..., ) - end - for uuid in new_ts_uuids - serialize_time_series!(mgr.data_store, time_series_uuids[uuid]) - end - if length(all_metadata) == 1 - add_metadata!(mgr.metadata_store, owners[1], all_metadata[1]) - else - add_metadata!(mgr.metadata_store, owners, all_metadata) + push!(ts_keys, key) end end + return ts_keys end -_get_forecast_params(ts::Forecast) = make_time_series_parameters(ts) -_get_forecast_params(::StaticTimeSeries) = nothing - function add_time_series!( mgr::TimeSeriesManager, owner::TimeSeriesOwners, time_series::TimeSeriesData; features..., ) - return add_time_series!( - mgr, - [TimeSeriesAssociation(owner, time_series; features...)], - )[1] + _throw_if_read_only(mgr) + forecast_params = _get_forecast_params(mgr, time_series) + sts_params = StaticTimeSeriesParameters() + throw_if_does_not_support_time_series(owner) + check_time_series_data(time_series) + metadata_type = time_series_data_to_metadata(typeof(time_series)) + metadata = metadata_type(time_series; features...) + ts_key = make_time_series_key(metadata) + check_params_compatibility(sts_params, forecast_params, time_series) + + if has_metadata( + mgr.metadata_store, + owner; + time_series_type = typeof(time_series), + name = get_name(metadata), + resolution = get_resolution(metadata), + features..., + ) + throw( + ArgumentError( + "Time series data with duplicate attributes are already stored: " * + "$(metadata)", + ), + ) + end + + if !has_metadata(mgr.metadata_store, get_uuid(time_series)) + serialize_time_series!(mgr.data_store, time_series) + end + + add_metadata!(mgr.metadata_store, owner, metadata) + return ts_key end function clear_time_series!(mgr::TimeSeriesManager) diff --git a/src/time_series_metadata_store.jl b/src/time_series_metadata_store.jl index e62a3661..e07c693d 100644 --- a/src/time_series_metadata_store.jl +++ b/src/time_series_metadata_store.jl @@ -294,121 +294,38 @@ function add_metadata!( owner::TimeSeriesOwners, metadata::TimeSeriesMetadata, ) - TimerOutputs.@timeit_debug SYSTEM_TIMERS "add time series metadata single" begin - owner_category = _get_owner_category(owner) - time_series_type = time_series_metadata_to_data(metadata) - ts_category = _get_time_series_category(time_series_type) - features = make_features_string(metadata.features) - vals = _create_row( - metadata, - owner, - owner_category, - _convert_ts_type_to_string(time_series_type), - ts_category, - features, - ) - params = chop(repeat("?,", length(vals))) - _execute_cached( - store, - "INSERT INTO $ASSOCIATIONS_TABLE_NAME VALUES($params)", - vals, - ) - metadata_uuid = get_uuid(metadata) - metadata_row = - (missing, string(metadata_uuid), JSON3.write(serialize(metadata))) - metadata_params = ("?,?,jsonb(?)") - TimerOutputs.@timeit_debug SYSTEM_TIMERS "add ts_metadata row" begin - _execute_cached( - store, - "INSERT OR IGNORE INTO $METADATA_TABLE_NAME VALUES($metadata_params)", - metadata_row, - ) - end - if !haskey(store.metadata_uuids, metadata_uuid) - store.metadata_uuids[metadata_uuid] = metadata - end - @debug "Added metadata = $metadata to $(summary(owner))" _group = - LOG_GROUP_TIME_SERIES - end - return -end - -function add_metadata!( - store::TimeSeriesMetadataStore, - owners::Vector{<:TimeSeriesOwners}, - all_metadata::Vector{<:TimeSeriesMetadata}, -) - TimerOutputs.@timeit_debug SYSTEM_TIMERS "add time series metadata bulk" begin - @assert_op length(owners) == length(all_metadata) - columns = ( - "id", - "time_series_uuid", - "time_series_type", - "time_series_category", - "initial_timestamp", - "resolution_ms", - "horizon_ms", - "interval_ms", - "window_count", - "length", - "name", - "owner_uuid", - "owner_type", - "owner_category", - "features", - "metadata_uuid", - ) - num_rows = length(all_metadata) - num_columns = length(columns) - data = OrderedDict(x => Vector{Any}(undef, num_rows) for x in columns) - metadata_rows = Tuple[] - new_metadata = TimeSeriesMetadata[] - for i in 1:num_rows - owner = owners[i] - metadata = all_metadata[i] - metadata_uuid = get_uuid(metadata) - if !haskey(store.metadata_uuids, metadata_uuid) - push!( - metadata_rows, - (missing, string(metadata_uuid), JSON3.write(serialize(metadata))), - ) - push!(new_metadata, metadata) - end - owner_category = _get_owner_category(owner) - time_series_type = time_series_metadata_to_data(metadata) - ts_category = _get_time_series_category(time_series_type) - features = make_features_string(metadata.features) - row = _create_row( - metadata, - owner, - owner_category, - _convert_ts_type_to_string(time_series_type), - ts_category, - features, - ) - for (j, column) in enumerate(columns) - data[column][i] = row[j] - end - end - - placeholder = chop(repeat("?,", num_columns)) - SQLite.DBInterface.executemany( - store.db, - "INSERT INTO $ASSOCIATIONS_TABLE_NAME VALUES($placeholder)", - NamedTuple(Symbol(k) => v for (k, v) in data), - ) - _add_rows!( - store, - metadata_rows, - ("id", "metadata_uuid", "metadata"), - METADATA_TABLE_NAME, - ) - for metadata in new_metadata - store.metadata_uuids[get_uuid(metadata)] = metadata - end - @debug "Added $num_rows instances of time series metadata" _group = - LOG_GROUP_TIME_SERIES + owner_category = _get_owner_category(owner) + time_series_type = time_series_metadata_to_data(metadata) + ts_category = _get_time_series_category(time_series_type) + features = make_features_string(metadata.features) + vals = _create_row( + metadata, + owner, + owner_category, + _convert_ts_type_to_string(time_series_type), + ts_category, + features, + ) + params = chop(repeat("?,", length(vals))) + _execute_cached( + store, + "INSERT INTO $ASSOCIATIONS_TABLE_NAME VALUES($params)", + vals, + ) + metadata_uuid = get_uuid(metadata) + metadata_row = + (missing, string(metadata_uuid), JSON3.write(serialize(metadata))) + metadata_params = ("?,?,jsonb(?)") + _execute_cached( + store, + "INSERT OR IGNORE INTO $METADATA_TABLE_NAME VALUES($metadata_params)", + metadata_row, + ) + if !haskey(store.metadata_uuids, metadata_uuid) + store.metadata_uuids[metadata_uuid] = metadata end + @debug "Added metadata = $metadata to $(summary(owner))" _group = + LOG_GROUP_TIME_SERIES return end @@ -531,19 +448,19 @@ function get_forecast_initial_times(store::TimeSeriesMetadataStore) return get_initial_times(params.initial_timestamp, params.count, params.interval) end +const _GET_FORECAST_PARAMS = """ + SELECT + horizon_ms + ,initial_timestamp + ,interval_ms + ,resolution_ms + ,window_count + FROM $ASSOCIATIONS_TABLE_NAME + WHERE horizon_ms IS NOT NULL + LIMIT 1 +""" function get_forecast_parameters(store::TimeSeriesMetadataStore) - query = """ - SELECT - horizon_ms - ,initial_timestamp - ,interval_ms - ,resolution_ms - ,window_count - FROM $ASSOCIATIONS_TABLE_NAME - WHERE horizon_ms IS NOT NULL - LIMIT 1 - """ - table = Tables.rowtable(_execute_cached(store, query)) + table = Tables.rowtable(_execute_cached(store, _GET_FORECAST_PARAMS)) isempty(table) && return nothing row = table[1] return ForecastParameters(; @@ -991,7 +908,14 @@ function list_existing_time_series_uuids(store::TimeSeriesMetadataStore, uuids) FROM $ASSOCIATIONS_TABLE_NAME WHERE time_series_uuid IN ($placeholder) """ - table = Tables.columntable(_execute(store, query, uuids_str)) + table = Tables.columntable(_execute_cached(store, query, uuids_str)) + return Base.UUID.(table.time_series_uuid) +end + +const _LIST_EXISTING_TS_UUIDS = "SELECT DISTINCT time_series_uuid FROM $ASSOCIATIONS_TABLE_NAME" + +function list_existing_time_series_uuids(store::TimeSeriesMetadataStore) + table = Tables.columntable(_execute_cached(store, _LIST_EXISTING_TS_UUIDS)) return Base.UUID.(table.time_series_uuid) end diff --git a/test/test_cost_functions.jl b/test/test_cost_functions.jl index 39b8ba7a..c189837c 100644 --- a/test/test_cost_functions.jl +++ b/test/test_cost_functions.jl @@ -1,325 +1,325 @@ -# Get all possible isomorphic representations of the given `ValueCurve` -function all_conversions(vc::IS.ValueCurve; - universe = (IS.InputOutputCurve, IS.IncrementalCurve, IS.AverageRateCurve), -) - convert_to = filter(!=(nameof(typeof(vc))) ∘ nameof, universe) # x -> nameof(x) != nameof(typeof(vc)) - result = Set{IS.ValueCurve}(constructor(vc) for constructor in convert_to) - (vc isa IS.InputOutputCurve{IS.LinearFunctionData}) && - push!(result, IS.InputOutputCurve{IS.QuadraticFunctionData}(vc)) - return result -end - -@testset "Test ValueCurves" begin - # IS.InputOutputCurve - io_quadratic = IS.InputOutputCurve(IS.QuadraticFunctionData(3, 2, 1)) - @test io_quadratic isa IS.InputOutputCurve{IS.QuadraticFunctionData} - @test IS.get_function_data(io_quadratic) == IS.QuadraticFunctionData(3, 2, 1) - @test IS.IncrementalCurve(io_quadratic) == - IS.IncrementalCurve(IS.LinearFunctionData(6, 2), 1.0) - @test IS.AverageRateCurve(io_quadratic) == - IS.AverageRateCurve(IS.LinearFunctionData(3, 2), 1.0) - @test zero(io_quadratic) == IS.InputOutputCurve(IS.LinearFunctionData(0, 0)) - @test zero(IS.InputOutputCurve) == IS.InputOutputCurve(IS.LinearFunctionData(0, 0)) - @test IS.is_cost_alias(io_quadratic) == IS.is_cost_alias(typeof(io_quadratic)) == true - @test repr(io_quadratic) == sprint(show, io_quadratic) == - "QuadraticCurve(3.0, 2.0, 1.0)" - @test sprint(show, "text/plain", io_quadratic) == - "QuadraticCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 3.0 x^2 + 2.0 x + 1.0" - - io_linear = IS.InputOutputCurve(IS.LinearFunctionData(2, 1)) - @test io_linear isa IS.InputOutputCurve{IS.LinearFunctionData} - @test IS.get_function_data(io_linear) == IS.LinearFunctionData(2, 1) - @test IS.InputOutputCurve{IS.QuadraticFunctionData}(io_linear) == - IS.InputOutputCurve(IS.QuadraticFunctionData(0, 2, 1)) - @test IS.IncrementalCurve(io_linear) == - IS.IncrementalCurve(IS.LinearFunctionData(0, 2), 1.0) - @test IS.AverageRateCurve(io_linear) == - IS.AverageRateCurve(IS.LinearFunctionData(0, 2), 1.0) - @test IS.is_cost_alias(io_linear) == IS.is_cost_alias(typeof(io_linear)) == true - @test repr(io_linear) == sprint(show, io_linear) == - "LinearCurve(2.0, 1.0)" - @test sprint(show, "text/plain", io_linear) == - "LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 2.0 x + 1.0" - - io_piecewise = IS.InputOutputCurve(IS.PiecewiseLinearData([(1, 6), (3, 9), (5, 13)])) - @test io_piecewise isa IS.InputOutputCurve{IS.PiecewiseLinearData} - @test IS.get_function_data(io_piecewise) == - IS.PiecewiseLinearData([(1, 6), (3, 9), (5, 13)]) - @test IS.IncrementalCurve(io_piecewise) == - IS.IncrementalCurve(IS.PiecewiseStepData([1, 3, 5], [1.5, 2]), 6.0) - @test IS.AverageRateCurve(io_piecewise) == - IS.AverageRateCurve(IS.PiecewiseStepData([1, 3, 5], [3, 2.6]), 6.0) - @test IS.is_cost_alias(io_piecewise) == IS.is_cost_alias(typeof(io_piecewise)) == true - @test repr(io_piecewise) == sprint(show, io_piecewise) == - "PiecewisePointCurve([(x = 1.0, y = 6.0), (x = 3.0, y = 9.0), (x = 5.0, y = 13.0)])" - @test sprint(show, "text/plain", io_piecewise) == - "PiecewisePointCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: piecewise linear y = f(x) connecting points:\n (x = 1.0, y = 6.0)\n (x = 3.0, y = 9.0)\n (x = 5.0, y = 13.0)" - - # IS.IncrementalCurve - inc_linear = IS.IncrementalCurve(IS.LinearFunctionData(6, 2), 1.0) - inc_linear_no_initial = IS.IncrementalCurve(IS.LinearFunctionData(6, 2), nothing) - @test inc_linear isa IS.IncrementalCurve{IS.LinearFunctionData} - @test inc_linear_no_initial isa IS.IncrementalCurve{IS.LinearFunctionData} - @test IS.get_function_data(inc_linear) == IS.LinearFunctionData(6, 2) - @test IS.get_initial_input(inc_linear) == 1 - @test IS.InputOutputCurve(inc_linear) == - IS.InputOutputCurve(IS.QuadraticFunctionData(3, 2, 1)) - @test IS.InputOutputCurve(IS.IncrementalCurve(IS.LinearFunctionData(0, 2), 1.0)) == - IS.InputOutputCurve(IS.LinearFunctionData(2, 1)) - @test IS.AverageRateCurve(inc_linear) == - IS.AverageRateCurve(IS.LinearFunctionData(3, 2), 1.0) - @test_throws ArgumentError IS.InputOutputCurve(inc_linear_no_initial) - @test_throws ArgumentError IS.AverageRateCurve(inc_linear_no_initial) - @test zero(inc_linear) == IS.IncrementalCurve(IS.LinearFunctionData(0, 0), 0.0) - @test zero(IS.IncrementalCurve) == IS.IncrementalCurve(IS.LinearFunctionData(0, 0), 0.0) - @test IS.is_cost_alias(inc_linear) == IS.is_cost_alias(typeof(inc_linear)) == false - @test repr(inc_linear) == sprint(show, inc_linear) == - "InfrastructureSystems.IncrementalCurve{InfrastructureSystems.LinearFunctionData}(InfrastructureSystems.LinearFunctionData(6.0, 2.0), 1.0, nothing)" - @test sprint(show, "text/plain", inc_linear) == - "IncrementalCurve where initial value is 1.0, derivative function f is: f(x) = 6.0 x + 2.0" - - inc_piecewise = IS.IncrementalCurve(IS.PiecewiseStepData([1, 3, 5], [1.5, 2]), 6.0) - inc_piecewise_no_initial = - IS.IncrementalCurve(IS.PiecewiseStepData([1, 3, 5], [1.5, 2]), nothing) - @test inc_piecewise isa IS.IncrementalCurve{IS.PiecewiseStepData} - @test inc_piecewise_no_initial isa IS.IncrementalCurve{IS.PiecewiseStepData} - @test IS.get_function_data(inc_piecewise) == IS.PiecewiseStepData([1, 3, 5], [1.5, 2]) - @test IS.get_initial_input(inc_piecewise) == 6 - @test IS.InputOutputCurve(inc_piecewise) == - IS.InputOutputCurve(IS.PiecewiseLinearData([(1, 6), (3, 9), (5, 13)])) - @test IS.AverageRateCurve(inc_piecewise) == - IS.AverageRateCurve(IS.PiecewiseStepData([1, 3, 5], [3, 2.6]), 6.0) - @test_throws ArgumentError IS.InputOutputCurve(inc_piecewise_no_initial) - @test_throws ArgumentError IS.AverageRateCurve(inc_piecewise_no_initial) - @test IS.is_cost_alias(inc_piecewise) == IS.is_cost_alias(typeof(inc_piecewise)) == - true - @test repr(inc_piecewise) == sprint(show, inc_piecewise) == - "PiecewiseIncrementalCurve(6.0, [1.0, 3.0, 5.0], [1.5, 2.0])" - @test sprint(show, "text/plain", inc_piecewise) == - "PiecewiseIncrementalCurve where initial value is 6.0, derivative function f is: f(x) =\n 1.5 for x in [1.0, 3.0)\n 2.0 for x in [3.0, 5.0)" - - # IS.AverageRateCurve - ar_linear = IS.AverageRateCurve(IS.LinearFunctionData(3, 2), 1.0) - ar_linear_no_initial = IS.AverageRateCurve(IS.LinearFunctionData(3, 2), nothing) - @test ar_linear isa IS.AverageRateCurve{IS.LinearFunctionData} - @test ar_linear_no_initial isa IS.AverageRateCurve{IS.LinearFunctionData} - @test IS.get_function_data(ar_linear) == IS.LinearFunctionData(3, 2) - @test IS.get_initial_input(ar_linear) == 1 - @test IS.InputOutputCurve(ar_linear) == - IS.InputOutputCurve(IS.QuadraticFunctionData(3, 2, 1)) - @test IS.InputOutputCurve(IS.AverageRateCurve(IS.LinearFunctionData(0, 2), 1.0)) == - IS.InputOutputCurve(IS.LinearFunctionData(2, 1)) - @test IS.IncrementalCurve(ar_linear) == - IS.IncrementalCurve(IS.LinearFunctionData(6, 2), 1.0) - @test_throws ArgumentError IS.InputOutputCurve(ar_linear_no_initial) - @test_throws ArgumentError IS.IncrementalCurve(ar_linear_no_initial) - @test zero(ar_linear) == IS.AverageRateCurve(IS.LinearFunctionData(0, 0), 0.0) - @test zero(IS.AverageRateCurve) == IS.AverageRateCurve(IS.LinearFunctionData(0, 0), 0.0) - @test IS.is_cost_alias(ar_linear) == IS.is_cost_alias(typeof(ar_linear)) == false - @test repr(ar_linear) == sprint(show, ar_linear) == - "InfrastructureSystems.AverageRateCurve{InfrastructureSystems.LinearFunctionData}(InfrastructureSystems.LinearFunctionData(3.0, 2.0), 1.0, nothing)" - @test sprint(show, "text/plain", ar_linear) == - "AverageRateCurve where initial value is 1.0, average rate function f is: f(x) = 3.0 x + 2.0" - - ar_piecewise = IS.AverageRateCurve(IS.PiecewiseStepData([1, 3, 5], [3, 2.6]), 6.0) - ar_piecewise_no_initial = - IS.AverageRateCurve(IS.PiecewiseStepData([1, 3, 5], [3, 2.6]), nothing) - @test ar_piecewise isa IS.AverageRateCurve{IS.PiecewiseStepData} - @test ar_piecewise_no_initial isa IS.AverageRateCurve{IS.PiecewiseStepData} - @test IS.get_function_data(ar_piecewise) == IS.PiecewiseStepData([1, 3, 5], [3, 2.6]) - @test IS.get_initial_input(ar_piecewise) == 6 - @test IS.InputOutputCurve(ar_piecewise) == - IS.InputOutputCurve(IS.PiecewiseLinearData([(1, 6), (3, 9), (5, 13)])) - @test IS.IncrementalCurve(ar_piecewise) == - IS.IncrementalCurve(IS.PiecewiseStepData([1, 3, 5], [1.5, 2]), 6.0) - @test_throws ArgumentError IS.InputOutputCurve(ar_piecewise_no_initial) - @test_throws ArgumentError IS.IncrementalCurve(ar_piecewise_no_initial) - @test IS.is_cost_alias(ar_piecewise) == IS.is_cost_alias(typeof(ar_piecewise)) == true - @test repr(ar_piecewise) == sprint(show, ar_piecewise) == - "PiecewiseAverageCurve(6.0, [1.0, 3.0, 5.0], [3.0, 2.6])" - @test sprint(show, "text/plain", ar_piecewise) == - "PiecewiseAverageCurve where initial value is 6.0, average rate function f is: f(x) =\n 3.0 for x in [1.0, 3.0)\n 2.6 for x in [3.0, 5.0)" - - # Serialization round trip - curves_by_type = [ # typeof() gives parameterized types - (io_quadratic, IS.InputOutputCurve), - (io_linear, IS.InputOutputCurve), - (io_piecewise, IS.InputOutputCurve), - (inc_linear, IS.IncrementalCurve), - (inc_piecewise, IS.IncrementalCurve), - (ar_linear, IS.AverageRateCurve), - (ar_piecewise, IS.AverageRateCurve), - (inc_linear_no_initial, IS.IncrementalCurve), - (inc_piecewise_no_initial, IS.IncrementalCurve), - (ar_linear_no_initial, IS.AverageRateCurve), - (ar_piecewise_no_initial, IS.AverageRateCurve), - ] - for (curve, curve_type) in curves_by_type - @test IS.serialize(curve) isa AbstractDict - @test IS.deserialize(curve_type, IS.serialize(curve)) == curve - end - - @test zero(IS.ValueCurve) == IS.InputOutputCurve(IS.LinearFunctionData(0, 0)) -end - -@testset "Test ValueCurve type conversion constructors" begin - @test IS.InputOutputCurve(IS.QuadraticFunctionData(3, 2, 1), 1) == - IS.InputOutputCurve(IS.QuadraticFunctionData(3, 2, 1), 1.0) - @test IS.IncrementalCurve(IS.LinearFunctionData(6, 2), 1) == - IS.IncrementalCurve(IS.LinearFunctionData(6, 2), 1.0) - @test IS.AverageRateCurve(IS.LinearFunctionData(3, 2), 1) == - IS.AverageRateCurve(IS.LinearFunctionData(3, 2), 1.0) -end - -@testset "Test cost aliases" begin - lc = IS.LinearCurve(3.0, 5.0) - @test lc == IS.InputOutputCurve(IS.LinearFunctionData(3.0, 5.0)) - @test IS.LinearCurve(3.0) == IS.InputOutputCurve(IS.LinearFunctionData(3.0, 0.0)) - @test IS.get_proportional_term(lc) == 3.0 - @test IS.get_constant_term(lc) == 5.0 - - qc = IS.QuadraticCurve(1.0, 2.0, 18.0) - @test qc == IS.InputOutputCurve(IS.QuadraticFunctionData(1.0, 2.0, 18.0)) - @test IS.get_quadratic_term(qc) == 1.0 - @test IS.get_proportional_term(qc) == 2.0 - @test IS.get_constant_term(qc) == 18.0 - - ppc = IS.PiecewisePointCurve([(1.0, 20.0), (2.0, 24.0), (3.0, 30.0)]) - @test ppc == - IS.InputOutputCurve( - IS.PiecewiseLinearData([(1.0, 20.0), (2.0, 24.0), (3.0, 30.0)]), - ) - @test IS.get_points(ppc) == - [(x = 1.0, y = 20.0), (x = 2.0, y = 24.0), (x = 3.0, y = 30.0)] - @test IS.get_x_coords(ppc) == [1.0, 2.0, 3.0] - @test IS.get_y_coords(ppc) == [20.0, 24.0, 30.0] - @test IS.get_slopes(ppc) == [4.0, 6.0] - - pic = IS.PiecewiseIncrementalCurve(20.0, [1.0, 2.0, 3.0], [4.0, 6.0]) - @test pic == - IS.IncrementalCurve(IS.PiecewiseStepData([1.0, 2.0, 3.0], [4.0, 6.0]), 20.0) - @test IS.get_x_coords(pic) == [1.0, 2.0, 3.0] - @test IS.get_slopes(pic) == [4.0, 6.0] - - pac = IS.PiecewiseAverageCurve(20.0, [1.0, 2.0, 3.0], [12.0, 10.0]) - @test pac == - IS.AverageRateCurve(IS.PiecewiseStepData([1.0, 2.0, 3.0], [12.0, 10.0]), 20.0) - @test IS.get_x_coords(pac) == [1.0, 2.0, 3.0] - @test IS.get_average_rates(pac) == [12.0, 10.0] - - # Make sure the aliases get registered properly - @test sprint(show, "text/plain", IS.QuadraticCurve) == - "QuadraticCurve (alias for InfrastructureSystems.InputOutputCurve{InfrastructureSystems.QuadraticFunctionData})" -end - -@testset "Test input_at_zero" begin - iaz = 1234.5 - pwinc_without_iaz = - IS.IncrementalCurve(IS.PiecewiseStepData([1, 3, 5], [1.5, 2]), 6.0, nothing) - pwinc_with_iaz = - IS.IncrementalCurve(IS.PiecewiseStepData([1, 3, 5], [1.5, 2]), 6.0, iaz) - all_without_iaz = [ - IS.InputOutputCurve(IS.QuadraticFunctionData(3, 2, 1), nothing), - IS.InputOutputCurve(IS.LinearFunctionData(2, 1), nothing), - IS.InputOutputCurve(IS.PiecewiseLinearData([(1, 6), (3, 9), (5, 13)]), nothing), - IS.IncrementalCurve(IS.LinearFunctionData(6, 2), 1.0, nothing), - pwinc_without_iaz, - IS.AverageRateCurve(IS.LinearFunctionData(3, 2), 1.0, nothing), - IS.AverageRateCurve(IS.PiecewiseStepData([1, 3, 5], [3, 2.6]), 6.0, nothing), - ] - all_with_iaz = [ - IS.InputOutputCurve(IS.QuadraticFunctionData(3, 2, 1), iaz), - IS.InputOutputCurve(IS.LinearFunctionData(2, 1), iaz), - IS.InputOutputCurve(IS.PiecewiseLinearData([(1, 6), (3, 9), (5, 13)]), iaz), - IS.IncrementalCurve(IS.LinearFunctionData(6, 2), 1.0, iaz), - pwinc_with_iaz, - IS.AverageRateCurve(IS.LinearFunctionData(3, 2), 1.0, iaz), - IS.AverageRateCurve(IS.PiecewiseStepData([1, 3, 5], [3, 2.6]), 6.0, iaz), - ] - - # Alias constructors - @test IS.PiecewiseIncrementalCurve(1234.5, 6.0, [1.0, 3.0, 5.0], [1.5, 2.0]) == - pwinc_with_iaz - - # Getters and printouts - for (without_iaz, with_iaz) in zip(all_without_iaz, all_with_iaz) - @test IS.get_input_at_zero(without_iaz) === nothing - @test IS.get_input_at_zero(with_iaz) == iaz - @test occursin(string(iaz), repr(with_iaz)) - @test sprint(show, with_iaz) == repr(with_iaz) - @test occursin(string(iaz), sprint(show, "text/plain", with_iaz)) - end - - @test repr(pwinc_with_iaz) == sprint(show, pwinc_with_iaz) == - "PiecewiseIncrementalCurve(1234.5, 6.0, [1.0, 3.0, 5.0], [1.5, 2.0])" - @test sprint(show, "text/plain", pwinc_with_iaz) == - "PiecewiseIncrementalCurve where value at zero is 1234.5, initial value is 6.0, derivative function f is: f(x) =\n 1.5 for x in [1.0, 3.0)\n 2.0 for x in [3.0, 5.0)" - - # Preserved under conversion - for without_iaz in Iterators.flatten(all_conversions.(all_without_iaz)) - @test IS.get_input_at_zero(without_iaz) === nothing - end - for with_iaz in Iterators.flatten(all_conversions.(all_with_iaz)) - @test IS.get_input_at_zero(with_iaz) == iaz - end -end - -@testset "Test IS.CostCurve and IS.FuelCurve" begin - cc = IS.CostCurve(IS.InputOutputCurve(IS.QuadraticFunctionData(1, 2, 3))) - fc = IS.FuelCurve(IS.InputOutputCurve(IS.QuadraticFunctionData(1, 2, 3)), 4.0) - # TODO also test fuel curves with time series - - @test IS.get_value_curve(cc) == IS.InputOutputCurve(IS.QuadraticFunctionData(1, 2, 3)) - @test IS.get_value_curve(fc) == IS.InputOutputCurve(IS.QuadraticFunctionData(1, 2, 3)) - @test IS.get_fuel_cost(fc) == 4 - - @test IS.serialize(cc) isa AbstractDict - @test IS.serialize(fc) isa AbstractDict - @test IS.deserialize(IS.CostCurve, IS.serialize(cc)) == cc - @test IS.deserialize(IS.FuelCurve, IS.serialize(fc)) == fc - - @test zero(cc) == IS.CostCurve(IS.InputOutputCurve(IS.LinearFunctionData(0.0, 0.0))) - @test zero(IS.CostCurve) == - IS.CostCurve(IS.InputOutputCurve(IS.LinearFunctionData(0.0, 0.0))) - @test zero(fc) == - IS.FuelCurve(IS.InputOutputCurve(IS.LinearFunctionData(0.0, 0.0)), 0.0) - @test zero(IS.FuelCurve) == - IS.FuelCurve(IS.InputOutputCurve(IS.LinearFunctionData(0.0, 0.0)), 0.0) - - @test repr(cc) == sprint(show, cc) == - "InfrastructureSystems.CostCurve{QuadraticCurve}(QuadraticCurve(1.0, 2.0, 3.0), InfrastructureSystems.UnitSystemModule.UnitSystem.NATURAL_UNITS = 2, LinearCurve(0.0, 0.0))" - @test repr(fc) == sprint(show, fc) == - "InfrastructureSystems.FuelCurve{QuadraticCurve}(QuadraticCurve(1.0, 2.0, 3.0), InfrastructureSystems.UnitSystemModule.UnitSystem.NATURAL_UNITS = 2, 4.0, LinearCurve(0.0, 0.0))" - @test sprint(show, "text/plain", cc) == - sprint(show, "text/plain", cc; context = :compact => false) == - "CostCurve:\n value_curve: QuadraticCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 1.0 x^2 + 2.0 x + 3.0\n power_units: InfrastructureSystems.UnitSystemModule.UnitSystem.NATURAL_UNITS = 2\n vom_cost: LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 0.0 x + 0.0" - @test sprint(show, "text/plain", fc) == - sprint(show, "text/plain", fc; context = :compact => false) == - "FuelCurve:\n value_curve: QuadraticCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 1.0 x^2 + 2.0 x + 3.0\n power_units: InfrastructureSystems.UnitSystemModule.UnitSystem.NATURAL_UNITS = 2\n fuel_cost: 4.0\n vom_cost: LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 0.0 x + 0.0" - @test sprint(show, "text/plain", cc; context = :compact => true) == - "CostCurve with power_units InfrastructureSystems.UnitSystemModule.UnitSystem.NATURAL_UNITS = 2, vom_cost LinearCurve(0.0, 0.0), and value_curve:\n QuadraticCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 1.0 x^2 + 2.0 x + 3.0" - @test sprint(show, "text/plain", fc; context = :compact => true) == - "FuelCurve with power_units InfrastructureSystems.UnitSystemModule.UnitSystem.NATURAL_UNITS = 2, fuel_cost 4.0, vom_cost LinearCurve(0.0, 0.0), and value_curve:\n QuadraticCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 1.0 x^2 + 2.0 x + 3.0" - - @test IS.get_power_units(cc) == IS.UnitSystem.NATURAL_UNITS - @test IS.get_power_units(fc) == IS.UnitSystem.NATURAL_UNITS - @test IS.get_power_units( - IS.CostCurve(zero(IS.InputOutputCurve), IS.UnitSystem.SYSTEM_BASE), - ) == - IS.UnitSystem.SYSTEM_BASE - @test IS.get_power_units( - IS.FuelCurve(zero(IS.InputOutputCurve), IS.UnitSystem.DEVICE_BASE, 1.0), - ) == - IS.UnitSystem.DEVICE_BASE - - @test IS.get_vom_cost(cc) == IS.LinearCurve(0.0) - @test IS.get_vom_cost(fc) == IS.LinearCurve(0.0) - @test IS.get_vom_cost( - IS.CostCurve(zero(IS.InputOutputCurve), IS.LinearCurve(1.0, 2.0)), - ) == - IS.LinearCurve(1.0, 2.0) - @test IS.get_vom_cost( - IS.FuelCurve(zero(IS.InputOutputCurve), 1.0, IS.LinearCurve(3.0, 4.0)), - ) == - IS.LinearCurve(3.0, 4.0) -end +## Get all possible isomorphic representations of the given `ValueCurve` +#function all_conversions(vc::IS.ValueCurve; +# universe = (IS.InputOutputCurve, IS.IncrementalCurve, IS.AverageRateCurve), +#) +# convert_to = filter(!=(nameof(typeof(vc))) ∘ nameof, universe) # x -> nameof(x) != nameof(typeof(vc)) +# result = Set{IS.ValueCurve}(constructor(vc) for constructor in convert_to) +# (vc isa IS.InputOutputCurve{IS.LinearFunctionData}) && +# push!(result, IS.InputOutputCurve{IS.QuadraticFunctionData}(vc)) +# return result +#end +# +#@testset "Test ValueCurves" begin +# # IS.InputOutputCurve +# io_quadratic = IS.InputOutputCurve(IS.QuadraticFunctionData(3, 2, 1)) +# @test io_quadratic isa IS.InputOutputCurve{IS.QuadraticFunctionData} +# @test IS.get_function_data(io_quadratic) == IS.QuadraticFunctionData(3, 2, 1) +# @test IS.IncrementalCurve(io_quadratic) == +# IS.IncrementalCurve(IS.LinearFunctionData(6, 2), 1.0) +# @test IS.AverageRateCurve(io_quadratic) == +# IS.AverageRateCurve(IS.LinearFunctionData(3, 2), 1.0) +# @test zero(io_quadratic) == IS.InputOutputCurve(IS.LinearFunctionData(0, 0)) +# @test zero(IS.InputOutputCurve) == IS.InputOutputCurve(IS.LinearFunctionData(0, 0)) +# @test IS.is_cost_alias(io_quadratic) == IS.is_cost_alias(typeof(io_quadratic)) == true +# @test repr(io_quadratic) == sprint(show, io_quadratic) == +# "QuadraticCurve(3.0, 2.0, 1.0)" +# @test sprint(show, "text/plain", io_quadratic) == +# "QuadraticCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 3.0 x^2 + 2.0 x + 1.0" +# +# io_linear = IS.InputOutputCurve(IS.LinearFunctionData(2, 1)) +# @test io_linear isa IS.InputOutputCurve{IS.LinearFunctionData} +# @test IS.get_function_data(io_linear) == IS.LinearFunctionData(2, 1) +# @test IS.InputOutputCurve{IS.QuadraticFunctionData}(io_linear) == +# IS.InputOutputCurve(IS.QuadraticFunctionData(0, 2, 1)) +# @test IS.IncrementalCurve(io_linear) == +# IS.IncrementalCurve(IS.LinearFunctionData(0, 2), 1.0) +# @test IS.AverageRateCurve(io_linear) == +# IS.AverageRateCurve(IS.LinearFunctionData(0, 2), 1.0) +# @test IS.is_cost_alias(io_linear) == IS.is_cost_alias(typeof(io_linear)) == true +# @test repr(io_linear) == sprint(show, io_linear) == +# "LinearCurve(2.0, 1.0)" +# @test sprint(show, "text/plain", io_linear) == +# "LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 2.0 x + 1.0" +# +# io_piecewise = IS.InputOutputCurve(IS.PiecewiseLinearData([(1, 6), (3, 9), (5, 13)])) +# @test io_piecewise isa IS.InputOutputCurve{IS.PiecewiseLinearData} +# @test IS.get_function_data(io_piecewise) == +# IS.PiecewiseLinearData([(1, 6), (3, 9), (5, 13)]) +# @test IS.IncrementalCurve(io_piecewise) == +# IS.IncrementalCurve(IS.PiecewiseStepData([1, 3, 5], [1.5, 2]), 6.0) +# @test IS.AverageRateCurve(io_piecewise) == +# IS.AverageRateCurve(IS.PiecewiseStepData([1, 3, 5], [3, 2.6]), 6.0) +# @test IS.is_cost_alias(io_piecewise) == IS.is_cost_alias(typeof(io_piecewise)) == true +# @test repr(io_piecewise) == sprint(show, io_piecewise) == +# "PiecewisePointCurve([(x = 1.0, y = 6.0), (x = 3.0, y = 9.0), (x = 5.0, y = 13.0)])" +# @test sprint(show, "text/plain", io_piecewise) == +# "PiecewisePointCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: piecewise linear y = f(x) connecting points:\n (x = 1.0, y = 6.0)\n (x = 3.0, y = 9.0)\n (x = 5.0, y = 13.0)" +# +# # IS.IncrementalCurve +# inc_linear = IS.IncrementalCurve(IS.LinearFunctionData(6, 2), 1.0) +# inc_linear_no_initial = IS.IncrementalCurve(IS.LinearFunctionData(6, 2), nothing) +# @test inc_linear isa IS.IncrementalCurve{IS.LinearFunctionData} +# @test inc_linear_no_initial isa IS.IncrementalCurve{IS.LinearFunctionData} +# @test IS.get_function_data(inc_linear) == IS.LinearFunctionData(6, 2) +# @test IS.get_initial_input(inc_linear) == 1 +# @test IS.InputOutputCurve(inc_linear) == +# IS.InputOutputCurve(IS.QuadraticFunctionData(3, 2, 1)) +# @test IS.InputOutputCurve(IS.IncrementalCurve(IS.LinearFunctionData(0, 2), 1.0)) == +# IS.InputOutputCurve(IS.LinearFunctionData(2, 1)) +# @test IS.AverageRateCurve(inc_linear) == +# IS.AverageRateCurve(IS.LinearFunctionData(3, 2), 1.0) +# @test_throws ArgumentError IS.InputOutputCurve(inc_linear_no_initial) +# @test_throws ArgumentError IS.AverageRateCurve(inc_linear_no_initial) +# @test zero(inc_linear) == IS.IncrementalCurve(IS.LinearFunctionData(0, 0), 0.0) +# @test zero(IS.IncrementalCurve) == IS.IncrementalCurve(IS.LinearFunctionData(0, 0), 0.0) +# @test IS.is_cost_alias(inc_linear) == IS.is_cost_alias(typeof(inc_linear)) == false +# @test repr(inc_linear) == sprint(show, inc_linear) == +# "InfrastructureSystems.IncrementalCurve{InfrastructureSystems.LinearFunctionData}(InfrastructureSystems.LinearFunctionData(6.0, 2.0), 1.0, nothing)" +# @test sprint(show, "text/plain", inc_linear) == +# "IncrementalCurve where initial value is 1.0, derivative function f is: f(x) = 6.0 x + 2.0" +# +# inc_piecewise = IS.IncrementalCurve(IS.PiecewiseStepData([1, 3, 5], [1.5, 2]), 6.0) +# inc_piecewise_no_initial = +# IS.IncrementalCurve(IS.PiecewiseStepData([1, 3, 5], [1.5, 2]), nothing) +# @test inc_piecewise isa IS.IncrementalCurve{IS.PiecewiseStepData} +# @test inc_piecewise_no_initial isa IS.IncrementalCurve{IS.PiecewiseStepData} +# @test IS.get_function_data(inc_piecewise) == IS.PiecewiseStepData([1, 3, 5], [1.5, 2]) +# @test IS.get_initial_input(inc_piecewise) == 6 +# @test IS.InputOutputCurve(inc_piecewise) == +# IS.InputOutputCurve(IS.PiecewiseLinearData([(1, 6), (3, 9), (5, 13)])) +# @test IS.AverageRateCurve(inc_piecewise) == +# IS.AverageRateCurve(IS.PiecewiseStepData([1, 3, 5], [3, 2.6]), 6.0) +# @test_throws ArgumentError IS.InputOutputCurve(inc_piecewise_no_initial) +# @test_throws ArgumentError IS.AverageRateCurve(inc_piecewise_no_initial) +# @test IS.is_cost_alias(inc_piecewise) == IS.is_cost_alias(typeof(inc_piecewise)) == +# true +# @test repr(inc_piecewise) == sprint(show, inc_piecewise) == +# "PiecewiseIncrementalCurve(6.0, [1.0, 3.0, 5.0], [1.5, 2.0])" +# @test sprint(show, "text/plain", inc_piecewise) == +# "PiecewiseIncrementalCurve where initial value is 6.0, derivative function f is: f(x) =\n 1.5 for x in [1.0, 3.0)\n 2.0 for x in [3.0, 5.0)" +# +# # IS.AverageRateCurve +# ar_linear = IS.AverageRateCurve(IS.LinearFunctionData(3, 2), 1.0) +# ar_linear_no_initial = IS.AverageRateCurve(IS.LinearFunctionData(3, 2), nothing) +# @test ar_linear isa IS.AverageRateCurve{IS.LinearFunctionData} +# @test ar_linear_no_initial isa IS.AverageRateCurve{IS.LinearFunctionData} +# @test IS.get_function_data(ar_linear) == IS.LinearFunctionData(3, 2) +# @test IS.get_initial_input(ar_linear) == 1 +# @test IS.InputOutputCurve(ar_linear) == +# IS.InputOutputCurve(IS.QuadraticFunctionData(3, 2, 1)) +# @test IS.InputOutputCurve(IS.AverageRateCurve(IS.LinearFunctionData(0, 2), 1.0)) == +# IS.InputOutputCurve(IS.LinearFunctionData(2, 1)) +# @test IS.IncrementalCurve(ar_linear) == +# IS.IncrementalCurve(IS.LinearFunctionData(6, 2), 1.0) +# @test_throws ArgumentError IS.InputOutputCurve(ar_linear_no_initial) +# @test_throws ArgumentError IS.IncrementalCurve(ar_linear_no_initial) +# @test zero(ar_linear) == IS.AverageRateCurve(IS.LinearFunctionData(0, 0), 0.0) +# @test zero(IS.AverageRateCurve) == IS.AverageRateCurve(IS.LinearFunctionData(0, 0), 0.0) +# @test IS.is_cost_alias(ar_linear) == IS.is_cost_alias(typeof(ar_linear)) == false +# @test repr(ar_linear) == sprint(show, ar_linear) == +# "InfrastructureSystems.AverageRateCurve{InfrastructureSystems.LinearFunctionData}(InfrastructureSystems.LinearFunctionData(3.0, 2.0), 1.0, nothing)" +# @test sprint(show, "text/plain", ar_linear) == +# "AverageRateCurve where initial value is 1.0, average rate function f is: f(x) = 3.0 x + 2.0" +# +# ar_piecewise = IS.AverageRateCurve(IS.PiecewiseStepData([1, 3, 5], [3, 2.6]), 6.0) +# ar_piecewise_no_initial = +# IS.AverageRateCurve(IS.PiecewiseStepData([1, 3, 5], [3, 2.6]), nothing) +# @test ar_piecewise isa IS.AverageRateCurve{IS.PiecewiseStepData} +# @test ar_piecewise_no_initial isa IS.AverageRateCurve{IS.PiecewiseStepData} +# @test IS.get_function_data(ar_piecewise) == IS.PiecewiseStepData([1, 3, 5], [3, 2.6]) +# @test IS.get_initial_input(ar_piecewise) == 6 +# @test IS.InputOutputCurve(ar_piecewise) == +# IS.InputOutputCurve(IS.PiecewiseLinearData([(1, 6), (3, 9), (5, 13)])) +# @test IS.IncrementalCurve(ar_piecewise) == +# IS.IncrementalCurve(IS.PiecewiseStepData([1, 3, 5], [1.5, 2]), 6.0) +# @test_throws ArgumentError IS.InputOutputCurve(ar_piecewise_no_initial) +# @test_throws ArgumentError IS.IncrementalCurve(ar_piecewise_no_initial) +# @test IS.is_cost_alias(ar_piecewise) == IS.is_cost_alias(typeof(ar_piecewise)) == true +# @test repr(ar_piecewise) == sprint(show, ar_piecewise) == +# "PiecewiseAverageCurve(6.0, [1.0, 3.0, 5.0], [3.0, 2.6])" +# @test sprint(show, "text/plain", ar_piecewise) == +# "PiecewiseAverageCurve where initial value is 6.0, average rate function f is: f(x) =\n 3.0 for x in [1.0, 3.0)\n 2.6 for x in [3.0, 5.0)" +# +# # Serialization round trip +# curves_by_type = [ # typeof() gives parameterized types +# (io_quadratic, IS.InputOutputCurve), +# (io_linear, IS.InputOutputCurve), +# (io_piecewise, IS.InputOutputCurve), +# (inc_linear, IS.IncrementalCurve), +# (inc_piecewise, IS.IncrementalCurve), +# (ar_linear, IS.AverageRateCurve), +# (ar_piecewise, IS.AverageRateCurve), +# (inc_linear_no_initial, IS.IncrementalCurve), +# (inc_piecewise_no_initial, IS.IncrementalCurve), +# (ar_linear_no_initial, IS.AverageRateCurve), +# (ar_piecewise_no_initial, IS.AverageRateCurve), +# ] +# for (curve, curve_type) in curves_by_type +# @test IS.serialize(curve) isa AbstractDict +# @test IS.deserialize(curve_type, IS.serialize(curve)) == curve +# end +# +# @test zero(IS.ValueCurve) == IS.InputOutputCurve(IS.LinearFunctionData(0, 0)) +#end +# +#@testset "Test ValueCurve type conversion constructors" begin +# @test IS.InputOutputCurve(IS.QuadraticFunctionData(3, 2, 1), 1) == +# IS.InputOutputCurve(IS.QuadraticFunctionData(3, 2, 1), 1.0) +# @test IS.IncrementalCurve(IS.LinearFunctionData(6, 2), 1) == +# IS.IncrementalCurve(IS.LinearFunctionData(6, 2), 1.0) +# @test IS.AverageRateCurve(IS.LinearFunctionData(3, 2), 1) == +# IS.AverageRateCurve(IS.LinearFunctionData(3, 2), 1.0) +#end +# +#@testset "Test cost aliases" begin +# lc = IS.LinearCurve(3.0, 5.0) +# @test lc == IS.InputOutputCurve(IS.LinearFunctionData(3.0, 5.0)) +# @test IS.LinearCurve(3.0) == IS.InputOutputCurve(IS.LinearFunctionData(3.0, 0.0)) +# @test IS.get_proportional_term(lc) == 3.0 +# @test IS.get_constant_term(lc) == 5.0 +# +# qc = IS.QuadraticCurve(1.0, 2.0, 18.0) +# @test qc == IS.InputOutputCurve(IS.QuadraticFunctionData(1.0, 2.0, 18.0)) +# @test IS.get_quadratic_term(qc) == 1.0 +# @test IS.get_proportional_term(qc) == 2.0 +# @test IS.get_constant_term(qc) == 18.0 +# +# ppc = IS.PiecewisePointCurve([(1.0, 20.0), (2.0, 24.0), (3.0, 30.0)]) +# @test ppc == +# IS.InputOutputCurve( +# IS.PiecewiseLinearData([(1.0, 20.0), (2.0, 24.0), (3.0, 30.0)]), +# ) +# @test IS.get_points(ppc) == +# [(x = 1.0, y = 20.0), (x = 2.0, y = 24.0), (x = 3.0, y = 30.0)] +# @test IS.get_x_coords(ppc) == [1.0, 2.0, 3.0] +# @test IS.get_y_coords(ppc) == [20.0, 24.0, 30.0] +# @test IS.get_slopes(ppc) == [4.0, 6.0] +# +# pic = IS.PiecewiseIncrementalCurve(20.0, [1.0, 2.0, 3.0], [4.0, 6.0]) +# @test pic == +# IS.IncrementalCurve(IS.PiecewiseStepData([1.0, 2.0, 3.0], [4.0, 6.0]), 20.0) +# @test IS.get_x_coords(pic) == [1.0, 2.0, 3.0] +# @test IS.get_slopes(pic) == [4.0, 6.0] +# +# pac = IS.PiecewiseAverageCurve(20.0, [1.0, 2.0, 3.0], [12.0, 10.0]) +# @test pac == +# IS.AverageRateCurve(IS.PiecewiseStepData([1.0, 2.0, 3.0], [12.0, 10.0]), 20.0) +# @test IS.get_x_coords(pac) == [1.0, 2.0, 3.0] +# @test IS.get_average_rates(pac) == [12.0, 10.0] +# +# # Make sure the aliases get registered properly +# @test sprint(show, "text/plain", IS.QuadraticCurve) == +# "QuadraticCurve (alias for InfrastructureSystems.InputOutputCurve{InfrastructureSystems.QuadraticFunctionData})" +#end +# +#@testset "Test input_at_zero" begin +# iaz = 1234.5 +# pwinc_without_iaz = +# IS.IncrementalCurve(IS.PiecewiseStepData([1, 3, 5], [1.5, 2]), 6.0, nothing) +# pwinc_with_iaz = +# IS.IncrementalCurve(IS.PiecewiseStepData([1, 3, 5], [1.5, 2]), 6.0, iaz) +# all_without_iaz = [ +# IS.InputOutputCurve(IS.QuadraticFunctionData(3, 2, 1), nothing), +# IS.InputOutputCurve(IS.LinearFunctionData(2, 1), nothing), +# IS.InputOutputCurve(IS.PiecewiseLinearData([(1, 6), (3, 9), (5, 13)]), nothing), +# IS.IncrementalCurve(IS.LinearFunctionData(6, 2), 1.0, nothing), +# pwinc_without_iaz, +# IS.AverageRateCurve(IS.LinearFunctionData(3, 2), 1.0, nothing), +# IS.AverageRateCurve(IS.PiecewiseStepData([1, 3, 5], [3, 2.6]), 6.0, nothing), +# ] +# all_with_iaz = [ +# IS.InputOutputCurve(IS.QuadraticFunctionData(3, 2, 1), iaz), +# IS.InputOutputCurve(IS.LinearFunctionData(2, 1), iaz), +# IS.InputOutputCurve(IS.PiecewiseLinearData([(1, 6), (3, 9), (5, 13)]), iaz), +# IS.IncrementalCurve(IS.LinearFunctionData(6, 2), 1.0, iaz), +# pwinc_with_iaz, +# IS.AverageRateCurve(IS.LinearFunctionData(3, 2), 1.0, iaz), +# IS.AverageRateCurve(IS.PiecewiseStepData([1, 3, 5], [3, 2.6]), 6.0, iaz), +# ] +# +# # Alias constructors +# @test IS.PiecewiseIncrementalCurve(1234.5, 6.0, [1.0, 3.0, 5.0], [1.5, 2.0]) == +# pwinc_with_iaz +# +# # Getters and printouts +# for (without_iaz, with_iaz) in zip(all_without_iaz, all_with_iaz) +# @test IS.get_input_at_zero(without_iaz) === nothing +# @test IS.get_input_at_zero(with_iaz) == iaz +# @test occursin(string(iaz), repr(with_iaz)) +# @test sprint(show, with_iaz) == repr(with_iaz) +# @test occursin(string(iaz), sprint(show, "text/plain", with_iaz)) +# end +# +# @test repr(pwinc_with_iaz) == sprint(show, pwinc_with_iaz) == +# "PiecewiseIncrementalCurve(1234.5, 6.0, [1.0, 3.0, 5.0], [1.5, 2.0])" +# @test sprint(show, "text/plain", pwinc_with_iaz) == +# "PiecewiseIncrementalCurve where value at zero is 1234.5, initial value is 6.0, derivative function f is: f(x) =\n 1.5 for x in [1.0, 3.0)\n 2.0 for x in [3.0, 5.0)" +# +# # Preserved under conversion +# for without_iaz in Iterators.flatten(all_conversions.(all_without_iaz)) +# @test IS.get_input_at_zero(without_iaz) === nothing +# end +# for with_iaz in Iterators.flatten(all_conversions.(all_with_iaz)) +# @test IS.get_input_at_zero(with_iaz) == iaz +# end +#end +# +#@testset "Test IS.CostCurve and IS.FuelCurve" begin +# cc = IS.CostCurve(IS.InputOutputCurve(IS.QuadraticFunctionData(1, 2, 3))) +# fc = IS.FuelCurve(IS.InputOutputCurve(IS.QuadraticFunctionData(1, 2, 3)), 4.0) +# # TODO also test fuel curves with time series +# +# @test IS.get_value_curve(cc) == IS.InputOutputCurve(IS.QuadraticFunctionData(1, 2, 3)) +# @test IS.get_value_curve(fc) == IS.InputOutputCurve(IS.QuadraticFunctionData(1, 2, 3)) +# @test IS.get_fuel_cost(fc) == 4 +# +# @test IS.serialize(cc) isa AbstractDict +# @test IS.serialize(fc) isa AbstractDict +# @test IS.deserialize(IS.CostCurve, IS.serialize(cc)) == cc +# @test IS.deserialize(IS.FuelCurve, IS.serialize(fc)) == fc +# +# @test zero(cc) == IS.CostCurve(IS.InputOutputCurve(IS.LinearFunctionData(0.0, 0.0))) +# @test zero(IS.CostCurve) == +# IS.CostCurve(IS.InputOutputCurve(IS.LinearFunctionData(0.0, 0.0))) +# @test zero(fc) == +# IS.FuelCurve(IS.InputOutputCurve(IS.LinearFunctionData(0.0, 0.0)), 0.0) +# @test zero(IS.FuelCurve) == +# IS.FuelCurve(IS.InputOutputCurve(IS.LinearFunctionData(0.0, 0.0)), 0.0) +# +# @test repr(cc) == sprint(show, cc) == +# "InfrastructureSystems.CostCurve{QuadraticCurve}(QuadraticCurve(1.0, 2.0, 3.0), InfrastructureSystems.UnitSystemModule.UnitSystem.NATURAL_UNITS = 2, LinearCurve(0.0, 0.0))" +# @test repr(fc) == sprint(show, fc) == +# "InfrastructureSystems.FuelCurve{QuadraticCurve}(QuadraticCurve(1.0, 2.0, 3.0), InfrastructureSystems.UnitSystemModule.UnitSystem.NATURAL_UNITS = 2, 4.0, LinearCurve(0.0, 0.0))" +# @test sprint(show, "text/plain", cc) == +# sprint(show, "text/plain", cc; context = :compact => false) == +# "CostCurve:\n value_curve: QuadraticCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 1.0 x^2 + 2.0 x + 3.0\n power_units: InfrastructureSystems.UnitSystemModule.UnitSystem.NATURAL_UNITS = 2\n vom_cost: LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 0.0 x + 0.0" +# @test sprint(show, "text/plain", fc) == +# sprint(show, "text/plain", fc; context = :compact => false) == +# "FuelCurve:\n value_curve: QuadraticCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 1.0 x^2 + 2.0 x + 3.0\n power_units: InfrastructureSystems.UnitSystemModule.UnitSystem.NATURAL_UNITS = 2\n fuel_cost: 4.0\n vom_cost: LinearCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 0.0 x + 0.0" +# @test sprint(show, "text/plain", cc; context = :compact => true) == +# "CostCurve with power_units InfrastructureSystems.UnitSystemModule.UnitSystem.NATURAL_UNITS = 2, vom_cost LinearCurve(0.0, 0.0), and value_curve:\n QuadraticCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 1.0 x^2 + 2.0 x + 3.0" +# @test sprint(show, "text/plain", fc; context = :compact => true) == +# "FuelCurve with power_units InfrastructureSystems.UnitSystemModule.UnitSystem.NATURAL_UNITS = 2, fuel_cost 4.0, vom_cost LinearCurve(0.0, 0.0), and value_curve:\n QuadraticCurve (a type of InfrastructureSystems.InputOutputCurve) where function is: f(x) = 1.0 x^2 + 2.0 x + 3.0" +# +# @test IS.get_power_units(cc) == IS.UnitSystem.NATURAL_UNITS +# @test IS.get_power_units(fc) == IS.UnitSystem.NATURAL_UNITS +# @test IS.get_power_units( +# IS.CostCurve(zero(IS.InputOutputCurve), IS.UnitSystem.SYSTEM_BASE), +# ) == +# IS.UnitSystem.SYSTEM_BASE +# @test IS.get_power_units( +# IS.FuelCurve(zero(IS.InputOutputCurve), IS.UnitSystem.DEVICE_BASE, 1.0), +# ) == +# IS.UnitSystem.DEVICE_BASE +# +# @test IS.get_vom_cost(cc) == IS.LinearCurve(0.0) +# @test IS.get_vom_cost(fc) == IS.LinearCurve(0.0) +# @test IS.get_vom_cost( +# IS.CostCurve(zero(IS.InputOutputCurve), IS.LinearCurve(1.0, 2.0)), +# ) == +# IS.LinearCurve(1.0, 2.0) +# @test IS.get_vom_cost( +# IS.FuelCurve(zero(IS.InputOutputCurve), 1.0, IS.LinearCurve(3.0, 4.0)), +# ) == +# IS.LinearCurve(3.0, 4.0) +#end diff --git a/test/test_time_series.jl b/test/test_time_series.jl index 9178d849..96a6f90b 100644 --- a/test/test_time_series.jl +++ b/test/test_time_series.jl @@ -1,3 +1,4 @@ +using Core: Argument @testset "Test add forecasts on the fly from dict" begin sys = IS.SystemData() name = "Component1" @@ -2943,6 +2944,102 @@ end ) end +@testset "Test bulk addition of time series with transaction" begin + sys = IS.SystemData() + name = "Component1" + component = IS.TestComponent(name, 5) + IS.add_component!(sys, component) + + initial_time = Dates.DateTime("2020-09-01") + resolution = Dates.Hour(1) + + other_time = initial_time + resolution + name = "test" + horizon_count = 24 + + make_values(count, index) = ones(count) * index + IS.begin_time_series_update(sys.time_series_manager) do + for i in 1:5 + forecast = IS.Deterministic(; + data = SortedDict( + initial_time => make_values(horizon_count, i), + other_time => make_values(horizon_count, i), + ), + name = "ts_$(i)", resolution = resolution, + ) + IS.add_time_series!(sys, component, forecast; model_year = "high") + end + end + ts_keys = IS.get_time_series_keys(component) + @test length(ts_keys) == 5 + actual_ts_data = Dict(IS.get_name(x) => x for x in ts_keys) + for i in 1:5 + name = "ts_$(i)" + @test haskey(actual_ts_data, name) + key = actual_ts_data[name] + ts = IS.get_time_series(component, key) + @test ts isa IS.Deterministic + data = IS.get_data(ts) + @test !isempty(values(data)) + for val in values(data) + @test val == make_values(horizon_count, i) + end + end +end + +@testset "Test bulk addition of time series with transaction and error" begin + sys = IS.SystemData() + name = "Component1" + component = IS.TestComponent(name, 5) + IS.add_component!(sys, component) + + initial_time = Dates.DateTime("2020-09-01") + resolution = Dates.Hour(1) + + other_time = initial_time + resolution + name = "test" + horizon_count = 24 + + make_values(count, index) = ones(count) * index + + bystander = IS.Deterministic(; + data = SortedDict( + initial_time => rand(horizon_count), + other_time => rand(horizon_count), + ), + name = "bystander", resolution = resolution, + ) + IS.add_time_series!(sys, component, bystander) + + @test_throws( + ArgumentError, + IS.begin_time_series_update(sys.time_series_manager) do + for i in 1:5 + if i < 5 + name = "ts_$i" + else + name = "ts_$(i - 1)" + end + forecast = IS.Deterministic(; + data = SortedDict( + initial_time => make_values(horizon_count, i), + other_time => make_values(horizon_count, i), + ), + name = name, resolution = resolution, + ) + IS.add_time_series!(sys, component, forecast) + end + end, + ) + ts_keys = IS.get_time_series_keys(component) + @test length(ts_keys) == 1 + key = ts_keys[1] + @test IS.get_name(key) == "bystander" + res = IS.get_time_series(component, key) + @test res isa IS.Deterministic + @test IS.get_data(res) == IS.get_data(bystander) +end + @testset "Test list_existing_metadata" begin sys = IS.SystemData() initial_time = Dates.DateTime("2020-09-01")