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

  1. Add autodoc to docs/source/api/

  2. Add usage examples to docs/source/user-guide/

  3. 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 (LX prefix for core classes)

Testing Requirements

All extensions must have:

  • Unit tests (>90% coverage)

  • Integration tests

  • Type annotations

  • Docstrings

Pull Request Process

  1. Fork the repository

  2. Create feature branch

  3. Add tests and documentation

  4. Run full test suite

  5. Submit PR with description

Next Steps