Skip to content
Closed
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
13 changes: 6 additions & 7 deletions linopy/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,14 @@

PWL_LAMBDA_SUFFIX = "_lambda"
PWL_CONVEX_SUFFIX = "_convex"
PWL_X_LINK_SUFFIX = "_x_link"
PWL_Y_LINK_SUFFIX = "_y_link"
PWL_LINK_SUFFIX = "_link"
PWL_DELTA_SUFFIX = "_delta"
PWL_FILL_SUFFIX = "_fill"
PWL_BINARY_SUFFIX = "_binary"
PWL_FILL_ORDER_SUFFIX = "_fill_order"
PWL_SEGMENT_BINARY_SUFFIX = "_segment_binary"
PWL_SELECT_SUFFIX = "_select"
PWL_INC_BINARY_SUFFIX = "_inc_binary"
PWL_INC_LINK_SUFFIX = "_inc_link"
PWL_INC_ORDER_SUFFIX = "_inc_order"
PWL_ORDER_BINARY_SUFFIX = "_order_binary"
PWL_DELTA_BOUND_SUFFIX = "_delta_bound"
PWL_BINARY_ORDER_SUFFIX = "_binary_order"
PWL_ACTIVE_BOUND_SUFFIX = "_active_bound"
BREAKPOINT_DIM = "_breakpoint"
SEGMENT_DIM = "_segment"
Expand Down
36 changes: 18 additions & 18 deletions linopy/piecewise.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@
HELPER_DIMS,
LP_SEG_DIM,
PWL_ACTIVE_BOUND_SUFFIX,
PWL_BINARY_SUFFIX,
PWL_BINARY_ORDER_SUFFIX,
PWL_CONVEX_SUFFIX,
PWL_DELTA_BOUND_SUFFIX,
PWL_DELTA_SUFFIX,
PWL_FILL_SUFFIX,
PWL_INC_BINARY_SUFFIX,
PWL_INC_LINK_SUFFIX,
PWL_INC_ORDER_SUFFIX,
PWL_FILL_ORDER_SUFFIX,
PWL_LAMBDA_SUFFIX,
PWL_LINK_SUFFIX,
PWL_ORDER_BINARY_SUFFIX,
PWL_SEGMENT_BINARY_SUFFIX,
PWL_SELECT_SUFFIX,
PWL_X_LINK_SUFFIX,
SEGMENT_DIM,
)

