diff --git a/src/operation/decision_model.jl b/src/operation/decision_model.jl index e5d61a2fea..f416865bde 100644 --- a/src/operation/decision_model.jl +++ b/src/operation/decision_model.jl @@ -268,8 +268,8 @@ end function validate_time_series(model::DecisionModel{<:DefaultDecisionProblem}) sys = get_system(model) - _, _, forecast_count = PSY.get_time_series_counts(sys) - if forecast_count < 1 + counts = PSY.get_time_series_counts(sys) + if counts.forecast_count < 1 error( "The system does not contain forecast data. A DecisionModel can't be built.", ) diff --git a/src/operation/emulation_model.jl b/src/operation/emulation_model.jl index 0f27b8ddc4..d946aa6331 100644 --- a/src/operation/emulation_model.jl +++ b/src/operation/emulation_model.jl @@ -247,8 +247,8 @@ end function validate_time_series(model::EmulationModel{<:DefaultEmulationProblem}) sys = get_system(model) - _, ts_count, _ = PSY.get_time_series_counts(sys) - if ts_count < 1 + counts = PSY.get_time_series_counts(sys) + if counts.static_time_series_count < 1 error( "The system does not contain Static TimeSeries data. An Emulation model can't be formulated.", ) diff --git a/src/simulation/hdf_simulation_store.jl b/src/simulation/hdf_simulation_store.jl index 09007a7b07..560a2922a5 100644 --- a/src/simulation/hdf_simulation_store.jl +++ b/src/simulation/hdf_simulation_store.jl @@ -679,6 +679,33 @@ function write_result!( return end +function serialize_system!(store::HdfSimulationStore, sys::PSY.System) + root = store.file[HDF_SIMULATION_ROOT_PATH] + systems_group = _get_group_or_create(root, "systems") + uuid = string(IS.get_uuid(sys)) + if haskey(systems_group, uuid) + @debug "System with UUID = $uuid is already stored" _group = + LOG_GROUP_SIMULATION_STORE + return + end + + json_text = PSY.to_json(sys) + systems_group[uuid] = json_text + return +end + +function deserialize_system(store::HdfSimulationStore, uuid::Base.UUID) + root = store.file[HDF_SIMULATION_ROOT_PATH] + systems_group = _get_group_or_create(root, "systems") + uuid_str = string(uuid) + if !haskey(systems_group, uuid_str) + error("No system with UUID $uuid_str is stored") + end + + json_text = HDF5.read(systems_group[uuid_str]) + return PSY.from_json(json_text, PSY.System) +end + function _check_state(store::HdfSimulationStore) if has_dirty(store.cache) error("BUG!!! dirty cache is present at shutdown: $(store.file)") diff --git a/src/simulation/in_memory_simulation_store.jl b/src/simulation/in_memory_simulation_store.jl index 37beb6f846..7fbe74d8e8 100644 --- a/src/simulation/in_memory_simulation_store.jl +++ b/src/simulation/in_memory_simulation_store.jl @@ -271,3 +271,5 @@ function write_optimizer_stats!( write_optimizer_stats!(em_data, stats, index) return end + +serialize_system!(::InMemorySimulationStore, ::PSY.System) = nothing diff --git a/src/simulation/simulation.jl b/src/simulation/simulation.jl index aa0a77754f..cac6d731de 100644 --- a/src/simulation/simulation.jl +++ b/src/simulation/simulation.jl @@ -590,13 +590,17 @@ function _setup_simulation_partitions(sim::Simulation) open_store(HdfSimulationStore, get_store_dir(sim), "w") do store set_simulation_store!(sim, store) - _initialize_problem_storage!( - sim, - DEFAULT_SIMULATION_STORE_CACHE_SIZE_MiB, - MIN_CACHE_FLUSH_SIZE_MiB, - ) + try + _initialize_problem_storage!( + sim, + DEFAULT_SIMULATION_STORE_CACHE_SIZE_MiB, + MIN_CACHE_FLUSH_SIZE_MiB, + ) + _serialize_systems_to_store!(store, sim) + finally + set_simulation_store!(sim, nothing) + end end - set_simulation_store!(sim, nothing) end """ @@ -1000,6 +1004,10 @@ function execute!(sim::Simulation; kwargs...) end @info ("\n$(RUN_SIMULATION_TIMER)\n") set_simulation_status!(sim, RunStatus.SUCCESSFUL) + if isnothing(sim.internal.partitions) + # Partitioned simulations serialize the systems once during build. + _serialize_systems_to_store!(store, sim) + end log_cache_hit_percentages(store) catch e set_simulation_status!(sim, RunStatus.FAILED) @@ -1084,6 +1092,18 @@ function serialize_simulation(sim::Simulation; path = nothing, force = false) return directory end +function _serialize_systems_to_store!(store::SimulationStore, sim::Simulation) + simulation_models = get_models(sim) + for dm in get_decision_models(simulation_models) + serialize_system!(store, get_system(dm)) + end + + em = get_emulation_model(simulation_models) + if !isnothing(em) + serialize_system!(store, get_system(em)) + end +end + function deserialize_model( ::Type{Simulation}, directory::AbstractString, diff --git a/src/simulation/simulation_problem_results.jl b/src/simulation/simulation_problem_results.jl index 5d0cf565e2..fefb6c768f 100644 --- a/src/simulation/simulation_problem_results.jl +++ b/src/simulation/simulation_problem_results.jl @@ -113,18 +113,52 @@ get_timestamps(result::SimulationProblemResults) = result.timestamps """ Return the system used for the problem. If the system hasn't already been deserialized or set with [`set_system!`](@ref) then deserialize and store it. + +If the simulation was configured to serialize all systems to file then the returned system +will include all data. If that was not configured then the returned system will include +all data except time series data. """ -function get_system!(results::SimulationProblemResults) +function get_system!(results::SimulationProblemResults; kwargs...) + !isnothing(results.system) && return results.system + file = joinpath( results.execution_path, "problems", results.problem, make_system_filename(results.system_uuid), ) - results.system = PSY.System(file; time_series_read_only = true) + + # This flag should remain unpublished because it should never be needed + # by the general audience. + if !get(kwargs, :use_h5_system, false) && isfile(file) + system = PSY.System(file; time_series_read_only = true) + @info "De-serialized the system from files." + else + system = _deserialize_system(results, results.store) + end + + results.system = system return results.system end +function _deserialize_system(results::SimulationProblemResults, ::Nothing) + open_store( + HdfSimulationStore, + joinpath(get_execution_path(results), "data_store"), + "r", + ) do store + system = deserialize_system(store, results.system_uuid) + @info "De-serialized the system from the simulation store. The system does " * + "not include time series data." + return system + end +end + +function _deserialize_system(::SimulationProblemResults, ::InMemorySimulationStore) + # This should never be necessary because the system is guaranteed to be in memory. + error("Deserializing a system from the InMemorySimulationStore is not supported.") +end + """ Set the system in the results instance. @@ -593,11 +627,8 @@ Return the optimizer stats for the problem as a DataFrame. - `store::SimulationStore`: a store that has been opened for reading """ function read_optimizer_stats(res::SimulationProblemResults; store = nothing) - if store === nothing && res.store !== nothing - # In this case we have an InMemorySimulationStore. - store = res.store - end - return _read_optimizer_stats(res, store) + _store = isnothing(store) ? res.store : store + return _read_optimizer_stats(res, _store) end function _read_optimizer_stats(res::SimulationProblemResults, ::Nothing) diff --git a/test/test_simulation_results.jl b/test/test_simulation_results.jl index cac3e635cb..d2cbd3ffe7 100644 --- a/test/test_simulation_results.jl +++ b/test/test_simulation_results.jl @@ -125,89 +125,115 @@ function make_export_all(problems) ] end -function test_simulation_results(file_path::String, export_path; in_memory = false) - @testset "Test simulation results in_memory = $in_memory" begin - template_uc = get_template_basic_uc_simulation() - template_ed = get_template_nomin_ed_simulation() - set_device_model!(template_ed, InterruptiblePowerLoad, StaticPowerLoad) - set_network_model!( - template_uc, - NetworkModel(CopperPlatePowerModel; duals = [CopperPlateBalanceConstraint]), - ) - set_network_model!( - template_ed, - NetworkModel( - CopperPlatePowerModel; - duals = [CopperPlateBalanceConstraint], - use_slacks = true, +function run_simulation( + c_sys5_hy_uc, + c_sys5_hy_ed, + file_path::String, + export_path; + in_memory = false, + system_to_file = true, +) + template_uc = get_template_basic_uc_simulation() + template_ed = get_template_nomin_ed_simulation() + set_device_model!(template_ed, InterruptiblePowerLoad, StaticPowerLoad) + set_network_model!( + template_uc, + NetworkModel(CopperPlatePowerModel; duals = [CopperPlateBalanceConstraint]), + ) + set_network_model!( + template_ed, + NetworkModel( + CopperPlatePowerModel; + duals = [CopperPlateBalanceConstraint], + use_slacks = true, + ), + ) + models = SimulationModels(; + decision_models = [ + DecisionModel( + template_uc, + c_sys5_hy_uc; + name = "UC", + optimizer = GLPK_optimizer, + system_to_file = system_to_file, ), - ) - c_sys5_hy_uc = PSB.build_system(PSITestSystems, "c_sys5_hy_uc") - c_sys5_hy_ed = PSB.build_system(PSITestSystems, "c_sys5_hy_ed") - models = SimulationModels(; - decision_models = [ - DecisionModel( - template_uc, - c_sys5_hy_uc; - name = "UC", - optimizer = GLPK_optimizer, - ), - DecisionModel( - template_ed, - c_sys5_hy_ed; - name = "ED", - optimizer = ipopt_optimizer, + DecisionModel( + template_ed, + c_sys5_hy_ed; + name = "ED", + optimizer = ipopt_optimizer, + system_to_file = system_to_file, + ), + ], + ) + + sequence = SimulationSequence(; + models = models, + feedforwards = Dict( + "ED" => [ + SemiContinuousFeedforward(; + component_type = ThermalStandard, + source = OnVariable, + affected_values = [ActivePowerVariable], ), ], - ) + ), + ini_cond_chronology = InterProblemChronology(), + ) + sim = Simulation(; + name = "no_cache", + steps = 2, + models = models, + sequence = sequence, + simulation_folder = file_path, + ) - sequence = SimulationSequence(; - models = models, - feedforwards = Dict( - "ED" => [ - SemiContinuousFeedforward(; - component_type = ThermalStandard, - source = OnVariable, - affected_values = [ActivePowerVariable], - ), - ], + build_out = build!(sim; console_level = Logging.Error) + @test build_out == PSI.BuildStatus.BUILT + + exports = Dict( + "models" => [ + Dict( + "name" => "UC", + "store_all_variables" => true, + "store_all_parameters" => true, + "store_all_duals" => true, + "store_all_aux_variables" => true, ), - ini_cond_chronology = InterProblemChronology(), - ) - sim = Simulation(; - name = "no_cache", - steps = 2, - models = models, - sequence = sequence, - simulation_folder = file_path, - ) + Dict( + "name" => "ED", + "store_all_variables" => true, + "store_all_parameters" => true, + "store_all_duals" => true, + "store_all_aux_variables" => true, + ), + ], + "path" => export_path, + "optimizer_stats" => true, + ) + execute_out = execute!(sim; exports = exports, in_memory = in_memory) + @test execute_out == PSI.RunStatus.SUCCESSFUL - build_out = build!(sim; console_level = Logging.Error) - @test build_out == PSI.BuildStatus.BUILT - - exports = Dict( - "models" => [ - Dict( - "name" => "UC", - "store_all_variables" => true, - "store_all_parameters" => true, - "store_all_duals" => true, - "store_all_aux_variables" => true, - ), - Dict( - "name" => "ED", - "store_all_variables" => true, - "store_all_parameters" => true, - "store_all_duals" => true, - "store_all_aux_variables" => true, - ), - ], - "path" => export_path, - "optimizer_stats" => true, - ) - execute_out = execute!(sim; exports = exports, in_memory = in_memory) - @test execute_out == PSI.RunStatus.SUCCESSFUL + return sim +end +function test_simulation_results( + file_path::String, + export_path; + in_memory = false, + system_to_file = true, +) + @testset "Test simulation results in_memory = $in_memory" begin + c_sys5_hy_uc = PSB.build_system(PSITestSystems, "c_sys5_hy_uc") + c_sys5_hy_ed = PSB.build_system(PSITestSystems, "c_sys5_hy_ed") + sim = run_simulation( + c_sys5_hy_uc, + c_sys5_hy_ed, + file_path, + export_path; + in_memory = in_memory, + system_to_file = system_to_file, + ) results = SimulationResults(sim) test_decision_problem_results(results, c_sys5_hy_ed, c_sys5_hy_uc, in_memory) test_emulation_problem_results(results, in_memory) @@ -704,3 +730,26 @@ end test_simulation_results(file_path, export_path; in_memory = in_memory) end end + +@testset "Test simulation results with system from store" begin + file_path = mktempdir(; cleanup = true) + export_path = mktempdir(; cleanup = true) + c_sys5_hy_uc = PSB.build_system(PSITestSystems, "c_sys5_hy_uc") + c_sys5_hy_ed = PSB.build_system(PSITestSystems, "c_sys5_hy_ed") + in_memory = false + sim = run_simulation( + c_sys5_hy_uc, + c_sys5_hy_ed, + file_path, + export_path; + system_to_file = false, + in_memory = in_memory, + ) + results = SimulationResults(PSI.get_simulation_folder(sim)) + uc = get_decision_problem_results(results, "UC") + ed = get_decision_problem_results(results, "ED") + sys_uc = get_system!(uc) + sys_ed = get_system!(ed) + test_decision_problem_results(results, sys_ed, sys_uc, in_memory) + test_emulation_problem_results(results, in_memory) +end