diff --git a/src/operation/operation_model_interface.jl b/src/operation/operation_model_interface.jl index 97c9faa313..688f6013f7 100644 --- a/src/operation/operation_model_interface.jl +++ b/src/operation/operation_model_interface.jl @@ -321,9 +321,27 @@ read_aux_variable(model::OperationModel, key::AuxVarKey) = _read_results(model, read_variable(model::OperationModel, key::VariableKey) = _read_results(model, key) read_expression(model::OperationModel, key::ExpressionKey) = _read_results(model, key) +function _read_col_name(axes) + if length(axes) == 1 + error("Axes of size 1 are not supported") + end + # Currently, variables that don't have timestamps have a dummy axes to keep + # two axes in the Store (HDF or Memory). This if-else is used to decide if a + # dummy axes is being used or not. + if typeof(axes[2]) <: UnitRange{Int} + return axes[1] + elseif typeof(axes[2]) <: Vector{String} + IS.@assert_op length(axes[1]) == 1 + return axes[2] + else + error("Second axes in store is not allowed to be $(typeof(axes[2]))") + end +end + function _read_results(model::OperationModel, key::OptimizationContainerKey) res = read_results(get_store(model), key) - return DataFrames.DataFrame(permutedims(res.data), axes(res)[1]) + col_name = _read_col_name(axes(res)) + return DataFrames.DataFrame(permutedims(res.data), col_name) end read_optimizer_stats(model::OperationModel) = read_optimizer_stats(get_store(model)) diff --git a/src/operation/problem_results.jl b/src/operation/problem_results.jl index 2f7ef03588..cfb1aca219 100644 --- a/src/operation/problem_results.jl +++ b/src/operation/problem_results.jl @@ -311,13 +311,23 @@ function _read_results( results = Dict{OptimizationContainerKey, DataFrames.DataFrame}() for (k, v) in result_values if k in container_keys - results[k] = - if convert_result_to_natural_units(k) - v[time_ids, :] .* base_power - else - v[time_ids, :] - end - DataFrames.insertcols!(results[k], 1, :DateTime => timestamps) + num_rows = DataFrames.nrow(v) + if num_rows == 1 && num_rows < length(time_ids) + results[k] = + if convert_result_to_natural_units(k) + v .* base_power + else + v + end + else + results[k] = + if convert_result_to_natural_units(k) + v[time_ids, :] .* base_power + else + v[time_ids, :] + end + DataFrames.insertcols!(results[k], 1, :DateTime => timestamps) + end end end return results diff --git a/test/Project.toml b/test/Project.toml index 1b71bf8a36..5ac2b5fe45 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -24,6 +24,7 @@ Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" SCS = "c946c3f1-0d1f-5ce8-9dea-7daa1f7e2d13" Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" +StorageSystemsSimulations = "e2f1a126-19d0-4674-9252-42b2384f8e3c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestSetExtensions = "98d24dd4-01ad-11ea-1b02-c9a08f80db04" TimeSeries = "9e3dc215-6440-5c97-bce1-76c03772f85e" diff --git a/test/runtests.jl b/test/runtests.jl index 91075c027c..9f559b3ab9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,6 +7,7 @@ using PowerNetworkMatrices using HydroPowerSimulations import PowerSystemCaseBuilder: PSITestSystems using PowerNetworkMatrices +using StorageSystemsSimulations # Test Packages using Test diff --git a/test/test_model_decision.jl b/test/test_model_decision.jl index 4137c9fe23..13fe6803f4 100644 --- a/test/test_model_decision.jl +++ b/test/test_model_decision.jl @@ -655,3 +655,31 @@ end @test build!(UC; output_dir = output_dir) == PSI.BuildStatus.BUILT @test solve!(UC) == RunStatus.SUCCESSFUL end + +@testset "Test for single row result variables" begin + template = get_thermal_dispatch_template_network() + c_sys5_bat = PSB.build_system(PSITestSystems, "c_sys5_bat_ems"; force_build = true) + device_model = DeviceModel( + BatteryEMS, + StorageDispatchWithReserves; + attributes = Dict{String, Any}( + "reservation" => true, + "cycling_limits" => false, + "energy_target" => true, + "complete_coverage" => false, + "regularization" => false, + ), + ) + set_device_model!(template, device_model) + output_dir = mktempdir(; cleanup = true) + model = DecisionModel( + template, + c_sys5_bat; + optimizer = GLPK_optimizer, + ) + @test build!(model; output_dir = output_dir) == PSI.BuildStatus.BUILT + @test solve!(model) == RunStatus.SUCCESSFUL + res = ProblemResults(model) + shortage = read_variable(res, "StorageEnergyShortageVariable__BatteryEMS") + @test nrow(shortage) == 1 +end