Expand Down Expand Up @@ -813,7 +813,7 @@ def _add_sos2(

lambda_name = f"{name}{PWL_LAMBDA_SUFFIX}"
convex_name = f"{name}{PWL_CONVEX_SUFFIX}"
link_name = f"{name}{PWL_X_LINK_SUFFIX}"
link_name = f"{name}{PWL_LINK_SUFFIX}"

lambda_var = model.add_variables(
lower=0, upper=1, coords=lambda_coords, name=lambda_name, mask=lambda_mask
Expand All @@ -840,11 +840,11 @@ def _add_incremental(
extra = _var_coords_from(stacked_bp, exclude={dim, link_dim})

delta_name = f"{name}{PWL_DELTA_SUFFIX}"
fill_name = f"{name}{PWL_FILL_SUFFIX}"
link_name = f"{name}{PWL_X_LINK_SUFFIX}"
inc_binary_name = f"{name}{PWL_INC_BINARY_SUFFIX}"
inc_link_name = f"{name}{PWL_INC_LINK_SUFFIX}"
inc_order_name = f"{name}{PWL_INC_ORDER_SUFFIX}"
fill_order_name = f"{name}{PWL_FILL_ORDER_SUFFIX}"
link_name = f"{name}{PWL_LINK_SUFFIX}"
order_binary_name = f"{name}{PWL_ORDER_BINARY_SUFFIX}"
delta_bound_name = f"{name}{PWL_DELTA_BOUND_SUFFIX}"
binary_order_name = f"{name}{PWL_BINARY_ORDER_SUFFIX}"

n_segments = stacked_bp.sizes[dim] - 1
seg_dim = f"{dim}_seg"
Expand Down Expand Up @@ -873,17 +873,17 @@ def _add_incremental(
model.add_constraints(delta_var <= active, name=active_bound_name)

binary_var = model.add_variables(
binary=True, coords=delta_coords, name=inc_binary_name, mask=delta_mask
binary=True, coords=delta_coords, name=order_binary_name, mask=delta_mask
)
model.add_constraints(delta_var <= binary_var, name=inc_link_name)
model.add_constraints(delta_var <= binary_var, name=delta_bound_name)

if n_segments >= 2:
delta_lo = delta_var.isel({seg_dim: slice(None, -1)}, drop=True)
delta_hi = delta_var.isel({seg_dim: slice(1, None)}, drop=True)
model.add_constraints(delta_hi <= delta_lo, name=fill_name)
model.add_constraints(delta_hi <= delta_lo, name=fill_order_name)

binary_hi = binary_var.isel({seg_dim: slice(1, None)}, drop=True)
model.add_constraints(binary_hi <= delta_lo, name=inc_order_name)
model.add_constraints(binary_hi <= delta_lo, name=binary_order_name)

bp0 = stacked_bp.isel({dim: 0})
bp0_term: DataArray | LinearExpression = bp0
Expand Down Expand Up @@ -947,11 +947,11 @@ def _add_disjunctive(
lambda_mask = agg_mask
binary_mask = agg_mask.any(dim=dim)

binary_name = f"{name}{PWL_BINARY_SUFFIX}"
binary_name = f"{name}{PWL_SEGMENT_BINARY_SUFFIX}"
select_name = f"{name}{PWL_SELECT_SUFFIX}"
lambda_name = f"{name}{PWL_LAMBDA_SUFFIX}"
convex_name = f"{name}{PWL_CONVEX_SUFFIX}"
link_name = f"{name}{PWL_X_LINK_SUFFIX}"
link_name = f"{name}{PWL_LINK_SUFFIX}"

binary_var = model.add_variables(
binary=True, coords=binary_coords, name=binary_name, mask=binary_mask
Expand Down
54 changes: 27 additions & 27 deletions test/test_piecewise_constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@
BREAKPOINT_DIM,
LP_SEG_DIM,
PWL_ACTIVE_BOUND_SUFFIX,
PWL_BINARY_SUFFIX,
PWL_BINARY_ORDER_SUFFIX,
PWL_CONVEX_SUFFIX,
PWL_DELTA_BOUND_SUFFIX,
PWL_DELTA_SUFFIX,
PWL_FILL_SUFFIX,
PWL_INC_BINARY_SUFFIX,
PWL_INC_LINK_SUFFIX,
PWL_INC_ORDER_SUFFIX,
PWL_FILL_ORDER_SUFFIX,
PWL_LAMBDA_SUFFIX,
PWL_LINK_SUFFIX,
PWL_ORDER_BINARY_SUFFIX,
PWL_SEGMENT_BINARY_SUFFIX,
PWL_SELECT_SUFFIX,
PWL_X_LINK_SUFFIX,
SEGMENT_DIM,
)
from linopy.solver_capabilities import SolverFeature, get_available_solvers_with_feature
Expand Down Expand Up @@ -289,7 +289,7 @@ def test_sos2(self) -> None:
)
assert f"pwl0{PWL_LAMBDA_SUFFIX}" in m.variables
assert f"pwl0{PWL_CONVEX_SUFFIX}" in m.constraints
assert f"pwl0{PWL_X_LINK_SUFFIX}" in m.constraints
assert f"pwl0{PWL_LINK_SUFFIX}" in m.constraints
# N-var path uses a single stacked link constraint (no separate y_link)
lam = m.variables[f"pwl0{PWL_LAMBDA_SUFFIX}"]
assert lam.attrs.get("sos_type") == 2
Expand Down Expand Up @@ -430,7 +430,7 @@ def test_creates_delta_vars(self) -> None:
assert f"pwl0{PWL_DELTA_SUFFIX}" in m.variables
delta = m.variables[f"pwl0{PWL_DELTA_SUFFIX}"]
assert delta.labels.sizes[LP_SEG_DIM] == 3
assert f"pwl0{PWL_FILL_SUFFIX}" in m.constraints
assert f"pwl0{PWL_FILL_ORDER_SUFFIX}" in m.constraints
assert f"pwl0{PWL_LAMBDA_SUFFIX}" not in m.variables

def test_nonmonotonic_raises(self) -> None:
Expand Down Expand Up @@ -467,7 +467,7 @@ def test_two_breakpoints_no_fill(self) -> None:
)
delta = m.variables[f"pwl0{PWL_DELTA_SUFFIX}"]
assert delta.labels.sizes[LP_SEG_DIM] == 1
assert f"pwl0{PWL_X_LINK_SUFFIX}" in m.constraints
assert f"pwl0{PWL_LINK_SUFFIX}" in m.constraints
# N-var path uses a single stacked link constraint (no separate y_link)

def test_creates_binary_indicator_vars(self) -> None:
Expand All @@ -479,10 +479,10 @@ def test_creates_binary_indicator_vars(self) -> None:
(y, [5, 10, 20, 80]),
method="incremental",
)
assert f"pwl0{PWL_INC_BINARY_SUFFIX}" in m.variables
binary = m.variables[f"pwl0{PWL_INC_BINARY_SUFFIX}"]
assert f"pwl0{PWL_ORDER_BINARY_SUFFIX}" in m.variables
binary = m.variables[f"pwl0{PWL_ORDER_BINARY_SUFFIX}"]
assert binary.labels.sizes[LP_SEG_DIM] == 3
assert f"pwl0{PWL_INC_LINK_SUFFIX}" in m.constraints
assert f"pwl0{PWL_DELTA_BOUND_SUFFIX}" in m.constraints

def test_creates_order_constraints(self) -> None:
m = Model()
Expand All @@ -493,7 +493,7 @@ def test_creates_order_constraints(self) -> None:
(y, [5, 10, 20, 80]),
method="incremental",
)
assert f"pwl0{PWL_INC_ORDER_SUFFIX}" in m.constraints
assert f"pwl0{PWL_BINARY_ORDER_SUFFIX}" in m.constraints

def test_two_breakpoints_no_order_constraint(self) -> None:
"""With only one segment, there's no order constraint needed."""
Expand All @@ -505,9 +505,9 @@ def test_two_breakpoints_no_order_constraint(self) -> None:
(y, [5, 80]),
method="incremental",
)
assert f"pwl0{PWL_INC_BINARY_SUFFIX}" in m.variables
assert f"pwl0{PWL_INC_LINK_SUFFIX}" in m.constraints
assert f"pwl0{PWL_INC_ORDER_SUFFIX}" not in m.constraints
assert f"pwl0{PWL_ORDER_BINARY_SUFFIX}" in m.variables
assert f"pwl0{PWL_DELTA_BOUND_SUFFIX}" in m.constraints
assert f"pwl0{PWL_BINARY_ORDER_SUFFIX}" not in m.constraints

