Skip to content

Commit f82876f

Browse files
committed
Add documentation (claude)
1 parent 0ba34f8 commit f82876f

File tree

2 files changed

+304
-0
lines changed

2 files changed

+304
-0
lines changed

doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ This package is published under MIT license.
109109
creating-variables
110110
creating-expressions
111111
creating-constraints
112+
sos-constraints
112113
manipulating-models
113114
testing-framework
114115
transport-tutorial

doc/sos-constraints.rst

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
.. _sos-constraints:
2+
3+
Special Ordered Sets (SOS) Constraints
4+
=======================================
5+
6+
Special Ordered Sets (SOS) are a constraint type used in mixed-integer programming to model situations where only one or two variables from an ordered set can be non-zero. Linopy supports both SOS Type 1 and SOS Type 2 constraints.
7+
8+
.. contents::
9+
:local:
10+
:depth: 2
11+
12+
Overview
13+
--------
14+
15+
SOS constraints are particularly useful for:
16+
17+
- **SOS1**: Modeling mutually exclusive choices (e.g., selecting one facility from multiple locations)
18+
- **SOS2**: Piecewise linear approximations of nonlinear functions
19+
- Improving branch-and-bound efficiency in mixed-integer programming
20+
21+
Types of SOS Constraints
22+
-------------------------
23+
24+
SOS Type 1 (SOS1)
25+
~~~~~~~~~~~~~~~~~~
26+
27+
In an SOS1 constraint, **at most one** variable in the ordered set can be non-zero.
28+
29+
**Example use cases:**
30+
- Facility location problems (choose one location among many)
31+
- Technology selection (choose one technology option)
32+
- Mutually exclusive investment decisions
33+
34+
SOS Type 2 (SOS2)
35+
~~~~~~~~~~~~~~~~~~
36+
37+
In an SOS2 constraint, **at most two adjacent** variables in the ordered set can be non-zero. The adjacency is determined by the ordering weights (coordinates) of the variables.
38+
39+
**Example use cases:**
40+
- Piecewise linear approximation of nonlinear functions
41+
- Portfolio optimization with discrete risk levels
42+
- Production planning with discrete capacity levels
43+
44+
Basic Usage
45+
-----------
46+
47+
Adding SOS Constraints
48+
~~~~~~~~~~~~~~~~~~~~~~~
49+
50+
To add SOS constraints to variables in linopy:
51+
52+
.. code-block:: python
53+
54+
import linopy
55+
import pandas as pd
56+
import xarray as xr
57+
58+
# Create model
59+
m = linopy.Model()
60+
61+
# Create variables with numeric coordinates
62+
coords = pd.Index([0, 1, 2], name="options")
63+
x = m.add_variables(coords=[coords], name="x", lower=0, upper=1)
64+
65+
# Add SOS1 constraint
66+
m.add_sos_constraints(x, sos_type=1, sos_dim="options")
67+
68+
# For SOS2 constraint
69+
breakpoints = pd.Index([0.0, 1.0, 2.0], name="breakpoints")
70+
lambdas = m.add_variables(coords=[breakpoints], name="lambdas", lower=0, upper=1)
71+
m.add_sos_constraints(lambdas, sos_type=2, sos_dim="breakpoints")
72+
73+
Method Signature
74+
~~~~~~~~~~~~~~~~
75+
76+
.. code-block:: python
77+
78+
Model.add_sos_constraints(variable, sos_type, sos_dim)
79+
80+
**Parameters:**
81+
82+
- ``variable`` : Variable
83+
The variable to which the SOS constraint should be applied
84+
- ``sos_type`` : {1, 2}
85+
Type of SOS constraint (1 or 2)
86+
- ``sos_dim`` : str
87+
Name of the dimension along which the SOS constraint applies
88+
89+
**Requirements:**
90+
91+
- The specified dimension must exist in the variable
92+
- The coordinates for the SOS dimension must be numeric (used as weights for ordering)
93+
- Only one SOS constraint can be applied per variable
94+
95+
Examples
96+
--------
97+
98+
Example 1: Facility Location (SOS1)
99+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
100+
101+
.. code-block:: python
102+
103+
import linopy
104+
import pandas as pd
105+
import xarray as xr
106+
107+
# Problem data
108+
locations = pd.Index([0, 1, 2, 3], name="locations")
109+
costs = xr.DataArray([100, 150, 120, 80], coords=[locations])
110+
benefits = xr.DataArray([200, 300, 250, 180], coords=[locations])
111+
112+
# Create model
113+
m = linopy.Model()
114+
115+
# Decision variables: build facility at location i
116+
build = m.add_variables(coords=[locations], name="build", lower=0, upper=1)
117+
118+
# SOS1 constraint: at most one facility can be built
119+
m.add_sos_constraints(build, sos_type=1, sos_dim="locations")
120+
121+
# Objective: maximize net benefit
122+
net_benefit = benefits - costs
123+
m.add_objective(-((net_benefit * build).sum()))
124+
125+
# Solve
126+
m.solve(solver_name="highs")
127+
128+
if m.status == "ok":
129+
solution = build.solution.to_pandas()
130+
selected_location = solution[solution > 0.5].index[0]
131+
print(f"Build facility at location {selected_location}")
132+
133+
Example 2: Piecewise Linear Approximation (SOS2)
134+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
135+
136+
.. code-block:: python
137+
138+
import numpy as np
139+
140+
# Approximate f(x) = x² over [0, 3] with breakpoints
141+
breakpoints = pd.Index([0, 1, 2, 3], name="breakpoints")
142+
143+
x_vals = xr.DataArray(breakpoints.to_series())
144+
y_vals = x_vals**2
145+
146+
# Create model
147+
m = linopy.Model()
148+
149+
# SOS2 variables (interpolation weights)
150+
lambdas = m.add_variables(lower=0, upper=1, coords=[breakpoints], name="lambdas")
151+
m.add_sos_constraints(lambdas, sos_type=2, sos_dim="breakpoints")
152+
153+
# Interpolated coordinates
154+
x = m.add_variables(name="x", lower=0, upper=3)
155+
y = m.add_variables(name="y", lower=0, upper=9)
156+
157+
# Constraints
158+
m.add_constraints(lambdas.sum() == 1, name="convexity")
159+
m.add_constraints(x == lambdas @ x_vals, name="x_interpolation")
160+
m.add_constraints(y == lambdas @ y_vals, name="y_interpolation")
161+
m.add_constraints(x >= 1.5, name="x_minimum")
162+
163+
# Objective: minimize approximated function value
164+
m.add_objective(y)
165+
166+
# Solve
167+
m.solve(solver_name="highs")
168+
169+
Working with Multi-dimensional Variables
170+
-----------------------------------------
171+
172+
SOS constraints are created for each dimension that is not sos_dim.
173+
174+
.. code-block:: python
175+
176+
# Multi-period production planning
177+
periods = pd.Index(range(3), name="periods")
178+
modes = pd.Index([0, 1, 2], name="modes")
179+
180+
# 2D variables: periods × modes
181+
period_modes = m.add_variables(
182+
lower=0, upper=1, coords=[periods, modes], name="use_mode"
183+
)
184+
185+
# Adds SOS1 constraint for each period
186+
m.add_sos_constraints(period_modes, sos_type=1, sos_dim="modes")
187+
188+
Accessing SOS Variables
189+
-----------------------
190+
191+
You can easily identify and access variables with SOS constraints:
192+
193+
.. code-block:: python
194+
195+
# Get all variables with SOS constraints
196+
sos_variables = m.variables.sos
197+
print(f"SOS variables: {list(sos_variables.keys())}")
198+
199+
# Check SOS properties of a variable
200+
for var_name in sos_variables:
201+
var = m.variables[var_name]
202+
sos_type = var.attrs["sos_type"]
203+
sos_dim = var.attrs["sos_dim"]
204+
print(f"{var_name}: SOS{sos_type} on dimension '{sos_dim}'")
205+
206+
Variable Representation
207+
~~~~~~~~~~~~~~~~~~~~~~~
208+
209+
Variables with SOS constraints show their SOS information in string representations:
210+
211+
.. code-block:: python
212+
213+
print(build)
214+
# Output: Variable (locations: 4) - sos1 on locations
215+
# -----------------------------------------------
216+
# [0]: build[0] ∈ [0, 1]
217+
# [1]: build[1] ∈ [0, 1]
218+
# [2]: build[2] ∈ [0, 1]
219+
# [3]: build[3] ∈ [0, 1]
220+
221+
LP File Export
222+
--------------
223+
224+
The generated LP file will include a SOS section:
225+
226+
.. code-block:: text
227+
228+
sos
229+
230+
s0: S1 :: x0:0 x1:1 x2:2
231+
s3: S2 :: x3:0.0 x4:1.0 x5:2.0
232+
233+
Solver Compatibility
234+
--------------------
235+
236+
SOS constraints are supported by most modern mixed-integer programming solvers through the LP file format:
237+
238+
**Supported solvers:**
239+
- HiGHS
240+
- Gurobi
241+
- CPLEX
242+
- COIN-OR CBC
243+
- SCIP
244+
- Xpress
245+
246+
**Note:** Some solvers may have varying levels of SOS support. Check your solver's documentation for specific capabilities.
247+
248+
Common Patterns
249+
---------------
250+
251+
Piecewise Linear Cost Function
252+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
253+
254+
.. code-block:: python
255+
256+
def add_piecewise_cost(model, variable, breakpoints, costs):
257+
"""Add piecewise linear cost function using SOS2."""
258+
n_segments = len(breakpoints)
259+
lambda_coords = pd.Index(range(n_segments), name="segments")
260+
261+
lambdas = model.add_variables(
262+
coords=[lambda_coords], name="cost_lambdas", lower=0, upper=1
263+
)
264+
model.add_sos_constraints(lambdas, sos_type=2, sos_dim="segments")
265+
266+
cost_var = model.add_variables(name="cost", lower=0)
267+
268+
x_vals = xr.DataArray(breakpoints, coords=[lambda_coords])
269+
c_vals = xr.DataArray(costs, coords=[lambda_coords])
270+
271+
model.add_constraints(lambdas.sum() == 1, name="cost_convexity")
272+
model.add_constraints(variable == (x_vals * lambdas).sum(), name="cost_x_def")
273+
model.add_constraints(cost_var == (c_vals * lambdas).sum(), name="cost_def")
274+
275+
return cost_var
276+
277+
Mutually Exclusive Investments
278+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
279+
280+
.. code-block:: python
281+
282+
def add_exclusive_investments(model, projects, costs, returns):
283+
"""Add mutually exclusive investment decisions using SOS1."""
284+
project_coords = pd.Index(projects, name="projects")
285+
286+
invest = model.add_variables(
287+
coords=[project_coords], name="invest", binary=True
288+
)
289+
model.add_sos_constraints(invest, sos_type=1, sos_dim="projects")
290+
291+
total_cost = (invest * costs).sum()
292+
total_return = (invest * returns).sum()
293+
294+
return invest, total_cost, total_return
295+
296+
297+
See Also
298+
--------
299+
300+
- :doc:`creating-variables`: Creating variables with coordinates
301+
- :doc:`creating-constraints`: Adding regular constraints
302+
- :doc:`user-guide`: General linopy usage patterns
303+
- Example notebook: ``examples/sos-constraints-example.ipynb``

0 commit comments

Comments
 (0)