diff --git a/src/InfrastructureOptimizationModels.jl b/src/InfrastructureOptimizationModels.jl index 6fa5498..e62ba70 100644 --- a/src/InfrastructureOptimizationModels.jl +++ b/src/InfrastructureOptimizationModels.jl @@ -468,6 +468,9 @@ export ReservationVariable export PiecewiseLinearCostVariable export RateofChangeConstraintSlackUp, RateofChangeConstraintSlackDown export DCVoltage +# Flow-direction trait for variable types +export FlowSign, FlowInjection, FlowWithdrawal, FlowUndirected +export flow_sign, multiplier_from_sign # Abstract types needed by POM for type hierarchy export SparseVariableType, InterpolationVariableType, BinaryInterpolationVariableType diff --git a/src/common_models/rateofchange_constraints.jl b/src/common_models/rateofchange_constraints.jl index 3785d6d..4aaae74 100644 --- a/src/common_models/rateofchange_constraints.jl +++ b/src/common_models/rateofchange_constraints.jl @@ -1,4 +1,3 @@ -# NOTE: not included currently. function _get_minutes_per_period(container::OptimizationContainer) resolution = get_resolution(container) if resolution > Dates.Minute(1) diff --git a/src/core/standard_variables_expressions.jl b/src/core/standard_variables_expressions.jl index 58cf876..6522e19 100644 --- a/src/core/standard_variables_expressions.jl +++ b/src/core/standard_variables_expressions.jl @@ -36,6 +36,38 @@ struct RateofChangeConstraintSlackDown <: VariableType end # HVDC Variables (used in add_pwl_methods) struct DCVoltage <: VariableType end +################################################################################# +# Flow direction trait +# +# Encodes the sign with which a variable contributes to a power-balance +# expression. Replaces scattered `get_variable_multiplier(...) = ±1.0` overrides +# whose only signal is the variable type itself. Device-driven sign overrides +# (e.g. anything on `PSY.ElectricLoad`) still live in POM as more-specific +# dispatches. +################################################################################# + +# Holy-traits style: `flow_sign` returns a *type*, and `multiplier_from_sign` +# dispatches on `::Type{<:FlowSign}`. Both layers resolve at compile time so the +# numeric multiplier folds away at every call site. +abstract type FlowSign end +struct FlowInjection <: FlowSign end +struct FlowWithdrawal <: FlowSign end +struct FlowUndirected <: FlowSign end + +# Default: no directional meaning attached to the variable type. +flow_sign(::Type{<:VariableType}) = FlowUndirected + +multiplier_from_sign(::Type{FlowInjection}) = 1.0 +multiplier_from_sign(::Type{FlowWithdrawal}) = -1.0 +# Variables without flow semantics (OnVariable, StartVariable, ...) keep the +# legacy 1.0 default so callers that don't care about sign still work. +multiplier_from_sign(::Type{FlowUndirected}) = 1.0 + +# Standard variable types defined here: +flow_sign(::Type{ActivePowerVariable}) = FlowInjection +flow_sign(::Type{ActivePowerInVariable}) = FlowWithdrawal +flow_sign(::Type{ActivePowerOutVariable}) = FlowInjection + ################################################################################# # Standard Expression Types # These are the base expression types for aggregating terms diff --git a/test/InfrastructureOptimizationModelsTests.jl b/test/InfrastructureOptimizationModelsTests.jl index f218b68..81adc61 100644 --- a/test/InfrastructureOptimizationModelsTests.jl +++ b/test/InfrastructureOptimizationModelsTests.jl @@ -114,6 +114,7 @@ function run_tests() # TODO service_model.jl include(joinpath(TEST_DIR, "test_settings.jl")) # standard_variables_expressions.jl: low complexity + include(joinpath(TEST_DIR, "test_flow_sign.jl")) # time_series_parameter_types.jl: low complexity # --- objective_function/ subfolder --- diff --git a/test/Project.toml b/test/Project.toml index fed99d0..0ec6c3d 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -9,7 +9,6 @@ DataFramesMeta = "1313f7d8-7da2-5740-9ea0-a2ca25f37964" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" HiGHS = "87dc4568-4c63-4d18-b0c0-bb2238e4078b" -InfrastructureOptimizationModels = "bed98974-b02a-5e2f-9ee0-a103f5c45069" InfrastructureSystems = "2cd47ed4-ca9b-11e9-27f2-ab636a7671f1" Ipopt = "b6b21f68-93f8-5de0-b562-5493be1d77c9" JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" @@ -30,7 +29,6 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" UnoSolver = "1baa60ac-02f7-4b39-a7a8-2f4f58486b05" [sources] -InfrastructureOptimizationModels = {path = ".."} InfrastructureSystems = {rev = "IS4", url = "https://github.com/Sienna-Platform/InfrastructureSystems.jl"} [compat] diff --git a/test/test_flow_sign.jl b/test/test_flow_sign.jl new file mode 100644 index 0000000..65947fb --- /dev/null +++ b/test/test_flow_sign.jl @@ -0,0 +1,28 @@ +@testset "FlowSign trait" begin + @testset "multiplier_from_sign maps to expected ±1.0" begin + @test IOM.multiplier_from_sign(IOM.FlowInjection) == 1.0 + @test IOM.multiplier_from_sign(IOM.FlowWithdrawal) == -1.0 + @test IOM.multiplier_from_sign(IOM.FlowUndirected) == 1.0 + end + + @testset "flow_sign defaults to FlowUndirected" begin + # Any VariableType without an explicit override falls back to FlowUndirected. + # OnVariable / StartVariable / etc. have no flow semantics by design. + @test IOM.flow_sign(OnVariable) === IOM.FlowUndirected + @test IOM.flow_sign(StartVariable) === IOM.FlowUndirected + @test IOM.flow_sign(StopVariable) === IOM.FlowUndirected + end + + @testset "Standard active-power variables carry the correct sign" begin + @test IOM.flow_sign(ActivePowerVariable) === IOM.FlowInjection + @test IOM.flow_sign(ActivePowerInVariable) === IOM.FlowWithdrawal + @test IOM.flow_sign(ActivePowerOutVariable) === IOM.FlowInjection + end + + @testset "Roundtrip: multiplier_from_sign ∘ flow_sign" begin + @test IOM.multiplier_from_sign(IOM.flow_sign(ActivePowerVariable)) == 1.0 + @test IOM.multiplier_from_sign(IOM.flow_sign(ActivePowerInVariable)) == -1.0 + @test IOM.multiplier_from_sign(IOM.flow_sign(ActivePowerOutVariable)) == 1.0 + @test IOM.multiplier_from_sign(IOM.flow_sign(OnVariable)) == 1.0 + end +end