def test_decreasing_monotonic(self) -> None:
m = Model()
Expand Down Expand Up @@ -535,7 +535,7 @@ def test_equality_creates_binary(self) -> None:
(x, segments([[0, 10], [50, 100]])),
(y, segments([[0, 5], [20, 80]])),
)
assert f"pwl0{PWL_BINARY_SUFFIX}" in m.variables
assert f"pwl0{PWL_SEGMENT_BINARY_SUFFIX}" in m.variables
assert f"pwl0{PWL_SELECT_SUFFIX}" in m.constraints
assert f"pwl0{PWL_LAMBDA_SUFFIX}" in m.variables
assert f"pwl0{PWL_CONVEX_SUFFIX}" in m.constraints
Expand Down Expand Up @@ -574,7 +574,7 @@ def test_multi_dimensional(self) -> None:
),
),
)
binary = m.variables[f"pwl0{PWL_BINARY_SUFFIX}"]
binary = m.variables[f"pwl0{PWL_SEGMENT_BINARY_SUFFIX}"]
lam = m.variables[f"pwl0{PWL_LAMBDA_SUFFIX}"]
assert "generator" in binary.dims
assert "generator" in lam.dims
Expand All @@ -590,10 +590,10 @@ def test_three_variables(self) -> None:
(y, segments([[0, 5], [20, 80]])),
(z, segments([[0, 3], [15, 60]])),
)
assert f"pwl0{PWL_BINARY_SUFFIX}" in m.variables
assert f"pwl0{PWL_SEGMENT_BINARY_SUFFIX}" in m.variables
assert f"pwl0{PWL_LAMBDA_SUFFIX}" in m.variables
# Single link constraint with _pwl_var dimension
link = m.constraints[f"pwl0{PWL_X_LINK_SUFFIX}"]
link = m.constraints[f"pwl0{PWL_LINK_SUFFIX}"]
assert "_pwl_var" in [str(d) for d in link.dims]


