Utils Module API

The utils module provides utility classes and protocols for enhanced functionality in LumiX, including logging, ORM integration, and rational number conversion.

Overview

The utils module consists of three main components:

        graph TD
    A[Utils Module] --> B[LXModelLogger]
    A --> C[ORM Integration]
    A --> D[LXRationalConverter]
    C --> E[LXORMModel]
    C --> F[LXORMContext]
    C --> G[LXTypedQuery]
    C --> H[LXNumeric]

    style A fill:#e1f5ff
    style B fill:#fff4e1
    style C fill:#ffe1e1
    style D fill:#e1ffe1
    style E fill:#f0e1ff
    style F fill:#f0e1ff
    style G fill:#f0e1ff
    style H fill:#f0e1ff
    

Components

Logging

lumix.utils.logger.LXModelLogger

Enhanced logging for optimization models.

The LXModelLogger provides optimization-specific logging capabilities for tracking model construction, solving progress, and solution analysis.

Key Features:

  • Automatic solve time tracking

  • Specialized logging methods for optimization events

  • Configurable logging levels

  • Integration with Python’s logging system

ORM Integration

lumix.utils.orm.LXORMModel

Structural protocol for any ORM model.

lumix.utils.orm.LXORMContext

Type-safe ORM query interface with generic support.

lumix.utils.orm.LXTypedQuery

Type-safe query builder with fluent API.

lumix.utils.orm.LXNumeric

Structural protocol for numeric types with optimization operations.

ORM integration components enable type-safe database queries and seamless integration with ORM libraries like SQLAlchemy, Django ORM, and Peewee.

Key Features:

  • Structural typing via Protocol (PEP 544)

  • Type-safe query builders

  • Full IDE autocomplete support

  • ORM-agnostic design

Rational Conversion

lumix.utils.rational.LXRationalConverter

Converts floating-point coefficients to rational numbers for integer-only solvers.

The LXRationalConverter converts floating-point coefficients to rational numbers for use with integer-only solvers.

Key Features:

  • Three approximation algorithms (Farey, Continued Fraction, Stern-Brocot)

  • Configurable precision control

  • Batch coefficient conversion

  • Error tracking and comparison

Detailed API Reference

Logging Module

Meaningful logging utilities for LumiX.

This module provides enhanced logging capabilities specifically designed for optimization models in LumiX. It offers specialized logging methods for tracking model construction, solving progress, and solution analysis.

The LXModelLogger class extends Python’s standard logging with domain-specific methods for logging optimization-related events like variable creation, constraint addition, solve status, and sensitivity analysis.

Key Features:
  • Optimization-Specific Logging: Methods tailored for model building and solving

  • Automatic Timing: Built-in solve time tracking

  • Formatted Output: Consistent, readable log messages for optimization events

  • Configurable Levels: Standard logging levels (DEBUG, INFO, WARNING, ERROR)

Examples

Basic usage for model logging:

from lumix.utils import LXModelLogger

logger = LXModelLogger(name="production_model", level=logging.INFO)

# Log model creation
logger.log_model_creation("ProductionPlan", num_vars=100, num_constraints=50)

# Log solving
logger.log_solve_start("Gurobi")
# ... solve model ...
logger.log_solve_end("Optimal", objective_value=12500.50)

Debug-level logging for detailed tracking:

logger = LXModelLogger(name="debug_model", level=logging.DEBUG)

# Track each variable and constraint
logger.log_variable_creation("production", "continuous", count=50)
logger.log_constraint_creation("capacity", "<=", count=10)

See also

class lumix.utils.logger.LXModelLogger(name='lumix', level=20)[source]

Bases: object

Enhanced logging for optimization models.

Provides specialized logging methods for tracking optimization model construction, solving progress, and solution analysis. Automatically handles timing and formatting for optimization-specific events.

Parameters:
logger

Underlying Python logger instance

Type:

logging.Logger

start_time

Timestamp when solve started (for timing)

