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¶
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¶
Structural protocol for any ORM model. |
|
Type-safe ORM query interface with generic support. |
|
Type-safe query builder with fluent API. |
|
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¶
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
LXModel: Model builder classPython’s logging module: https://docs.python.org/3/library/logging.html
- class lumix.utils.logger.LXModelLogger(name='lumix', level=20)[source]¶
Bases:
objectEnhanced 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.
- logger¶
Underlying Python logger instance
- Type:
- 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:
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:
- Return type:
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:
- Return type:
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:
- Return type:
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:
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:
- Return type:
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:
- Return type:
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:
- Return type:
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:
- Return type:
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:
- Return type:
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.
Examples
Log custom info message:
logger.info("Model preprocessing completed")
- debug(message)[source]¶
Log a debug message.
Examples
Log debug details:
logger.debug("Checking constraint matrix sparsity")
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
LXVariable: Variable families with ORM dataLXConstraint: Constraints with ORM data
- class lumix.utils.orm.LXORMModel(*args, **kwargs)[source]¶
Bases:
ProtocolStructural 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.
- __init__(*args, **kwargs)¶
- class lumix.utils.orm.LXNumeric(*args, **kwargs)[source]¶
Bases:
ProtocolStructural 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:
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.
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:
- 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
Python fractions module: https://docs.python.org/3/library/fractions.html
GLPK documentation: https://www.gnu.org/software/glpk/
- class lumix.utils.rational.LXRationalConverter(max_denominator=10000, method='farey', float_tolerance=1e-09)[source]¶
Bases:
objectConverts 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:
- __init__(max_denominator=10000, method='farey', float_tolerance=1e-09)[source]¶
Initialize rational converter.
- Parameters:
max_denominator (
int) – Maximum denominator for rational approximationmethod (
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.
Quick Reference¶
Most Commonly Used Classes
lumix.utils.logger.LXModelLogger- Enhanced logging for optimizationlumix.utils.orm.LXORMContext- Type-safe ORM query interfacelumix.utils.orm.LXTypedQuery- Fluent query builderlumix.utils.rational.LXRationalConverter- Float-to-rational conversion
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¶
Utils Module Guide - Utils module usage guide
Utils Module Architecture - Utils architecture details
Core Module API - Core module API reference