Extending Core Components¶
Guide for extending LumiX’s core functionality.
Adding New Variable Types¶
Semi-Continuous Variables¶
Variables that are either 0 or within [L, U]:
from lumix.core import LXVariable, LXVarType
from typing_extensions import Self
class LXSemiContinuousVariable(LXVariable[TModel, float]):
"""Variable that is 0 or in [lower, upper]."""
def semi_continuous(self, lower: float, upper: float) -> Self:
"""Set as semi-continuous variable."""
self.var_type = LXVarType.CONTINUOUS
self.lower_bound = lower
self.upper_bound = upper
# Mark for special handling in solver
self._is_semi_continuous = True
return self
Special Ordered Sets¶
SOS1 or SOS2 variables:
class LXSOSVariable(LXVariable[TModel, float]):
"""Special Ordered Set variable."""
def sos1(self) -> Self:
"""Mark as SOS1 (at most one non-zero)."""
self._sos_type = "SOS1"
return self
def sos2(self) -> Self:
"""Mark as SOS2 (at most two non-zero, adjacent)."""
self._sos_type = "SOS2"
return self
Adding New Expression Types¶
Conic Expressions¶
For second-order cone programming:
from lumix.core.expressions import LXLinearExpression
from dataclasses import dataclass, field
@dataclass
class LXConicExpression:
"""Second-order cone expression: ||Ax + b|| <= c^T x + d"""
A_matrix: List[List[float]] = field(default_factory=list)
b_vector: List[float] = field(default_factory=list)
c_vector: List[float] = field(default_factory=list)
d_scalar: float = 0.0
def add_to_cone(self, var: LXVariable, coeff: float) -> Self:
"""Add variable to cone constraint."""
# Implementation
return self
Custom Objective Types¶
For specialized objectives:
@dataclass
class LXRobustExpression:
"""Robust optimization expression."""
nominal: LXLinearExpression
uncertainty_set: Dict[str, Tuple[float, float]]
def add_robust_term(self, var: LXVariable, nominal: float,
uncertainty: float) -> Self:
"""Add term with uncertainty."""
return self
Adding New Constraint Types¶
Indicator Constraints¶
Conditional constraints:
from lumix.core.constraints import LXConstraint
class LXIndicatorConstraint(LXConstraint[TModel]):
"""If binary_var == 1, then linear_expr <= rhs."""
def __init__(self, name: str):
super().__init__(name)
self.binary_var: Optional[LXVariable] = None
self.indicator_value: int = 1
def indicator(self, var: LXVariable, value: int = 1) -> Self:
"""Set indicator variable and value."""
self.binary_var = var
self.indicator_value = value
return self
Complementarity Constraints¶
For equilibrium problems:
class LXComplementarityConstraint(LXConstraint[TModel]):
"""Complementarity: x >= 0, y >= 0, x * y = 0"""
def complementary_to(self, other_var: LXVariable) -> Self:
"""Set complementary variable."""
self._complementary_var = other_var
return self
Extending the Model¶
Adding Model Metadata¶
from lumix.core.model import LXModel
class LXModelWithMetadata(LXModel[TModel]):
"""Model with additional metadata."""
def __init__(self, name: str):
super().__init__(name)
self.description: str = ""
self.author: str = ""
self.created_at: datetime = datetime.now()
def set_description(self, desc: str) -> Self:
"""Set model description."""
self.description = desc
return self
Model Validation¶
class LXValidatedModel(LXModel[TModel]):
"""Model with validation."""
def validate(self) -> List[str]:
"""Validate model and return errors."""
errors = []
if not self.variables:
errors.append("Model has no variables")
if not self.constraints:
errors.append("Model has no constraints")
if self.objective_expr is None:
errors.append("Model has no objective")
return errors
def validate_or_raise(self):
"""Validate and raise if errors."""
errors = self.validate()
if errors:
raise ValueError(f"Invalid model: {errors}")
Creating Custom Indexing¶
Custom Index Dimensions¶
from lumix.indexing import LXIndexDimension
class LXTimeIndexDimension(LXIndexDimension[TModel]):
"""Time-based indexing with periods."""
def __init__(self, model: Type[TModel], key_func: Callable):
super().__init__(model, key_func)
self.time_periods: List[int] = []
def for_periods(self, periods: List[int]) -> Self:
"""Restrict to specific time periods."""
self.time_periods = periods
return self
Sparse Indexing¶
For efficient sparse variable creation:
class LXSparseCartesianProduct:
"""Cartesian product with sparsity pattern."""
def __init__(self, *dimensions):
self.dimensions = dimensions
self.sparsity_matrix: Optional[np.ndarray] = None
def set_sparsity(self, matrix: np.ndarray) -> Self:
"""Set sparsity pattern (1 = create, 0 = skip)."""
self.sparsity_matrix = matrix
return self
Testing Extensions¶
Unit Tests¶
import pytest
from lumix.core import LXModel
def test_custom_variable_type():
var = LXSemiContinuousVariable[Product, float]("x")
var.semi_continuous(lower=10, upper=100)
assert var.lower_bound == 10
assert var.upper_bound == 100
assert var._is_semi_continuous
Integration Tests¶
def test_custom_expression_in_model():
model = LXModel("test")
conic_expr = LXConicExpression()
# Build and solve
model.add_constraint(...)
solution = optimizer.solve(model)
assert solution.is_optimal()
Type Checking¶
Ensure extensions maintain type safety:
# Your extension should pass mypy
var: LXSemiContinuousVariable[Product, float] = \\
LXSemiContinuousVariable("x")
Documentation¶
Docstring Template¶
Use Google-style docstrings:
class LXCustomVariable(LXVariable[TModel, TValue]):
"""One-line summary.
Longer description explaining the variable type,
when to use it, and any special considerations.
Args:
name: Variable name
Examples:
Basic usage::
custom_var = (
LXCustomVariable[Product, float]("var")
.custom_method()
.from_data(products)
)
Note:
Any important notes or warnings.
See Also:
- :class:`~lumix.core.variables.LXVariable`
- Related documentation
"""
Adding to Documentation¶
Add autodoc to
docs/source/api/Add usage examples to
docs/source/user-guide/Update main index files
Contributing Guidelines¶
Code Style¶
Follow existing patterns:
Use Google-style docstrings
Type all function signatures
Use fluent API (return
Self)Name consistently (
LXprefix for core classes)
Testing Requirements¶
All extensions must have:
Unit tests (>90% coverage)
Integration tests
Type annotations
Docstrings
Pull Request Process¶
Fork the repository
Create feature branch
Add tests and documentation
Run full test suite
Submit PR with description
Next Steps¶
Design Decisions - Understand the rationale
Core Architecture - Deep dive into architecture
Core Module API - Full API reference