Type:

Optional[datetime]

Examples

Create and use a model logger:

from lumix.utils import LXModelLogger
import logging

logger = LXModelLogger(name="my_model", level=logging.INFO)
logger.log_model_creation("ProductionModel", 100, 50)
logger.log_solve_start("Gurobi")
# ... solving happens ...
logger.log_solve_end("Optimal", objective_value=42500.0)

Note

The logger automatically creates a console handler if none exists. Multiple instances with the same name will share the same underlying Python logger.

__init__(name='lumix', level=20)[source]

Initialize model logger with specified name and logging level.

Parameters:
  • name (str) – Logger name for identification. Defaults to “lumix”. Use different names to distinguish between multiple models.

  • level (int) – Logging level from Python’s logging module. Defaults to logging.INFO. Common values: logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR.

Examples

Create logger with default settings:

logger = LXModelLogger()

Create logger with custom name and debug level:

logger = LXModelLogger(name="my_model", level=logging.DEBUG)
log_model_creation(name, num_vars, num_constraints)[source]

Log the creation of an optimization model.

Parameters:
  • name (str) – Name of the model being created

  • num_vars (int) – Total number of decision variables in the model

  • num_constraints (int) – Total number of constraints in the model

Return type:

None

Examples

Log model creation after building:

logger.log_model_creation("ProductionPlan", num_vars=150, num_constraints=75)
# Output: Created model 'ProductionPlan' with 150 variables and 75 constraints
log_variable_creation(var_name, var_type, count=1)[source]

Log the creation of decision variables.

Parameters:
  • var_name (str) – Name or identifier of the variable family

  • var_type (str) – Type of variable (“continuous”, “integer”, “binary”)

  • count (int) – Number of variables created in this family. Defaults to 1.

Return type:

None

Examples

Log single variable creation:

logger.log_variable_creation("profit", "continuous")

Log variable family creation:

logger.log_variable_creation("production", "continuous", count=50)
# Output: Created 50 continuous variable(s): production
log_constraint_creation(constraint_name, sense, count=1)[source]

Log the creation of constraints.

Parameters:
  • constraint_name (str) – Name or identifier of the constraint family

  • sense (str) – Constraint sense (“<=”, “>=”, “==”)

  • count (int) – Number of constraints created in this family. Defaults to 1.

Return type:

None

Examples

Log single constraint:

logger.log_constraint_creation("budget", "<=")

Log constraint family:

logger.log_constraint_creation("capacity", "<=", count=20)
# Output: Created 20 constraint(s): capacity (<=)
log_solve_start(solver_name)[source]

Log the start of model solving and begin timing.

Records the current timestamp for automatic solve time calculation.

Parameters:

solver_name (str) – Name of the solver being used (“Gurobi”, “CPLEX”, “OR-Tools”, etc.)

Return type:

None

Examples

Start solve logging:

logger.log_solve_start("Gurobi")
# Output: Starting solve with Gurobi...
log_solve_end(status, objective_value=None, solve_time=None)[source]

Log the completion of model solving with results.

Automatically calculates elapsed time if log_solve_start was called. Otherwise, uses the provided solve_time parameter.

Parameters:
  • status (str) – Solve status (“Optimal”, “Infeasible”, “Unbounded”, “TimeLimit”, etc.)

  • objective_value (Optional[float]) – Optimal objective value if available. Defaults to None.

  • solve_time (Optional[float]) – Explicit solve time in seconds. Used if start time was not recorded. Defaults to None.

Return type:

None

Examples

Log successful solve with objective:

logger.log_solve_end("Optimal", objective_value=42500.75)
# Output: Solve completed: Optimal | Objective: 42500.7500 | Time: 2.35s

Log solve without objective:

logger.log_solve_end("Infeasible")
# Output: Solve completed: Infeasible | Time: 0.15s
log_solution_summary(num_nonzero, total_vars)[source]

Log a summary of the solution.

