Skip to content
Merged
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
5 changes: 4 additions & 1 deletion src/core/optimization_container.jl
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,10 @@ end
Execute the optimizer on the container's JuMP model, compute aux/dual variables,
and return the run status. Called `solve_impl!(container, system)` in PSI.
"""
function execute_optimizer!(container::OptimizationContainer, system::IS.InfrastructureSystemsContainer)
function execute_optimizer!(
container::OptimizationContainer,
system::IS.InfrastructureSystemsContainer,
)
optimizer_stats = get_optimizer_stats(container)

jump_model = get_jump_model(container)
Expand Down
80 changes: 79 additions & 1 deletion test/mocks/constructors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,85 @@ function make_mock_thermal(
bus = MockBus("bus1", 1, :PV),
limits = (min = 0.0, max = 100.0),
base_power = 100.0,
operation_cost = MockOperationCost(0.0),
operation_cost = MockProportionalCost(0.0),
)
return MockThermalGen(name, available, bus, limits, base_power, operation_cost)
end

"""
Generate convex piecewise-linear cost curve points with `n_tranches` segments over [0, pmax].
Returns a vector of (x, y) tuples with strictly increasing slopes.
"""
function _random_convex_pwl_points(n_tranches::Int, pmax::Float64)
xs = sort(rand(n_tranches - 1)) .* pmax
points = [(0.0, 0.0)]
cumulative_cost = 0.0
prev_x = 0.0
slope = 5.0 + 20.0 * rand()
for x in xs
cumulative_cost += slope * (x - prev_x)
push!(points, (x, cumulative_cost))
prev_x = x
slope += 5.0 + 10.0 * rand()
end
cumulative_cost += slope * (pmax - prev_x)
push!(points, (pmax, cumulative_cost))
return points
end

"""
Create a mock system on `n` nodes where the graph is connected (and fairly random),
half the nodes have generators, and half have loads.
"""
function make_mock_test_network(n::Int; max_tranches::Int = 4)
@assert n >= 2 "Need at least 2 nodes for a network"
sys = MockSystem(100.0)

# Create buses
buses = [MockBus("bus$i", i, :PV) for i in 1:n]
for bus in buses
add_component!(sys, bus)
end

# Build a connected graph: chain 1-2-3-...-n plus random extra edges
branch_pairs = Set{Tuple{Int, Int}}()
for i in 1:(n - 1)
push!(branch_pairs, (i, i + 1))
end
# Add some random cross-links for variety
n_extra = div(n, 3)
for _ in 1:n_extra
a, b = rand(1:n), rand(1:n)
if a != b
pair = a < b ? (a, b) : (b, a)
push!(branch_pairs, pair)
end
end
for (idx, (i, j)) in enumerate(branch_pairs)
r = 0.005 + 0.005 * rand()
branch = MockBranch("branch$idx", true, buses[i], buses[j], 100.0, r)
add_component!(sys, branch)
end

# Half the nodes get generators with convex PWL costs, the other half get loads
n_gens = div(n, 2)
for i in 1:n_gens
n_tranches = rand(2:max_tranches)
pmax = 50.0 + 100.0 * rand()
points = _random_convex_pwl_points(n_tranches, pmax)

pwl = IS.PiecewiseLinearData(points)
cost_curve = IS.CostCurve(IS.InputOutputCurve(pwl))
op_cost = MockOperationalCost(cost_curve, 0.0, 0.0)
gen = MockThermalGen(
"gen$i", true, buses[i], (min = 0.0, max = pmax), 100.0, op_cost,
)
add_component!(sys, gen)
end
for i in (n_gens + 1):n
load = MockLoad("load$(i - n_gens)", true, buses[i], 50.0)
add_component!(sys, load)
end

return sys
end
44 changes: 36 additions & 8 deletions test/mocks/mock_components.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,31 @@ const IS = InfrastructureSystems
# Mock formulation type for testing DeviceModel
struct TestDeviceFormulation <: PSI.AbstractDeviceFormulation end

abstract type AbstractMockCost end

# Mock operation cost for testing proportional cost functions
struct MockOperationCost
struct MockProportionalCost <: AbstractMockCost
proportional_term::Float64
is_time_variant::Bool
fuel_cost::Float64
end

MockOperationCost(proportional_term::Float64) =
MockOperationCost(proportional_term, false, 0.0)
MockOperationCost(proportional_term::Float64, is_time_variant::Bool) =
MockOperationCost(proportional_term, is_time_variant, 0.0)
MockProportionalCost(proportional_term::Float64) =
MockProportionalCost(proportional_term, false, 0.0)
MockProportionalCost(proportional_term::Float64, is_time_variant::Bool) =
MockProportionalCost(proportional_term, is_time_variant, 0.0)

# FIXME mildly awkward that we need both fixed and fuel_cost here, but otherwise
# can't define get_fuel_cost for MockThermalGen.
struct MockOperationalCost <: AbstractMockCost
variable::IS.CostCurve
fixed::Float64
fuel_cost::Float64
end

get_variable(cost::MockOperationalCost) = cost.variable
get_fixed(cost::MockOperationalCost) = cost.fixed
get_fuel_cost(cost::MockOperationalCost) = cost.fuel_cost

# Abstract mock device type for testing rejection of abstract types in DeviceModel
# Subtypes IS.InfrastructureSystemsComponent so they work with DeviceModel and container keys
Expand All @@ -51,14 +65,23 @@ struct MockThermalGen <: AbstractMockGenerator
bus::MockBus
active_power_limits::NamedTuple{(:min, :max), Tuple{Float64, Float64}}
base_power::Float64
operation_cost::MockOperationCost
operation_cost::AbstractMockCost
end

# Constructor with default base_power and no operation cost for backward compatibility
MockThermalGen(name, available, bus, limits) =
MockThermalGen(name, available, bus, limits, 100.0, MockOperationCost(0.0))
MockThermalGen(name, available, bus, limits, 100.0, MockProportionalCost(0.0))
MockThermalGen(name, available, bus, limits, base_power) =
MockThermalGen(name, available, bus, limits, base_power, MockOperationCost(0.0))
MockThermalGen(name, available, bus, limits, base_power, MockProportionalCost(0.0))
MockThermalGen(name, available, bus, limits, base_power, operation_cost::IS.CostCurve) =
MockThermalGen(
name,
available,
bus,
limits,
base_power,
MockOperationalCost(operation_cost, 0.0, 0.0),
)

get_name(g::MockThermalGen) = g.name
get_available(g::MockThermalGen) = g.available
Expand Down Expand Up @@ -101,13 +124,18 @@ struct MockBranch <: AbstractMockDevice
from_bus::MockBus
to_bus::MockBus
rating::Float64
r::Float64
end

MockBranch(name, available, from_bus, to_bus, rating) =
MockBranch(name, available, from_bus, to_bus, rating, 0.0)

get_name(b::MockBranch) = b.name
get_available(b::MockBranch) = b.available
get_from_bus(b::MockBranch) = b.from_bus
get_to_bus(b::MockBranch) = b.to_bus
get_rate(b::MockBranch) = b.rating
get_r(b::MockBranch) = b.r

# Mock component type for use as type parameter in container keys
# This replaces PSY.ThermalStandard etc. in tests that don't need real PSY types
Expand Down
70 changes: 60 additions & 10 deletions test/test_bilinear_delta_benchmark.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,66 @@ include(joinpath(TEST_DIR, "mocks/constructors.jl"))
include(joinpath(TEST_DIR, "test_utils/test_types.jl"))
include(joinpath(TEST_DIR, "test_utils/objective_function_helpers.jl"))

time_steps = 1:1
sys = make_mock_test_network(10)
println(length(get_components(MockThermalGen, sys)))
settings = IOM.Settings(
sys;
horizon = Dates.Hour(length(time_steps)),
resolution = Dates.Hour(1),
optimizer = HiGHS.Optimizer,
)
container = IOM.OptimizationContainer(sys, settings, JuMP.Model(), IS.Deterministic)
generators = Vector{MockThermalGen}(get_components(MockThermalGen, sys))
loads = get_components(MockLoad, sys)

IOM.get_variable_lower_bound(
::Type{ActivePowerVariable},
g::MockThermalGen,
::TestDeviceFormulation,
) = g.active_power_limits.min
IOM.get_variable_upper_bound(
::Type{ActivePowerVariable},
g::MockThermalGen,
::TestDeviceFormulation,
) = g.active_power_limits.max
IOM.get_variable_binary(
::ActivePowerVariable,
::Type{MockThermalGen},
::TestDeviceFormulation,
) = false
# TODO: voltage, current variables at each bus
# (do those already exist in IOM? if not make mocks)

add_variables!(
container,
ActivePowerVariable,
generators,
TestDeviceFormulation(),
)

# TODO implement true bilinear constraints. Currently we only have quadratic and a few
# types of PWL-approximation-to-bilinear. Then create P_i = V_i * I_i (generators)
# and -d_i = V_i * I_i (loads) constraints.

# TODO add constraints for buses: I_i = sum of 1/r_ij * (V_i - V_j) for all j connected to i.

for g in generators
IOM.add_variable_cost_to_objective!(
container,
ActivePowerVariable(),
g,
get_variable(IOM.get_operation_cost(g)),
TestDeviceFormulation(),
)
end

obj = get_objective_expression(container)
println(IOM.get_invariant_terms(obj))

# TODO check what are the expected units on the cost function.

#=
struct MockPowerModel <: IS.Optimization.AbstractPowerModel end

function IOM.intermediate_set_units_base_system!(::MockSystem, base) end
Expand All @@ -37,21 +96,12 @@ function IOM.get_available_components(
return 0
end

time_steps = 1:1
sys = MockSystem(100.0)
bus = MockBus("bus1", 1, :bus1)
settings = IOM.Settings(
sys;
horizon = Dates.Hour(length(time_steps)),
resolution = Dates.Hour(1),
optimizer = HiGHS.Optimizer
)
container = IOM.OptimizationContainer(sys, settings, JuMP.Model(), IS.Deterministic)
IOM.set_time_steps!(container, time_steps)

network = NetworkModel(MockPowerModel)

init_optimization_container!(container, network, sys)
IOM.execute_optimizer!(container, sys)

"end"
=#
4 changes: 2 additions & 2 deletions test/test_piecewise_linear.jl
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ function setup_pwl_test(;
# When fuel_cost is provided, the device's operation_cost must also have it
# because get_fuel_cost(device) is called to look up the cost multiplier
if isnothing(fuel_cost)
op_cost = MockOperationCost(0.0, false, 0.0)
op_cost = MockProportionalCost(0.0, false, 0.0)
else
op_cost = MockOperationCost(0.0, false, fuel_cost)
op_cost = MockProportionalCost(0.0, false, fuel_cost)
end
device = make_mock_thermal(
device_name;
Expand Down
28 changes: 14 additions & 14 deletions test/test_proportional.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ InfrastructureOptimizationModels.objective_function_multiplier(

# Interface implementations for mock types

# Non-time-varying proportional_cost: return the proportional_term from MockOperationCost
# Non-time-varying proportional_cost: return the proportional_term from MockProportionalCost
InfrastructureOptimizationModels.proportional_cost(
op_cost::MockOperationCost,
op_cost::MockProportionalCost,
::TestProportionalVariable,
d::MockThermalGen,
::TestProportionalFormulation,
Expand All @@ -29,17 +29,17 @@ InfrastructureOptimizationModels.proportional_cost(
# Time-varying proportional_cost: same value for all time steps (could vary if needed)
InfrastructureOptimizationModels.proportional_cost(
::InfrastructureOptimizationModels.OptimizationContainer,
op_cost::MockOperationCost,
op_cost::MockProportionalCost,
::TestProportionalVariable,
d::MockThermalGen,
::TestProportionalFormulation,
::Int,
) = op_cost.proportional_term

# is_time_variant_term: return the is_time_variant flag from MockOperationCost
# is_time_variant_term: return the is_time_variant flag from MockProportionalCost
InfrastructureOptimizationModels.is_time_variant_term(
::InfrastructureOptimizationModels.OptimizationContainer,
op_cost::MockOperationCost,
op_cost::MockProportionalCost,
::TestProportionalVariable,
::Type{MockThermalGen},
::TestProportionalFormulation,
Expand Down Expand Up @@ -99,7 +99,7 @@ end
cost_value = 15.0
device = make_mock_thermal(
"gen1";
operation_cost = MockOperationCost(cost_value, false),
operation_cost = MockProportionalCost(cost_value, false),
)
devices = [device]
container = setup_proportional_test_container(time_steps, devices)
Expand Down Expand Up @@ -138,7 +138,7 @@ end
time_steps = 1:2
device = make_mock_thermal(
"gen1";
operation_cost = MockOperationCost(0.0, false),
operation_cost = MockProportionalCost(0.0, false),
)
devices = [device]
container = setup_proportional_test_container(time_steps, devices)
Expand Down Expand Up @@ -170,11 +170,11 @@ end
cost2 = 20.0
device1 = make_mock_thermal(
"gen1";
operation_cost = MockOperationCost(cost1, false),
operation_cost = MockProportionalCost(cost1, false),
)
device2 = make_mock_thermal(
"gen2";
operation_cost = MockOperationCost(cost2, false),
operation_cost = MockProportionalCost(cost2, false),
)
devices = [device1, device2]
container = setup_proportional_test_container(time_steps, devices)
Expand Down Expand Up @@ -213,7 +213,7 @@ end
# is_time_variant = false
device = make_mock_thermal(
"gen1";
operation_cost = MockOperationCost(cost_value, false),
operation_cost = MockProportionalCost(cost_value, false),
)
devices = [device]
container = setup_proportional_test_container(time_steps, devices)
Expand Down Expand Up @@ -254,7 +254,7 @@ end
# is_time_variant = true
device = make_mock_thermal(
"gen1";
operation_cost = MockOperationCost(cost_value, true),
operation_cost = MockProportionalCost(cost_value, true),
)
devices = [device]
container = setup_proportional_test_container(time_steps, devices)
Expand Down Expand Up @@ -296,11 +296,11 @@ end

device_invariant = make_mock_thermal(
"gen_inv";
operation_cost = MockOperationCost(cost_invariant, false),
operation_cost = MockProportionalCost(cost_invariant, false),
)
device_variant = make_mock_thermal(
"gen_var";
operation_cost = MockOperationCost(cost_variant, true),
operation_cost = MockProportionalCost(cost_variant, true),
)
devices = [device_invariant, device_variant]
container = setup_proportional_test_container(time_steps, devices)
Expand Down Expand Up @@ -356,7 +356,7 @@ end
# Zero cost, even if marked as time variant
device = make_mock_thermal(
"gen1";
operation_cost = MockOperationCost(0.0, true),
operation_cost = MockProportionalCost(0.0, true),
)
devices = [device]
container = setup_proportional_test_container(time_steps, devices)
Expand Down
2 changes: 1 addition & 1 deletion test/test_pwl_methods.jl
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function make_mock_thermal_pwl(
fuel_cost = 0.0,
)
bus = MockBus("bus1", 1, :PV)
op_cost = MockOperationCost(0.0, false, fuel_cost)
op_cost = MockProportionalCost(0.0, false, fuel_cost)
return MockThermalGen(
name,
true,
Expand Down
Loading
Loading