diff --git a/.claude/claude.md b/.claude/claude.md index ff4ff67e..4de737af 100644 --- a/.claude/claude.md +++ b/.claude/claude.md @@ -72,7 +72,7 @@ src/ decision_model_store.jl # DecisionModel output store emulation_model.jl # EmulationModel (rolling horizon) emulation_model_store.jl # EmulationModel output store - operation_model_interface.jl # Shared model interface methods + optimization_model_interface.jl # Shared model interface methods operation_model_serialization.jl # Serialization/deserialization problem_template.jl # ProblemTemplate (model specification) problem_outputs.jl # Output post-processing diff --git a/src/InfrastructureOptimizationModels.jl b/src/InfrastructureOptimizationModels.jl index b7b500e2..38812a69 100644 --- a/src/InfrastructureOptimizationModels.jl +++ b/src/InfrastructureOptimizationModels.jl @@ -112,6 +112,10 @@ function get_bustype end function has_service end function set_units_base_system! end +# Operation-model lifecycle extension points — downstream (e.g. POM) supplies methods +# dispatched on concrete problem types. +function validate_time_series! end + import TimerOutputs # Base Imports @@ -375,7 +379,7 @@ export get_incompatible_devices # Bulk export: symbols POM needs that weren't previously exported # Core types -export OptimizationContainer, OperationModel +export OptimizationContainer, AbstractOptimizationModel export ArgumentConstructStage, ModelConstructStage export EmulationModelStore, DeviceModelForBranches export SOSStatusVariable @@ -500,10 +504,7 @@ export RunStatus export SimulationBuildStatus # Problem Types -export DecisionProblem -export EmulationProblem -export DefaultDecisionProblem -export DefaultEmulationProblem +export AbstractOptimizationProblem # Settings and Data Types export Settings @@ -648,7 +649,7 @@ include("bilinear_approximations/nmdt.jl") # (which defines VariableValueParameter and FixValueParameter) include("common_models/add_param_container.jl") -include("operation/operation_model_interface.jl") +include("operation/optimization_model_interface.jl") include("operation/decision_model_store.jl") include("operation/emulation_model_store.jl") include("operation/store_common.jl") diff --git a/src/core/operation_model_abstract_types.jl b/src/core/operation_model_abstract_types.jl index fd98bbec..8ed49f89 100644 --- a/src/core/operation_model_abstract_types.jl +++ b/src/core/operation_model_abstract_types.jl @@ -1,75 +1,19 @@ """ -Abstract type for Decision Model and Emulation Model. OperationModel structs are parameterized with DecisionProblem or Emulation Problem structs -""" -abstract type OperationModel end - -#TODO: Document the required interfaces for custom types -""" -Abstract type for Decision Problems +Abstract type for any optimization problem. Concrete subtypes are provided by +downstream domain libraries (e.g. PowerOperationsModels). `DecisionModel{M}` and +`EmulationModel{M}` are parameterized over this type so any optimization problem +can be wrapped without IOM knowing its domain. # Example import InfrastructureOptimizationModels -const POM = InfrastructureOptimizationModels -struct MyCustomProblem <: POM.DecisionProblem +const IOM = InfrastructureOptimizationModels +struct MyCustomProblem <: IOM.AbstractOptimizationProblem end """ -abstract type DecisionProblem end +abstract type AbstractOptimizationProblem end """ -Abstract type for Emulation Problems - -# Example - -import InfrastructureOptimizationModels -const POM = InfrastructureOptimizationModels -struct MyCustomEmulator <: POM.EmulationProblem +Abstract supertype for `DecisionModel` and `EmulationModel`. Concrete subtypes +are parameterized with an `AbstractOptimizationProblem` subtype. """ -abstract type EmulationProblem end - -################################################################################# -# Simulation Models Container -# Holds references to models in a simulation -# Used for display/printing purposes -struct SimulationModels - decision_models::Vector{<:OperationModel} - emulation_model::Union{Nothing, OperationModel} -end - -function SimulationModels(; - decision_models, - emulation_model::Union{Nothing, OperationModel} = nothing, -) - return SimulationModels(decision_models, emulation_model) -end - -################################################################################# -# Simulation Sequence -# Holds the execution sequence information for a simulation -# This is a placeholder struct - concrete implementation in PowerSimulations -struct SimulationSequence - executions_by_model::Dict - horizons::Dict - intervals::Dict - SimulationSequence() = new(Dict(), Dict(), Dict()) -end - -# Placeholder accessor function for simulation sequence -get_step_resolution(::SimulationSequence) = Dates.Hour(1) - -################################################################################# -# Simulation Type -# Abstract type for simulation objects -# Concrete implementation should be in PowerSimulations -abstract type Simulation end - -################################################################################# -# Simulation Outputs Type -# Abstract type for simulation outputs -# Concrete implementation should be in PowerSimulations -abstract type SimulationOutputs end - -################################################################################# -# Simulation Problem Outputs Type -# Abstract type for individual problem outputs within a simulation -# Concrete implementation should be in PowerSimulations -abstract type SimulationProblemOutputs end +abstract type AbstractOptimizationModel end diff --git a/src/core/optimization_problem_outputs.jl b/src/core/optimization_problem_outputs.jl index 2db94f1d..2793fbd5 100644 --- a/src/core/optimization_problem_outputs.jl +++ b/src/core/optimization_problem_outputs.jl @@ -356,7 +356,7 @@ const _PROBLEM_OUTPUTS_FILENAME = "problem_outputs.bin" Serialize the outputs to a binary file. It is recommended that `directory` be the directory that contains a serialized -OperationModel. That will allow automatic deserialization of the PowerSystems.System. +AbstractOptimizationModel. That will allow automatic deserialization of the PowerSystems.System. The `OptimizationProblemOutputs` instance can be deserialized with `OptimizationProblemOutputs(directory)`. """ function serialize_outputs(res::OptimizationProblemOutputs, directory::AbstractString) @@ -1050,7 +1050,7 @@ Save the optimizer statistics to CSV or JSON # Arguments - - `res::Union{OptimizationProblemOutputs, SimulationProblmeOutputs`: Outputs + - `res::OptimizationProblemOutputs`: Outputs - `directory::AbstractString` : target directory - `format = "CSV"` : can be "csv" or "json """ diff --git a/src/operation/decision_model.jl b/src/operation/decision_model.jl index 5585c422..548c7cca 100644 --- a/src/operation/decision_model.jl +++ b/src/operation/decision_model.jl @@ -18,18 +18,7 @@ function get_deterministic_time_series_type(sys::IS.InfrastructureSystemsContain end end -""" -Abstract type for models that use default InfrastructureOptimizationModels formulations. For custom decision problems - use DecisionProblem as the super type. -""" -abstract type DefaultDecisionProblem <: DecisionProblem end - -""" -Generic InfrastructureOptimizationModels Operation Problem Type for unspecified models -""" -struct GenericOpProblem <: DefaultDecisionProblem end - -mutable struct DecisionModel{M <: DecisionProblem} <: OperationModel +mutable struct DecisionModel{M <: AbstractOptimizationProblem} <: AbstractOptimizationModel name::Symbol template::AbstractProblemTemplate sys::IS.InfrastructureSystemsContainer @@ -43,44 +32,14 @@ end DecisionModel{M}( template::AbstractProblemTemplate, sys::IS.InfrastructureSystemsContainer, + settings::Settings, jump_model::Union{Nothing, JuMP.Model}=nothing; - kwargs...) where {M<:DecisionProblem} - -Build the optimization problem of type M with the specific system and template. + name = nothing) where {M<:AbstractOptimizationProblem} -# Arguments - - - `::Type{M} where M<:DecisionProblem`: The abstract operation model type - - `template::AbstractProblemTemplate`: The model reference made up of transmission, devices, branches, and services. - - `sys::IS.InfrastructureSystemsContainer`: the system created using Power Systems - - `jump_model::Union{Nothing, JuMP.Model}`: Enables passing a custom JuMP model. Use with care - - `name = nothing`: name of model, string or symbol; defaults to the type of template converted to a symbol. - - `optimizer::Union{Nothing,MOI.OptimizerWithAttributes} = nothing` : The optimizer does - not get serialized. Callers should pass whatever they passed to the original problem. - - `horizon::Dates.Period = UNSET_HORIZON`: Manually specify the length of the forecast Horizon - - `resolution::Dates.Period = UNSET_RESOLUTION`: Manually specify the model's resolution - - `warm_start::Bool = true`: True will use the current operation point in the system to initialize variable values. False initializes all variables to zero. Default is true - - `check_components::Bool = true`: True to check the components valid fields when building - - `initialize_model::Bool = true`: Option to decide to initialize the model or not. - - `initialization_file::String = ""`: This allows to pass pre-existing initialization values to avoid the solution of an optimization problem to find feasible initial conditions. - - `deserialize_initial_conditions::Bool = false`: Option to deserialize conditions - - `export_pwl_vars::Bool = false`: True to export all the pwl intermediate variables. It can slow down significantly the build and solve time. - - `allow_fails::Bool = false`: True to allow the simulation to continue even if the optimization step fails. Use with care. - - `optimizer_solve_log_print::Bool = false`: Uses JuMP.unset_silent() to print the optimizer's log. By default all solvers are set to MOI.Silent() - - `detailed_optimizer_stats::Bool = false`: True to save detailed optimizer stats log. - - `calculate_conflict::Bool = false`: True to use solver to calculate conflicts for infeasible problems. Only specific solvers are able to calculate conflicts. - - `direct_mode_optimizer::Bool = false`: True to use the solver in direct mode. Creates a [JuMP.direct_model](https://jump.dev/JuMP.jl/dev/reference/models/#JuMP.direct_model). - - `store_variable_names::Bool = false`: to store variable names in optimization model. Decreases the build times. - - `rebuild_model::Bool = false`: It will force the rebuild of the underlying JuMP model with each call to update the model. It increases solution times, use only if the model can't be updated in memory. - - `initial_time::Dates.DateTime = UNSET_INI_TIME`: Initial Time for the model solve. - - `time_series_cache_size::Int = IS.TIME_SERIES_CACHE_SIZE_BYTES`: Size in bytes to cache for each time array. Default is 1 MiB. Set to 0 to disable. - -# Example - -```julia -template = ProblemTemplate(CopperPlatePowerModel, devices, branches, services) -OpModel = DecisionModel(MockOperationProblem, template, system) -``` +Settings-taking constructor — builds the `OptimizationContainer`, finalizes +the template, and validates time series. Both `finalize_template!` and +`validate_time_series!` are extension points: downstream packages provide +methods for their concrete `AbstractProblemTemplate` and model types. """ function DecisionModel{M}( template::AbstractProblemTemplate, @@ -88,7 +47,7 @@ function DecisionModel{M}( settings::Settings, jump_model::Union{Nothing, JuMP.Model} = nothing; name = nothing, -) where {M <: DecisionProblem} +) where {M <: AbstractOptimizationProblem} if name === nothing name = nameof(M) elseif name isa String @@ -115,6 +74,17 @@ function DecisionModel{M}( return model end +""" + DecisionModel{M}( + template::AbstractProblemTemplate, + sys::IS.InfrastructureSystemsContainer, + jump_model::Union{Nothing, JuMP.Model}=nothing; + kwargs...) where {M<:AbstractOptimizationProblem} + +Kwargs constructor — accepts horizon/resolution/interval and all the standard +solver/model settings, builds a `Settings`, and delegates to the +settings-taking constructor. +""" function DecisionModel{M}( template::AbstractProblemTemplate, sys::IS.InfrastructureSystemsContainer, @@ -141,7 +111,7 @@ function DecisionModel{M}( check_numerical_bounds = true, initial_time = UNSET_INI_TIME, time_series_cache_size::Int = IS.TIME_SERIES_CACHE_SIZE_BYTES, -) where {M <: DecisionProblem} +) where {M <: AbstractOptimizationProblem} settings = Settings( sys; horizon = horizon, @@ -170,21 +140,10 @@ function DecisionModel{M}( end """ -Build the optimization problem of type M with the specific system and template - -# Arguments + DecisionModel(::Type{M}, template, sys, jump_model=nothing; kwargs...) + where {M <: AbstractOptimizationProblem} - - `::Type{M} where M<:DecisionProblem`: The abstract operation model type - - `template::AbstractProblemTemplate`: The model reference made up of transmission, devices, branches, and services. - - `sys::IS.InfrastructureSystemsContainer`: the system created using Power Systems - - `jump_model::Union{Nothing, JuMP.Model}` = nothing: Enables passing a custom JuMP model. Use with care. - -# Example - -```julia -template = ProblemTemplate(CopperPlatePowerModel, devices, branches, services) -problem = DecisionModel(MyOpProblemType, template, system, optimizer) -``` +Type-first dispatch variant. """ function DecisionModel( ::Type{M}, @@ -192,39 +151,16 @@ function DecisionModel( sys::IS.InfrastructureSystemsContainer, jump_model::Union{Nothing, JuMP.Model} = nothing; kwargs..., -) where {M <: DecisionProblem} +) where {M <: AbstractOptimizationProblem} return DecisionModel{M}(template, sys, jump_model; kwargs...) end -function DecisionModel( - template::AbstractProblemTemplate, - sys::IS.InfrastructureSystemsContainer, - jump_model::Union{Nothing, JuMP.Model} = nothing; - kwargs..., -) - return DecisionModel{GenericOpProblem}(template, sys, jump_model; kwargs...) -end - -function DecisionModel{M}( - sys::IS.InfrastructureSystemsContainer, - jump_model::Union{Nothing, JuMP.Model} = nothing; - kwargs..., -) where {M <: DefaultDecisionProblem} - IS.ArgumentError( - "DefaultDecisionProblem subtypes require a template. Use DecisionModel subtyping instead.", - ) -end - -get_problem_type(::DecisionModel{M}) where {M <: DecisionProblem} = M +get_problem_type(::DecisionModel{M}) where {M <: AbstractOptimizationProblem} = M -function validate_template(::DecisionModel{M}) where {M <: DecisionProblem} +function validate_template(::DecisionModel{M}) where {M <: AbstractOptimizationProblem} error("validate_template is not implemented for DecisionModel{$M}") end -function validate_template(::DecisionModel{<:DefaultDecisionProblem}) - return nothing -end - # Probably could be more efficient by storing the info in the internal function get_current_time(model::DecisionModel) execution_count = get_execution_count(model) @@ -260,63 +196,4 @@ function init_model_store_params!(model::DecisionModel) return end -function validate_time_series!(model::DecisionModel{<:DefaultDecisionProblem}) - sys = get_system(model) - settings = get_settings(model) - available_resolutions = get_time_series_resolutions(sys) - - if get_resolution(settings) == UNSET_RESOLUTION && length(available_resolutions) != 1 - throw( - IS.ConflictingInputsError( - "Data contains multiple resolutions, the resolution keyword argument must be added to the Model. Time Series Resolutions: $(available_resolutions)", - ), - ) - elseif get_resolution(settings) != UNSET_RESOLUTION && length(available_resolutions) > 1 - if get_resolution(settings) ∉ available_resolutions - throw( - IS.ConflictingInputsError( - "Resolution $(get_resolution(settings)) is not available in the system data. Time Series Resolutions: $(available_resolutions)", - ), - ) - end - else - set_resolution!(settings, first(available_resolutions)) - end - - model_interval = get_interval(settings) - available_intervals = get_forecast_intervals(sys) - if model_interval == UNSET_INTERVAL && length(available_intervals) > 1 - throw( - IS.ConflictingInputsError( - "The system contains multiple forecast intervals $(available_intervals). " * - "The `interval` keyword argument must be provided to the DecisionModel constructor " * - "to select which interval to use.", - ), - ) - elseif model_interval != UNSET_INTERVAL && !isempty(available_intervals) - if model_interval ∉ available_intervals - throw( - IS.ConflictingInputsError( - "Interval $(Dates.canonicalize(model_interval)) is not available in the system data. " * - "Available forecast intervals: $(available_intervals)", - ), - ) - end - end - if get_horizon(settings) == UNSET_HORIZON - set_horizon!( - settings, - get_forecast_horizon(sys; interval = _to_is_interval(model_interval)), - ) - end - - counts = get_time_series_counts(sys) - if counts.forecast_count < 1 - error( - "The system does not contain forecast data. A DecisionModel can't be built.", - ) - end - return -end - get_horizon(model::DecisionModel) = get_horizon(get_settings(model)) diff --git a/src/operation/emulation_model.jl b/src/operation/emulation_model.jl index 1898e5b0..3e2eb964 100644 --- a/src/operation/emulation_model.jl +++ b/src/operation/emulation_model.jl @@ -1,57 +1,4 @@ -# FIXME untested. Moved to accommodate a few methods dispatching on EmulationModelStore, -# but not run in the tests and not yet refactored for IOM-POM split. -""" -Abstract type for models that use default InfrastructureOptimizationModels formulations. For custom emulation problems - use EmulationProblem as the super type. -""" -abstract type DefaultEmulationProblem <: EmulationProblem end - -""" -Default InfrastructureOptimizationModels Emulation Problem Type for unspecified problems -""" -struct GenericEmulationProblem <: DefaultEmulationProblem end - -""" - EmulationModel{M}( - template::AbstractProblemTemplate, - sys::IS.InfrastructureSystemsContainer, - jump_model::Union{Nothing, JuMP.Model}=nothing; - kwargs...) where {M<:EmulationProblem} - -Build the optimization problem of type M with the specific system and template. - -# Arguments - - - `::Type{M} where M<:EmulationProblem`: The abstract Emulation model type - - `template::AbstractProblemTemplate`: The model reference made up of transmission, devices, branches, and services. - - `sys::IS.InfrastructureSystemsContainer`: the system created using Power Systems - - `jump_model::Union{Nothing, JuMP.Model}`: Enables passing a custom JuMP model. Use with care - - `name = nothing`: name of model, string or symbol; defaults to the type of template converted to a symbol. - - `optimizer::Union{Nothing,MOI.OptimizerWithAttributes} = nothing` : The optimizer does - not get serialized. Callers should pass whatever they passed to the original problem. - - `warm_start::Bool = true`: True will use the current operation point in the system to initialize variable values. False initializes all variables to zero. Default is true - - `initialize_model::Bool = true`: Option to decide to initialize the model or not. - - `initialization_file::String = ""`: This allows to pass pre-existing initialization values to avoid the solution of an optimization problem to find feasible initial conditions. - - `deserialize_initial_conditions::Bool = false`: Option to deserialize conditions - - `export_pwl_vars::Bool = false`: True to export all the pwl intermediate variables. It can slow down significantly the build and solve time. - - `allow_fails::Bool = false`: True to allow the simulation to continue even if the optimization step fails. Use with care. - - `calculate_conflict::Bool = false`: True to use solver to calculate conflicts for infeasible problems. Only specific solvers are able to calculate conflicts. - - `optimizer_solve_log_print::Bool = false`: Uses JuMP.unset_silent() to print the optimizer's log. By default all solvers are set to MOI.Silent() - - `detailed_optimizer_stats::Bool = false`: True to save detailed optimizer stats log. - - `direct_mode_optimizer::Bool = false`: True to use the solver in direct mode. Creates a [JuMP.direct_model](https://jump.dev/JuMP.jl/dev/reference/models/#JuMP.direct_model). - - `store_variable_names::Bool = false`: True to store variable names in optimization model. - - `rebuild_model::Bool = false`: It will force the rebuild of the underlying JuMP model with each call to update the model. It increases solution times, use only if the model can't be updated in memory. - - `initial_time::Dates.DateTime = UNSET_INI_TIME`: Initial Time for the model solve. - - `time_series_cache_size::Int = IS.TIME_SERIES_CACHE_SIZE_BYTES`: Size in bytes to cache for each time array. Default is 1 MiB. Set to 0 to disable. - -# Example - -```julia -template = ProblemTemplate(CopperPlatePowerModel, devices, branches, services) -OpModel = EmulationModel(MockEmulationProblem, template, system) -``` -""" -mutable struct EmulationModel{M <: EmulationProblem} <: OperationModel +mutable struct EmulationModel{M <: AbstractOptimizationProblem} <: AbstractOptimizationModel name::Symbol template::AbstractProblemTemplate sys::IS.InfrastructureSystemsContainer @@ -59,35 +6,62 @@ mutable struct EmulationModel{M <: EmulationProblem} <: OperationModel simulation_info::SimulationInfo store::EmulationModelStore # might be extended to other stores for simulation ext::Dict{String, Any} +end - function EmulationModel{M}( +""" + EmulationModel{M}( template::AbstractProblemTemplate, sys::IS.InfrastructureSystemsContainer, settings::Settings, - jump_model::Union{Nothing, JuMP.Model} = nothing; - name = nothing, - ) where {M <: EmulationProblem} - if name === nothing - name = nameof(M) - elseif name isa String - name = Symbol(name) - end - finalize_template!(template, sys) - internal = ModelInternal( - OptimizationContainer(sys, settings, jump_model, IS.SingleTimeSeries), - ) - new{M}( - name, - template, - sys, - internal, - SimulationInfo(), - EmulationModelStore(), - Dict{String, Any}(), - ) + jump_model::Union{Nothing, JuMP.Model}=nothing; + name = nothing) where {M<:AbstractOptimizationProblem} + +Settings-taking constructor — finalizes the template, builds the +`OptimizationContainer` with single-time-series data, and validates time +series. Both `finalize_template!` and `validate_time_series!` are extension +points implemented by downstream packages for their concrete template/model +types. +""" +function EmulationModel{M}( + template::AbstractProblemTemplate, + sys::IS.InfrastructureSystemsContainer, + settings::Settings, + jump_model::Union{Nothing, JuMP.Model} = nothing; + name = nothing, +) where {M <: AbstractOptimizationProblem} + if name === nothing + name = nameof(M) + elseif name isa String + name = Symbol(name) end + finalize_template!(template, sys) + internal = ModelInternal( + OptimizationContainer(sys, settings, jump_model, IS.SingleTimeSeries), + ) + model = EmulationModel{M}( + name, + template, + sys, + internal, + SimulationInfo(), + EmulationModelStore(), + Dict{String, Any}(), + ) + validate_time_series!(model) + return model end +""" + EmulationModel{M}( + template::AbstractProblemTemplate, + sys::IS.InfrastructureSystemsContainer, + jump_model::Union{Nothing, JuMP.Model}=nothing; + kwargs...) where {M<:AbstractOptimizationProblem} + +Kwargs constructor — builds a `Settings` (with `horizon == resolution`, since +emulation models solve one step at a time) and delegates to the Settings-taking +constructor. +""" function EmulationModel{M}( template::AbstractProblemTemplate, sys::IS.InfrastructureSystemsContainer, @@ -110,7 +84,7 @@ function EmulationModel{M}( rebuild_model = false, initial_time = UNSET_INI_TIME, time_series_cache_size::Int = IS.TIME_SERIES_CACHE_SIZE_BYTES, -) where {M <: EmulationProblem} +) where {M <: AbstractOptimizationProblem} settings = Settings( sys; initial_time = initial_time, @@ -132,28 +106,14 @@ function EmulationModel{M}( horizon = resolution, resolution = resolution, ) - model = EmulationModel{M}(template, sys, settings, jump_model; name = name) - validate_time_series!(model) - return model + return EmulationModel{M}(template, sys, settings, jump_model; name = name) end """ -Build the optimization problem of type M with the specific system and template - -# Arguments + EmulationModel(::Type{M}, template, sys, jump_model=nothing; kwargs...) + where {M <: AbstractOptimizationProblem} - - `::Type{M} where M<:EmulationProblem`: The abstract Emulation model type - - `template::AbstractProblemTemplate`: The model reference made up of transmission, devices, - branches, and services. - - `sys::IS.InfrastructureSystemsContainer`: the system created using Power Systems - - `jump_model::Union{Nothing, JuMP.Model}`: Enables passing a custom JuMP model. Use with care - -# Example - -```julia -template = ProblemTemplate(CopperPlatePowerModel, devices, branches, services) -problem = EmulationModel(MyEmProblemType, template, system, optimizer) -``` +Type-first dispatch variant. """ function EmulationModel( ::Type{M}, @@ -161,90 +121,16 @@ function EmulationModel( sys::IS.InfrastructureSystemsContainer, jump_model::Union{Nothing, JuMP.Model} = nothing; kwargs..., -) where {M <: EmulationProblem} - return EmulationModel{M}(template, sys, jump_model; kwargs...) -end - -function EmulationModel( - template::AbstractProblemTemplate, - sys::IS.InfrastructureSystemsContainer, - jump_model::Union{Nothing, JuMP.Model} = nothing; - kwargs..., -) - return EmulationModel{GenericEmulationProblem}(template, sys, jump_model; kwargs...) -end - -""" -Builds an empty emulation model. This constructor is used for the implementation of custom -emulation models that do not require a template. - -# Arguments - - - `::Type{M} where M<:EmulationProblem`: The abstract operation model type - - `sys::IS.InfrastructureSystemsContainer`: the system created using Power Systems - - `jump_model::Union{Nothing, JuMP.Model}` = nothing: Enables passing a custom JuMP model. Use with care. - -# Example - -```julia -problem = EmulationModel(system, optimizer) -``` -""" -function EmulationModel{M}( - sys::IS.InfrastructureSystemsContainer, - jump_model::Union{Nothing, JuMP.Model} = nothing; - kwargs..., -) where {M <: EmulationProblem} +) where {M <: AbstractOptimizationProblem} return EmulationModel{M}(template, sys, jump_model; kwargs...) end -get_problem_type(::EmulationModel{M}) where {M <: EmulationProblem} = M +get_problem_type(::EmulationModel{M}) where {M <: AbstractOptimizationProblem} = M -function validate_template(::EmulationModel{M}) where {M <: EmulationProblem} +function validate_template(::EmulationModel{M}) where {M <: AbstractOptimizationProblem} error("validate_template is not implemented for EmulationModel{$M}") end -function validate_template(::EmulationModel{<:DefaultEmulationProblem}) - return nothing -end - -function validate_time_series!(model::EmulationModel{<:DefaultEmulationProblem}) - sys = get_system(model) - settings = get_settings(model) - available_resolutions = get_time_series_resolutions(sys) - - if get_resolution(settings) == UNSET_RESOLUTION && length(available_resolutions) != 1 - throw( - IS.ConflictingInputsError( - "Data contains multiple resolutions, the resolution keyword argument must be added to the Model. Time Series Resolutions: $(available_resolutions)", - ), - ) - elseif get_resolution(settings) != UNSET_RESOLUTION && length(available_resolutions) > 1 - if get_resolution(settings) ∉ available_resolutions - throw( - IS.ConflictingInputsError( - "Resolution $(get_resolution(settings)) is not available in the system data. Time Series Resolutions: $(available_resolutions)", - ), - ) - end - else - set_resolution!(settings, first(available_resolutions)) - end - - if get_horizon(settings) == UNSET_HORIZON - # Emulation Models Only solve one "step" so Horizon and Resolution must match - set_horizon!(settings, get_resolution(settings)) - end - - counts = get_time_series_counts(sys) - if counts.static_time_series_count < 1 - error( - "The system does not contain Static Time Series data. A EmulationModel can't be built.", - ) - end - return -end - function get_current_time(model::EmulationModel) execution_count = get_execution_count(model) initial_time = get_initial_time(model) @@ -273,82 +159,3 @@ function init_model_store_params!(model::EmulationModel) ) return end - -function update_parameters!( - model::EmulationModel, - store::EmulationModelStore{InMemoryDataset}, -) - update_parameters!(model, store.data_container) - return -end - -function update_parameters!(model::EmulationModel, data::DatasetContainer{InMemoryDataset}) - cost_function_unsynch(get_optimization_container(model)) - for key in keys(get_parameters(model)) - update_parameter_values!(model, key, data) - end - if !is_synchronized(model) - update_objective_function!(get_optimization_container(model)) - obj_func = get_objective_expression(get_optimization_container(model)) - set_synchronized_status!(obj_func, true) - end - return -end - -function update_model!( - model::EmulationModel, - source::EmulationModelStore{InMemoryDataset}, - ini_cond_chronology, -) - TimerOutputs.@timeit RUN_SIMULATION_TIMER "Parameter Updates" begin - update_parameters!(model, source) - end - TimerOutputs.@timeit RUN_SIMULATION_TIMER "Ini Cond Updates" begin - update_initial_conditions!(model, source, ini_cond_chronology) - end - return -end - -""" -Standalone update for EmulationModel (non-simulation context). -Updates parameters and initial conditions from the model's own store. -""" -function update_model!(model::EmulationModel) - source = get_store(model) - TimerOutputs.@timeit RUN_OPERATION_MODEL_TIMER "Parameter Updates" begin - update_parameters!(model, source) - end - TimerOutputs.@timeit RUN_OPERATION_MODEL_TIMER "Ini Cond Updates" begin - for key in keys(get_initial_conditions(model)) - update_initial_conditions!(model, key, source) - end - end - return -end - -""" -Update parameter function an OperationModel -""" -function update_parameter_values!( - model::EmulationModel, - key::ParameterKey{T, U}, - input::DatasetContainer{InMemoryDataset}, -) where {T <: ParameterType, U <: IS.InfrastructureSystemsComponent} - # Enable again for detailed debugging - # TimerOutputs.@timeit RUN_SIMULATION_TIMER "$T $U Parameter Update" begin - optimization_container = get_optimization_container(model) - # FIXME: This parameter update logic belongs in POM or PSI, not IOM. - # Move this function (and the surrounding update chain) once EmulationModel - # lifecycle code is fully migrated. - update_container_parameter_values!(optimization_container, model, key, input) - parameter_attributes = get_parameter_attributes(optimization_container, key) - IS.@record :execution ParameterUpdateEvent( - T, - U, - "event", # parameter_attributes, - get_current_timestamp(model), - get_name(model), - ) - #end - return -end diff --git a/src/operation/initial_conditions_update_in_memory_store.jl b/src/operation/initial_conditions_update_in_memory_store.jl index 0b00970d..7188264a 100644 --- a/src/operation/initial_conditions_update_in_memory_store.jl +++ b/src/operation/initial_conditions_update_in_memory_store.jl @@ -8,7 +8,7 @@ Update initial conditions for a specific key from the model store. Dispatches to the per-IC-type `update_initial_conditions!(ics, store, resolution)` method. """ function update_initial_conditions!( - model::OperationModel, + model::AbstractOptimizationModel, key::InitialConditionKey{T, U}, source, ) where {T <: InitialConditionType, U <: IS.InfrastructureSystemsComponent} diff --git a/src/operation/model_numerical_analysis_utils.jl b/src/operation/model_numerical_analysis_utils.jl index b27324a9..835befcd 100644 --- a/src/operation/model_numerical_analysis_utils.jl +++ b/src/operation/model_numerical_analysis_utils.jl @@ -103,7 +103,7 @@ update_numerical_bounds(::NumericalBounds, func, idx) = nothing update_coefficient_bounds(::ConstraintBounds, func, idx) = nothing update_rhs_bounds(::ConstraintBounds, func, idx) = nothing -function get_constraint_numerical_bounds(model::OperationModel) +function get_constraint_numerical_bounds(model::AbstractOptimizationModel) if !is_built(model) error("Model not built, can't calculate constraint numerical bounds") end @@ -129,7 +129,7 @@ function get_constraint_numerical_bounds(model::OperationModel) return bounds end -function get_variable_numerical_bounds(model::OperationModel) +function get_variable_numerical_bounds(model::AbstractOptimizationModel) if !is_built(model) error("Model not built, can't calculate variable numerical bounds") end diff --git a/src/operation/optimization_debugging.jl b/src/operation/optimization_debugging.jl index b67d7b1e..b9808aec 100644 --- a/src/operation/optimization_debugging.jl +++ b/src/operation/optimization_debugging.jl @@ -1,7 +1,7 @@ """ Each Tuple corresponds to (con_name, internal_index, moi_index) """ -function get_all_constraint_index(model::OperationModel) +function get_all_constraint_index(model::AbstractOptimizationModel) con_index = Vector{Tuple{ConstraintKey, Int, Int}}() container = get_optimization_container(model) for (key, value) in get_constraints(container) @@ -16,12 +16,12 @@ end """ Each Tuple corresponds to (con_name, internal_index, moi_index) """ -function get_all_variable_index(model::OperationModel) +function get_all_variable_index(model::AbstractOptimizationModel) var_keys = get_all_variable_keys(model) return [(encode_key(v[1]), v[2], v[3]) for v in var_keys] end -function get_all_variable_keys(model::OperationModel) +function get_all_variable_keys(model::AbstractOptimizationModel) var_index = Vector{Tuple{VariableKey, Int, Int}}() container = get_optimization_container(model) for (key, value) in get_variables(container) @@ -33,7 +33,7 @@ function get_all_variable_keys(model::OperationModel) return var_index end -function get_constraint_index(model::OperationModel, index::Int) +function get_constraint_index(model::AbstractOptimizationModel, index::Int) container = get_optimization_container(model) constraints = get_constraints(container) for i in get_all_constraint_index(model) @@ -45,7 +45,7 @@ function get_constraint_index(model::OperationModel, index::Int) return end -function get_variable_index(model::OperationModel, index::Int) +function get_variable_index(model::AbstractOptimizationModel, index::Int) container = get_optimization_container(model) variables = get_variables(container) for i in get_all_variable_keys(model) @@ -57,7 +57,7 @@ function get_variable_index(model::OperationModel, index::Int) return end -function get_detailed_constraint_numerical_bounds(model::OperationModel) +function get_detailed_constraint_numerical_bounds(model::AbstractOptimizationModel) if !is_built(model) error("Model not built, can't calculate constraint numerical bounds") end @@ -85,7 +85,7 @@ function get_detailed_constraint_numerical_bounds(model::OperationModel) return constraint_bounds end -function get_detailed_variable_numerical_bounds(model::OperationModel) +function get_detailed_variable_numerical_bounds(model::AbstractOptimizationModel) if !is_built(model) error("Model not built, can't calculate variable numerical bounds") end diff --git a/src/operation/operation_model_interface.jl b/src/operation/optimization_model_interface.jl similarity index 58% rename from src/operation/operation_model_interface.jl rename to src/operation/optimization_model_interface.jl index 20f50e5c..a3126feb 100644 --- a/src/operation/operation_model_interface.jl +++ b/src/operation/optimization_model_interface.jl @@ -1,29 +1,30 @@ -# Default implementations of getter/setter functions for OperationModel. -is_built(model::OperationModel) = +# Default implementations of getter/setter functions for AbstractOptimizationModel. +is_built(model::AbstractOptimizationModel) = get_status(get_internal(model)) == ModelBuildStatus.BUILT -isempty(model::OperationModel) = +isempty(model::AbstractOptimizationModel) = get_status(get_internal(model)) == ModelBuildStatus.EMPTY -warm_start_enabled(model::OperationModel) = +warm_start_enabled(model::AbstractOptimizationModel) = get_warm_start(get_optimization_container(model).settings) -built_for_recurrent_solves(model::OperationModel) = +built_for_recurrent_solves(model::AbstractOptimizationModel) = get_optimization_container(model).built_for_recurrent_solves -get_constraints(model::OperationModel) = +get_constraints(model::AbstractOptimizationModel) = get_constraints(get_internal(model)) -get_execution_count(model::OperationModel) = +get_execution_count(model::AbstractOptimizationModel) = get_execution_count(get_internal(model)) -get_executions(model::OperationModel) = get_executions(get_internal(model)) -get_initial_time(model::OperationModel) = get_initial_time(get_settings(model)) -get_internal(model::OperationModel) = model.internal +get_executions(model::AbstractOptimizationModel) = get_executions(get_internal(model)) +get_initial_time(model::AbstractOptimizationModel) = get_initial_time(get_settings(model)) +get_internal(model::AbstractOptimizationModel) = model.internal -function get_jump_model(model::OperationModel) +function get_jump_model(model::AbstractOptimizationModel) return get_jump_model(get_container(get_internal(model))) end -get_name(model::OperationModel) = model.name -get_store(model::OperationModel) = model.store -is_synchronized(model::OperationModel) = is_synchronized(get_optimization_container(model)) +get_name(model::AbstractOptimizationModel) = model.name +get_store(model::AbstractOptimizationModel) = model.store +is_synchronized(model::AbstractOptimizationModel) = + is_synchronized(get_optimization_container(model)) -function get_rebuild_model(model::OperationModel) +function get_rebuild_model(model::AbstractOptimizationModel) sim_info = model.simulation_info if sim_info === nothing error("Model not part of a simulation") @@ -31,85 +32,89 @@ function get_rebuild_model(model::OperationModel) return get_rebuild_model(get_optimization_container(model).settings) end -function get_optimization_container(model::OperationModel) +function get_optimization_container(model::AbstractOptimizationModel) return get_optimization_container(get_internal(model)) end -function get_resolution(model::OperationModel) +function get_resolution(model::AbstractOptimizationModel) resolution = get_resolution(get_settings(model)) return resolution end -get_problem_base_power(model::OperationModel) = get_base_power(model.sys) -get_settings(model::OperationModel) = get_optimization_container(model).settings +get_problem_base_power(model::AbstractOptimizationModel) = get_base_power(model.sys) +get_settings(model::AbstractOptimizationModel) = get_optimization_container(model).settings -get_optimizer_stats(model::OperationModel) = +get_optimizer_stats(model::AbstractOptimizationModel) = # This deepcopy is important because the optimization container is overwritten # at each solve in a simulation. deepcopy(get_optimizer_stats(get_optimization_container(model))) -get_simulation_info(model::OperationModel) = model.simulation_info +get_simulation_info(model::AbstractOptimizationModel) = model.simulation_info -function get_simulation_number(model::OperationModel) +function get_simulation_number(model::AbstractOptimizationModel) sim_info = get_simulation_info(model) isnothing(sim_info) && error("Model is not part of a simulation. Cannot get simulation number.") return get_number(sim_info) end -function set_simulation_number!(model::OperationModel, val) +function set_simulation_number!(model::AbstractOptimizationModel, val) sim_info = get_simulation_info(model) isnothing(sim_info) && error("Model is not part of a simulation. Cannot set simulation number.") return set_number!(sim_info, val) end -function get_sequence_uuid(model::OperationModel) +function get_sequence_uuid(model::AbstractOptimizationModel) sim_info = get_simulation_info(model) isnothing(sim_info) && error("Model is not part of a simulation. Cannot get sequence UUID.") return get_sequence_uuid(sim_info) end -function set_sequence_uuid!(model::OperationModel, val) +function set_sequence_uuid!(model::AbstractOptimizationModel, val) sim_info = get_simulation_info(model) isnothing(sim_info) && error("Model is not part of a simulation. Cannot set sequence UUID.") return set_sequence_uuid!(sim_info, val) end -get_status(model::OperationModel) = get_status(get_internal(model)) -get_system(model::OperationModel) = model.sys -get_template(model::OperationModel) = model.template -get_log_file(model::OperationModel) = joinpath(get_output_dir(model), PROBLEM_LOG_FILENAME) -get_store_params(model::OperationModel) = +get_status(model::AbstractOptimizationModel) = get_status(get_internal(model)) +get_system(model::AbstractOptimizationModel) = model.sys +get_template(model::AbstractOptimizationModel) = model.template +get_log_file(model::AbstractOptimizationModel) = + joinpath(get_output_dir(model), PROBLEM_LOG_FILENAME) +get_store_params(model::AbstractOptimizationModel) = get_store_params(get_internal(model)) -get_output_dir(model::OperationModel) = get_output_dir(get_internal(model)) -get_initial_conditions_file(model::OperationModel) = +get_output_dir(model::AbstractOptimizationModel) = get_output_dir(get_internal(model)) +get_initial_conditions_file(model::AbstractOptimizationModel) = joinpath(get_output_dir(model), "initial_conditions.bin") -get_recorder_dir(model::OperationModel) = +get_recorder_dir(model::AbstractOptimizationModel) = joinpath(get_output_dir(model), "recorder") -get_variables(model::OperationModel) = get_variables(get_optimization_container(model)) -get_parameters(model::OperationModel) = get_parameters(get_optimization_container(model)) -get_duals(model::OperationModel) = get_duals(get_optimization_container(model)) -get_initial_conditions(model::OperationModel) = +get_variables(model::AbstractOptimizationModel) = + get_variables(get_optimization_container(model)) +get_parameters(model::AbstractOptimizationModel) = + get_parameters(get_optimization_container(model)) +get_duals(model::AbstractOptimizationModel) = get_duals(get_optimization_container(model)) +get_initial_conditions(model::AbstractOptimizationModel) = get_initial_conditions(get_optimization_container(model)) -get_interval(model::OperationModel) = get_store_params(model).interval +get_interval(model::AbstractOptimizationModel) = get_store_params(model).interval -get_run_status(model::OperationModel) = get_run_status(get_simulation_info(model)) +get_run_status(model::AbstractOptimizationModel) = + get_run_status(get_simulation_info(model)) -set_run_status!(model::OperationModel, status) = +set_run_status!(model::AbstractOptimizationModel, status) = set_run_status!(get_simulation_info(model), status) -get_time_series_cache(model::OperationModel) = +get_time_series_cache(model::AbstractOptimizationModel) = get_time_series_cache(get_internal(model)) -empty_time_series_cache!(x::OperationModel) = empty!(get_time_series_cache(x)) +empty_time_series_cache!(x::AbstractOptimizationModel) = empty!(get_time_series_cache(x)) -function get_current_timestamp(model::OperationModel) +function get_current_timestamp(model::AbstractOptimizationModel) # For EmulationModel interval and resolution are the same. return get_initial_time(model) + get_execution_count(model) * get_interval(model) end -function get_timestamps(model::OperationModel) +function get_timestamps(model::AbstractOptimizationModel) optimization_container = get_optimization_container(model) start_time = get_initial_time(optimization_container) resolution = get_resolution(model) @@ -117,13 +122,13 @@ function get_timestamps(model::OperationModel) return range(start_time; length = horizon_count, step = resolution) end -function write_data(model::OperationModel, output_dir::AbstractString; kwargs...) +function write_data(model::AbstractOptimizationModel, output_dir::AbstractString; kwargs...) write_data(get_optimization_container(model), output_dir; kwargs...) return end function get_initial_conditions( - model::OperationModel, + model::AbstractOptimizationModel, ::T, ::U, ) where {T <: InitialConditionType, U <: IS.InfrastructureSystemsComponent} @@ -131,7 +136,7 @@ function get_initial_conditions( end # Called `solve_impl!(model)` in PSI. -function solve_model!(model::OperationModel) +function solve_model!(model::AbstractOptimizationModel) container = get_optimization_container(model) model_name = get_name(model) ts = get_current_timestamp(model) @@ -165,42 +170,42 @@ function solve_model!(model::OperationModel) return end -set_console_level!(model::OperationModel, val) = +set_console_level!(model::AbstractOptimizationModel, val) = set_console_level!(get_internal(model), val) -set_file_level!(model::OperationModel, val) = +set_file_level!(model::AbstractOptimizationModel, val) = set_file_level!(get_internal(model), val) -function set_executions!(model::OperationModel, val::Int) +function set_executions!(model::AbstractOptimizationModel, val::Int) set_executions!(get_internal(model), val) return end -function set_execution_count!(model::OperationModel, val::Int) +function set_execution_count!(model::AbstractOptimizationModel, val::Int) set_execution_count!(get_internal(model), val) return end -set_initial_time!(model::OperationModel, val::Dates.DateTime) = +set_initial_time!(model::AbstractOptimizationModel, val::Dates.DateTime) = set_initial_time!(get_settings(model), val) -get_simulation_info(model::OperationModel, val) = model.simulation_info = val +get_simulation_info(model::AbstractOptimizationModel, val) = model.simulation_info = val -function set_status!(model::OperationModel, status::ModelBuildStatus) +function set_status!(model::AbstractOptimizationModel, status::ModelBuildStatus) set_status!(get_internal(model), status) return end -function set_output_dir!(model::OperationModel, path::AbstractString) +function set_output_dir!(model::AbstractOptimizationModel, path::AbstractString) set_output_dir!(get_internal(model), path) return end -function advance_execution_count!(model::OperationModel) +function advance_execution_count!(model::AbstractOptimizationModel) internal = get_internal(model) internal.execution_count += 1 return end -function _check_numerical_bounds(model::OperationModel) +function _check_numerical_bounds(model::AbstractOptimizationModel) variable_bounds = get_variable_numerical_bounds(model) if variable_bounds.bounds.max - variable_bounds.bounds.min > 1e9 @warn "Variable bounds range is $(variable_bounds.bounds.max - variable_bounds.bounds.min) and can result in numerical problems for the solver. \\ @@ -232,7 +237,7 @@ function _check_numerical_bounds(model::OperationModel) return end -function _pre_solve_model_checks(model::OperationModel, optimizer = nothing) +function _pre_solve_model_checks(model::AbstractOptimizationModel, optimizer = nothing) jump_model = get_jump_model(model) if optimizer !== nothing JuMP.set_optimizer(jump_model, optimizer) @@ -258,33 +263,41 @@ function _pre_solve_model_checks(model::OperationModel, optimizer = nothing) return end -function list_names(model::OperationModel, ::Type{T}) where {T <: OptimizationKeyType} +function list_names( + model::AbstractOptimizationModel, + ::Type{T}, +) where {T <: OptimizationKeyType} return encode_keys_as_strings( list_keys(get_store(model), T), ) end -read_dual(model::OperationModel, key::ConstraintKey) = _read_outputs(model, key) -read_parameter(model::OperationModel, key::ParameterKey) = _read_outputs(model, key) -read_aux_variable(model::OperationModel, key::AuxVarKey) = _read_outputs(model, key) -read_variable(model::OperationModel, key::VariableKey) = _read_outputs(model, key) -read_expression(model::OperationModel, key::ExpressionKey) = _read_outputs(model, key) - -function _read_outputs(model::OperationModel, key::OptimizationContainerKey) +read_dual(model::AbstractOptimizationModel, key::ConstraintKey) = _read_outputs(model, key) +read_parameter(model::AbstractOptimizationModel, key::ParameterKey) = + _read_outputs(model, key) +read_aux_variable(model::AbstractOptimizationModel, key::AuxVarKey) = + _read_outputs(model, key) +read_variable(model::AbstractOptimizationModel, key::VariableKey) = + _read_outputs(model, key) +read_expression(model::AbstractOptimizationModel, key::ExpressionKey) = + _read_outputs(model, key) + +function _read_outputs(model::AbstractOptimizationModel, key::OptimizationContainerKey) array = read_outputs(get_store(model), key) return to_outputs_dataframe(array, nothing, Val(TableFormat.LONG)) end -read_optimizer_stats(model::OperationModel) = read_optimizer_stats(get_store(model)) +read_optimizer_stats(model::AbstractOptimizationModel) = + read_optimizer_stats(get_store(model)) -function add_recorders!(model::OperationModel, recorders) +function add_recorders!(model::AbstractOptimizationModel, recorders) internal = get_internal(model) for name in recorders add_recorder!(internal, name) end end -function register_recorders!(model::OperationModel, file_mode) +function register_recorders!(model::AbstractOptimizationModel, file_mode) recorder_dir = get_recorder_dir(model) mkpath(recorder_dir) for name in get_recorders(get_internal(model)) @@ -292,7 +305,7 @@ function register_recorders!(model::OperationModel, file_mode) end end -function unregister_recorders!(model::OperationModel) +function unregister_recorders!(model::AbstractOptimizationModel) for name in get_recorders(get_internal(model)) IS.unregister_recorder!(name) end @@ -300,7 +313,7 @@ end const _JUMP_MODEL_FILENAME = "jump_model.json" -function serialize_optimization_model(model::OperationModel) +function serialize_optimization_model(model::AbstractOptimizationModel) serialize_optimization_model( get_optimization_container(model), joinpath(get_output_dir(model), _JUMP_MODEL_FILENAME), @@ -308,7 +321,7 @@ function serialize_optimization_model(model::OperationModel) return end -function instantiate_network_model!(model::OperationModel) +function instantiate_network_model!(model::AbstractOptimizationModel) template = get_template(model) network_model = get_network_model(template) branch_models = get_branch_models(template) @@ -322,24 +335,25 @@ function instantiate_network_model!(model::OperationModel) return end -list_aux_variable_keys(x::OperationModel) = list_keys(get_store(x), AuxVariableType) -list_aux_variable_names(x::OperationModel) = list_names(x, AuxVariableType) -list_variable_keys(x::OperationModel) = list_keys(get_store(x), VariableType) -list_variable_names(x::OperationModel) = list_names(x, VariableType) -list_parameter_keys(x::OperationModel) = list_keys(get_store(x), ParameterType) -list_parameter_names(x::OperationModel) = list_names(x, ParameterType) -list_dual_keys(x::OperationModel) = list_keys(get_store(x), ConstraintType) -list_dual_names(x::OperationModel) = list_names(x, ConstraintType) -list_expression_keys(x::OperationModel) = list_keys(get_store(x), ExpressionType) -list_expression_names(x::OperationModel) = list_names(x, ExpressionType) - -function list_all_keys(x::OperationModel) +list_aux_variable_keys(x::AbstractOptimizationModel) = + list_keys(get_store(x), AuxVariableType) +list_aux_variable_names(x::AbstractOptimizationModel) = list_names(x, AuxVariableType) +list_variable_keys(x::AbstractOptimizationModel) = list_keys(get_store(x), VariableType) +list_variable_names(x::AbstractOptimizationModel) = list_names(x, VariableType) +list_parameter_keys(x::AbstractOptimizationModel) = list_keys(get_store(x), ParameterType) +list_parameter_names(x::AbstractOptimizationModel) = list_names(x, ParameterType) +list_dual_keys(x::AbstractOptimizationModel) = list_keys(get_store(x), ConstraintType) +list_dual_names(x::AbstractOptimizationModel) = list_names(x, ConstraintType) +list_expression_keys(x::AbstractOptimizationModel) = list_keys(get_store(x), ExpressionType) +list_expression_names(x::AbstractOptimizationModel) = list_names(x, ExpressionType) + +function list_all_keys(x::AbstractOptimizationModel) return Iterators.flatten( list_fields(get_store(x), T) for T in STORE_CONTAINER_TYPES ) end -function serialize_optimization_model(model::OperationModel, save_path::String) +function serialize_optimization_model(model::AbstractOptimizationModel, save_path::String) serialize_jump_optimization_model( get_jump_model(get_optimization_container(model)), save_path, diff --git a/src/operation/store_common.jl b/src/operation/store_common.jl index eb09754f..a3e0b79b 100644 --- a/src/operation/store_common.jl +++ b/src/operation/store_common.jl @@ -42,7 +42,7 @@ end function write_outputs!( store::AbstractModelStore, - model::OperationModel, + model::AbstractOptimizationModel, index::Union{DecisionModelIndexType, EmulationModelIndexType}, update_timestamp::Dates.DateTime; exports = nothing, @@ -73,7 +73,7 @@ function write_model_dual_outputs!( index::Union{DecisionModelIndexType, EmulationModelIndexType}, update_timestamp::Dates.DateTime, export_params::Union{ExportParameters, Nothing}, -) where {T <: OperationModel} +) where {T <: AbstractOptimizationModel} container = get_optimization_container(model) model_name = get_name(model) if export_params !== nothing @@ -100,7 +100,7 @@ function write_model_parameter_outputs!( index::Union{DecisionModelIndexType, EmulationModelIndexType}, update_timestamp::Dates.DateTime, export_params::Union{ExportParameters, Nothing}, -) where {T <: OperationModel} +) where {T <: AbstractOptimizationModel} container = get_optimization_container(model) model_name = get_name(model) if export_params !== nothing @@ -133,7 +133,7 @@ function write_model_variable_outputs!( index::Union{DecisionModelIndexType, EmulationModelIndexType}, update_timestamp::Dates.DateTime, export_params::Union{ExportParameters, Nothing}, -) where {T <: OperationModel} +) where {T <: AbstractOptimizationModel} container = get_optimization_container(model) model_name = get_name(model) if export_params !== nothing @@ -171,7 +171,7 @@ function write_model_aux_variable_outputs!( index::Union{DecisionModelIndexType, EmulationModelIndexType}, update_timestamp::Dates.DateTime, export_params::Union{ExportParameters, Nothing}, -) where {T <: OperationModel} +) where {T <: AbstractOptimizationModel} container = get_optimization_container(model) model_name = get_name(model) if export_params !== nothing @@ -203,7 +203,7 @@ function write_model_expression_outputs!( index::Union{DecisionModelIndexType, EmulationModelIndexType}, update_timestamp::Dates.DateTime, export_params::Union{ExportParameters, Nothing}, -) where {T <: OperationModel} +) where {T <: AbstractOptimizationModel} container = get_optimization_container(model) model_name = get_name(model) if export_params !== nothing diff --git a/src/utils/print_pt_v3.jl b/src/utils/print_pt_v3.jl index 743bfaca..93505a21 100644 --- a/src/utils/print_pt_v3.jl +++ b/src/utils/print_pt_v3.jl @@ -188,254 +188,24 @@ function _show_method(io::IO, network_model::NetworkModel, backend::Symbol; kwar return end -function Base.show(io::IO, ::MIME"text/plain", input::OperationModel) +function Base.show(io::IO, ::MIME"text/plain", input::AbstractOptimizationModel) _show_method(io, input, :auto) end -function Base.show(io::IO, ::MIME"text/html", input::OperationModel) +function Base.show(io::IO, ::MIME"text/html", input::AbstractOptimizationModel) # The tf_html_simple format was eliminated from PrettyTables and it was added to PowerSystems _show_method(io, input, :html; stand_alone = false, table_format = tf_html_simple) end -function _show_method(io::IO, model::OperationModel, backend::Symbol; kwargs...) +function _show_method(io::IO, model::AbstractOptimizationModel, backend::Symbol; kwargs...) _show_method(io, model.template, backend; kwargs...) end -function Base.show(io::IO, ::MIME"text/plain", input::SimulationModels) +function Base.show(io::IO, ::MIME"text/plain", input::OptimizationProblemOutputs) _show_method(io, input, :auto) end -function Base.show(io::IO, ::MIME"text/html", input::SimulationModels) - # The tf_html_simple format was eliminated from PrettyTables and it was added to PowerSystems - _show_method(io, input, :html; stand_alone = false, table_format = tf_html_simple) -end - -_get_model_type(::DecisionModel{T}) where {T <: DecisionProblem} = T -_get_model_type(::EmulationModel{T}) where {T <: EmulationProblem} = T - -function _show_method(io::IO, sim_models::SimulationModels, backend::Symbol; kwargs...) - println(io) - header = ["Model Name", "Model Type", "Status", "Output Directory"] - - table = Matrix{Any}(undef, length(sim_models.decision_models), length(header)) - for (ix, model) in enumerate(sim_models.decision_models) - table[ix, 1] = string(get_name(model)) - table[ix, 2] = IS.strip_module_name(string(_get_model_type(model))) - table[ix, 3] = string(get_status(model)) - table[ix, 4] = get_output_dir(model) - end - - PrettyTables.pretty_table( - io, - table; - column_labels = header, - backend = backend, - title = "Decision Models", - alignment = :l, - kwargs..., - ) - - if !isnothing(sim_models.emulation_model) - println(io) - table = Matrix{Any}(undef, 1, length(header)) - table[1, 1] = string(get_name(sim_models.emulation_model)) - table[1, 2] = - IS.strip_module_name(string(_get_model_type(sim_models.emulation_model))) - table[1, 3] = string(get_status(sim_models.emulation_model)) - table[1, 4] = get_output_dir(sim_models.emulation_model) - - PrettyTables.pretty_table( - io, - table; - column_labels = header, - backend = backend, - title = "Emulator Models", - alignment = :l, - kwargs..., - ) - else - println(io) - println(io, "No Emulator Model Specified") - end -end - -function Base.show(io::IO, ::MIME"text/plain", input::SimulationSequence) - _show_method(io, input, :auto) -end - -function Base.show(io::IO, ::MIME"text/html", input::SimulationSequence) - # The tf_html_simple format was eliminated from PrettyTables and it was added to PowerSystems - _show_method(io, input, :html; stand_alone = false, table_format = tf_html_simple) -end - -function _show_method(io::IO, sequence::SimulationSequence, backend::Symbol; kwargs...) - println(io) - table = [ - "Simulation Step Interval" Dates.Hour(get_step_resolution(sequence)) - "Number of Problems" length(sequence.executions_by_model) - ] - - PrettyTables.pretty_table( - io, - table; - backend = backend, - show_column_labels = false, - title = "Simulation Sequence", - alignment = :l, - kwargs..., - ) - - println(io) - header = ["Model Name", "Horizon", "Interval", "Executions Per Step"] - - table = Matrix{Any}(undef, length(sequence.executions_by_model), length(header)) - for (ix, (model, executions)) in enumerate(sequence.executions_by_model) - table[ix, 1] = string(model) - table[ix, 2] = Dates.canonicalize(sequence.horizons[model]) - table[ix, 3] = Dates.canonicalize(sequence.intervals[model]) - table[ix, 4] = executions - end - - PrettyTables.pretty_table( - io, - table; - column_labels = header, - backend = backend, - title = "Simulation Problems", - alignment = :l, - ) - - if !isempty(sequence.feedforwards) - println(io) - header = ["Model Name", "Feed Forward Type"] - table = Matrix{Any}(undef, length(sequence.feedforwards), length(header)) - for (ix, (k, ff)) in enumerate(sequence.feedforwards) - table[ix, 1] = k - table[ix, 2] = join(string.(typeof.(ff)), " ") - end - PrettyTables.pretty_table( - io, - table; - column_labels = header, - backend = backend, - title = "Feedforwards", - alignment = :l, - kwargs..., - ) - end -end - -function Base.show(io::IO, ::MIME"text/plain", input::Simulation) - _show_method(io, input, :auto) -end - -function Base.show(io::IO, ::MIME"text/html", input::Simulation) - # The tf_html_simple format was eliminated from PrettyTables and it was added to PowerSystems - _show_method(io, input, :html; stand_alone = false, table_format = tf_html_simple) -end - -function _get_initial_time_for_show(sim::Simulation) - ini_time = get_initial_time(sim) - if isnothing(ini_time) - return "Unset Initial Time" - else - return string(ini_time) - end -end - -function _get_build_status_for_show(sim::Simulation) - internal = sim.internal - if isnothing(internal) - return "EMPTY" - else - return string(internal.build_status) - end -end - -function _get_run_status_for_show(sim::Simulation) - internal = sim.internal - if isnothing(internal) - return "NOT_READY" - else - return string(internal.status) - end -end - -function _show_method(io::IO, sim::Simulation, backend::Symbol; kwargs...) - table = [ - "Simulation Name" get_name(sim) - "Build Status" _get_build_status_for_show(sim) - "Run Status" _get_run_status_for_show(sim) - "Initial Time" _get_initial_time_for_show(sim) - "Steps" get_steps(sim) - ] - - PrettyTables.pretty_table( - io, - table; - backend = backend, - show_column_labels = false, - title = "Simulation", - alignment = :l, - kwargs..., - ) - - _show_method(io, sim.models, backend; kwargs...) - _show_method(io, sim.sequence, backend; kwargs...) -end - -function Base.show(io::IO, ::MIME"text/plain", input::SimulationOutputs) - _show_method(io, input, :auto) -end - -function Base.show(io::IO, ::MIME"text/html", input::SimulationOutputs) - # The tf_html_simple format was eliminated from PrettyTables and it was added to PowerSystems - _show_method(io, input, :html; stand_alone = false, table_format = tf_html_simple) -end - -function _show_method(io::IO, outputs::SimulationOutputs, backend::Symbol; kwargs...) - header = ["Problem Name", "Initial Time", "Resolution", "Last Solution Timestamp"] - - table = Matrix{Any}(undef, length(outputs.decision_problem_outputs), length(header)) - for (ix, (key, output)) in enumerate(outputs.decision_problem_outputs) - table[ix, 1] = key - table[ix, 2] = first(output.timestamps) - table[ix, 3] = Dates.canonicalize(output.resolution) - table[ix, 4] = last(output.timestamps) - end - println(io) - PrettyTables.pretty_table( - io, - table; - column_labels = header, - backend = backend, - title = "Decision Problem Outputs", - alignment = :l, - ) - - println(io) - table = [ - "Name" outputs.emulation_problem_outputs.problem - "Resolution" Dates.Minute(outputs.emulation_problem_outputs.resolution) - "Number of steps" length(outputs.emulation_problem_outputs.timestamps) - ] - PrettyTables.pretty_table( - io, - table; - show_column_labels = false, - backend = backend, - title = "Emulator Outputs", - alignment = :l, - kwargs..., - ) -end - -ProblemOutputsTypes = Union{OptimizationProblemOutputs, SimulationProblemOutputs} -function Base.show(io::IO, ::MIME"text/plain", input::ProblemOutputsTypes) - _show_method(io, input, :auto) -end - -function Base.show(io::IO, ::MIME"text/html", input::ProblemOutputsTypes) +function Base.show(io::IO, ::MIME"text/html", input::OptimizationProblemOutputs) # The tf_html_simple format was eliminated from PrettyTables and it was added to PowerSystems _show_method(io, input, :html; stand_alone = false, table_format = tf_html_simple) end @@ -445,7 +215,7 @@ function _show_method( outputs::T, backend::Symbol; kwargs..., -) where {T <: ProblemOutputsTypes} +) where {T <: OptimizationProblemOutputs} timestamps = get_timestamps(outputs) if backend == :html diff --git a/test/test_utils/run_simulation.jl b/test/test_utils/run_simulation.jl deleted file mode 100644 index 58ff3031..00000000 --- a/test/test_utils/run_simulation.jl +++ /dev/null @@ -1,105 +0,0 @@ -function load_pf_export(root, export_subdir) - raw_path, md_path = get_psse_export_paths(export_subdir) - sys = System(joinpath(root, raw_path), JSON3.read(joinpath(root, md_path), Dict)) - set_units_base_system!(sys, "NATURAL_UNITS") - return sys -end - -function run_simulation( - c_sys5_hy_uc, - c_sys5_hy_ed, - file_path::String, - export_path; - in_memory = false, - uc_network_model = nothing, - ed_network_model = nothing, -) - template_uc = get_template_basic_uc_simulation() - template_ed = get_template_nomin_ed_simulation() - isnothing(uc_network_model) && ( - uc_network_model = - NetworkModel(CopperPlatePowerModel; duals = [CopperPlateBalanceConstraint]) - ) - isnothing(ed_network_model) && ( - ed_network_model = - NetworkModel( - CopperPlatePowerModel; - duals = [CopperPlateBalanceConstraint], - use_slacks = true, - ) - ) - set_device_model!(template_ed, InterruptiblePowerLoad, StaticPowerLoad) - set_network_model!( - template_uc, - uc_network_model, - ) - set_network_model!( - template_ed, - ed_network_model, - ) - models = SimulationModels(; - decision_models = [ - DecisionModel( - template_uc, - c_sys5_hy_uc; - name = "UC", - optimizer = HiGHS_optimizer, - ), - DecisionModel( - template_ed, - c_sys5_hy_ed; - name = "ED", - optimizer = ipopt_optimizer, - ), - ], - ) - - 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, - ) - - build_out = build!(sim; console_level = Logging.Error) - @test build_out == PSI.SimulationBuildStatus.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.SUCCESSFULLY_FINALIZED - - return sim -end