Parameters:
  • num_nonzero (int) – Number of variables with non-zero values in the solution

  • total_vars (int) – Total number of variables in the model

Return type:

None

Examples

Log solution sparsity:

logger.log_solution_summary(num_nonzero=25, total_vars=100)
# Output: Solution has 25/100 non-zero variables
log_linearization(term_type, method, aux_vars)[source]

Log automatic linearization applied to non-linear terms.

Parameters:
  • term_type (str) – Type of non-linear term (“bilinear”, “absolute”, “min/max”, etc.)

  • method (str) – Linearization method used (“McCormick”, “big-M”, “piecewise”, etc.)

  • aux_vars (int) – Number of auxiliary variables added during linearization

Return type:

None

Examples

Log bilinear linearization:

logger.log_linearization("bilinear", "McCormick", aux_vars=4)
# Output: Linearized bilinear using McCormick (added 4 auxiliary variables)
log_scenario(scenario_name, modifications)[source]

Log scenario analysis execution.

Parameters:
  • scenario_name (str) – Name or identifier of the scenario being analyzed

  • modifications (int) – Number of parameter modifications in this scenario

Return type:

None

Examples

Log scenario run:

logger.log_scenario("high_demand", modifications=5)
# Output: Running scenario 'high_demand' with 5 modifications
log_sensitivity(var_name, reduced_cost)[source]

Log sensitivity analysis results for a variable.

Parameters:
  • var_name (str) – Name of the variable being analyzed

  • reduced_cost (float) – Reduced cost (shadow price) of the variable

Return type:

None

Examples

Log variable sensitivity:

logger.log_sensitivity("production[Widget_A]", reduced_cost=-2.5)
# Output: Sensitivity: production[Widget_A] reduced cost = -2.500000
info(message)[source]

Log an informational message.

Parameters:

message (str) – Message to log at INFO level

Return type:

None

Examples

Log custom info message:

logger.info("Model preprocessing completed")
debug(message)[source]

Log a debug message.

Parameters:

message (str) – Message to log at DEBUG level

Return type:

None

Examples

Log debug details:

logger.debug("Checking constraint matrix sparsity")
warning(message)[source]

Log a warning message.

Parameters:

message (str) – Message to log at WARNING level

Return type:

None

Examples

Log warning:

logger.warning("Model contains unbounded variables")
error(message)[source]

Log an error message.

Parameters:

message (str) – Message to log at ERROR level

Return type:

None

Examples

Log error:

logger.error("Solver failed to find feasible solution")

ORM Module

ORM integration protocols and utilities for LumiX.

This module provides type-safe protocols and utilities for integrating LumiX with Object-Relational Mapping (ORM) libraries like SQLAlchemy, Django ORM, or Peewee.

The module defines structural typing protocols that allow LumiX to work with any ORM model that satisfies the interface, without requiring inheritance or explicit implementation.

Key Features:
  • Structural Typing: Works with any ORM via Protocol (PEP 544)

  • Type Safety: Full type checking and IDE autocomplete for ORM queries

  • Generic Support: Type-safe query builders with Generic types

  • ORM Agnostic: Compatible with SQLAlchemy, Django ORM, Peewee, etc.

Architecture:

The module uses Python’s Protocol to define structural interfaces. Any object that has the required attributes automatically satisfies the protocol without needing explicit inheritance.

Examples

Using with SQLAlchemy models:

from sqlalchemy import Column, Integer, String, Float
from sqlalchemy.ext.declarative import declarative_base
from lumix.utils import LXORMContext

Base = declarative_base()

class Product(Base):
    __tablename__ = 'products'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    profit = Column(Float)

# Use type-safe ORM context
session = Session()
ctx = LXORMContext(session)

# Query with full type safety
products = ctx.query(Product).filter(lambda p: p.profit > 100).all()

Integration with LumiX models:

from lumix import LXVariable