Expand Down Expand Up @@ -663,7 +663,7 @@ def test_custom_name(self) -> None:
name="my_pwl",
)
assert f"my_pwl{PWL_DELTA_SUFFIX}" in m.variables
assert f"my_pwl{PWL_X_LINK_SUFFIX}" in m.constraints
assert f"my_pwl{PWL_LINK_SUFFIX}" in m.constraints
# N-var path uses a single stacked link constraint (no separate y_link)


Expand Down Expand Up @@ -1148,7 +1148,7 @@ def test_sos2_creates_lambda_and_link(self) -> None:
)
assert f"pwl0{PWL_LAMBDA_SUFFIX}" in m.variables
assert f"pwl0{PWL_CONVEX_SUFFIX}" in m.constraints
assert f"pwl0{PWL_X_LINK_SUFFIX}" in m.constraints
assert f"pwl0{PWL_LINK_SUFFIX}" in m.constraints

def test_incremental_creates_delta(self) -> None:
m = Model()
Expand All @@ -1160,7 +1160,7 @@ def test_incremental_creates_delta(self) -> None:
method="incremental",
)
assert f"pwl0{PWL_DELTA_SUFFIX}" in m.variables
assert f"pwl0{PWL_X_LINK_SUFFIX}" in m.constraints
assert f"pwl0{PWL_LINK_SUFFIX}" in m.constraints

def test_auto_selects_method(self) -> None:
m = Model()
Expand Down Expand Up @@ -1193,9 +1193,9 @@ def test_three_variables(self) -> None:
method="sos2",
)
assert f"pwl0{PWL_LAMBDA_SUFFIX}" in m.variables
assert f"pwl0{PWL_X_LINK_SUFFIX}" in m.constraints
assert f"pwl0{PWL_LINK_SUFFIX}" in m.constraints
# link constraint should have _pwl_var dimension
link = m.constraints[f"pwl0{PWL_X_LINK_SUFFIX}"]
link = m.constraints[f"pwl0{PWL_LINK_SUFFIX}"]
assert "_pwl_var" in link.labels.dims

def test_custom_name(self) -> None:
Expand Down Expand Up @@ -1307,9 +1307,9 @@ def test_disjunctive_three_pairs(self) -> None:
(y, seg),
(z, seg),
)
assert f"pwl0{PWL_BINARY_SUFFIX}" in m.variables
assert f"pwl0{PWL_SEGMENT_BINARY_SUFFIX}" in m.variables
assert f"pwl0{PWL_LAMBDA_SUFFIX}" in m.variables
assert f"pwl0{PWL_X_LINK_SUFFIX}" in m.constraints
assert f"pwl0{PWL_LINK_SUFFIX}" in m.constraints

def test_disjunctive_interior_nan_raises(self) -> None:
"""Disjunctive with interior NaN raises ValueError."""
Expand Down