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
2 changes: 1 addition & 1 deletion src/InfrastructureOptimizationModels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ export add_sparse_pwl_interpolation_variables!
export JuMPOrFloat
# Constraint helpers
export add_range_constraints!, add_parameterized_upper_bound_range_constraints
export add_reserve_bound_range_constraints!
export add_reserve_bound_range_constraints!, add_commitment_bound_range_constraints!
export add_semicontinuous_range_constraints!, add_semicontinuous_ramp_constraints!
# Cost helpers
export add_shut_down_cost!, add_start_up_cost!
Expand Down
45 changes: 26 additions & 19 deletions src/bilinear_approximations/bin2.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ Bin2Config(quad_config::QuadraticApproxConfig) = Bin2Config(quad_config, true)
# --- Unified bilinear approximation dispatch ---

"""
_add_bilinear_approx!(config::Bin2Config, container, C, names, time_steps, x_var, y_var, x_min, x_max, y_min, y_max, meta)
_add_bilinear_approx!(config::Bin2Config, container, C, names, time_steps, x_var, y_var, x_bounds, y_bounds, meta)

Standard form: compute x² and y² quadratic approximations, then delegate to precomputed form.

# Arguments
- `x_bounds::Vector{MinMax}`: per-name lower and upper bounds of x
- `y_bounds::Vector{MinMax}`: per-name lower and upper bounds of y
"""
function _add_bilinear_approx!(
config::Bin2Config,
Expand All @@ -46,32 +50,34 @@ function _add_bilinear_approx!(
time_steps::UnitRange{Int},
x_var,
y_var,
x_min::Float64,
x_max::Float64,
y_min::Float64,
y_max::Float64,
x_bounds::Vector{MinMax},
y_bounds::Vector{MinMax},
meta::String,
) where {C <: IS.InfrastructureSystemsComponent}
xsq = _add_quadratic_approx!(
config.quad_config, container, C, names, time_steps,
x_var, x_min, x_max, meta * "_x",
x_var, x_bounds, meta * "_x",
)
ysq = _add_quadratic_approx!(
config.quad_config, container, C, names, time_steps,
y_var, y_min, y_max, meta * "_y",
y_var, y_bounds, meta * "_y",
)
return _add_bilinear_approx!(
config, container, C, names, time_steps,
xsq, ysq, x_var, y_var,
x_min, x_max, y_min, y_max, meta,
x_bounds, y_bounds, meta,
)
end

"""
_add_bilinear_approx!(config::Bin2Config, container, C, names, time_steps, xsq, ysq, x_var, y_var, x_min, x_max, y_min, y_max, meta)
_add_bilinear_approx!(config::Bin2Config, container, C, names, time_steps, xsq, ysq, x_var, y_var, x_bounds, y_bounds, meta)

Precomputed form: Bin2 identity z = ½((x+y)² − x² − y²) with optional PWMCC concave cuts.
Accepts pre-computed quadratic approximations `xsq` ≈ x² and `ysq` ≈ y².

# Arguments
- `x_bounds::Vector{MinMax}`: per-name lower and upper bounds of x
- `y_bounds::Vector{MinMax}`: per-name lower and upper bounds of y
"""
function _add_bilinear_approx!(
config::Bin2Config,
Expand All @@ -83,18 +89,19 @@ function _add_bilinear_approx!(
ysq,
x_var,
y_var,
x_min::Float64,
x_max::Float64,
y_min::Float64,
y_max::Float64,
x_bounds::Vector{MinMax},
y_bounds::Vector{MinMax},
meta::String,
) where {C <: IS.InfrastructureSystemsComponent}
# --- Bin2 identity: z = ½((x+y)² − x² − y²) ---

# Bounds for p = x + y
p_min = x_min + y_min
p_max = x_max + y_max
IS.@assert_op p_min <= p_max
# Bounds for p = x + y (per-name)
p_bounds = [
MinMax((
min = x_bounds[i].min + y_bounds[i].min,
max = x_bounds[i].max + y_bounds[i].max,
)) for i in eachindex(x_bounds)
]

meta_plus = meta * "_plus"

Expand All @@ -116,7 +123,7 @@ function _add_bilinear_approx!(
# Approximate p² = (x+y)² using the provided quadratic config
psq = _add_quadratic_approx!(
config.quad_config, container, C, names, time_steps,
p_expr, p_min, p_max, meta_plus,
p_expr, p_bounds, meta_plus,
)

result_expr = add_expression_container!(
Expand All @@ -141,7 +148,7 @@ function _add_bilinear_approx!(
_add_reformulated_mccormick!(
container, C, names, time_steps,
x_var, y_var, psq, xsq, ysq,
x_min, x_max, y_min, y_max, meta,
x_bounds, y_bounds, meta,
)
end

Expand Down
69 changes: 42 additions & 27 deletions src/bilinear_approximations/hybs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@ HybSConfig(quad_config::QuadraticApproxConfig, epigraph_depth::Int) =
# --- Unified HybS dispatch methods ---

"""
_add_bilinear_approx!(config::HybSConfig, container, C, names, time_steps, x_var, y_var, x_min, x_max, y_min, y_max, meta)
_add_bilinear_approx!(config::HybSConfig, container, C, names, time_steps, x_var, y_var, x_bounds, y_bounds, meta)

Approximate x·y using HybS (Hybrid Separable) relaxation with config-selected quadratic method.

# Arguments
- `x_bounds::Vector{MinMax}`: per-name lower and upper bounds of x
- `y_bounds::Vector{MinMax}`: per-name lower and upper bounds of y
"""
function _add_bilinear_approx!(
config::HybSConfig,
Expand All @@ -40,29 +44,27 @@ function _add_bilinear_approx!(
time_steps::UnitRange{Int},
x_var,
y_var,
x_min::Float64,
x_max::Float64,
y_min::Float64,
y_max::Float64,
x_bounds::Vector{MinMax},
y_bounds::Vector{MinMax},
meta::String,
) where {C <: IS.InfrastructureSystemsComponent}
xsq = _add_quadratic_approx!(
config.quad_config, container, C, names, time_steps,
x_var, x_min, x_max, meta * "_x",
x_var, x_bounds, meta * "_x",
)
ysq = _add_quadratic_approx!(
config.quad_config, container, C, names, time_steps,
y_var, y_min, y_max, meta * "_y",
y_var, y_bounds, meta * "_y",
)
return _add_bilinear_approx!(
config, container, C, names, time_steps,
xsq, ysq, x_var, y_var,
x_min, x_max, y_min, y_max, meta,
x_bounds, y_bounds, meta,
)
end

"""
_add_bilinear_approx!(config::HybSConfig, container, C, names, time_steps, xsq, ysq, x_var, y_var, x_min, x_max, y_min, y_max, meta)
_add_bilinear_approx!(config::HybSConfig, container, C, names, time_steps, xsq, ysq, x_var, y_var, x_bounds, y_bounds, meta)

HybS bilinear approximation with pre-computed quadratic approximations for x² and y².

Expand All @@ -71,6 +73,10 @@ Combines Bin2 and Bin3 separable identities:
- Bin3 upper bound: z ≤ ½(z_x + z_y − z_p2) where z_p2 lower-bounds (x−y)²

The cross-terms (x+y)² and (x−y)² always use epigraph Q^{L1} (pure LP).

# Arguments
- `x_bounds::Vector{MinMax}`: per-name lower and upper bounds of x
- `y_bounds::Vector{MinMax}`: per-name lower and upper bounds of y
"""
function _add_bilinear_approx!(
config::HybSConfig,
Expand All @@ -82,19 +88,23 @@ function _add_bilinear_approx!(
ysq,
x_var,
y_var,
x_min::Float64,
x_max::Float64,
y_min::Float64,
y_max::Float64,
x_bounds::Vector{MinMax},
y_bounds::Vector{MinMax},
meta::String,
) where {C <: IS.InfrastructureSystemsComponent}
# Bounds for auxiliary variables
p1_min = x_min + y_min
p1_max = x_max + y_max
p2_min = x_min - y_max # p2 = x − y, so min uses −y_max
p2_max = x_max - y_min # and max uses −y_min
IS.@assert_op x_max > x_min
IS.@assert_op y_max > y_min
# Bounds for auxiliary variables (per-name)
p1_bounds = [
MinMax((
min = x_bounds[i].min + y_bounds[i].min,
max = x_bounds[i].max + y_bounds[i].max,
)) for i in eachindex(x_bounds)
]
p2_bounds = [
MinMax((
min = x_bounds[i].min - y_bounds[i].max,
max = x_bounds[i].max - y_bounds[i].min,
)) for i in eachindex(x_bounds)
]

jump_model = get_jump_model(container)

Expand Down Expand Up @@ -139,12 +149,12 @@ function _add_bilinear_approx!(
zp1_expr = _add_quadratic_approx!(
epi_cfg,
container, C, names, time_steps,
p1_expr, p1_min, p1_max, meta_p1,
p1_expr, p1_bounds, meta_p1,
)
zp2_expr = _add_quadratic_approx!(
epi_cfg,
container, C, names, time_steps,
p2_expr, p2_min, p2_max, meta_p2,
p2_expr, p2_bounds, meta_p2,
)

# --- Create z variable and two-sided HybS bounds ---
Expand Down Expand Up @@ -175,11 +185,16 @@ function _add_bilinear_approx!(
meta,
)

# Compute valid bounds for z ≈ x·y from variable bounds
z_lo = min(x_min * y_min, x_min * y_max, x_max * y_min, x_max * y_max)
z_hi = max(x_min * y_min, x_min * y_max, x_max * y_min, x_max * y_max)
for (i, name) in enumerate(names), t in time_steps
xb = x_bounds[i]
yb = y_bounds[i]
IS.@assert_op xb.max > xb.min
IS.@assert_op yb.max > yb.min

# Compute valid bounds for z ≈ x·y from variable bounds
z_lo = min(xb.min * yb.min, xb.min * yb.max, xb.max * yb.min, xb.max * yb.max)
z_hi = max(xb.min * yb.min, xb.min * yb.max, xb.max * yb.min, xb.max * yb.max)

for name in names, t in time_steps
z =
z_var[name, t] = JuMP.@variable(
jump_model,
Expand Down Expand Up @@ -212,7 +227,7 @@ function _add_bilinear_approx!(
_add_mccormick_envelope!(
container, C, names, time_steps,
x_var, y_var, z_var,
x_min, x_max, y_min, y_max, meta,
x_bounds, y_bounds, meta,
)
end

Expand Down
106 changes: 97 additions & 9 deletions src/bilinear_approximations/mccormick.jl
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,90 @@ function _add_mccormick_envelope!(
return
end

"""
_add_mccormick_envelope!(container, C, names, time_steps, x_var, y_var, z_var, x_bounds, y_bounds, meta)

Add McCormick envelope constraints for the bilinear product z ≈ x·y with per-name bounds.

For each (name, t), adds 4 linear inequalities using bounds looked up by name index.

# Arguments
- `container::OptimizationContainer`: the optimization container
- `::Type{C}`: component type
- `names::Vector{String}`: component names
- `time_steps::UnitRange{Int}`: time periods
- `x_var`: container of x variables indexed by (name, t)
- `y_var`: container of y variables indexed by (name, t)
- `z_var`: container of z variables indexed by (name, t)
- `x_bounds::Vector{MinMax}`: per-name lower and upper bounds of x
- `y_bounds::Vector{MinMax}`: per-name lower and upper bounds of y
- `meta::String`: identifier for container keys

# Returns
- Nothing. Constraints are added in-place.
"""
function _add_mccormick_envelope!(
container::OptimizationContainer,
::Type{C},
names::Vector{String},
time_steps::UnitRange{Int},
x_var,
y_var,
z_var,
x_bounds::Vector{MinMax},
y_bounds::Vector{MinMax},
meta::String;
lower_bounds::Bool = true,
) where {C <: IS.InfrastructureSystemsComponent}
jump_model = get_jump_model(container)

mc_cons = add_constraints_container!(
container,
McCormickConstraint,
C,
names,
1:4,
time_steps;
sparse = true,
meta,
)
Comment thread
acostarelli marked this conversation as resolved.

for (i, name) in enumerate(names), t in time_steps
xb = x_bounds[i]
yb = y_bounds[i]
IS.@assert_op xb.max > xb.min
IS.@assert_op yb.max > yb.min
_add_mccormick_envelope!(
jump_model, mc_cons, (name, t),
x_var[name, t], y_var[name, t], z_var[name, t],
xb.min, xb.max, yb.min, yb.max;
lower_bounds,
)
end

return
end

function _add_mccormick_envelope!(
container::OptimizationContainer,
::Type{C},
names::Vector{String},
time_steps::UnitRange{Int},
x_var,
z_var,
bounds::Vector{MinMax},
meta::String;
lower_bounds::Bool = true,
) where {C <: IS.InfrastructureSystemsComponent}
_add_mccormick_envelope!(
container, C, names, time_steps,
x_var, x_var, z_var,
bounds, bounds,
meta; lower_bounds,
)
return
end

function _add_mccormick_envelope!(
jump_model::JuMP.Model,
cons,
Expand Down Expand Up @@ -265,12 +349,16 @@ function _add_reformulated_mccormick_bin2!(
end

"""
_add_reformulated_mccormick!(container, C, names, time_steps, x_var, y_var, psq, xsq, ysq, x_min, x_max, y_min, y_max, meta)
_add_reformulated_mccormick!(container, C, names, time_steps, x_var, y_var, psq, xsq, ysq, x_bounds, y_bounds, meta)

Add 4 reformulated McCormick cuts for Bin2 separable bilinear approximation.
Substitutes z = ½(z_p1 − z_x − z_y) into the standard McCormick envelope.

`psq`, `xsq`, `ysq` are expression containers for (x+y)², x², y² approximations.

# Arguments
- `x_bounds::Vector{MinMax}`: per-name lower and upper bounds of x
- `y_bounds::Vector{MinMax}`: per-name lower and upper bounds of y
"""
function _add_reformulated_mccormick!(
container::OptimizationContainer,
Expand All @@ -282,14 +370,10 @@ function _add_reformulated_mccormick!(
psq,
xsq,
ysq,
x_min::Float64,
x_max::Float64,
y_min::Float64,
y_max::Float64,
x_bounds::Vector{MinMax},
y_bounds::Vector{MinMax},
meta::String,
) where {C <: IS.InfrastructureSystemsComponent}
IS.@assert_op x_max > x_min
IS.@assert_op y_max > y_min
jump_model = get_jump_model(container)

mc_cons = add_constraints_container!(
Expand All @@ -303,12 +387,16 @@ function _add_reformulated_mccormick!(
meta,
)

for name in names, t in time_steps
for (i, name) in enumerate(names), t in time_steps
xb = x_bounds[i]
yb = y_bounds[i]
IS.@assert_op xb.max > xb.min
IS.@assert_op yb.max > yb.min
_add_reformulated_mccormick_bin2!(
jump_model, mc_cons, (name, t),
x_var[name, t], y_var[name, t],
psq[name, t], xsq[name, t], ysq[name, t],
x_min, x_max, y_min, y_max,
xb.min, xb.max, yb.min, yb.max,
)
end

Expand Down
Loading
Loading