# Create variable family from ORM data
production = (
    LXVariable[Product, float]("production")
    .continuous()
    .bounds(lower=0)
    .from_data(products)  # products from ORM query
    .indexed_by(lambda p: p.id)
)

See also

class lumix.utils.orm.LXORMModel(*args, **kwargs)[source]

Bases: Protocol

Structural protocol for any ORM model.

This protocol defines the minimal interface that an ORM model must satisfy to be used with LumiX. Any class with an ‘id’ attribute automatically satisfies this protocol through structural typing.

The protocol is runtime-checkable, meaning you can use isinstance() to verify if an object satisfies the protocol at runtime.

id

Unique identifier for the model instance. Can be any type (int, str, UUID, etc.)

Examples

SQLAlchemy model automatically satisfies the protocol:

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Product(Base):
    __tablename__ = 'products'
    id = Column(Integer, primary_key=True)
    name = Column(String)

# Product automatically satisfies LXORMModel
assert isinstance(Product(), LXORMModel)

Plain dataclass also works:

from dataclasses import dataclass

@dataclass
class Customer:
    id: int
    name: str

# Customer satisfies the protocol
assert isinstance(Customer(1, "Alice"), LXORMModel)

Note

This is a structural Protocol (PEP 544), not a base class. You don’t need to inherit from it - any object with an ‘id’ attribute satisfies it.

id: Any
__init__(*args, **kwargs)
class lumix.utils.orm.LXNumeric(*args, **kwargs)[source]

Bases: Protocol

Structural protocol for numeric types with optimization operations.

Defines the interface for types that can be used as numeric coefficients in optimization expressions. Supports addition, multiplication, and conversion to float.

This protocol allows type-safe usage of various numeric types (int, float, Decimal, Fraction, etc.) in optimization expressions.

Examples

Standard numeric types satisfy the protocol:

x: LXNumeric = 5        # int satisfies protocol
y: LXNumeric = 3.14     # float satisfies protocol

Custom numeric types can also satisfy it:

from decimal import Decimal

z: LXNumeric = Decimal("10.5")  # Decimal satisfies protocol

Note

This is a structural Protocol. Any type with the required methods automatically satisfies it without explicit inheritance.

__init__(*args, **kwargs)
class lumix.utils.orm.LXORMContext(session)[source]

Bases: Generic[TModel]

Type-safe ORM query interface with generic support.

Provides a type-safe wrapper around ORM sessions that enables IDE autocomplete and type checking for database queries. The generic type parameter ensures that query results have proper type information.

Parameters:

session (Any)

session

The underlying ORM session (SQLAlchemy, Django, etc.)

Type Parameters:

TModel: The ORM model type being queried

Examples

Create context from SQLAlchemy session:

from sqlalchemy.orm import Session
from lumix.utils import LXORMContext

session = Session()
ctx = LXORMContext(session)

# Query with full type safety
products = ctx.query(Product).all()  # Type: List[Product]

Chain query operations:

expensive_products = (
    ctx.query(Product)
    .filter(lambda p: p.price > 100)
    .filter(lambda p: p.in_stock)
    .all()
)

See also

LXTypedQuery: The query builder returned by query()

__init__(session)[source]

Initialize ORM context with a session.

Parameters:

session (Any) – ORM session object (e.g., SQLAlchemy Session, Django QuerySet)

Examples

Initialize with SQLAlchemy session:

from sqlalchemy.orm import Session
session = Session()
ctx = LXORMContext(session)
query(model)[source]

Start a type-safe query for the specified model.

Parameters:

model (Type[TypeVar(TModel)]) – The ORM model class to query

Return type:

LXTypedQuery[TypeVar(TModel)]

Returns:

A type-safe query builder for the model

Examples

Basic query:

products = ctx.query(Product).all()

With filtering:

active_products = ctx.query(Product).filter(lambda p: p.active).all()
class lumix.utils.orm.LXTypedQuery(session, model)[source]

Bases: Generic[TModel]

Type-safe query builder with fluent API.

