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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/InfrastructureOptimizationModels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@ export AbstractOptimizationProblem
# Settings and Data Types
export Settings
export get_warm_start
export set_start_value!
export get_horizon, get_initial_time, get_optimizer, get_ext, get_interval
export get_check_components, get_initialize_model, get_initialization_file
export get_deserialize_initial_conditions, get_export_pwl_vars
Expand Down
41 changes: 29 additions & 12 deletions src/common_models/add_variable.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
"""
Set `value` as the start value of `variable` when warm start is enabled in the container
settings. A `nothing` value leaves the variable without a start value.

Every start value in a model built on this package should go through this function, so a
model built with `warm_start = false` reaches the solver with no start values at all. Some
solvers treat a partial start as a sub-problem to complete before presolve, so a single
stray start value can cost as much as a full warm start.
"""
function set_start_value!(
container::OptimizationContainer,
variable::JuMP.VariableRef,
value::Union{Nothing, Real},
)
value === nothing && return
get_warm_start(get_settings(container)) || return
JuMP.set_start_value(variable, value)
return
end

@doc raw"""
Adds a variable to the optimization model and to the affine expressions contained
in the optimization_container model according to the specified sign. Based on the inputs, the variable can
Expand Down Expand Up @@ -43,18 +63,18 @@ function add_variables!(
} where {D <: IS.InfrastructureSystemsComponent}
@assert !isempty(devices)
time_steps = get_time_steps(container)
settings = get_settings(container)
binary = get_variable_binary(T, D, F)
included = [d for d in devices if !skip_variable(T, d, F)]

variable = add_variable_container!(
container,
T,
D,
get_name.(devices),
String[get_name(d) for d in included],
time_steps,
)

for t in time_steps, d in devices
for t in time_steps, d in included
name = get_name(d)
variable[name, t] = JuMP.@variable(
get_jump_model(container),
Expand All @@ -67,10 +87,11 @@ function add_variables!(
lb = get_variable_lower_bound(T, d, F)
lb !== nothing && JuMP.set_lower_bound(variable[name, t], lb)

if get_warm_start(settings)
init = get_variable_warm_start_value(T, d, F)
init !== nothing && JuMP.set_start_value(variable[name, t], init)
end
set_start_value!(
container,
variable[name, t],
get_variable_warm_start_value(T, d, F),
)
end

return
Expand Down Expand Up @@ -154,7 +175,6 @@ function _add_service_device_variables!(
D <: IS.InfrastructureSystemsComponent,
F <: AbstractServiceFormulation,
}
settings = get_settings(container)
binary = get_variable_binary(T, U, F)
service_name = IS.get_name(service)
jump_model = get_jump_model(container)
Expand All @@ -170,10 +190,7 @@ function _add_service_device_variables!(
ub !== nothing && JuMP.set_upper_bound(var, ub)
lb = get_variable_lower_bound(T, service, d, F)
lb !== nothing && !binary && JuMP.set_lower_bound(var, lb)
if get_warm_start(settings)
init = get_variable_warm_start_value(T, d, F)
init !== nothing && JuMP.set_start_value(var, init)
end
set_start_value!(container, var, get_variable_warm_start_value(T, d, F))
end
return
end
6 changes: 6 additions & 0 deletions src/common_models/interfaces.jl
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ get_variable_upper_bound(
::Type{<:Union{AbstractDeviceFormulation, AbstractServiceFormulation}},
) = nothing

"""
Extension point: whether `add_variables!` creates no variable for this device. A skipped
device is left off the variable container's axis.
"""
skip_variable(::Type{<:VariableType}, ::IS.InfrastructureSystemsComponent, ::Type) = false

"""
Extension point: Get variable warm start value.
"""
Expand Down
1 change: 1 addition & 0 deletions test/InfrastructureOptimizationModelsTests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ function run_tests()
# TODO tests?
include(joinpath(TEST_DIR, "test_jump_utils.jl"))
include(joinpath(TEST_DIR, "test_pwl_methods.jl"))
include(joinpath(TEST_DIR, "test_start_values.jl"))
include(joinpath(TEST_DIR, "test_ramp_constraints.jl"))
include(joinpath(TEST_DIR, "test_parameterized_range_constraints.jl"))
include(joinpath(TEST_DIR, "test_duration_constraints.jl"))
Expand Down
95 changes: 95 additions & 0 deletions test/test_start_values.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
struct WarmStartTestVariable <: IOM.VariableType end

IOM.get_variable_binary(
::Type{WarmStartTestVariable},
::Type{MockThermalGen},
::Type{TestDeviceFormulation},
) = false
IOM.get_variable_warm_start_value(
::Type{WarmStartTestVariable},
d::MockThermalGen,
::Type{TestDeviceFormulation},
) = d.active_power_limits.max

struct SkippedTestVariable <: IOM.VariableType end

IOM.get_variable_binary(
::Type{SkippedTestVariable},
::Type{MockThermalGen},
::Type{TestDeviceFormulation},
) = false
IOM.skip_variable(
::Type{SkippedTestVariable},
d::MockThermalGen,
::Type{TestDeviceFormulation},
) = d.must_run

function _warm_start_container(warm_start::Bool)
sys = MockSystem(100.0)
settings = IOM.Settings(
sys;
horizon = Dates.Hour(2),
resolution = Dates.Hour(1),
warm_start = warm_start,
)
container = IOM.OptimizationContainer(sys, settings, JuMP.Model(), IS.Deterministic)
IOM.set_time_steps!(container, 1:2)
return container
end

@testset "Start values" begin
@testset "set_start_value! follows the warm_start setting" begin
for warm_start in (true, false)
container = _warm_start_container(warm_start)
var = JuMP.@variable(IOM.get_jump_model(container))
IOM.set_start_value!(container, var, 2.5)
@test JuMP.start_value(var) == (warm_start ? 2.5 : nothing)
end
end

@testset "set_start_value! with nothing sets no start value" begin
container = _warm_start_container(true)
var = JuMP.@variable(IOM.get_jump_model(container))
IOM.set_start_value!(container, var, nothing)
@test JuMP.start_value(var) === nothing
end

@testset "add_variables! applies the warm start hook only with warm_start" begin
devices = [make_mock_thermal("gen1"; limits = (min = 0.0, max = 40.0))]
for warm_start in (true, false)
container = _warm_start_container(warm_start)
IOM.add_variables!(
container,
WarmStartTestVariable,
devices,
TestDeviceFormulation,
)
var = IOM.get_variable(container, WarmStartTestVariable, MockThermalGen)
for t in 1:2
@test JuMP.start_value(var["gen1", t]) == (warm_start ? 40.0 : nothing)
end
end
end

@testset "add_variables! leaves skipped devices off the axis" begin
container = _warm_start_container(true)
devices = [
make_mock_thermal("committed"),
make_mock_thermal("must_run"; must_run = true),
]
IOM.add_variables!(container, SkippedTestVariable, devices, TestDeviceFormulation)
var = IOM.get_variable(container, SkippedTestVariable, MockThermalGen)
@test axes(var)[1] == ["committed"]

container = _warm_start_container(true)
all_skipped = [make_mock_thermal("must_run"; must_run = true)]
IOM.add_variables!(
container,
SkippedTestVariable,
all_skipped,
TestDeviceFormulation,
)
var = IOM.get_variable(container, SkippedTestVariable, MockThermalGen)
@test isempty(axes(var)[1])
end
end
Loading