From 6da7392cfedf594bb1c06b84bfa547d516b441cb Mon Sep 17 00:00:00 2001 From: Anthony Costarelli Date: Wed, 20 May 2026 11:28:53 -0400 Subject: [PATCH 1/5] Introduce AbstractOptimizationProblem; remove power-flavoured taxonomy IOM is meant to be a domain-neutral optimization-modeling library. This removes the power-flavoured problem-type chain (DecisionProblem, EmulationProblem, DefaultDecisionProblem, DefaultEmulationProblem, GenericOpProblem, GenericEmulationProblem) and the five Simulation* placeholder types that were stubs for PowerSimulations.jl. In their place IOM gains a single abstract type: abstract type AbstractOptimizationProblem end DecisionModel and EmulationModel are re-parameterized over it, retaining their phantom {M} parameter so POM-side dispatch on concrete problem tags keeps working. IOM-side methods (get_problem_type, get_current_time, init_model_store_params!, OptimizationProblemOutputs constructors, error-stub validate_template) are re-rooted on the new abstract. validate_time_series! becomes an extension-point stub (function declaration with no methods); POM provides concrete methods on its own problem-type chain. Outer constructors that call finalize_template! and the EmulationModel update_parameters!/update_model!/update_parameter_values! chain move to POM, where they belong. Base.show methods for the removed Simulation* placeholders are deleted from print_pt_v3.jl; the ProblemOutputsTypes union is collapsed to OptimizationProblemOutputs. Verified: 1144/1144 unit tests pass, plus 10/10 Aqua quality checks. Co-Authored-By: Claude Opus 4.7 --- src/InfrastructureOptimizationModels.jl | 9 +- src/core/operation_model_abstract_types.jl | 76 +----- src/core/optimization_problem_outputs.jl | 2 +- src/operation/decision_model.jl | 256 +----------------- src/operation/emulation_model.jl | 291 +-------------------- src/utils/print_pt_v3.jl | 236 +---------------- test/test_utils/run_simulation.jl | 105 -------- 7 files changed, 26 insertions(+), 949 deletions(-) delete mode 100644 test/test_utils/run_simulation.jl diff --git a/src/InfrastructureOptimizationModels.jl b/src/InfrastructureOptimizationModels.jl index b7b500e2..19a03cf5 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 @@ -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 diff --git a/src/core/operation_model_abstract_types.jl b/src/core/operation_model_abstract_types.jl index fd98bbec..4da5353d 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`. `OperationModel` structs +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 OperationModel end diff --git a/src/core/optimization_problem_outputs.jl b/src/core/optimization_problem_outputs.jl index 2db94f1d..f4794262 100644 --- a/src/core/optimization_problem_outputs.jl +++ b/src/core/optimization_problem_outputs.jl @@ -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..11ba4164 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} <: OperationModel name::Symbol template::AbstractProblemTemplate sys::IS.InfrastructureSystemsContainer @@ -39,192 +28,12 @@ mutable struct DecisionModel{M <: DecisionProblem} <: OperationModel ext::Dict{String, Any} end -""" - DecisionModel{M}( - template::AbstractProblemTemplate, - sys::IS.InfrastructureSystemsContainer, - jump_model::Union{Nothing, JuMP.Model}=nothing; - kwargs...) where {M<:DecisionProblem} - -Build the optimization problem of type M with the specific system and template. - -# 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) -``` -""" -function DecisionModel{M}( - template::AbstractProblemTemplate, - sys::IS.InfrastructureSystemsContainer, - settings::Settings, - jump_model::Union{Nothing, JuMP.Model} = nothing; - name = nothing, -) where {M <: DecisionProblem} - if name === nothing - name = nameof(M) - elseif name isa String - name = Symbol(name) - end - auto_transform_time_series!(sys, settings) - ts_type = get_deterministic_time_series_type(sys) - internal = ModelInternal( - OptimizationContainer(sys, settings, jump_model, ts_type), - ) - - template_ = deepcopy(template) - finalize_template!(template_, sys) - model = DecisionModel{M}( - name, - template_, - sys, - internal, - SimulationInfo(), - DecisionModelStore(), - Dict{String, Any}(), - ) - validate_time_series!(model) - return model -end - -function DecisionModel{M}( - template::AbstractProblemTemplate, - sys::IS.InfrastructureSystemsContainer, - jump_model::Union{Nothing, JuMP.Model} = nothing; - name = nothing, - optimizer = nothing, - horizon = UNSET_HORIZON, - resolution = UNSET_RESOLUTION, - interval = UNSET_INTERVAL, - warm_start = true, - check_components = true, - initialize_model = true, - initialization_file = "", - deserialize_initial_conditions = false, - export_pwl_vars = false, - allow_fails = false, - optimizer_solve_log_print = false, - detailed_optimizer_stats = false, - calculate_conflict = false, - direct_mode_optimizer = false, - store_variable_names = false, - rebuild_model = false, - export_optimization_model = false, - check_numerical_bounds = true, - initial_time = UNSET_INI_TIME, - time_series_cache_size::Int = IS.TIME_SERIES_CACHE_SIZE_BYTES, -) where {M <: DecisionProblem} - settings = Settings( - sys; - horizon = horizon, - resolution = resolution, - interval = interval, - initial_time = initial_time, - optimizer = optimizer, - time_series_cache_size = time_series_cache_size, - warm_start = warm_start, - check_components = check_components, - initialize_model = initialize_model, - initialization_file = initialization_file, - deserialize_initial_conditions = deserialize_initial_conditions, - export_pwl_vars = export_pwl_vars, - allow_fails = allow_fails, - calculate_conflict = calculate_conflict, - optimizer_solve_log_print = optimizer_solve_log_print, - detailed_optimizer_stats = detailed_optimizer_stats, - direct_mode_optimizer = direct_mode_optimizer, - check_numerical_bounds = check_numerical_bounds, - store_variable_names = store_variable_names, - rebuild_model = rebuild_model, - export_optimization_model = export_optimization_model, - ) - return DecisionModel{M}(template, sys, settings, jump_model; name = name) -end - -""" -Build the optimization problem of type M with the specific system and template - -# 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}` = nothing: Enables passing a custom JuMP model. Use with care. - -# Example - -```julia -template = ProblemTemplate(CopperPlatePowerModel, devices, branches, services) -problem = DecisionModel(MyOpProblemType, template, system, optimizer) -``` -""" -function DecisionModel( - ::Type{M}, - template::AbstractProblemTemplate, - sys::IS.InfrastructureSystemsContainer, - jump_model::Union{Nothing, JuMP.Model} = nothing; - kwargs..., -) where {M <: DecisionProblem} - 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 +get_problem_type(::DecisionModel{M}) where {M <: AbstractOptimizationProblem} = M -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 - -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 +69,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..8f969e13 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} <: OperationModel name::Symbol template::AbstractProblemTemplate sys::IS.InfrastructureSystemsContainer @@ -66,7 +13,7 @@ mutable struct EmulationModel{M <: EmulationProblem} <: OperationModel settings::Settings, jump_model::Union{Nothing, JuMP.Model} = nothing; name = nothing, - ) where {M <: EmulationProblem} + ) where {M <: AbstractOptimizationProblem} if name === nothing name = nameof(M) elseif name isa String @@ -88,163 +35,12 @@ mutable struct EmulationModel{M <: EmulationProblem} <: OperationModel end end -function EmulationModel{M}( - template::AbstractProblemTemplate, - sys::IS.InfrastructureSystemsContainer, - jump_model::Union{Nothing, JuMP.Model} = nothing; - resolution = UNSET_RESOLUTION, - name = nothing, - optimizer = nothing, - warm_start = true, - initialize_model = true, - initialization_file = "", - deserialize_initial_conditions = false, - export_pwl_vars = false, - allow_fails = false, - calculate_conflict = false, - optimizer_solve_log_print = false, - detailed_optimizer_stats = false, - direct_mode_optimizer = false, - check_numerical_bounds = true, - store_variable_names = false, - rebuild_model = false, - initial_time = UNSET_INI_TIME, - time_series_cache_size::Int = IS.TIME_SERIES_CACHE_SIZE_BYTES, -) where {M <: EmulationProblem} - settings = Settings( - sys; - initial_time = initial_time, - optimizer = optimizer, - time_series_cache_size = time_series_cache_size, - warm_start = warm_start, - initialize_model = initialize_model, - initialization_file = initialization_file, - deserialize_initial_conditions = deserialize_initial_conditions, - export_pwl_vars = export_pwl_vars, - allow_fails = allow_fails, - calculate_conflict = calculate_conflict, - optimizer_solve_log_print = optimizer_solve_log_print, - detailed_optimizer_stats = detailed_optimizer_stats, - direct_mode_optimizer = direct_mode_optimizer, - check_numerical_bounds = check_numerical_bounds, - store_variable_names = store_variable_names, - rebuild_model = rebuild_model, - horizon = resolution, - resolution = resolution, - ) - model = EmulationModel{M}(template, sys, settings, jump_model; name = name) - validate_time_series!(model) - return model -end - -""" -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 - -# Example - -```julia -template = ProblemTemplate(CopperPlatePowerModel, devices, branches, services) -problem = EmulationModel(MyEmProblemType, template, system, optimizer) -``` -""" -function EmulationModel( - ::Type{M}, - template::AbstractProblemTemplate, - 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} - 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 +69,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/utils/print_pt_v3.jl b/src/utils/print_pt_v3.jl index 743bfaca..fad0d895 100644 --- a/src/utils/print_pt_v3.jl +++ b/src/utils/print_pt_v3.jl @@ -201,241 +201,11 @@ function _show_method(io::IO, model::OperationModel, 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 From 58e5901083f550da2a9d68702167b18cfbe39fbe Mon Sep 17 00:00:00 2001 From: Anthony Costarelli Date: Wed, 20 May 2026 11:43:41 -0400 Subject: [PATCH 2/5] Remove power-flavoured inner constructor from EmulationModel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The EmulationModel struct previously carried an explicit inner constructor that called finalize_template! — a POM-side function. That left power-flavoured construction logic in IOM, inconsistent with how DecisionModel handled it (no inner constructor; all construction logic in outer methods that moved to POM in the prior commit). Drop the inner constructor, leaving the auto-generated default EmulationModel{M}(name, template, sys, internal, simulation_info, store, ext) constructor. POM's Settings-taking outer constructor will do the finalize_template!/OptimizationContainer/validate_time_series! work and call the auto-default to construct the model. Co-Authored-By: Claude Opus 4.7 --- src/operation/emulation_model.jl | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/src/operation/emulation_model.jl b/src/operation/emulation_model.jl index 8f969e13..d75f9b99 100644 --- a/src/operation/emulation_model.jl +++ b/src/operation/emulation_model.jl @@ -6,33 +6,6 @@ mutable struct EmulationModel{M <: AbstractOptimizationProblem} <: OperationMode simulation_info::SimulationInfo store::EmulationModelStore # might be extended to other stores for simulation ext::Dict{String, Any} - - 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), - ) - new{M}( - name, - template, - sys, - internal, - SimulationInfo(), - EmulationModelStore(), - Dict{String, Any}(), - ) - end end get_problem_type(::EmulationModel{M}) where {M <: AbstractOptimizationProblem} = M From a2a0135c7bfa39795b7fd822a690b4ad47a5cc87 Mon Sep 17 00:00:00 2001 From: Anthony Costarelli Date: Wed, 20 May 2026 12:06:33 -0400 Subject: [PATCH 3/5] Rename OperationModel to AbstractOptimizationModel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OperationModel was IOM's abstract supertype for DecisionModel and EmulationModel. The "Operation" prefix carried unnecessary power-domain flavor for what is a fully generic wrapper-level abstraction in IOM — it describes "any optimization-model wrapper" with no domain commitment. Rename it to AbstractOptimizationModel: - Follows the Julia Abstract* convention (consistent with the AbstractOptimizationProblem introduced in the prior commit). - Removes the residual "Operation" naming from IOM. - Reads correctly: DecisionModel/EmulationModel are concrete optimization models, and their supertype names that role generically. All 14 affected IOM files updated (struct supertypes, method dispatches on the abstract type, module export). 1144/1144 unit tests pass plus 10/10 Aqua checks. Co-Authored-By: Claude Opus 4.7 --- src/InfrastructureOptimizationModels.jl | 2 +- src/core/operation_model_abstract_types.jl | 4 +- src/core/optimization_problem_outputs.jl | 2 +- src/operation/decision_model.jl | 2 +- src/operation/emulation_model.jl | 2 +- ...itial_conditions_update_in_memory_store.jl | 2 +- .../model_numerical_analysis_utils.jl | 4 +- src/operation/operation_model_interface.jl | 168 +++++++++--------- src/operation/optimization_debugging.jl | 14 +- src/operation/store_common.jl | 12 +- src/utils/print_pt_v3.jl | 6 +- 11 files changed, 109 insertions(+), 109 deletions(-) diff --git a/src/InfrastructureOptimizationModels.jl b/src/InfrastructureOptimizationModels.jl index 19a03cf5..6ecb4352 100644 --- a/src/InfrastructureOptimizationModels.jl +++ b/src/InfrastructureOptimizationModels.jl @@ -379,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 diff --git a/src/core/operation_model_abstract_types.jl b/src/core/operation_model_abstract_types.jl index 4da5353d..8ed49f89 100644 --- a/src/core/operation_model_abstract_types.jl +++ b/src/core/operation_model_abstract_types.jl @@ -13,7 +13,7 @@ struct MyCustomProblem <: IOM.AbstractOptimizationProblem end abstract type AbstractOptimizationProblem end """ -Abstract supertype for `DecisionModel` and `EmulationModel`. `OperationModel` structs +Abstract supertype for `DecisionModel` and `EmulationModel`. Concrete subtypes are parameterized with an `AbstractOptimizationProblem` subtype. """ -abstract type OperationModel end +abstract type AbstractOptimizationModel end diff --git a/src/core/optimization_problem_outputs.jl b/src/core/optimization_problem_outputs.jl index f4794262..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) diff --git a/src/operation/decision_model.jl b/src/operation/decision_model.jl index 11ba4164..ae5e93f6 100644 --- a/src/operation/decision_model.jl +++ b/src/operation/decision_model.jl @@ -18,7 +18,7 @@ function get_deterministic_time_series_type(sys::IS.InfrastructureSystemsContain end end -mutable struct DecisionModel{M <: AbstractOptimizationProblem} <: OperationModel +mutable struct DecisionModel{M <: AbstractOptimizationProblem} <: AbstractOptimizationModel name::Symbol template::AbstractProblemTemplate sys::IS.InfrastructureSystemsContainer diff --git a/src/operation/emulation_model.jl b/src/operation/emulation_model.jl index d75f9b99..7ea3ef22 100644 --- a/src/operation/emulation_model.jl +++ b/src/operation/emulation_model.jl @@ -1,4 +1,4 @@ -mutable struct EmulationModel{M <: AbstractOptimizationProblem} <: OperationModel +mutable struct EmulationModel{M <: AbstractOptimizationProblem} <: AbstractOptimizationModel name::Symbol template::AbstractProblemTemplate sys::IS.InfrastructureSystemsContainer 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/operation_model_interface.jl b/src/operation/operation_model_interface.jl index 20f50e5c..eebd03e2 100644 --- a/src/operation/operation_model_interface.jl +++ b/src/operation/operation_model_interface.jl @@ -1,29 +1,29 @@ -# 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 +31,85 @@ 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 +117,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 +131,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 +165,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 +232,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 +258,33 @@ 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) +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::OperationModel, key::OptimizationContainerKey) +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 +292,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 +300,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 +308,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 +322,24 @@ 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/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/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 fad0d895..93505a21 100644 --- a/src/utils/print_pt_v3.jl +++ b/src/utils/print_pt_v3.jl @@ -188,16 +188,16 @@ 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 From 8f4a79bb25752b146532d041324aa3e868e35b04 Mon Sep 17 00:00:00 2001 From: Anthony Costarelli Date: Wed, 20 May 2026 13:45:41 -0400 Subject: [PATCH 4/5] Move DecisionModel/EmulationModel generic constructors back to IOM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Settings-taking, kwargs, and type-first constructors for DecisionModel and EmulationModel are not power-specific — their bodies use only IOM types and IOM-declared extension points (finalize_template! and validate_time_series!). The only thing tying them to POM in the prior commit was the `where {M <: AbstractPowerDecisionProblem}` constraint. Move all six generic constructors (3 for DecisionModel, 3 for EmulationModel) back to IOM, parameterized on `<: AbstractOptimizationProblem`. The construction path is now: 1. User calls DecisionModel{MyProblem}(template, sys; horizon, ...) 2. IOM kwargs constructor builds Settings, delegates to Settings-taking 3. IOM Settings-taking constructor finalizes the template, builds the OptimizationContainer, calls validate_time_series! 4. finalize_template! and validate_time_series! dispatch to whichever domain library (POM, hypothetical GasOperationsModels, etc.) has supplied methods for the concrete template/model types. POM keeps only: - The default-tag convenience constructor that picks GenericPowerDecisionProblem/GenericPowerEmulationProblem - The bare-system error stub for DefaultPowerDecisionProblem - All the build/solve/run/reset dispatches on AbstractPower* types - validate_template and validate_time_series! method implementations Net effect: future domain libraries get the construction path for free by defining their template type, implementing finalize_template! and validate_time_series! for it, and optionally a default-tag convenience. IOM unit tests: 1144/1144 pass, plus 10/10 Aqua checks. Co-Authored-By: Claude Opus 4.7 --- src/operation/decision_model.jl | 127 +++++++++++++++++++++++++++++++ src/operation/emulation_model.jl | 117 ++++++++++++++++++++++++++++ 2 files changed, 244 insertions(+) diff --git a/src/operation/decision_model.jl b/src/operation/decision_model.jl index ae5e93f6..548c7cca 100644 --- a/src/operation/decision_model.jl +++ b/src/operation/decision_model.jl @@ -28,6 +28,133 @@ mutable struct DecisionModel{M <: AbstractOptimizationProblem} <: AbstractOptimi ext::Dict{String, Any} end +""" + DecisionModel{M}( + template::AbstractProblemTemplate, + sys::IS.InfrastructureSystemsContainer, + settings::Settings, + jump_model::Union{Nothing, JuMP.Model}=nothing; + name = nothing) where {M<:AbstractOptimizationProblem} + +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, + 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 + auto_transform_time_series!(sys, settings) + ts_type = get_deterministic_time_series_type(sys) + internal = ModelInternal( + OptimizationContainer(sys, settings, jump_model, ts_type), + ) + + template_ = deepcopy(template) + finalize_template!(template_, sys) + model = DecisionModel{M}( + name, + template_, + sys, + internal, + SimulationInfo(), + DecisionModelStore(), + Dict{String, Any}(), + ) + validate_time_series!(model) + 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, + jump_model::Union{Nothing, JuMP.Model} = nothing; + name = nothing, + optimizer = nothing, + horizon = UNSET_HORIZON, + resolution = UNSET_RESOLUTION, + interval = UNSET_INTERVAL, + warm_start = true, + check_components = true, + initialize_model = true, + initialization_file = "", + deserialize_initial_conditions = false, + export_pwl_vars = false, + allow_fails = false, + optimizer_solve_log_print = false, + detailed_optimizer_stats = false, + calculate_conflict = false, + direct_mode_optimizer = false, + store_variable_names = false, + rebuild_model = false, + export_optimization_model = false, + check_numerical_bounds = true, + initial_time = UNSET_INI_TIME, + time_series_cache_size::Int = IS.TIME_SERIES_CACHE_SIZE_BYTES, +) where {M <: AbstractOptimizationProblem} + settings = Settings( + sys; + horizon = horizon, + resolution = resolution, + interval = interval, + initial_time = initial_time, + optimizer = optimizer, + time_series_cache_size = time_series_cache_size, + warm_start = warm_start, + check_components = check_components, + initialize_model = initialize_model, + initialization_file = initialization_file, + deserialize_initial_conditions = deserialize_initial_conditions, + export_pwl_vars = export_pwl_vars, + allow_fails = allow_fails, + calculate_conflict = calculate_conflict, + optimizer_solve_log_print = optimizer_solve_log_print, + detailed_optimizer_stats = detailed_optimizer_stats, + direct_mode_optimizer = direct_mode_optimizer, + check_numerical_bounds = check_numerical_bounds, + store_variable_names = store_variable_names, + rebuild_model = rebuild_model, + export_optimization_model = export_optimization_model, + ) + return DecisionModel{M}(template, sys, settings, jump_model; name = name) +end + +""" + DecisionModel(::Type{M}, template, sys, jump_model=nothing; kwargs...) + where {M <: AbstractOptimizationProblem} + +Type-first dispatch variant. +""" +function DecisionModel( + ::Type{M}, + template::AbstractProblemTemplate, + sys::IS.InfrastructureSystemsContainer, + jump_model::Union{Nothing, JuMP.Model} = nothing; + kwargs..., +) where {M <: AbstractOptimizationProblem} + return DecisionModel{M}(template, sys, jump_model; kwargs...) +end + get_problem_type(::DecisionModel{M}) where {M <: AbstractOptimizationProblem} = M function validate_template(::DecisionModel{M}) where {M <: AbstractOptimizationProblem} diff --git a/src/operation/emulation_model.jl b/src/operation/emulation_model.jl index 7ea3ef22..3e2eb964 100644 --- a/src/operation/emulation_model.jl +++ b/src/operation/emulation_model.jl @@ -8,6 +8,123 @@ mutable struct EmulationModel{M <: AbstractOptimizationProblem} <: AbstractOptim ext::Dict{String, Any} end +""" + EmulationModel{M}( + template::AbstractProblemTemplate, + sys::IS.InfrastructureSystemsContainer, + settings::Settings, + 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, + jump_model::Union{Nothing, JuMP.Model} = nothing; + resolution = UNSET_RESOLUTION, + name = nothing, + optimizer = nothing, + warm_start = true, + initialize_model = true, + initialization_file = "", + deserialize_initial_conditions = false, + export_pwl_vars = false, + allow_fails = false, + calculate_conflict = false, + optimizer_solve_log_print = false, + detailed_optimizer_stats = false, + direct_mode_optimizer = false, + check_numerical_bounds = true, + store_variable_names = false, + rebuild_model = false, + initial_time = UNSET_INI_TIME, + time_series_cache_size::Int = IS.TIME_SERIES_CACHE_SIZE_BYTES, +) where {M <: AbstractOptimizationProblem} + settings = Settings( + sys; + initial_time = initial_time, + optimizer = optimizer, + time_series_cache_size = time_series_cache_size, + warm_start = warm_start, + initialize_model = initialize_model, + initialization_file = initialization_file, + deserialize_initial_conditions = deserialize_initial_conditions, + export_pwl_vars = export_pwl_vars, + allow_fails = allow_fails, + calculate_conflict = calculate_conflict, + optimizer_solve_log_print = optimizer_solve_log_print, + detailed_optimizer_stats = detailed_optimizer_stats, + direct_mode_optimizer = direct_mode_optimizer, + check_numerical_bounds = check_numerical_bounds, + store_variable_names = store_variable_names, + rebuild_model = rebuild_model, + horizon = resolution, + resolution = resolution, + ) + return EmulationModel{M}(template, sys, settings, jump_model; name = name) +end + +""" + EmulationModel(::Type{M}, template, sys, jump_model=nothing; kwargs...) + where {M <: AbstractOptimizationProblem} + +Type-first dispatch variant. +""" +function EmulationModel( + ::Type{M}, + template::AbstractProblemTemplate, + sys::IS.InfrastructureSystemsContainer, + jump_model::Union{Nothing, JuMP.Model} = nothing; + kwargs..., +) where {M <: AbstractOptimizationProblem} + return EmulationModel{M}(template, sys, jump_model; kwargs...) +end + get_problem_type(::EmulationModel{M}) where {M <: AbstractOptimizationProblem} = M function validate_template(::EmulationModel{M}) where {M <: AbstractOptimizationProblem} From efd19e8fa132f27ce77678aec1ffa9f57ecd0180 Mon Sep 17 00:00:00 2001 From: Anthony Costarelli Date: Wed, 20 May 2026 13:50:01 -0400 Subject: [PATCH 5/5] formatting; rename operation -> optimization --- .claude/claude.md | 2 +- src/InfrastructureOptimizationModels.jl | 2 +- ...ace.jl => optimization_model_interface.jl} | 38 +++++++++++++------ 3 files changed, 28 insertions(+), 14 deletions(-) rename src/operation/{operation_model_interface.jl => optimization_model_interface.jl} (93%) 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 6ecb4352..38812a69 100644 --- a/src/InfrastructureOptimizationModels.jl +++ b/src/InfrastructureOptimizationModels.jl @@ -649,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/operation/operation_model_interface.jl b/src/operation/optimization_model_interface.jl similarity index 93% rename from src/operation/operation_model_interface.jl rename to src/operation/optimization_model_interface.jl index eebd03e2..a3126feb 100644 --- a/src/operation/operation_model_interface.jl +++ b/src/operation/optimization_model_interface.jl @@ -21,7 +21,8 @@ end get_name(model::AbstractOptimizationModel) = model.name get_store(model::AbstractOptimizationModel) = model.store -is_synchronized(model::AbstractOptimizationModel) = is_synchronized(get_optimization_container(model)) +is_synchronized(model::AbstractOptimizationModel) = + is_synchronized(get_optimization_container(model)) function get_rebuild_model(model::AbstractOptimizationModel) sim_info = model.simulation_info @@ -80,7 +81,8 @@ end 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_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::AbstractOptimizationModel) = get_output_dir(get_internal(model)) @@ -88,15 +90,18 @@ get_initial_conditions_file(model::AbstractOptimizationModel) = joinpath(get_output_dir(model), "initial_conditions.bin") get_recorder_dir(model::AbstractOptimizationModel) = joinpath(get_output_dir(model), "recorder") -get_variables(model::AbstractOptimizationModel) = get_variables(get_optimization_container(model)) -get_parameters(model::AbstractOptimizationModel) = get_parameters(get_optimization_container(model)) +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::AbstractOptimizationModel) = get_store_params(model).interval -get_run_status(model::AbstractOptimizationModel) = get_run_status(get_simulation_info(model)) +get_run_status(model::AbstractOptimizationModel) = + get_run_status(get_simulation_info(model)) set_run_status!(model::AbstractOptimizationModel, status) = set_run_status!(get_simulation_info(model), status) @@ -258,24 +263,32 @@ function _pre_solve_model_checks(model::AbstractOptimizationModel, optimizer = n return end -function list_names(model::AbstractOptimizationModel, ::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::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) +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::AbstractOptimizationModel) = read_optimizer_stats(get_store(model)) +read_optimizer_stats(model::AbstractOptimizationModel) = + read_optimizer_stats(get_store(model)) function add_recorders!(model::AbstractOptimizationModel, recorders) internal = get_internal(model) @@ -322,7 +335,8 @@ function instantiate_network_model!(model::AbstractOptimizationModel) return end -list_aux_variable_keys(x::AbstractOptimizationModel) = list_keys(get_store(x), AuxVariableType) +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)