Provides a chainable query interface with full type safety and IDE autocomplete. The generic type parameter ensures that filter predicates and results have proper type information.

The query builder supports filtering via lambda predicates, where the IDE knows the structure of the model being queried.

Parameters:
  • session (Any)

  • model (Type[TModel])

session

The underlying ORM session

model

The ORM model class being queried

_filters

Internal list of filter predicates to apply

Type Parameters:

TModel: The ORM model type being queried

Examples

Basic querying with type safety:

query = LXTypedQuery(session, Product)
products = query.all()  # Type: List[Product]

Filtering with lambda predicates:

expensive = (
    LXTypedQuery(session, Product)
    .filter(lambda p: p.price > 100)
    .filter(lambda p: p.in_stock)
    .all()
)

Using with LXORMContext:

ctx = LXORMContext(session)
active_products = ctx.query(Product).filter(lambda p: p.active).all()

Note

The filter predicates are applied in Python after retrieving results from the ORM. For large datasets, consider using ORM-specific filtering before wrapping in LXTypedQuery.

__init__(session, model)[source]

Initialize typed query builder.

Parameters:
  • session (Any) – ORM session object

  • model (Type[TypeVar(TModel)]) – ORM model class to query

Examples

Create query builder directly:

query = LXTypedQuery(session, Product)
filter(predicate)[source]

Add a type-safe filter predicate to the query.

The predicate receives model instances with full type information, enabling IDE autocomplete for all model attributes.

Parameters:

predicate (Callable[[TypeVar(TModel)], bool]) – A callable that takes a model instance and returns True/False. The IDE will autocomplete model attributes in the lambda.

Return type:

Self

Returns:

Self for method chaining

Examples

Filter by single condition:

query.filter(lambda p: p.price > 100)

Chain multiple filters:

query.filter(lambda p: p.active).filter(lambda p: p.in_stock)

Complex filtering:

query.filter(lambda p: p.price > 50 and p.category == "Electronics")
all()[source]

Execute query and return all matching results.

Retrieves all records from the ORM, then applies the filter predicates in order.

Return type:

List[TypeVar(TModel)]

Returns:

List of model instances matching all filter conditions

Examples

Get all filtered results:

products = query.filter(lambda p: p.active).all()

Use in LumiX variable:

production = (
    LXVariable[Product, float]("production")
    .from_data(query.filter(lambda p: p.available).all())
    .indexed_by(lambda p: p.id)
)
first()[source]

Execute query and return the first matching result.

Return type:

Optional[TypeVar(TModel)]

Returns:

The first model instance matching all filters, or None if no matches

Examples

Get single result:

product = query.filter(lambda p: p.id == 5).first()

Check for existence:

if query.filter(lambda p: p.name == "Widget").first():
    print("Widget exists")

Rational Conversion Module

Float-to-rational conversion utilities for integer-only solvers.

This module provides utilities for converting floating-point coefficients to rational numbers, which is essential when using integer-only solvers like GLPK’s integer programming mode or other solvers that require exact rational arithmetic.

The module implements three different rational approximation algorithms, each with different performance characteristics and accuracy trade-offs.

Key Features:
  • Multiple Algorithms: Farey sequence, continued fractions, Stern-Brocot tree

  • Configurable Precision: Control maximum denominator for approximations

  • Batch Conversion: Convert entire coefficient dictionaries at once

  • Error Tracking: Optional error reporting for approximation quality

Algorithms:
  • Farey: Fastest method using Farey sequence with floor/ceil optimization (recommended)

  • Continued Fraction: Classic continued fraction algorithm

  • Stern-Brocot: Binary search through Stern-Brocot tree (equivalent to Farey)

Use Cases:
  • Converting LP models for integer-only solvers (GLPK)

  • Exact rational arithmetic requirements

  • Avoiding floating-point precision issues

  • Symbolic computation integration

Examples

Basic rational conversion:

