Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 7 additions & 6 deletions src/InfrastructureOptimizationModels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down
76 changes: 10 additions & 66 deletions src/core/operation_model_abstract_types.jl
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions src/core/optimization_problem_outputs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
"""
Expand Down
175 changes: 26 additions & 149 deletions src/operation/decision_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -43,52 +32,22 @@ 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,
sys::IS.InfrastructureSystemsContainer,
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
Expand All @@ -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}(
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jd-lara What do you think about keeping the constructors here?

template::AbstractProblemTemplate,
sys::IS.InfrastructureSystemsContainer,
Expand All @@ -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,
Expand Down Expand Up @@ -170,61 +140,27 @@ 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},
template::AbstractProblemTemplate,
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)
Expand Down Expand Up @@ -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))
Loading
Loading