diff --git a/.claude/iom_port_plan.md b/.claude/iom_port_plan.md new file mode 100644 index 0000000..a46e57a --- /dev/null +++ b/.claude/iom_port_plan.md @@ -0,0 +1,141 @@ +# IOM port plan — PSI → InfrastructureOptimizationModels.jl + +Scope: bring IOM (`InfrastructureOptimizationModels.jl`, the **generic optimization core** that POM +depends on) up to date with PSI (`PowerSimulations.jl`). IOM owns what POM does not: the +optimization container, dual processing, settings, serialization, parameter machinery, build/solve +lifecycle, objective-function machinery, and model-export. + +Companion file: `pom_port_plan.md` (formulation changes + in-progress G-1 → PowerOperationsModels). + +## Topology / ground rules +- A PSI change belongs in IOM iff it touches generic optimization infrastructure (NOT formulation-, + device-, network-, or simulation-specific). Formulation parts go to POM; many PSI PRs are **SPLIT**. +- IOM independently tracks PSI and has its **own** PSI-port history (commits cite PSI #1559, 1568, + 1569, 1579, 1637). IOM sometimes **improves on** PSI (e.g. stores the in-flight serialization + `Task` on `OptimizationContainer.serialization_task` rather than PSI's `ext`-dict hack). +- Verify by **symbol**, not by PR-number reference. Fork baseline ≈ PSI #1503; latest PSI PR = #1640. + +--- + +## Confirmed NEEDS-PORT (symbol-verified ABSENT in IOM) + +### 1. PR #1585 — dual-cache MIP-tolerance rounding (PRIORITY) +`core/dual_processing.jl` still uses the old `Dict{Symbol,Any}` cache and is missing: +- `struct VarRestoreInfo` +- `_round_cache_values!` (rounds cached values to dodge MIP-tolerance infeasibility on integer restore) +- the integer-variable restore fix. + +→ `InfrastructureOptimizationModels.jl/src/core/dual_processing.jl`. +**⚠ Coordinate:** IOM appears to be independently reworking `dual_processing.jl`. Confirm with the +IOM maintainers that the rounding fix lands in the rework rather than blind-porting onto a moving file. + +### 2. PR #1559 + #1561 (IOM portion of DLR) — Dynamic Line Ratings +The DLR feature is absent from both clones. The formulation/network parts go to POM (see pom plan); +the **IOM portion** is the generic parameter-type registration / container plumbing for the new +`DynamicBranchRatingTimeSeriesParameter` (time-series parameter type + `add_param_container!` wiring), +if it is to live in IOM's `core/time_series_parameter_types.jl` / `core/parameter_container.jl` +rather than POM. **Confirm the intended home** before porting — DLR may be implemented entirely in +POM using IOM's existing generic param machinery, in which case there is no IOM work here. + +--- + +## Optional / minor residuals (decide if completeness matters) + +### PR #1539 (and the #1505/#1506 slack fix it consolidates) +IOM is mostly already ported (`assign_maybe_broadcast!`/`expand_ixs`/`fix_maybe_broadcast!` in +`src/utils/indexing.jl` are generalized; `decision_model_store.jl` has 3D `write_result!`). Residual +gaps if you want full parity: +- 1-D integer-indexed `write_result!(... DenseAxisArray{T,1,<:Tuple{UnitRange}})` (the #1506 slack + fix) — absent from `src/operation/decision_model_store.jl`. +- `emulation_model_store.jl` shows no 3D `write_result!` branch. +- `_update_parameter_values!` Service+EmulationModel method has no target (param-update path is + restructured in IOM — likely intentional, verify). + +These are low-value unless a concrete failure points at them. + +--- + +## Already ported to IOM — do NOT redo (symbol-verified PRESENT) +For reviewer confidence; these PSI changes are confirmed present in IOM: +- **#1568** — perf consolidation (`dual_processing.jl`, optimization-container/settings trims, lifecycle). +- **#1563 / #1564** — production-cost expression refactor: `ConstituentCostExpression`, + `FuelCostExpression`, etc. + `optimization_container` generalization (device cost terms live in POM). +- **#1591** — decision-model `interval` / `get_interval` support. +- **#1609** — parameter-broadcast fast path: `get_parameter_array_data`, `_set_parameter_at!`, + `_set_multiplier_at!` (`core/parameter_container.jl`). +- **#1625 / #1626** — `OptimizationModelExportFormat` enum (`core/definitions.jl`), + `Settings.export_optimization_model` + `_validate_export_optimization_model` (`core/settings.jl`), + backgrounded serialization (`serialize_optimization_model`, `wait_for_serialization!`, + `_copy_jump_model_for_export`/`_write_export_model`) — refined beyond PSI (task on + `OptimizationContainer.serialization_task`). The `branches_modeled` trait is the POM half. +- **#1629** — time-varying ORDC objective params: generic `add_param_container!` for + `ObjectiveFunctionParameter` (`optimization_container.jl:1055`) + `*PiecewiseLinearSlopeParameter` + types (`src/objective_function/`). +- **#1633 / #1637** — SC slacks + pf/print fixes (IOM uses `column_labels` directly; the PrettyTables + v2/v3 shim PSI added is unnecessary in IOM). +- **#1569** — serialize-System-into-HDF lifecycle (its surface `store_system_in_results` lives in POM + operation models; the HDF write is simulation-layer = PSI-only). + +--- + +## PSI-only (never IOM): docs, CI, version bumps, results-IO (`to_results_dataframe`, +`table_format` cache reads), simulation_state/partitions/store, PowerFlows-naming bumps. + +--- + +## Suggested execution order +1. **#1585** — coordinate with the in-flight `dual_processing.jl` rework, then land the rounding fix. +2. **DLR IOM portion** — only after confirming DLR's param-type home is IOM (else it's all POM). +3. **#1539 residuals** — optional; do only if a failure implicates the 1-D/3D `write_result!` paths. + +--- + +## Execution log (2026-06-26, verified against local PSI clone) + +### 1. PR #1585 — **PORTED** (branch `ac/port-psi-1585-dual-mip-rounding`) +Did NOT blind-port PSI's `VarRestoreInfo` struct refactor. IOM's `dual_processing.jl` has +intentionally diverged and is *ahead* of PSI: it adds the `axes(dual) == axes(constraint)` +assertion in `_copy_dual_values!`, a sparse binary/integer relaxation warning, a detailed +`error` instead of `@assert !isempty(cache)`, and re-fixes fixed *binary* vars (not just +integer) via `_refix!`. Porting the struct would have undone those. + +The actual bug #1585 fixes is **MIP-tolerance rounding** (a solver returns `0.9999997` +instead of `1.0`; fixing an integer var to that makes the relaxation infeasible). Landed the +functional fix only: +- Added `_round_cache_values!(cache::DenseAxisArray)` — only the dense method, because IOM + `continue`s past `SparseAxisArray` containers before any fixing, so a sparse method would + be dead code. +- Call it on `var_cache[key]` immediately before `JuMP.fix.(...)`. IOM's restore path already + reuses `var_cache[key]` through `_refix!`, so rounding once also covers the integer-restore + path PSI patched separately. +- Added a regression test (`_round_cache_values! snaps MIP-tolerance values to integers`). + +Full suite green: 1330/1330 unit + 10/10 Aqua. + +### 2. DLR IOM portion — **NO IOM WORK** (home confirmed = POM) +The DLR time-series rating parameter types live entirely in POM, not IOM: +`AbstractBranchRatingTimeSeriesParameter`, `BranchRatingTimeSeriesParameter`, +`PostContingencyBranchRatingTimeSeriesParameter` are defined in POM +`src/core/parameters.jl` and exported from `PowerOperationsModels.jl` (≈ lines 918–919). +They are referenced only by POM branch/network formulations +(`template_validation.jl`, `instantiate_network_model.jl`, `add_parameters.jl`, the AC-branch +device models) and build on IOM's existing generic `TimeSeriesParameter` + `add_param_container!` +machinery. IOM's `core/time_series_parameter_types.jl` deliberately keeps only the small +domain-agnostic set (`ActivePower*`, `ReactivePower*`, `Requirement*`). So the "confirm the +intended home" question resolves to POM — nothing to add in IOM. + +### 3. PR #1539 residuals — **1-D `UnitRange` `write_output!` PORTED**; 3-D already present +IOM renamed PSI's `write_result!` to `write_output!` in `src/operation/decision_model_store.jl`. +The genuine #1539 broadcast generalizations are present and verified: `expand_ixs`, +`assign_maybe_broadcast!`, `fix_maybe_broadcast!` in `src/utils/indexing.jl`. + +Re-checking the store dispatch by symbol (not by name) found a real gap, not a stale ref: +`initialize_storage!` allocates a time-only result (empty `column_names`) as a 1-D `UnitRange` +`DenseAxisArray`, but `write_output!` had no `DenseAxisArray{T, 1, <:Tuple{UnitRange}}` method — +only the 1-D `Vector{String}` one — so writing such a result would `MethodError`. This is exactly +PSI's #1506 system-slack method consolidated by #1539. **Ported** the 4-line method (mirrors the +existing 1-D `Vector{String}` method) plus a `DecisionModelStore` write/read round-trip test in +`test/test_model_store.jl`. The 3-D paths were already covered (IOM carries two 3-D methods, one +more than PSI). The EmulationModel store already handled 1-D generically +(`DenseAxisArray{Float64, 1}`), so only the decision store needed the fix. "No failures" reflected +that no current IOM/POM formulation builds a time-only result, not robustness. diff --git a/src/core/dual_processing.jl b/src/core/dual_processing.jl index b621850..d2f413a 100644 --- a/src/core/dual_processing.jl +++ b/src/core/dual_processing.jl @@ -59,6 +59,7 @@ function process_duals(container::OptimizationContainer, lp_optimizer) :ub => _upper_bound_or_nothing.(variable), :fixed => JuMP.is_fixed.(variable), ) + var_cache[key].data .= round.(var_cache[key].data) JuMP.fix.(variable, var_cache[key]; force = true) end if isempty(cache) diff --git a/src/operation/decision_model_store.jl b/src/operation/decision_model_store.jl index 471870a..4018779 100644 --- a/src/operation/decision_model_store.jl +++ b/src/operation/decision_model_store.jl @@ -99,6 +99,19 @@ function write_output!( return end +function write_output!( + store::DecisionModelStore, + name::Symbol, + key::OptimizationContainerKey, + index::DecisionModelIndexType, + update_timestamp::Dates.DateTime, + array::DenseAxisArray{T, 1, <:Tuple{UnitRange}}, +) where {T} + container = getfield(store, get_store_container_type(key)) + container[key][index] = array + return +end + function write_output!( store::DecisionModelStore, name::Symbol, diff --git a/test/InfrastructureOptimizationModelsTests.jl b/test/InfrastructureOptimizationModelsTests.jl index 7e0e315..dbf9855 100644 --- a/test/InfrastructureOptimizationModelsTests.jl +++ b/test/InfrastructureOptimizationModelsTests.jl @@ -137,6 +137,7 @@ function run_tests() include(joinpath(TEST_DIR, "test_ramp_constraints.jl")) include(joinpath(TEST_DIR, "test_duration_constraints.jl")) include(joinpath(TEST_DIR, "test_emulation_model_store.jl")) + include(joinpath(TEST_DIR, "test_model_store.jl")) # --- quadratic_approximations/ subfolder --- include(joinpath(TEST_DIR, "test_quadratic_approximations.jl")) @@ -152,7 +153,7 @@ function run_tests() ============================================================================ - test_basic_model_structs.jl, test_model_decision.jl, test_model_emulation.jl: previously used PSY/PowerModels types; rewrite with mocks before re-enabling. - - test_model_store.jl, test_offer_curve_cost.jl: PSY/PSB-backed integration tests + - test_offer_curve_cost.jl: PSY/PSB-backed integration tests removed when IOM dropped PSY/PSB dependencies; rewrite with mocks. ============================================================================ =# diff --git a/test/mocks/mock_components.jl b/test/mocks/mock_components.jl index 75b990d..ccaae11 100644 --- a/test/mocks/mock_components.jl +++ b/test/mocks/mock_components.jl @@ -10,11 +10,10 @@ These types can be used: using InfrastructureOptimizationModels using InfrastructureSystems -const PSI = InfrastructureOptimizationModels const IS = InfrastructureSystems # Mock formulation type for testing DeviceModel -struct TestDeviceFormulation <: PSI.AbstractDeviceFormulation end +struct TestDeviceFormulation <: IOM.AbstractDeviceFormulation end struct TestPowerModel <: IS.Optimization.AbstractPowerModel end # Mock operation costs for testing objective function construction. diff --git a/test/test_basic_model_structs.jl b/test/test_basic_model_structs.jl index ecbae94..6961565 100644 --- a/test/test_basic_model_structs.jl +++ b/test/test_basic_model_structs.jl @@ -1,6 +1,6 @@ @testset "DeviceModel Tests" begin @test_throws ArgumentError DeviceModel(ThermalGen, ThermalStandardUnitCommitment) - @test_throws ArgumentError DeviceModel(ThermalStandard, PSI.AbstractThermalFormulation) + @test_throws ArgumentError DeviceModel(ThermalStandard, IOM.AbstractThermalFormulation) @test_throws ArgumentError NetworkModel(AbstractPowerModel) end @@ -62,8 +62,8 @@ end ] for ff in ffs - for av in PSI.get_affected_values(ff) - @test isa(av, PSI.VariableKey) + for av in IOM.get_affected_values(ff) + @test isa(av, IOM.VariableKey) end end @@ -73,8 +73,8 @@ end affected_values = [OnStatusParameter], ) - for av in PSI.get_affected_values(ff) - @test isa(av, PSI.ParameterKey) + for av in IOM.get_affected_values(ff) + @test isa(av, IOM.ParameterKey) end @test_throws ErrorException UpperBoundFeedforward( diff --git a/test/test_device_model.jl b/test/test_device_model.jl index e157206..5e0a77c 100644 --- a/test/test_device_model.jl +++ b/test/test_device_model.jl @@ -6,111 +6,106 @@ Uses mock types from mocks/mock_components.jl - no PowerSystems dependency. using Test using InfrastructureOptimizationModels -# Define aliases if not already defined (mock_components.jl defines PSI) -if !@isdefined(PSI) - const PSI = InfrastructureOptimizationModels -end - # TestDeviceFormulation, MockThermalGen, MockRenewableGen, MockLoad, # AbstractMockDevice, AbstractMockGenerator are defined in mocks/mock_components.jl @testset "DeviceModel" begin @testset "Construction with defaults" begin - model = PSI.DeviceModel(MockThermalGen, TestDeviceFormulation) - - @test PSI.get_component_type(model) == MockThermalGen - @test PSI.get_formulation(model) == TestDeviceFormulation - @test isempty(PSI.get_feedforwards(model)) - @test PSI.get_use_slacks(model) == false - @test isempty(PSI.get_duals(model)) - @test isempty(PSI.get_services(model)) - @test PSI.get_subsystem(model) === nothing - @test !PSI.has_service_model(model) + model = IOM.DeviceModel(MockThermalGen, TestDeviceFormulation) + + @test IOM.get_component_type(model) == MockThermalGen + @test IOM.get_formulation(model) == TestDeviceFormulation + @test isempty(IOM.get_feedforwards(model)) + @test IOM.get_use_slacks(model) == false + @test isempty(IOM.get_duals(model)) + @test isempty(IOM.get_services(model)) + @test IOM.get_subsystem(model) === nothing + @test !IOM.has_service_model(model) end @testset "Construction with custom values" begin - model = PSI.DeviceModel( + model = IOM.DeviceModel( MockThermalGen, TestDeviceFormulation; use_slacks = true, attributes = Dict{String, Any}("custom_attr" => 42), ) - @test PSI.get_use_slacks(model) == true - @test PSI.get_attribute(model, "custom_attr") == 42 - @test PSI.get_attribute(model, "nonexistent") === nothing + @test IOM.get_use_slacks(model) == true + @test IOM.get_attribute(model, "custom_attr") == 42 + @test IOM.get_attribute(model, "nonexistent") === nothing end @testset "Attributes merging" begin # Custom attributes should merge with defaults from get_default_attributes - model = PSI.DeviceModel( + model = IOM.DeviceModel( MockThermalGen, TestDeviceFormulation; attributes = Dict{String, Any}("my_key" => "my_value"), ) - attrs = PSI.get_attributes(model) + attrs = IOM.get_attributes(model) @test attrs isa Dict{String, Any} @test attrs["my_key"] == "my_value" end @testset "Time series names" begin - model = PSI.DeviceModel(MockThermalGen, TestDeviceFormulation) - ts_names = PSI.get_time_series_names(model) + model = IOM.DeviceModel(MockThermalGen, TestDeviceFormulation) + ts_names = IOM.get_time_series_names(model) @test ts_names isa Dict end @testset "Subsystem management" begin - model = PSI.DeviceModel(MockThermalGen, TestDeviceFormulation) + model = IOM.DeviceModel(MockThermalGen, TestDeviceFormulation) - @test PSI.get_subsystem(model) === nothing + @test IOM.get_subsystem(model) === nothing - PSI.set_subsystem!(model, "subsystem_1") - @test PSI.get_subsystem(model) == "subsystem_1" + IOM.set_subsystem!(model, "subsystem_1") + @test IOM.get_subsystem(model) == "subsystem_1" end @testset "get_attribute with nothing model" begin - @test PSI.get_attribute(nothing, "any_key") === nothing + @test IOM.get_attribute(nothing, "any_key") === nothing end @testset "get_services with nothing" begin - @test PSI.get_services(nothing) === nothing + @test IOM.get_services(nothing) === nothing end @testset "_check_device_formulation rejects abstract types" begin # Should reject abstract device type - @test_throws ArgumentError PSI._check_device_formulation(AbstractMockDevice) - @test_throws ArgumentError PSI._check_device_formulation(AbstractMockGenerator) + @test_throws ArgumentError IOM._check_device_formulation(AbstractMockDevice) + @test_throws ArgumentError IOM._check_device_formulation(AbstractMockGenerator) # Should reject abstract formulation type - @test_throws ArgumentError PSI._check_device_formulation( - PSI.AbstractDeviceFormulation, + @test_throws ArgumentError IOM._check_device_formulation( + IOM.AbstractDeviceFormulation, ) # Should accept concrete types - @test PSI._check_device_formulation(MockThermalGen) === nothing - @test PSI._check_device_formulation(TestDeviceFormulation) === nothing + @test IOM._check_device_formulation(MockThermalGen) === nothing + @test IOM._check_device_formulation(TestDeviceFormulation) === nothing end @testset "DeviceModel rejects abstract types in constructor" begin # Abstract device type should fail - @test_throws ArgumentError PSI.DeviceModel( + @test_throws ArgumentError IOM.DeviceModel( AbstractMockDevice, TestDeviceFormulation, ) # Abstract formulation type should fail - @test_throws ArgumentError PSI.DeviceModel( + @test_throws ArgumentError IOM.DeviceModel( MockThermalGen, - PSI.AbstractDeviceFormulation, + IOM.AbstractDeviceFormulation, ) end @testset "set_model!" begin dict = Dict{Symbol, Any}() - model = PSI.DeviceModel(MockThermalGen, TestDeviceFormulation) + model = IOM.DeviceModel(MockThermalGen, TestDeviceFormulation) - PSI.set_model!(dict, model) + IOM.set_model!(dict, model) @test haskey(dict, :MockThermalGen) @test dict[:MockThermalGen] === model @@ -118,31 +113,31 @@ end @testset "set_model! warns on overwrite" begin dict = Dict{Symbol, Any}() - model1 = PSI.DeviceModel(MockThermalGen, TestDeviceFormulation) - model2 = PSI.DeviceModel(MockThermalGen, TestDeviceFormulation) + model1 = IOM.DeviceModel(MockThermalGen, TestDeviceFormulation) + model2 = IOM.DeviceModel(MockThermalGen, TestDeviceFormulation) - PSI.set_model!(dict, model1) + IOM.set_model!(dict, model1) # Second call should warn about overwriting - @test_logs (:warn, r"Overwriting.*existing model") PSI.set_model!(dict, model2) + @test_logs (:warn, r"Overwriting.*existing model") IOM.set_model!(dict, model2) @test dict[:MockThermalGen] === model2 end @testset "Multiple device types" begin # Test with different mock device types - thermal_model = PSI.DeviceModel(MockThermalGen, TestDeviceFormulation) - @test PSI.get_component_type(thermal_model) == MockThermalGen + thermal_model = IOM.DeviceModel(MockThermalGen, TestDeviceFormulation) + @test IOM.get_component_type(thermal_model) == MockThermalGen - renewable_model = PSI.DeviceModel(MockRenewableGen, TestDeviceFormulation) - @test PSI.get_component_type(renewable_model) == MockRenewableGen + renewable_model = IOM.DeviceModel(MockRenewableGen, TestDeviceFormulation) + @test IOM.get_component_type(renewable_model) == MockRenewableGen - load_model = PSI.DeviceModel(MockLoad, TestDeviceFormulation) - @test PSI.get_component_type(load_model) == MockLoad + load_model = IOM.DeviceModel(MockLoad, TestDeviceFormulation) + @test IOM.get_component_type(load_model) == MockLoad end @testset "FixedOutput formulation" begin # FixedOutput is defined in device_model.jl - model = PSI.DeviceModel(MockThermalGen, PSI.FixedOutput) - @test PSI.get_formulation(model) == PSI.FixedOutput + model = IOM.DeviceModel(MockThermalGen, IOM.FixedOutput) + @test IOM.get_formulation(model) == IOM.FixedOutput end end diff --git a/test/test_duration_constraints.jl b/test/test_duration_constraints.jl index 6e0a8f2..c1b08e1 100644 --- a/test/test_duration_constraints.jl +++ b/test/test_duration_constraints.jl @@ -7,17 +7,17 @@ permanently-#undef rows for asymmetric up/down ICs. function _make_duration_test_container(names, time_steps) mock_sys = MockSystem(100.0) - settings = PSI.Settings( + settings = IOM.Settings( mock_sys; horizon = Dates.Hour(length(time_steps)), resolution = Dates.Hour(1), time_series_cache_size = 0, ) - container = PSI.OptimizationContainer(mock_sys, settings, nothing, MockDeterministic) - PSI.set_time_steps!(container, time_steps) - jump_model = PSI.get_jump_model(container) - for T in (PSI.OnVariable, PSI.StartVariable, PSI.StopVariable) - var = PSI.add_variable_container!(container, T, MockThermalGen, names, time_steps) + container = IOM.OptimizationContainer(mock_sys, settings, nothing, MockDeterministic) + IOM.set_time_steps!(container, time_steps) + jump_model = IOM.get_jump_model(container) + for T in (IOM.OnVariable, IOM.StartVariable, IOM.StopVariable) + var = IOM.add_variable_container!(container, T, MockThermalGen, names, time_steps) for name in names, t in time_steps var[name, t] = JuMP.@variable(jump_model, binary = true) end @@ -32,15 +32,15 @@ end dev_b = make_mock_thermal("B") # A has only an up IC; B has only a down IC. The element type must be the # abstract InitialCondition to match the builders' Matrix{InitialCondition} signature. - ics = PSI.InitialCondition[ - PSI.InitialCondition(MockInitialCondition, dev_a, 2.0) PSI.InitialCondition{ + ics = IOM.InitialCondition[ + IOM.InitialCondition(MockInitialCondition, dev_a, 2.0) IOM.InitialCondition{ MockInitialCondition, Nothing }( dev_a, nothing ) - PSI.InitialCondition{MockInitialCondition, Nothing}(dev_b, nothing) PSI.InitialCondition( + IOM.InitialCondition{MockInitialCondition, Nothing}(dev_b, nothing) IOM.InitialCondition( MockInitialCondition, dev_b, 3.0 @@ -49,15 +49,15 @@ end duration_data = [(up = 2.0, down = 2.0), (up = 2.0, down = 2.0)] container = _make_duration_test_container(names, time_steps) - PSI.device_duration_retrospective!( + IOM.device_duration_retrospective!( container, duration_data, ics, TestConstraintType, MockThermalGen, ) - con_up = PSI.get_constraint(container, TestConstraintType, MockThermalGen, "up") - con_down = PSI.get_constraint(container, TestConstraintType, MockThermalGen, "dn") + con_up = IOM.get_constraint(container, TestConstraintType, MockThermalGen, "up") + con_down = IOM.get_constraint(container, TestConstraintType, MockThermalGen, "dn") @test axes(con_up)[1] == ["A"] @test axes(con_down)[1] == ["B"] # Every container slot must be written — no #undef rows. @@ -65,7 +65,7 @@ end @test all(i -> isassigned(con_down.data, i), eachindex(con_down.data)) container = _make_duration_test_container(names, time_steps) - PSI.device_duration_look_ahead!( + IOM.device_duration_look_ahead!( container, duration_data, ics, @@ -73,23 +73,23 @@ end TestCostConstraint, MockThermalGen, ) - con_up = PSI.get_constraint(container, TestConstraintType, MockThermalGen) - con_down = PSI.get_constraint(container, TestCostConstraint, MockThermalGen) + con_up = IOM.get_constraint(container, TestConstraintType, MockThermalGen) + con_down = IOM.get_constraint(container, TestCostConstraint, MockThermalGen) @test axes(con_up)[1] == ["A"] @test axes(con_down)[1] == ["B"] @test all(i -> isassigned(con_up.data, i), eachindex(con_up.data)) @test all(i -> isassigned(con_down.data, i), eachindex(con_down.data)) container = _make_duration_test_container(names, time_steps) - PSI.device_duration_parameters!( + IOM.device_duration_parameters!( container, duration_data, ics, TestConstraintType, MockThermalGen, ) - con_up = PSI.get_constraint(container, TestConstraintType, MockThermalGen, "up") - con_down = PSI.get_constraint(container, TestConstraintType, MockThermalGen, "dn") + con_up = IOM.get_constraint(container, TestConstraintType, MockThermalGen, "up") + con_down = IOM.get_constraint(container, TestConstraintType, MockThermalGen, "dn") @test axes(con_up)[1] == ["A"] @test axes(con_down)[1] == ["B"] @test all(i -> isassigned(con_up.data, i), eachindex(con_up.data)) diff --git a/test/test_emulation_model_store.jl b/test/test_emulation_model_store.jl index 3a0415b..ff0ff68 100644 --- a/test/test_emulation_model_store.jl +++ b/test/test_emulation_model_store.jl @@ -5,21 +5,21 @@ DatasetContainer fields (Task 2.10). """ @testset "EmulationModelStore accessors and empty!/isempty" begin - store = PSI.EmulationModelStore() + store = IOM.EmulationModelStore() @test isempty(store) - key = PSI.VariableKey(TestVariableType, MockComponentType) + key = IOM.VariableKey(TestVariableType, MockComponentType) data = DenseAxisArray(zeros(2, 3), ["d1", "d2"], 1:3) - PSI.set_dataset!(store.data_container, key, PSI.InMemoryDataset(data)) + IOM.set_dataset!(store.data_container, key, IOM.InMemoryDataset(data)) @test !isempty(store) # Generated accessors previously called getfield(store, :variables), which # errors for EmulationModelStore (containers live inside data_container). - @test PSI.list_keys(store, PSI.VariableType) == [key] - @test collect(PSI.list_fields(store, PSI.VariableType)) == [key] - @test PSI.get_value(store, TestVariableType, MockComponentType).values == data + @test IOM.list_keys(store, IOM.VariableType) == [key] + @test collect(IOM.list_fields(store, IOM.VariableType)) == [key] + @test IOM.get_value(store, TestVariableType, MockComponentType).values == data empty!(store) @test isempty(store) - @test isempty(PSI.list_keys(store, PSI.VariableType)) + @test isempty(IOM.list_keys(store, IOM.VariableType)) end diff --git a/test/test_model_emulation.jl b/test/test_model_emulation.jl index 581162e..d84c235 100644 --- a/test/test_model_emulation.jl +++ b/test/test_model_emulation.jl @@ -11,8 +11,8 @@ model = EmulationModel(template, c_sys5; optimizer = HiGHS_optimizer) @test build!(model; executions = 10, output_dir = mktempdir(; cleanup = true)) == - PSI.ModelBuildStatus.BUILT - @test run!(model) == PSI.RunStatus.SUCCESSFULLY_FINALIZED + IOM.ModelBuildStatus.BUILT + @test run!(model) == IOM.RunStatus.SUCCESSFULLY_FINALIZED template = get_thermal_standard_uc_template() c_sys5_uc_re = PSB.build_system( @@ -25,9 +25,9 @@ model = EmulationModel(template, c_sys5_uc_re; optimizer = HiGHS_optimizer) @test build!(model; executions = 10, output_dir = mktempdir(; cleanup = true)) == - PSI.ModelBuildStatus.BUILT - @test run!(model) == PSI.RunStatus.SUCCESSFULLY_FINALIZED - @test !isempty(collect(readdir(PSI.get_recorder_dir(model)))) + IOM.ModelBuildStatus.BUILT + @test run!(model) == IOM.RunStatus.SUCCESSFULLY_FINALIZED + @test !isempty(collect(readdir(IOM.get_recorder_dir(model)))) end @testset "Emulation Model initial_conditions test for ThermalGen" begin @@ -42,10 +42,10 @@ end set_device_model!(template, RenewableDispatch, RenewableFullDispatch) model = EmulationModel(template, c_sys5_uc_re; optimizer = HiGHS_optimizer) @test build!(model; executions = 10, output_dir = mktempdir(; cleanup = true)) == - PSI.ModelBuildStatus.BUILT + IOM.ModelBuildStatus.BUILT check_duration_on_initial_conditions_values(model, ThermalStandard) check_duration_off_initial_conditions_values(model, ThermalStandard) - @test run!(model) == PSI.RunStatus.SUCCESSFULLY_FINALIZED + @test run!(model) == IOM.RunStatus.SUCCESSFULLY_FINALIZED ######## Test with ThermalMultiStartUnitCommitment ######## template = get_thermal_standard_uc_template() @@ -58,13 +58,13 @@ end set_device_model!(template, ThermalMultiStart, ThermalMultiStartUnitCommitment) model = EmulationModel(template, c_sys5_uc; optimizer = HiGHS_optimizer) @test build!(model; executions = 1, output_dir = mktempdir(; cleanup = true)) == - PSI.ModelBuildStatus.BUILT + IOM.ModelBuildStatus.BUILT check_duration_on_initial_conditions_values(model, ThermalStandard) check_duration_off_initial_conditions_values(model, ThermalStandard) check_duration_on_initial_conditions_values(model, ThermalMultiStart) check_duration_off_initial_conditions_values(model, ThermalMultiStart) - @test run!(model) == PSI.RunStatus.SUCCESSFULLY_FINALIZED + @test run!(model) == IOM.RunStatus.SUCCESSFULLY_FINALIZED ######## Test with ThermalStandardUnitCommitment ######## template = get_thermal_standard_uc_template() @@ -77,12 +77,12 @@ end set_device_model!(template, ThermalMultiStart, ThermalStandardUnitCommitment) model = EmulationModel(template, c_sys5_uc; optimizer = HiGHS_optimizer) @test build!(model; executions = 1, output_dir = mktempdir(; cleanup = true)) == - PSI.ModelBuildStatus.BUILT + IOM.ModelBuildStatus.BUILT check_duration_on_initial_conditions_values(model, ThermalStandard) check_duration_off_initial_conditions_values(model, ThermalStandard) check_duration_on_initial_conditions_values(model, ThermalMultiStart) check_duration_off_initial_conditions_values(model, ThermalMultiStart) - @test run!(model) == PSI.RunStatus.SUCCESSFULLY_FINALIZED + @test run!(model) == IOM.RunStatus.SUCCESSFULLY_FINALIZED ######## Test with ThermalStandardDispatch ######## template = get_thermal_standard_uc_template() @@ -92,11 +92,11 @@ end add_single_time_series = true, force_build = true, ) - device_model = DeviceModel(PSY.ThermalStandard, PSI.ThermalStandardDispatch) + device_model = DeviceModel(PSY.ThermalStandard, IOM.ThermalStandardDispatch) set_device_model!(template, device_model) model = EmulationModel(template, c_sys5_uc; optimizer = HiGHS_optimizer) @test build!(model; executions = 10, output_dir = mktempdir(; cleanup = true)) == - PSI.ModelBuildStatus.BUILT + IOM.ModelBuildStatus.BUILT end @testset "Emulation Model initial_conditions test for Hydro" begin @@ -113,15 +113,15 @@ end set_device_model!(template, HydroReservoir, HydroEnergyModelReservoir) model = EmulationModel(template, c_sys5_hyd; optimizer = HiGHS_optimizer) @test build!(model; executions = 10, output_dir = mktempdir(; cleanup = true)) == - PSI.ModelBuildStatus.BUILT + IOM.ModelBuildStatus.BUILT initial_conditions_data = - PSI.get_initial_conditions_data(PSI.get_optimization_container(model)) - @test !PSI.has_initial_condition_value( + IOM.get_initial_conditions_data(IOM.get_optimization_container(model)) + @test !IOM.has_initial_condition_value( initial_conditions_data, ActivePowerVariable, HydroTurbine, ) - @test run!(model) == PSI.RunStatus.SUCCESSFULLY_FINALIZED + @test run!(model) == IOM.RunStatus.SUCCESSFULLY_FINALIZED ######## Test with HydroCommitmentRunOfRiver ######## template = get_thermal_dispatch_template_network() @@ -137,15 +137,15 @@ end model = EmulationModel(template, c_sys5_hyd; optimizer = HiGHS_optimizer) @test build!(model; executions = 10, output_dir = mktempdir(; cleanup = true)) == - PSI.ModelBuildStatus.BUILT + IOM.ModelBuildStatus.BUILT initial_conditions_data = - PSI.get_initial_conditions_data(PSI.get_optimization_container(model)) - @test PSI.has_initial_condition_value( + IOM.get_initial_conditions_data(IOM.get_optimization_container(model)) + @test IOM.has_initial_condition_value( initial_conditions_data, OnVariable, HydroTurbine, ) - @test run!(model) == PSI.RunStatus.SUCCESSFULLY_FINALIZED + @test run!(model) == IOM.RunStatus.SUCCESSFULLY_FINALIZED end @testset "Emulation Model Outputs" begin @@ -164,25 +164,25 @@ end executions = executions, output_dir = mktempdir(; cleanup = true), ) == - PSI.ModelBuildStatus.BUILT - @test run!(model) == PSI.RunStatus.SUCCESSFULLY_FINALIZED + IOM.ModelBuildStatus.BUILT + @test run!(model) == IOM.RunStatus.SUCCESSFULLY_FINALIZED outputs = OptimizationProblemOutputs(model) @test list_aux_variable_names(outputs) == [] @test list_aux_variable_keys(outputs) == [] @test list_variable_names(outputs) == ["ActivePowerVariable__ThermalStandard"] @test list_variable_keys(outputs) == - [PSI.VariableKey(ActivePowerVariable, ThermalStandard)] + [IOM.VariableKey(ActivePowerVariable, ThermalStandard)] @test list_dual_names(outputs) == [] @test list_dual_keys(outputs) == [] @test list_parameter_names(outputs) == ["ActivePowerTimeSeriesParameter__PowerLoad"] @test list_parameter_keys(outputs) == - [PSI.ParameterKey(ActivePowerTimeSeriesParameter, PowerLoad)] + [IOM.ParameterKey(ActivePowerTimeSeriesParameter, PowerLoad)] @test read_variable(outputs, "ActivePowerVariable__ThermalStandard") isa DataFrame @test read_variable(outputs, ActivePowerVariable, ThermalStandard) isa DataFrame @test read_variable( outputs, - PSI.VariableKey(ActivePowerVariable, ThermalStandard), + IOM.VariableKey(ActivePowerVariable, ThermalStandard), ) isa DataFrame @@ -190,7 +190,7 @@ end @test read_parameter(outputs, ActivePowerTimeSeriesParameter, PowerLoad) isa DataFrame @test read_parameter( outputs, - PSI.ParameterKey(ActivePowerTimeSeriesParameter, PowerLoad), + IOM.ParameterKey(ActivePowerTimeSeriesParameter, PowerLoad), ) isa DataFrame @test read_optimizer_stats(model) isa DataFrame @@ -228,7 +228,7 @@ end executions = 10, output_dir = mktempdir(; cleanup = true), export_optimization_model = serialize, - ) == PSI.RunStatus.SUCCESSFULLY_FINALIZED + ) == IOM.RunStatus.SUCCESSFULLY_FINALIZED end end @@ -245,8 +245,8 @@ end model = EmulationModel(template, c_sys5; optimizer = HiGHS_optimizer) executions = 10 @test build!(model; executions = executions, output_dir = path) == - PSI.ModelBuildStatus.BUILT - @test run!(model; export_problem_outputs = true) == PSI.RunStatus.SUCCESSFULLY_FINALIZED + IOM.ModelBuildStatus.BUILT + @test run!(model; export_problem_outputs = true) == IOM.RunStatus.SUCCESSFULLY_FINALIZED outputs1 = OptimizationProblemOutputs(model) var1_a = read_variable(outputs1, ActivePowerVariable, ThermalStandard) # Ensure that we can deserialize strings into keys. @@ -254,7 +254,7 @@ end @test var1_a == var1_b # Outputs were automatically serialized here. - outputs2 = OptimizationProblemOutputs(PSI.get_output_dir(model)) + outputs2 = OptimizationProblemOutputs(IOM.get_output_dir(model)) var2 = read_variable(outputs2, ActivePowerVariable, ThermalStandard) @test var1_a == var2 @test get_source_data(outputs2) === nothing @@ -294,17 +294,17 @@ end output_dir = mktempdir(; cleanup = true) @test build!(model; executions = 1, output_dir = output_dir) == - PSI.ModelBuildStatus.BUILT - ic_file = PSI.get_initial_conditions_file(model) + IOM.ModelBuildStatus.BUILT + ic_file = IOM.get_initial_conditions_file(model) test_ic_serialization_outputs(model; ic_file_exists = true, message = "make") - @test run!(model) == PSI.RunStatus.SUCCESSFULLY_FINALIZED + @test run!(model) == IOM.RunStatus.SUCCESSFULLY_FINALIZED # Build again, use existing initial conditions. - PSI.reset!(model) + IOM.reset!(model) @test build!(model; executions = 1, output_dir = output_dir) == - PSI.ModelBuildStatus.BUILT + IOM.ModelBuildStatus.BUILT test_ic_serialization_outputs(model; ic_file_exists = true, message = "make") - @test run!(model) == PSI.RunStatus.SUCCESSFULLY_FINALIZED + @test run!(model) == IOM.RunStatus.SUCCESSFULLY_FINALIZED # Build again, use existing initial conditions. model = EmulationModel( @@ -314,9 +314,9 @@ end deserialize_initial_conditions = true, ) @test build!(model; executions = 1, output_dir = output_dir) == - PSI.ModelBuildStatus.BUILT + IOM.ModelBuildStatus.BUILT test_ic_serialization_outputs(model; ic_file_exists = true, message = "deserialize") - @test run!(model) == PSI.RunStatus.SUCCESSFULLY_FINALIZED + @test run!(model) == IOM.RunStatus.SUCCESSFULLY_FINALIZED # Construct and build again with custom initial conditions file. initialization_file = joinpath(output_dir, ic_file * ".old") @@ -330,14 +330,14 @@ end deserialize_initial_conditions = true, ) @test build!(model; executions = 1, output_dir = output_dir) == - PSI.ModelBuildStatus.BUILT + IOM.ModelBuildStatus.BUILT test_ic_serialization_outputs(model; ic_file_exists = true, message = "deserialize") # Construct and build again while skipping build of initial conditions. model = EmulationModel(template, sys; optimizer = optimizer, initialize_model = false) rm(ic_file) @test build!(model; executions = 1, output_dir = output_dir) == - PSI.ModelBuildStatus.BUILT + IOM.ModelBuildStatus.BUILT test_ic_serialization_outputs(model; ic_file_exists = false, message = "skip") - @test run!(model) == PSI.RunStatus.SUCCESSFULLY_FINALIZED + @test run!(model) == IOM.RunStatus.SUCCESSFULLY_FINALIZED end diff --git a/test/test_model_store.jl b/test/test_model_store.jl index 19b9fb0..e19c38f 100644 --- a/test/test_model_store.jl +++ b/test/test_model_store.jl @@ -1 +1,17 @@ -@testset "Test Model Store" begin end +@testset "Test Model Store" begin + @testset "DecisionModelStore round-trips a 1-D time-only result" begin + store = IOM.DecisionModelStore() + key = IOM.VariableKey(TestVariableType, MockComponentType) + index = Dates.DateTime(2024, 1, 1) + store.variables[key] = valtype(store.variables)() + + # A result indexed only by time (no component axis) is a 1-D UnitRange + # DenseAxisArray; write_output! must handle it without a MethodError. + array = DenseAxisArray([1.0, 2.0, 3.0], 1:3) + IOM.write_output!(store, :test, key, index, index, array) + + result = IOM.read_outputs(store, key; index = index) + @test result == array + @test axes(result) == axes(array) + end +end diff --git a/test/test_optimization_container.jl b/test/test_optimization_container.jl index 46407ec..25a95bf 100644 --- a/test/test_optimization_container.jl +++ b/test/test_optimization_container.jl @@ -5,10 +5,6 @@ Tests container machinery without requiring real PowerSystems data or solvers. using InfrastructureSystems -# Define aliases if not already defined by test harness -if !@isdefined(PSI) - const PSI = InfrastructureOptimizationModels -end const ISOPT = InfrastructureSystems.Optimization # Mock constraint/expression types for testing container machinery @@ -21,7 +17,7 @@ struct MockExpressionType <: ISOPT.ExpressionType end mock_sys = MockSystem(100.0) # Create settings with mock system - settings = PSI.Settings( + settings = IOM.Settings( mock_sys; horizon = Dates.Hour(24), resolution = Dates.Hour(1), @@ -29,28 +25,28 @@ struct MockExpressionType <: ISOPT.ExpressionType end ) # Create container - uses duck-typed system and mock time series type - container = PSI.OptimizationContainer( + container = IOM.OptimizationContainer( mock_sys, settings, nothing, MockDeterministic, ) - @test PSI.get_model_base_power(container) == 100.0 - @test isempty(PSI.get_variables(container)) - @test isempty(PSI.get_constraints(container)) - @test isempty(PSI.get_expressions(container)) + @test IOM.get_model_base_power(container) == 100.0 + @test isempty(IOM.get_variables(container)) + @test isempty(IOM.get_constraints(container)) + @test isempty(IOM.get_expressions(container)) end @testset "add_variable_container!" begin mock_sys = MockSystem(100.0) - settings = PSI.Settings( + settings = IOM.Settings( mock_sys; horizon = Dates.Hour(24), resolution = Dates.Hour(1), time_series_cache_size = 0, # Bypass stores_time_series_in_memory check ) - container = PSI.OptimizationContainer( + container = IOM.OptimizationContainer( mock_sys, settings, nothing, @@ -58,54 +54,54 @@ struct MockExpressionType <: ISOPT.ExpressionType end ) # Set time steps (normally done by init_optimization_container!) - PSI.set_time_steps!(container, 1:24) + IOM.set_time_steps!(container, 1:24) # Add a variable container using mock component type as the key # (the container just needs a type - doesn't need actual component instances) device_names = ["gen1", "gen2", "gen3"] - time_steps = PSI.get_time_steps(container) + time_steps = IOM.get_time_steps(container) - var_container = PSI.add_variable_container!( + var_container = IOM.add_variable_container!( container, - PSI.ActivePowerVariable, + IOM.ActivePowerVariable, MockComponentType, device_names, time_steps, ) # Verify the container was created - @test !isempty(PSI.get_variables(container)) + @test !isempty(IOM.get_variables(container)) # Verify we can retrieve it - var_key = PSI.VariableKey(PSI.ActivePowerVariable, MockComponentType) - @test haskey(PSI.get_variables(container), var_key) + var_key = IOM.VariableKey(IOM.ActivePowerVariable, MockComponentType) + @test haskey(IOM.get_variables(container), var_key) # Verify dimensions retrieved = - PSI.get_variable(container, PSI.ActivePowerVariable, MockComponentType) + IOM.get_variable(container, IOM.ActivePowerVariable, MockComponentType) @test size(retrieved) == (length(device_names), length(time_steps)) end @testset "add_constraints_container!" begin mock_sys = MockSystem(100.0) - settings = PSI.Settings( + settings = IOM.Settings( mock_sys; horizon = Dates.Hour(24), resolution = Dates.Hour(1), time_series_cache_size = 0, # Bypass stores_time_series_in_memory check ) - container = PSI.OptimizationContainer( + container = IOM.OptimizationContainer( mock_sys, settings, nothing, MockDeterministic, ) - PSI.set_time_steps!(container, 1:24) + IOM.set_time_steps!(container, 1:24) device_names = ["gen1", "gen2"] - time_steps = PSI.get_time_steps(container) + time_steps = IOM.get_time_steps(container) - cons_container = PSI.add_constraints_container!( + cons_container = IOM.add_constraints_container!( container, MockConstraintType, MockComponentType, @@ -113,32 +109,32 @@ struct MockExpressionType <: ISOPT.ExpressionType end time_steps, ) - @test !isempty(PSI.get_constraints(container)) + @test !isempty(IOM.get_constraints(container)) - cons_key = PSI.ConstraintKey(MockConstraintType, MockComponentType) - @test haskey(PSI.get_constraints(container), cons_key) + cons_key = IOM.ConstraintKey(MockConstraintType, MockComponentType) + @test haskey(IOM.get_constraints(container), cons_key) end @testset "add_expression_container!" begin mock_sys = MockSystem(100.0) - settings = PSI.Settings( + settings = IOM.Settings( mock_sys; horizon = Dates.Hour(24), resolution = Dates.Hour(1), time_series_cache_size = 0, # Bypass stores_time_series_in_memory check ) - container = PSI.OptimizationContainer( + container = IOM.OptimizationContainer( mock_sys, settings, nothing, MockDeterministic, ) - PSI.set_time_steps!(container, 1:24) + IOM.set_time_steps!(container, 1:24) device_names = ["gen1", "gen2"] - time_steps = PSI.get_time_steps(container) + time_steps = IOM.get_time_steps(container) - expr_container = PSI.add_expression_container!( + expr_container = IOM.add_expression_container!( container, MockExpressionType, MockComponentType, @@ -146,10 +142,10 @@ struct MockExpressionType <: ISOPT.ExpressionType end time_steps, ) - @test !isempty(PSI.get_expressions(container)) + @test !isempty(IOM.get_expressions(container)) - expr_key = PSI.ExpressionKey(MockExpressionType, MockComponentType) - @test haskey(PSI.get_expressions(container), expr_key) + expr_key = IOM.ExpressionKey(MockExpressionType, MockComponentType) + @test haskey(IOM.get_expressions(container), expr_key) end @testset "Parameter multiplier applied exactly once" begin @@ -159,12 +155,12 @@ struct MockExpressionType <: ISOPT.ExpressionType end # multiplier this silently flipped the sign of written parameter outputs. param_array = DenseAxisArray([5.0], ["dev1"]) multiplier_array = DenseAxisArray([-1.0], ["dev1"]) - container = PSI.ParameterContainer(param_array, multiplier_array) + container = IOM.ParameterContainer(param_array, multiplier_array) # get_parameter_values returns raw values (no multiplier applied) - @test PSI.get_parameter_values(container)["dev1"] == 5.0 + @test IOM.get_parameter_values(container)["dev1"] == 5.0 # calculate_parameter_values applies the multiplier exactly once - @test PSI.calculate_parameter_values(container)["dev1"] == -5.0 + @test IOM.calculate_parameter_values(container)["dev1"] == -5.0 end @testset "process_duals preserves general-integer variables (Task 1.8)" begin @@ -172,24 +168,24 @@ struct MockExpressionType <: ISOPT.ExpressionType end # re-declared every integer variable as binary and crashed on fixed # integers (UndefVarError on the closed-loop variable `v`). mock_sys = MockSystem(100.0) - settings = PSI.Settings( + settings = IOM.Settings( mock_sys; horizon = Dates.Hour(1), resolution = Dates.Hour(1), time_series_cache_size = 0, ) jump_model = JuMP.Model(HiGHS_optimizer) - container = PSI.OptimizationContainer( + container = IOM.OptimizationContainer( mock_sys, settings, jump_model, MockDeterministic, ) - PSI.set_time_steps!(container, 1:1) - model = PSI.get_jump_model(container) + IOM.set_time_steps!(container, 1:1) + model = IOM.get_jump_model(container) names = ["g1"] - var = PSI.add_variable_container!( + var = IOM.add_variable_container!( container, TestVariableType, MockComponentType, @@ -199,7 +195,7 @@ struct MockExpressionType <: ISOPT.ExpressionType end v = JuMP.@variable(model, integer = true, lower_bound = 0.0, upper_bound = 5.0) var["g1", 1] = v - cons = PSI.add_constraints_container!( + cons = IOM.add_constraints_container!( container, TestConstraintType, MockComponentType, @@ -208,7 +204,7 @@ struct MockExpressionType <: ISOPT.ExpressionType end ) cons["g1", 1] = JuMP.@constraint(model, v <= 3.0) - PSI.add_dual_container!( + IOM.add_dual_container!( container, TestConstraintType, MockComponentType, @@ -220,8 +216,8 @@ struct MockExpressionType <: ISOPT.ExpressionType end JuMP.optimize!(model) @test JuMP.value(v) ≈ 3.0 - status = PSI.process_duals(container, HiGHS_optimizer) - @test status == PSI.RunStatus.SUCCESSFULLY_FINALIZED + status = IOM.process_duals(container, HiGHS_optimizer) + @test status == IOM.RunStatus.SUCCESSFULLY_FINALIZED # The general-integer variable must remain integer (not silently binary), # be unfixed, and have its bounds restored. @@ -235,21 +231,21 @@ struct MockExpressionType <: ISOPT.ExpressionType end @testset "Key-based InitialCondition constructor (Task 2.4)" begin # Previously instantiated InitialCondition{T, U} with U the component type, # violating the value-type bound → TypeError on every call. - ic_key = PSI.InitialConditionKey(MockInitialCondition, MockComponentType) + ic_key = IOM.InitialConditionKey(MockInitialCondition, MockComponentType) component = MockComponentType() - ic = PSI.InitialCondition(ic_key, component, 1.0) - @test PSI.get_value(ic) == 1.0 - @test PSI.get_ic_type(ic) === MockInitialCondition + ic = IOM.InitialCondition(ic_key, component, 1.0) + @test IOM.get_value(ic) == 1.0 + @test IOM.get_ic_type(ic) === MockInitialCondition end @testset "get_objective_expression is idempotent (Task 2.17)" begin model = JuMP.Model() JuMP.@variable(model, x) - obj = PSI.ObjectiveFunction() - JuMP.add_to_expression!(PSI.get_invariant_terms(obj), 2.0, x) - JuMP.add_to_expression!(PSI.get_variant_terms(obj), 3.0, x) - expr1 = PSI.get_objective_expression(obj) - expr2 = PSI.get_objective_expression(obj) + obj = IOM.ObjectiveFunction() + JuMP.add_to_expression!(IOM.get_invariant_terms(obj), 2.0, x) + JuMP.add_to_expression!(IOM.get_variant_terms(obj), 3.0, x) + expr1 = IOM.get_objective_expression(obj) + expr2 = IOM.get_objective_expression(obj) # Calling twice must not double-count the invariant terms. @test expr1 == expr2 @test JuMP.coefficient(expr2, x) == 5.0 diff --git a/test/test_settings.jl b/test/test_settings.jl index 47f8b45..c6b0492 100644 --- a/test/test_settings.jl +++ b/test/test_settings.jl @@ -7,48 +7,43 @@ using Dates using Test using InfrastructureOptimizationModels -# Define PSI alias if not already defined (mock_components.jl defines it) -if !@isdefined(PSI) - const PSI = InfrastructureOptimizationModels -end - # MockSystem is defined in mocks/mock_system.jl, MockMOIOptimizer in mocks/mock_optimizer.jl # Both are loaded by InfrastructureOptimizationModelsTests.jl @testset "Settings" begin @testset "Construction with defaults" begin sys = MockSystem(100.0, false) - settings = PSI.Settings( + settings = IOM.Settings( sys; horizon = Hour(24), resolution = Hour(1), ) - @test PSI.get_horizon(settings) == Dates.Millisecond(Hour(24)) - @test PSI.get_resolution(settings) == Dates.Millisecond(Hour(1)) - @test PSI.get_warm_start(settings) == true - @test PSI.get_optimizer(settings) === nothing - @test PSI.get_direct_mode_optimizer(settings) == false - @test PSI.get_optimizer_solve_log_print(settings) == false - @test PSI.get_detailed_optimizer_stats(settings) == false - @test PSI.get_calculate_conflict(settings) == false - @test PSI.get_check_components(settings) == true - @test PSI.get_initialize_model(settings) == true - @test PSI.get_initialization_file(settings) == "" - @test PSI.get_deserialize_initial_conditions(settings) == false - @test PSI.get_export_pwl_vars(settings) == false - @test PSI.get_allow_fails(settings) == false - @test PSI.get_rebuild_model(settings) == false - @test PSI.get_export_optimization_model(settings) == - PSI.OptimizationModelExportFormat.NONE - @test PSI.get_store_variable_names(settings) == false - @test PSI.get_check_numerical_bounds(settings) == true - @test PSI.get_ext(settings) isa Dict{String, Any} + @test IOM.get_horizon(settings) == Dates.Millisecond(Hour(24)) + @test IOM.get_resolution(settings) == Dates.Millisecond(Hour(1)) + @test IOM.get_warm_start(settings) == true + @test IOM.get_optimizer(settings) === nothing + @test IOM.get_direct_mode_optimizer(settings) == false + @test IOM.get_optimizer_solve_log_print(settings) == false + @test IOM.get_detailed_optimizer_stats(settings) == false + @test IOM.get_calculate_conflict(settings) == false + @test IOM.get_check_components(settings) == true + @test IOM.get_initialize_model(settings) == true + @test IOM.get_initialization_file(settings) == "" + @test IOM.get_deserialize_initial_conditions(settings) == false + @test IOM.get_export_pwl_vars(settings) == false + @test IOM.get_allow_fails(settings) == false + @test IOM.get_rebuild_model(settings) == false + @test IOM.get_export_optimization_model(settings) == + IOM.OptimizationModelExportFormat.NONE + @test IOM.get_store_variable_names(settings) == false + @test IOM.get_check_numerical_bounds(settings) == true + @test IOM.get_ext(settings) isa Dict{String, Any} end @testset "Construction with custom values" begin sys = MockSystem(100.0, false) - settings = PSI.Settings( + settings = IOM.Settings( sys; horizon = Hour(48), resolution = Minute(30), @@ -58,108 +53,108 @@ end ext = Dict{String, Any}("custom" => 123), ) - @test PSI.get_horizon(settings) == Dates.Millisecond(Hour(48)) - @test PSI.get_resolution(settings) == Dates.Millisecond(Minute(30)) - @test PSI.get_warm_start(settings) == false - @test PSI.get_check_components(settings) == true - @test PSI.get_allow_fails(settings) == true - @test PSI.get_check_numerical_bounds(settings) == false - @test PSI.get_ext(settings)["custom"] == 123 + @test IOM.get_horizon(settings) == Dates.Millisecond(Hour(48)) + @test IOM.get_resolution(settings) == Dates.Millisecond(Minute(30)) + @test IOM.get_warm_start(settings) == false + @test IOM.get_check_components(settings) == true + @test IOM.get_allow_fails(settings) == true + @test IOM.get_check_numerical_bounds(settings) == false + @test IOM.get_ext(settings)["custom"] == 123 end @testset "Optimizer handling" begin sys = MockSystem(100.0, false) # Test with nothing (default) - settings_none = PSI.Settings(sys; horizon = Hour(24), resolution = Hour(1)) - @test PSI.get_optimizer(settings_none) === nothing + settings_none = IOM.Settings(sys; horizon = Hour(24), resolution = Hour(1)) + @test IOM.get_optimizer(settings_none) === nothing # Test with MOI.OptimizerWithAttributes (passes through directly) opt_with_attrs = JuMP.MOI.OptimizerWithAttributes(MockMOIOptimizer) - settings_owa = PSI.Settings( + settings_owa = IOM.Settings( sys; horizon = Hour(24), resolution = Hour(1), optimizer = opt_with_attrs, ) - @test PSI.get_optimizer(settings_owa) isa JuMP.MOI.OptimizerWithAttributes + @test IOM.get_optimizer(settings_owa) isa JuMP.MOI.OptimizerWithAttributes # Test with DataType: should be coerced to MOI.OptimizerWithAttributes - settings_dt = PSI.Settings( + settings_dt = IOM.Settings( sys; horizon = Hour(24), resolution = Hour(1), optimizer = MockMOIOptimizer, ) - @test PSI.get_optimizer(settings_dt) isa JuMP.MOI.OptimizerWithAttributes + @test IOM.get_optimizer(settings_dt) isa JuMP.MOI.OptimizerWithAttributes end @testset "Time series cache override for in-memory storage" begin # When system stores time series in memory, cache size should be overridden to 0 sys_in_memory = MockSystem(100.0, true) - settings = PSI.Settings( + settings = IOM.Settings( sys_in_memory; horizon = Hour(24), resolution = Hour(1), time_series_cache_size = 1000, ) - @test !PSI.use_time_series_cache(settings) + @test !IOM.use_time_series_cache(settings) end @testset "Time series cache preserved for non-memory storage" begin sys_not_in_memory = MockSystem(100.0, false) - settings = PSI.Settings( + settings = IOM.Settings( sys_not_in_memory; horizon = Hour(24), resolution = Hour(1), time_series_cache_size = 1000, ) - @test PSI.use_time_series_cache(settings) + @test IOM.use_time_series_cache(settings) end @testset "Setters" begin sys = MockSystem(100.0, false) - settings = PSI.Settings(sys; horizon = Hour(24), resolution = Hour(1)) + settings = IOM.Settings(sys; horizon = Hour(24), resolution = Hour(1)) # Test set_horizon! - PSI.set_horizon!(settings, Hour(48)) - @test PSI.get_horizon(settings) == Dates.Millisecond(Hour(48)) + IOM.set_horizon!(settings, Hour(48)) + @test IOM.get_horizon(settings) == Dates.Millisecond(Hour(48)) # Test set_resolution! - PSI.set_resolution!(settings, Minute(15)) - @test PSI.get_resolution(settings) == Dates.Millisecond(Minute(15)) + IOM.set_resolution!(settings, Minute(15)) + @test IOM.get_resolution(settings) == Dates.Millisecond(Minute(15)) # Test set_initial_time! new_time = DateTime(2024, 6, 15, 12, 0, 0) - PSI.set_initial_time!(settings, new_time) - @test PSI.get_initial_time(settings) == new_time + IOM.set_initial_time!(settings, new_time) + @test IOM.get_initial_time(settings) == new_time # Test set_warm_start! - PSI.set_warm_start!(settings, false) - @test PSI.get_warm_start(settings) == false - PSI.set_warm_start!(settings, true) - @test PSI.get_warm_start(settings) == true + IOM.set_warm_start!(settings, false) + @test IOM.get_warm_start(settings) == false + IOM.set_warm_start!(settings, true) + @test IOM.get_warm_start(settings) == true end @testset "Different time period types" begin sys = MockSystem(100.0, false) # Test with Hour - settings_hour = PSI.Settings(sys; horizon = Hour(12), resolution = Hour(1)) - @test PSI.get_horizon(settings_hour) == Dates.Millisecond(Hour(12)) + settings_hour = IOM.Settings(sys; horizon = Hour(12), resolution = Hour(1)) + @test IOM.get_horizon(settings_hour) == Dates.Millisecond(Hour(12)) # Test with Minute - settings_minute = PSI.Settings(sys; horizon = Minute(360), resolution = Minute(15)) - @test PSI.get_horizon(settings_minute) == Dates.Millisecond(Minute(360)) - @test PSI.get_resolution(settings_minute) == Dates.Millisecond(Minute(15)) + settings_minute = IOM.Settings(sys; horizon = Minute(360), resolution = Minute(15)) + @test IOM.get_horizon(settings_minute) == Dates.Millisecond(Minute(360)) + @test IOM.get_resolution(settings_minute) == Dates.Millisecond(Minute(15)) # Test with Second settings_second = - PSI.Settings(sys; horizon = Second(3600), resolution = Second(300)) - @test PSI.get_horizon(settings_second) == Dates.Millisecond(Second(3600)) + IOM.Settings(sys; horizon = Second(3600), resolution = Second(300)) + @test IOM.get_horizon(settings_second) == Dates.Millisecond(Second(3600)) end end diff --git a/test/test_utils/common_operation_model.jl b/test/test_utils/common_operation_model.jl index 3d46523..57ddbcd 100644 --- a/test/test_utils/common_operation_model.jl +++ b/test/test_utils/common_operation_model.jl @@ -2,13 +2,13 @@ const _DESERIALIZE_MESSAGE = "Deserialized initial_conditions_data" const _MAKE_IC_MESSAGE = "Make Initial Conditions Model" const _SKIP_IC_MESSAGE = "Skip build of initial conditions" -function test_ic_serialization_outputs(model::PSI.OperationModel; ic_file_exists, message) - ic_file = PSI.get_initial_conditions_file(model) - log_file = PSI.get_log_file(model) +function test_ic_serialization_outputs(model::IOM.OperationModel; ic_file_exists, message) + ic_file = IOM.get_initial_conditions_file(model) + log_file = IOM.get_log_file(model) @test isfile(ic_file) == ic_file_exists if ic_file_exists - @test Serialization.deserialize(ic_file) isa PSI.InitialConditionsData + @test Serialization.deserialize(ic_file) isa IOM.InitialConditionsData end make = false diff --git a/test/test_utils/objective_function_helpers.jl b/test/test_utils/objective_function_helpers.jl index 8182d7c..3d2a478 100644 --- a/test/test_utils/objective_function_helpers.jl +++ b/test/test_utils/objective_function_helpers.jl @@ -156,7 +156,7 @@ function setup_delta_pwl_parameters!( ) where {C <: IS.InfrastructureSystemsComponent} # Handle varying tranche counts: pad to the maximum across all devices and times. # Slopes are padded with 0.0, breakpoints with the last value (so dx = 0), - # creating degenerate zero-width tranches — same convention as PSI. + # creating degenerate zero-width tranches — same convention as IOM. n_segments = maximum(length, slopes) n_points = n_segments + 1 @assert maximum(length, breakpoints) == n_points || @@ -209,15 +209,15 @@ Get the coefficient of a variable in the objective function's invariant terms. Returns 0.0 if the variable is not present. """ function get_objective_coefficient( - container::PSI.OptimizationContainer, + container::IOM.OptimizationContainer, ::Type{V}, ::Type{T}, name::String, t::Int, ) where {V, T} - obj = PSI.get_objective_expression(container) - invariant = PSI.get_invariant_terms(obj) - var = PSI.get_variable(container, V, T)[name, t] + obj = IOM.get_objective_expression(container) + invariant = IOM.get_invariant_terms(obj) + var = IOM.get_variable(container, V, T)[name, t] return JuMP.coefficient(invariant, var) end @@ -226,15 +226,15 @@ Get the coefficient of a variable in the objective function's variant terms. Returns 0.0 if the variable is not present. """ function get_objective_variant_coefficient( - container::PSI.OptimizationContainer, + container::IOM.OptimizationContainer, ::Type{V}, ::Type{T}, name::String, t::Int, ) where {V, T} - obj = PSI.get_objective_expression(container) - variant = PSI.get_variant_terms(obj) - var = PSI.get_variable(container, V, T)[name, t] + obj = IOM.get_objective_expression(container) + variant = IOM.get_variant_terms(obj) + var = IOM.get_variable(container, V, T)[name, t] return JuMP.coefficient(variant, var) end @@ -254,7 +254,7 @@ Checks invariant terms by default. Returns true if all coefficients match within tolerance. """ function verify_objective_coefficients( - container::PSI.OptimizationContainer, + container::IOM.OptimizationContainer, ::Type{V}, ::Type{T}, name::String, @@ -262,7 +262,7 @@ function verify_objective_coefficients( atol = 1e-10, variant = false, ) where {V, T} - time_steps = PSI.get_time_steps(container) + time_steps = IOM.get_time_steps(container) get_coef = variant ? get_objective_variant_coefficient : get_objective_coefficient for t in time_steps @@ -280,9 +280,9 @@ end Get the total number of terms in the objective function's invariant expression. Useful for verifying that the expected number of cost terms were added. """ -function count_objective_terms(container::PSI.OptimizationContainer; variant = false) - obj = PSI.get_objective_expression(container) - expr = variant ? PSI.get_variant_terms(obj) : PSI.get_invariant_terms(obj) +function count_objective_terms(container::IOM.OptimizationContainer; variant = false) + obj = IOM.get_objective_expression(container) + expr = variant ? IOM.get_variant_terms(obj) : IOM.get_invariant_terms(obj) if expr isa JuMP.GenericAffExpr return length(expr.terms) elseif expr isa JuMP.GenericQuadExpr @@ -297,15 +297,15 @@ Get the quadratic coefficient of a variable (coefficient of var^2) in the object Returns 0.0 if the variable is not present in quadratic terms. """ function get_objective_quadratic_coefficient( - container::PSI.OptimizationContainer, + container::IOM.OptimizationContainer, ::Type{V}, ::Type{T}, name::String, t::Int, ) where {V, T} - obj = PSI.get_objective_expression(container) - invariant = PSI.get_invariant_terms(obj) - var = PSI.get_variable(container, V, T)[name, t] + obj = IOM.get_objective_expression(container) + invariant = IOM.get_invariant_terms(obj) + var = IOM.get_variable(container, V, T)[name, t] # JuMP.coefficient(expr, var, var) gets the coefficient of var^2 return JuMP.coefficient(invariant, var, var) end @@ -326,7 +326,7 @@ Checks both linear and quadratic coefficients. Returns true if all coefficients match within tolerance. """ function verify_quadratic_objective_coefficients( - container::PSI.OptimizationContainer, + container::IOM.OptimizationContainer, ::Type{V}, ::Type{T}, name::String, @@ -334,7 +334,7 @@ function verify_quadratic_objective_coefficients( expected_quadratic::Union{Float64, Vector{Float64}}; atol = 1e-10, ) where {V, T} - time_steps = PSI.get_time_steps(container) + time_steps = IOM.get_time_steps(container) for t in time_steps exp_lin = expected_linear isa Vector ? expected_linear[t] : expected_linear