from lumix.utils import LXRationalConverter

converter = LXRationalConverter(max_denominator=10000)
frac = converter.to_rational(3.14159)  # Fraction(355, 113)
print(f"{frac}{float(frac):.5f}")

Convert coefficients for integer solver:

coeffs = {"x1": 3.5, "x2": 2.333, "x3": 1.25}
int_coeffs, denom = converter.convert_coefficients(coeffs)
# int_coeffs: {"x1": 42, "x2": 28, "x3": 15}, denom: 12

Compare approximation methods:

results = converter.compare_methods(3.14159)
for method, (frac, error, time) in results.items():
    print(f"{method}: {frac} (error={error:.2e}, time={time:.6f}s)")

See also

class lumix.utils.rational.LXRationalConverter(max_denominator=10000, method='farey', float_tolerance=1e-09)[source]

Bases: object

Converts floating-point coefficients to rational numbers for integer-only solvers.

Supports three approximation methods: - “farey”: Farey sequence with floor/ceil optimization (fastest, recommended) - “continued_fraction”: Continued fraction algorithm - “stern_brocot”: Stern-Brocot tree (equivalent to Farey, alternative framing)

Parameters:
  • max_denominator (int)

  • method (Literal['farey', 'continued_fraction', 'stern_brocot'])

  • float_tolerance (float)

__init__(max_denominator=10000, method='farey', float_tolerance=1e-09)[source]

Initialize rational converter.

Parameters:
  • max_denominator (int) – Maximum denominator for rational approximation

  • method (Literal['farey', 'continued_fraction', 'stern_brocot']) – Approximation algorithm (“farey”, “continued_fraction”, “stern_brocot”)

  • float_tolerance (float) – Tolerance for float comparisons to handle precision errors (default: 1e-9)

to_rational(value, return_error=False)[source]

Convert float to rational number using configured method.

Parameters:
  • value (float) – Float value to convert

  • return_error (bool) – If True, return (fraction, error) tuple

Return type:

Union[Fraction, Tuple[Fraction, float]]

Returns:

Fraction approximation (or tuple with error if return_error=True)

convert_coefficients(coefficients)[source]

Convert dictionary of float coefficients to integers.

Parameters:

coefficients (dict[str, float]) – Dictionary mapping variable names to float coefficients

Return type:

tuple[dict[str, int], int]

Returns:

Tuple of (integer_coefficients, common_denominator)

compare_methods(value)[source]

Compare all three approximation methods for a given value.

Parameters:

value (float) – Float value to approximate

Return type:

dict[str, Tuple[Fraction, float, float]]

Returns:

Dictionary mapping method name to (fraction, error, computation_time)

Quick Reference

Most Commonly Used Classes

Usage Examples

Model Logging

Quick example of using the model logger:

from lumix.utils import LXModelLogger
import logging

logger = LXModelLogger(name="production_model", level=logging.INFO)
logger.log_model_creation("ProductionPlan", num_vars=100, num_constraints=50)
logger.log_solve_start("Gurobi")
# ... solving happens ...
logger.log_solve_end("Optimal", objective_value=42500.0)

ORM Integration

Type-safe ORM queries:

from lumix.utils import LXORMContext
from lumix import LXVariable

# Create ORM context
ctx = LXORMContext(session)

# Query with type safety
products = ctx.query(Product).filter(lambda p: p.active).all()

# Use in optimization model
production = (
    LXVariable[Product, float]("production")
    .continuous()
    .from_data(products)
    .indexed_by(lambda p: p.id)
)

Rational Conversion

Convert floats to rationals:

from lumix.utils import LXRationalConverter

converter = LXRationalConverter(max_denominator=10000)

# Single conversion
frac = converter.to_rational(3.14159)  # Fraction(355, 113)

# Batch conversion
coeffs = {"x1": 3.5, "x2": 2.333, "x3": 1.25}
int_coeffs, denom = converter.convert_coefficients(coeffs)

See Also