Skip to content
Closed
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
37 changes: 27 additions & 10 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Platform-wide Sienna conventions (performance, type stability, formatter, enviro

## Purpose & place in the stack

POM is the **collection of operational optimization models for power systems**: the concrete device, service, and network formulations plus the `PowerOperationModel` problem-type chain. It builds JuMP models from PowerSystems components.
POM is the **collection of operational optimization models for power systems**: the concrete device, service, and network formulations plus the `AbstractPowerOperationProblem` problem-type chain. It builds JuMP models from PowerSystems components.

Abstraction hierarchy (low → high level of generality):

Expand All @@ -16,13 +16,30 @@ PSI (the old PowerSimulations.jl) ≈ POM + IOM. Many ports into POM originate f

IS and IOM are **external package dependencies** (resolved via `Project.toml`/`[sources]`), not subdirectories.

### The IOM/POM split (PR #104 "Redistribute operation models")

IOM was made domain-neutral. The power-flavoured taxonomy was **moved out of IOM into POM** (`src/core/problem_types.jl`):

- POM owns `PowerOperationModel <: IOM.AbstractOptimizationProblem`, then `DecisionProblem`/`EmulationProblem`, `DefaultDecisionProblem`/`DefaultEmulationProblem`, `GenericOpProblem`/`GenericEmulationProblem`. POM dispatches on `DecisionModel{<:PowerOperationModel}` etc. The no-type-param `DecisionModel(template, sys)` default + the EmulationModel update chain live in POM (`src/operation/`).
- IOM removed `OperationModel`, `DecisionProblem`, `EmulationProblem`, the `Generic*`/`Default*` problem types, and the `Simulation*` stubs. The old `OperationModel` abstract → `IOM.AbstractOptimizationModel`. `validate_time_series!` / `validate_template` are stubs on the neutral abstract.
- **Gotcha:** when a symbol seems "not defined in IOM", check whether #104 moved it to POM — define/dispatch on the POM chain, do **not** re-add it to IOM.
### The IOM/POM split

IOM is domain-neutral and owns the **operation-model lifecycle**. The power-flavoured
problem taxonomy lives in POM (`src/core/problem_types.jl`):

- POM owns a single linear chain `AbstractPowerOperationProblem <: IOM.AbstractOptimizationProblem`
→ `GenericPowerOperationProblem` (template-driven) → `DefaultPowerOperationProblem` (the
default `M`). Decision↔emulation is **not** in the problem chain — it is the wrapper's job
(`DecisionModel` vs `EmulationModel`, both in IOM). The no-type-param
`DecisionModel(template, sys)` / `EmulationModel(template, sys)` defaulting constructors live
in POM (`src/operation/`), defaulting `M = DefaultPowerOperationProblem`.
- **IOM owns the generic lifecycle**: `build!`/`solve!`/`run!`/`build_model!`/`build_pre_step!`/
`reset!`/`handle_initial_conditions!`/`execute_emulation!`/`build_if_not_already_built!` are
single methods on `AbstractOptimizationModel`/`DecisionModel`/`EmulationModel` in IOM. POM
supplies the power-specific extension points: `build_problem!` (`src/operation/build_problem.jl`),
`build_initial_conditions!` (`src/initial_conditions/initialization.jl`),
`instantiate_network_model!`, `validate_template`, `validate_time_series!`. The two DM/EM
lifecycle differences are IOM compile-time hooks (`_mark_recurrent_solves!`,
`_apply_build_executions!`, `reset_time_series_type`).
- The operation timers (`BUILD_PROBLEMS_TIMER`, …) live in IOM; POM imports them so its
`build_problem!` timings aggregate into IOM's `build!` printout.
- **Gotcha:** when a symbol seems "not defined in IOM", check whether it is a POM extension-point
implementation (`build_problem!`/`build_initial_conditions!`/`validate_*`) — extend the IOM
generic (import it), do **not** create a shadowing POM function.

## Optimization Model Construction Conventions

Expand Down Expand Up @@ -51,7 +68,7 @@ src/
area_interchange.jl # Area interchange balance
core/ # Type definitions (no heavy logic)
definitions.jl, physical_constant_definitions.jl
problem_types.jl # PowerOperationModel chain (moved from IOM, PR #104)
problem_types.jl # AbstractPowerOperationProblem chain (linear; d/e is the wrapper's job)
interfaces.jl # incl. PowerFlowEvaluator wrapping PF models
variables.jl, auxiliary_variables.jl, constraints.jl, expressions.jl, parameters.jl
formulations.jl, network_formulations.jl # native DCP/ACP/PTDF/CopperPlate/Area structs
Expand Down Expand Up @@ -122,7 +139,7 @@ Solvers: `HiGHS` (LP/MILP), `Ipopt` (NLP), `SCS` (SDP) — helpers `HiGHS_optimi

## POM-specific conventions, invariants, gotchas

- **Layer boundaries:** device/network formulations + `PowerOperationModel` chain live in POM; `OptimizationContainer`/`DecisionModel`/settings/generic builders in IOM; base key/TS types in IS. Don't push power-specific logic down into IOM.
- **Layer boundaries:** device/network formulations + `AbstractPowerOperationProblem` chain live in POM; `OptimizationContainer`/`DecisionModel`/`EmulationModel`/the build/solve/run lifecycle/settings/generic builders in IOM; base key/TS types in IS. Don't push power-specific logic down into IOM.
- **`network_reduction` must always be populated** by POM's `instantiate_network_model!` for every network model (CopperPlate/AreaBalance set an identity reduction). It is `Union{Nothing,...}` on the IOM side. Concrete `BranchReductionOptimizationTracker` + `get_constraint_map_by_type` live in POM `network_models/network_reductions.jl`; IOM keeps only the abstract `AbstractBranchReductionTracker`. IOM evaluators must be `<:IOM.AbstractEvaluator`; POM wraps PF models in `PowerFlowEvaluator` (`core/interfaces.jl`).
- **Parallel branches** between the same `(from_bus, to_bus)` pair are reduced to one equivalent branch by PowerNetworkMatrices **before** reaching POM. Index branch-pair-keyed variables (LPAC cosine vars, voltage approximations) by branch name directly — do not add dedupe bookkeeping; it's a non-problem at this layer.
- **Security-constrained N-1 uses MODF** (Modified Outage Distribution Factors: PNM `VirtualMODF`/`ContingencySpec`), in `ac_transmission_models/security_constrained_branch.jl`. The old LODF-based `network_models/security_constrained_models.jl` and all generator-side (G-1) SC have been **removed** — do not reintroduce LODF or gen-side MODF. `NetworkModel.MODF_matrix`, `DeviceModel.outages`, and `supports_outages` (default false; POM specializes `true` for `AbstractSecurityConstrainedStaticBranch`) live in IOM.
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ PowerFlows = "94fada2c-fd9a-4e89-8d82-81405f5cb4f6"
[sources]
InfrastructureSystems = {rev = "IS4", url = "https://github.com/Sienna-Platform/InfrastructureSystems.jl"}
PowerSystems = {rev = "psy6", url = "https://github.com/Sienna-Platform/PowerSystems.jl"}
InfrastructureOptimizationModels = {rev = "main", url = "https://github.com/Sienna-Platform/InfrastructureOptimizationModels.jl"}
InfrastructureOptimizationModels = {rev = "ac/move-op-models", url = "https://github.com/Sienna-Platform/InfrastructureOptimizationModels.jl"}
PowerNetworkMatrices = {rev = "psy6", url = "https://github.com/Sienna-Platform/PowerNetworkMatrices.jl"}

[extensions]
Expand Down
22 changes: 13 additions & 9 deletions src/PowerOperationsModels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,14 @@ import InfrastructureOptimizationModels:
DecisionModel,
EmulationModel,
make_empty_jump_model_with_settings,
set_model!
set_model!,
# Operation-model lifecycle: build!/solve!/run! live in IOM (re-exported below);
# build_problem!/build_initial_conditions! are IOM extension points POM implements.
build!,
solve!,
run!,
build_problem!,
build_initial_conditions!

using InfrastructureOptimizationModels # TODO: use explicit imports.

Expand Down Expand Up @@ -349,9 +356,10 @@ import InfrastructureOptimizationModels:
CONTAINER_KEY_EMPTY_META

# Import high-frequency IOM internals used throughout operation lifecycle code.
# Note: BUILD_PROBLEMS_TIMER and RUN_OPERATION_MODEL_TIMER are defined in POM's
# definitions.jl, so they are NOT imported from IOM.
# `BUILD_PROBLEMS_TIMER` is shared with IOM so POM's `build_problem!` timings aggregate
# into the same timer printout produced by IOM's `build!`/`solve!`.
import InfrastructureOptimizationModels:
BUILD_PROBLEMS_TIMER,
LOG_GROUP_OPTIMIZATION_CONTAINER,
get_store,
set_status!,
Expand All @@ -378,10 +386,7 @@ export EmulationModel
export PowerOperationsProblemTemplate
export InitialCondition
export AbstractPowerOperationProblem
export AbstractPowerDecisionProblem
export AbstractPowerEmulationProblem
export DefaultPowerDecisionProblem
export DefaultPowerEmulationProblem
export DefaultPowerOperationProblem

# Network
export NetworkModel
Expand Down Expand Up @@ -476,8 +481,7 @@ export RunStatus
export SimulationBuildStatus

# Problem Types
export GenericPowerDecisionProblem
export GenericPowerEmulationProblem
export GenericPowerOperationProblem

# Settings and Data Types
export Settings
Expand Down
4 changes: 0 additions & 4 deletions src/core/definitions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ const NamedMinMax = Tuple{String, MinMax}
const UpDown = NamedTuple{(:up, :down), NTuple{2, Float64}}
const InOut = NamedTuple{(:in, :out), NTuple{2, Float64}}

const BUILD_PROBLEMS_TIMER = TimerOutputs.TimerOutput()
const RUN_OPERATION_MODEL_TIMER = TimerOutputs.TimerOutput()
const RUN_SIMULATION_TIMER = TimerOutputs.TimerOutput()

# Type Alias for JuMP containers
const JuMPOrFloat = Union{JuMP.AbstractJuMPScalar, Float64}
const GAE = JuMP.GenericAffExpr{Float64, JuMP.VariableRef}
Expand Down
51 changes: 14 additions & 37 deletions src/core/problem_types.jl
Original file line number Diff line number Diff line change
@@ -1,53 +1,30 @@
#################################################################################
# Operation-problem type hierarchy
#
# IOM (#104 "Redistribute operation models") is a domain-neutral optimization
# library: it owns `DecisionModel{M}`/`EmulationModel{M}` and parameterizes them
# over the single abstract tag `IOM.AbstractOptimizationProblem`. The concrete,
# power-flavoured problem-type chain lives here in POM. POM-side methods dispatch
# on `DecisionModel{<:AbstractPowerOperationProblem}` (and its sub-tags) while IOM keeps only
# error-stub / extension-point methods on the neutral abstract.
# IOM is a domain-neutral optimization library: it owns `DecisionModel{M}`/
# `EmulationModel{M}` and parameterizes them over `IOM.AbstractOptimizationProblem`.
# The decision↔emulation distinction is the wrapper's job (DecisionModel vs
# EmulationModel), so POM's power problem chain is a single linear hierarchy: the
# concrete problem type only describes domain content, not the solve strategy.
#################################################################################

"""
Umbrella supertype for every PowerOperationsModels optimization problem. A
`DecisionModel`/`EmulationModel` parameterized over a subtype of this is a POM
operation model; dispatch on `DecisionModel{<:AbstractPowerOperationProblem}` selects the
POM-side implementations of IOM's extension points.
operation model.
"""
abstract type AbstractPowerOperationProblem <: IOM.AbstractOptimizationProblem end

"""
Supertype for single-period (decision) operation problems solved by `DecisionModel`.
Operation problems whose build/validate behavior is fully driven by the

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.

Don't like this docstring

`ProblemTemplate` (the common case). The seam for a future custom-build problem is
to subtype `AbstractPowerOperationProblem` directly instead of this.
"""
abstract type AbstractPowerDecisionProblem <: AbstractPowerOperationProblem end
abstract type GenericPowerOperationProblem <: AbstractPowerOperationProblem end

"""
Supertype for rolling-horizon (emulation) operation problems solved by `EmulationModel`.
Default concrete template-driven operation problem. The `M` used when a

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.

This comment is confusing.

`DecisionModel`/`EmulationModel` is built from a template without an explicit problem
type.
"""
abstract type AbstractPowerEmulationProblem <: AbstractPowerOperationProblem end

"""
Decision problems whose build/validate behavior is fully driven by the
`ProblemTemplate` (the common case). `DecisionModel{<:GenericPowerDecisionProblem}`
gets the generic template-driven `build_model!`/`validate_template` methods.
"""
abstract type GenericPowerDecisionProblem <: AbstractPowerDecisionProblem end

"""
Emulation problems whose build/validate behavior is fully driven by the
`ProblemTemplate`.
"""
abstract type GenericPowerEmulationProblem <: AbstractPowerEmulationProblem end

"""
Default concrete template-driven decision problem. The `M` used when a `DecisionModel`
is built from a template without an explicit problem type.
"""
struct DefaultPowerDecisionProblem <: GenericPowerDecisionProblem end

"""
Default concrete template-driven emulation problem. The `M` used when an `EmulationModel`
is built from a template without an explicit problem type.
"""
struct DefaultPowerEmulationProblem <: GenericPowerEmulationProblem end
struct DefaultPowerOperationProblem <: GenericPowerOperationProblem end
25 changes: 0 additions & 25 deletions src/initial_conditions/initialization.jl
Original file line number Diff line number Diff line change
Expand Up @@ -126,28 +126,3 @@ function build_initial_conditions!(model::IOM.AbstractOptimizationModel)
end
return
end

# Called `initialize!` in PSI (lived in operation_model_interface.jl).
function solve_and_write_initial_conditions!(model::IOM.AbstractOptimizationModel)
container = get_optimization_container(model)
if get_initial_conditions_model_container(get_internal(model)) === nothing
return
end
@info "Solving Initialization Model for $(get_name(model))"
status = IOM.execute_optimizer!(
get_initial_conditions_model_container(get_internal(model)),
get_system(model),
)
if status == RunStatus.FAILED
error("Model failed to initialize")
end

IOM.write_initial_conditions_data!(
container,
get_initial_conditions_model_container(get_internal(model)),
)
init_file = IOM.get_initial_conditions_file(model)
Serialization.serialize(init_file, IOM.get_initial_conditions_data(container))
@info "Serialized initial conditions to $init_file"
return
end
Loading
Loading