Solvers Module

The solvers module provides solver interfaces and implementations for solving optimization problems. OptiX supports multiple solvers through a unified interface, allowing easy switching between different optimization engines.

Solver Factory

solvers.solve(problem: OXCSPProblem, solver: str, **kwargs)[source]

Unified optimization problem solving interface with multi-solver support.

This function serves as the primary entry point for solving optimization problems within the OptiX framework, providing a standardized interface that abstracts away solver-specific implementation details while ensuring consistent problem setup, solving workflows, and solution extraction across different optimization engines and algorithmic approaches.

The function implements a comprehensive solving pipeline that automatically handles variable creation, constraint translation, objective function configuration, and solution extraction, enabling users to focus on problem modeling rather than solver-specific integration complexities.

Solving Pipeline:

The function orchestrates a standardized solving workflow:

  1. Solver Validation: Verifies solver availability and compatibility with the specified problem type and configuration parameters

  2. Solver Instantiation: Creates solver instance with custom parameters and configuration options for performance tuning and behavior control

  3. Variable Setup: Translates OptiX decision variables to solver-specific variable representations with proper bounds, types, and naming conventions

  4. Constraint Translation: Converts OptiX constraints to native solver constraint formats with accurate coefficient handling and operator mapping

  5. Special Constraint Handling: Processes advanced constraint types including multiplicative, division, modulo, and conditional constraints using solver-specific implementation strategies

  6. Objective Configuration: Sets up optimization objectives for linear and goal programming problems with proper minimization/maximization handling

  7. Solution Execution: Executes the core solving algorithm with progress monitoring and early termination capabilities

  8. Result Extraction: Retrieves optimization results and translates them to standardized OptiX solution formats for consistent analysis

Parameters:
  • problem (OXCSPProblem) – The optimization problem instance to solve. Must be a properly configured OptiX problem with defined variables, constraints, and (for LP/GP problems) objective functions. Supports constraint satisfaction problems (CSP), linear programming (LP), and goal programming (GP) formulations.

  • solver (str) – The identifier of the optimization solver to use for problem solving. Must match a key in the _available_solvers registry. Supported values include: - ‘ORTools’: Google’s open-source constraint programming solver - ‘Gurobi’: Commercial high-performance optimization solver Additional solvers may be available through plugin extensions.

  • **kwargs – Arbitrary keyword arguments passed directly to the solver constructor for custom parameter configuration. Enables solver-specific performance tuning, algorithmic customization, and behavior control. Common parameters include: - maxTime (int): Maximum solving time in seconds - solutionCount (int): Maximum number of solutions to enumerate - equalizeDenominators (bool): Enable fractional coefficient handling - use_continuous (bool): Enable continuous variable optimization - Additional solver-specific parameters as documented by each solver

Returns:

A two-element tuple containing comprehensive solving results:

  • status (OXSolutionStatus): The termination status of the optimization process indicating solution quality and solver performance. Possible values: * OXSolutionStatus.OPTIMAL: Globally optimal solution found * OXSolutionStatus.FEASIBLE: Feasible solution found, optimality not guaranteed * OXSolutionStatus.INFEASIBLE: No feasible solution exists * OXSolutionStatus.UNBOUNDED: Problem is unbounded * OXSolutionStatus.TIMEOUT: Solver reached time limit * OXSolutionStatus.ERROR: Solving error occurred * OXSolutionStatus.UNKNOWN: Status cannot be determined

  • solver_obj (OXSolverInterface): The configured solver instance used for problem solving. Provides access to all found solutions through iteration protocols, individual solution access through indexing, and solver-specific diagnostic information through logging methods. The solver maintains complete solution history and enables detailed post-solving analysis and validation.

Return type:

tuple

Raises:
  • OXception – Raised when the specified solver is not available in the solver registry. This typically occurs when: - The solver name is misspelled or incorrect - The solver backend is not installed or properly configured - Required dependencies for the solver are missing - The solver registration failed during framework initialization

  • Additional solver-specific exceptions may be raised during the solving process

  • and should be handled appropriately by calling code for robust error management.

Example

Basic problem solving with default parameters:

from problem.OXProblem import OXCSPProblem
from solvers.OXSolverFactory import solve

# Create and configure problem
problem = OXCSPProblem()
x = problem.create_decision_variable("x", 0, 10)
y = problem.create_decision_variable("y", 0, 10)
problem.create_constraint([x, y], [1, 1], "<=", 15)

# Solve with default OR-Tools configuration
status, solver = solve(problem, 'ORTools')

# Analyze results
if status == OXSolutionStatus.OPTIMAL:
    print("Found optimal solution")
    for solution in solver:
        print(f"Variables: {solution.decision_variable_values}")
elif status == OXSolutionStatus.INFEASIBLE:
    print("Problem has no feasible solution")

Advanced solving with custom parameters:

from problem.OXProblem import OXLPProblem
from solvers.OXSolverFactory import solve

# Create linear programming problem
problem = OXLPProblem()
x = problem.create_decision_variable("x", 0, 100)
y = problem.create_decision_variable("y", 0, 100)
problem.create_constraint([x, y], [1, 1], "<=", 150)
problem.create_objective_function([x, y], [2, 3], "maximize")

# Solve with custom Gurobi parameters
status, solver = solve(
    problem,
    'Gurobi',
    use_continuous=True,
    maxTime=3600,
    optimality_gap=0.01
)

# Access optimal solution
if status == OXSolutionStatus.OPTIMAL:
    solution = solver[0]
    print(f"Optimal value: {solution.objective_function_value}")
    print(f"Variables: {solution.decision_variable_values}")
Performance Considerations:
  • Solver instantiation overhead is minimized through efficient registry lookup

  • Problem setup is optimized for large-scale problems with thousands of variables

  • Memory usage scales linearly with problem size and solution enumeration

  • Parallel solving capabilities depend on individual solver implementations

Solver Selection Guidelines:
  • OR-Tools: Recommended for constraint satisfaction, discrete optimization, and problems requiring advanced constraint types with good open-source support

  • Gurobi: Optimal for large-scale linear/quadratic programming requiring commercial-grade performance and advanced optimization algorithms

  • Custom Solvers: Consider for specialized problem domains or when specific algorithmic approaches are required for particular optimization scenarios

Thread Safety:

The solve function creates independent solver instances for each call, ensuring thread safety for concurrent optimization operations. However, individual solver implementations may have their own thread safety considerations that should be reviewed for multi-threaded optimization scenarios.

Solver Interfaces

Base Interface

class solvers.OXSolverInterface(**kwargs)[source]

Bases: object

Abstract base class defining the standard interface for all optimization solver implementations.

This class establishes the fundamental contract that all concrete solver implementations must adhere to within the OptiX optimization framework. It provides a comprehensive template for integrating diverse optimization engines while maintaining consistent behavior, standardized method signatures, and uniform solution handling patterns.

The interface design follows the Template Method pattern, defining the overall algorithm structure for solving optimization problems while allowing subclasses to implement solver-specific details. This ensures consistent problem setup, solving workflows, and solution extraction across different optimization engines.

Core Responsibilities:
  • Variable Management: Standardized creation and mapping of decision variables from OptiX problem formulations to solver-specific representations

  • Constraint Translation: Systematic conversion of OptiX constraints to native solver constraint formats with proper operator and coefficient handling

  • Objective Configuration: Setup of optimization objectives for linear and goal programming problems with support for minimization and maximization

  • Solution Extraction: Comprehensive retrieval of optimization results including variable values, constraint evaluations, and solver statistics

  • Parameter Management: Flexible configuration of solver-specific parameters for performance tuning and algorithmic customization

Interface Methods:

The class defines both abstract methods (must be implemented by subclasses) and concrete methods (provide common functionality):

Abstract Methods (require implementation): - _create_single_variable(): Variable creation in solver-specific format - _create_single_constraint(): Constraint creation with proper translation - create_special_constraints(): Advanced constraint type handling - create_objective(): Objective function setup for optimization problems - solve(): Core solving algorithm execution and solution extraction - get_solver_logs(): Diagnostic and debugging information retrieval

Concrete Methods (provided by base class): - create_variable(): Orchestrates creation of all problem variables - create_constraints(): Manages setup of all standard constraints - Collection access methods for solution enumeration and analysis

_parameters

Comprehensive dictionary storing solver-specific configuration parameters including algorithmic settings, performance tuning options, and behavioral controls. This enables flexible customization of solver behavior without modifying core implementation code.

Type:

Parameters

_solutions

Ordered collection of optimization solutions found during the solving process. Supports multiple solution enumeration for problems with multiple optimal or feasible solutions. Provides efficient access through indexing and iteration protocols.

Type:

List[OXSolverSolution]

Solving Workflow:

The standard solving process follows a well-defined sequence:

  1. Problem Setup: Variable and constraint creation from OptiX problem definition

  2. Solver Configuration: Parameter application and algorithmic customization

  3. Objective Setup: Optimization direction and objective function configuration

  4. Solution Process: Core solving algorithm execution with progress monitoring

  5. Result Extraction: Solution data retrieval and status determination

  6. Validation: Solution verification and constraint satisfaction checking

# Standard workflow implementation
solver = ConcretesolverInterface(**parameters)
solver.create_variable(problem)
solver.create_constraints(problem)
solver.create_special_constraints(problem)

if isinstance(problem, OXLPProblem):
    solver.create_objective(problem)

status = solver.solve(problem)

# Access results
for solution in solver:
    analyze_solution(solution)
Extensibility Design:

The interface is designed to accommodate diverse optimization paradigms:

  • Linear Programming: Continuous optimization with linear constraints

  • Integer Programming: Discrete optimization with integer variables

  • Constraint Programming: Logical constraint satisfaction and enumeration

  • Goal Programming: Multi-objective optimization with priority levels

  • Heuristic Algorithms: Approximate optimization with custom algorithms

Parameter Management:

Solver parameters enable fine-grained control over optimization behavior:

  • Algorithmic Parameters: Solver-specific algorithm selection and tuning

  • Performance Parameters: Time limits, memory limits, and precision settings

  • Output Parameters: Logging levels, solution enumeration, and debugging options

  • Problem-Specific Parameters: Customization for particular problem characteristics

Solution Management:

The interface provides comprehensive solution handling capabilities:

  • Multiple Solutions: Support for enumeration of alternative optimal solutions

  • Solution Quality: Status tracking and optimality verification

  • Incremental Results: Progressive solution improvement tracking

  • Solution Comparison: Utilities for comparing and ranking multiple solutions

Error Handling:

The interface defines consistent error handling patterns:

  • Implementation Errors: NotImplementedError for missing abstract methods

  • Parameter Validation: Custom exceptions for invalid solver parameters

  • Numerical Issues: Graceful handling of solver-specific numerical problems

  • Resource Limitations: Proper handling of memory and time limit violations

Performance Considerations:
  • Solution storage uses efficient data structures for large solution sets

  • Parameter dictionaries provide O(1) configuration access

  • Iterator protocols enable memory-efficient solution enumeration

  • Abstract method design minimizes overhead in concrete implementations

Example Implementation:

Basic structure for implementing a custom solver interface:

class CustomSolverInterface(OXSolverInterface):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._native_solver = initialize_custom_solver()
        self._var_mapping = {}

    def _create_single_variable(self, var: OXVariable):
        native_var = self._native_solver.add_variable(
            name=var.name,
            lower_bound=var.lower_bound,
            upper_bound=var.upper_bound
        )
        self._var_mapping[var.id] = native_var

    def solve(self, prb: OXCSPProblem) -> OXSolutionStatus:
        status = self._native_solver.solve()
        if status == 'optimal':
            solution = self._extract_solution()
            self._solutions.append(solution)
            return OXSolutionStatus.OPTIMAL
        return OXSolutionStatus.UNKNOWN

Note

Concrete implementations should carefully handle solver-specific exceptions and translate them to appropriate OXSolutionStatus values for consistent error reporting across the framework.

__init__(**kwargs)[source]

Initialize the solver interface with optional parameters.

Parameters:

**kwargs – Solver-specific parameters passed as keyword arguments.

create_variable(prb: OXCSPProblem)[source]

Create all variables from the problem in the solver.

Parameters:

prb (OXCSPProblem) – The problem containing variables to create.

create_constraints(prb: OXCSPProblem)[source]

Create all regular constraints from the problem in the solver.

This method creates all constraints except those that are part of special constraints (which are handled separately).

Parameters:

prb (OXCSPProblem) – The problem containing constraints to create.

create_special_constraints(prb: OXCSPProblem)[source]

Create all special constraints from the problem in the solver.

Parameters:

prb (OXCSPProblem) – The problem containing special constraints to create.

Raises:

NotImplementedError – Must be implemented by subclasses.

create_objective(prb: OXLPProblem)[source]

Create the objective function in the solver.

Parameters:

prb (OXLPProblem) – The linear programming problem containing the objective function.

Raises:

NotImplementedError – Must be implemented by subclasses.

solve(prb: OXCSPProblem) OXSolutionStatus[source]

Solve the optimization problem.

Parameters:

prb (OXCSPProblem) – The problem to solve.

Returns:

The status of the solution process.

Return type:

OXSolutionStatus

Raises:

NotImplementedError – Must be implemented by subclasses.

get_solver_logs() List[str | List[str]] | None[source]

Get solver-specific logs and debugging information.

Returns:

Solver logs if available, None otherwise.

Return type:

Optional[LogsType]

Raises:

NotImplementedError – Must be implemented by subclasses.

__getitem__(item) OXSolverSolution[source]

Get a solution by index.

Parameters:

item – The index of the solution to retrieve.

Returns:

The solution at the specified index.

Return type:

OXSolverSolution

__len__() int[source]

Get the number of solutions found.

Returns:

The number of solutions in the solution list.

Return type:

int

__iter__() Iterator[OXSolverSolution][source]

Iterate over all solutions.

Returns:

An iterator over the solutions.

Return type:

Iterator[OXSolverSolution]

property parameters: Dict[str, Any]

Get the solver parameters.

Returns:

Dictionary of solver parameters.

Return type:

Parameters

Warning

Users can modify these parameters, but validation mechanisms should be implemented to ensure parameters are valid for the specific solver.

OR-Tools Solver

OR-Tools Solver Integration Module

This module provides comprehensive integration between the OptiX optimization framework and Google’s OR-Tools constraint programming solver. It implements the OptiX solver interface using OR-Tools’ CP-SAT engine to enable solving complex discrete optimization problems including constraint satisfaction, integer programming, and goal programming.

The module serves as a critical component of OptiX’s multi-solver architecture, offering high-performance constraint programming capabilities alongside other solver backends like Gurobi for different optimization scenarios.

Architecture:
  • Solver Interface: Complete implementation of OXSolverInterface for OR-Tools

  • Constraint Programming: Leverages CP-SAT for discrete optimization excellence

  • Multi-Problem Support: Handles CSP, LP, and GP problem types seamlessly

  • Advanced Constraints: Supports complex non-linear constraint relationships

Key Components:
  • OXORToolsSolverInterface: Primary solver implementation class providing complete integration with OR-Tools CP-SAT solver including variable management, constraint translation, objective handling, and solution extraction capabilities

Solver Capabilities:
  • Variable Types: Boolean and bounded integer decision variables with automatic type detection based on variable bounds and mathematical properties

  • Linear Constraints: Full support for relational operators (=, <=, >=, <, >) with efficient constraint expression evaluation and validation

  • Special Constraints: Advanced non-linear relationships including:

    • Multiplicative: Product relationships between multiple variables

    • Division/Modulo: Integer division and remainder operations for discrete math

    • Summation: Explicit sum constraints for complex variable relationships

    • Conditional: If-then-else logic with indicator variables for decision modeling

  • Objective Functions: Optimization support for minimization and maximization with linear and goal programming objective types

  • Multi-Solution Enumeration: Configurable solution collection with callback mechanisms for exploring solution spaces and alternative optima

  • Performance Tuning: Comprehensive parameter configuration for time limits, solution counts, and algorithmic behavior customization

Mathematical Features:
  • Float Coefficient Handling: Automatic denominator equalization for fractional weights enabling seamless integration of real-valued problem formulations

  • Integer Programming: Native support for discrete optimization with advanced branching and cutting plane algorithms from OR-Tools

  • Constraint Propagation: Sophisticated constraint propagation techniques for efficient problem space reduction and faster solving

Configuration Parameters:

The solver accepts multiple parameters for fine-tuning performance and behavior:

  • equalizeDenominators (bool): Enables automatic conversion of float coefficients to integers using common denominator calculation, allowing OR-Tools to handle fractional weights in constraints and objectives. Default: False

  • solutionCount (int): Maximum number of solutions to enumerate during solving. Higher values enable comprehensive solution space exploration but increase computational overhead. Default: 1

  • maxTime (int): Maximum solving time in seconds before automatic termination. Prevents indefinite solving on computationally difficult problem instances. Default: 600 seconds (10 minutes)

Integration Patterns:

The module follows OptiX’s standardized solver integration patterns for consistent usage across different solver backends:

from problem.OXProblem import OXCSPProblem, OXLPProblem
from solvers.ortools import OXORToolsSolverInterface
from solvers.OXSolverFactory import solve

# Direct solver instantiation approach
problem = OXCSPProblem()
# ... configure problem variables and constraints ...

solver = OXORToolsSolverInterface(
    equalizeDenominators=True,
    solutionCount=5,
    maxTime=300
)

solver.create_variables(problem)
solver.create_constraints(problem)
solver.create_special_constraints(problem)
status = solver.solve(problem)

# Factory pattern approach (recommended)
problem = OXLPProblem()
# ... configure problem ...

status, solutions = solve(problem, "ORTools",
                         equalizeDenominators=True,
                         solutionCount=10,
                         maxTime=600)
Performance Considerations:
  • OR-Tools CP-SAT excels at discrete optimization problems with complex constraints

  • Integer variable domains should be bounded for optimal performance

  • Large solution enumeration (>100 solutions) may require increased time limits

  • Float coefficient conversion adds preprocessing overhead but enables broader compatibility

  • Special constraints leverage native CP-SAT primitives for efficient solving

Compatibility:
  • Python Version: Requires Python 3.7 or higher for full feature support

  • OR-Tools Version: Compatible with OR-Tools 9.0+ constraint programming library

  • OptiX Framework: Fully integrated with OptiX problem modeling and solving architecture

  • Operating Systems: Cross-platform support on Windows, macOS, and Linux

Use Cases:

This solver implementation is particularly well-suited for:

  • Scheduling and resource allocation problems with discrete time slots

  • Combinatorial optimization problems with complex constraint relationships

  • Integer programming formulations requiring advanced constraint types

  • Multi-objective optimization with goal programming approaches

  • Constraint satisfaction problems with large solution spaces requiring enumeration

Notes

  • For continuous optimization problems, consider using the Gurobi solver interface

  • Large-scale linear programming may benefit from specialized LP solver backends

  • Memory usage scales with problem size and solution enumeration requirements

  • Solver logs and debugging information available through get_solver_logs() method

class solvers.ortools.OXORToolsSolverInterface(**kwargs)[source]

Bases: OXSolverInterface

Concrete implementation of OptiX solver interface using Google OR-Tools CP-SAT solver.

This class provides a comprehensive bridge between OptiX’s problem modeling framework and Google’s OR-Tools Constraint Programming solver. It handles the complete lifecycle of problem solving from variable and constraint creation through solution extraction and analysis.

The implementation leverages OR-Tools’ CP-SAT solver, which excels at discrete optimization problems including constraint satisfaction, integer programming, and mixed-integer programming. The class automatically handles type conversions, constraint translations, and solution callbacks to provide seamless integration with OptiX workflows.

Key Capabilities:
  • Variable Management: Automatic creation and mapping of boolean and integer variables

  • Constraint Translation: Comprehensive support for linear and special constraint types

  • Multi-Solution Handling: Configurable solution enumeration with callback system

  • Parameter Configuration: Flexible solver parameter management for performance tuning

  • Solution Analysis: Complete solution data extraction including constraint violations

Solver Parameters:

The class accepts various initialization parameters to customize solver behavior:

  • equalizeDenominators (bool): When True, enables automatic conversion of float coefficients to integers using common denominator calculation. This allows OR-Tools to handle fractional weights that would otherwise be rejected. Default: False

  • solutionCount (int): Maximum number of solutions to collect during enumeration. Higher values enable finding multiple feasible solutions but increase solving time. Default: 1

  • maxTime (int): Maximum solving time in seconds before termination. Prevents infinite solving on difficult instances. Default: 600 seconds (10 minutes)

_model

The underlying OR-Tools CP-SAT model instance that stores all variables, constraints, and objectives for the optimization problem.

Type:

CpModel

_var_mapping

Bidirectional mapping from OptiX variable UUIDs to their corresponding OR-Tools variable objects for efficient lookup during solving.

Type:

Dict[str, IntVar|BoolVar]

_constraint_mapping

Mapping from OptiX constraint UUIDs to OR-Tools constraint objects for tracking and solution analysis purposes.

Type:

Dict[str, Constraint]

_constraint_expr_mapping

Mapping from constraint UUIDs to their mathematical expressions for solution value calculation.

Type:

Dict[str, LinearExpr]

Type Support:
  • Boolean Variables: Automatically detected from 0-1 bounds, mapped to BoolVar

  • Integer Variables: Bounded integer variables with custom ranges, mapped to IntVar

  • Linear Expressions: Sum of variables with integer or float coefficients

  • Special Constraints: Non-linear relationships handled through CP-SAT primitives

Example

Comprehensive solver setup and configuration:

# Create solver with advanced configuration
solver = OXORToolsSolverInterface(
    equalizeDenominators=True,  # Handle fractional coefficients
    solutionCount=10,           # Find up to 10 solutions
    maxTime=1800               # 30-minute time limit
)

# Setup problem
solver.create_variables(problem)
solver.create_constraints(problem)
solver.create_special_constraints(problem)

if isinstance(problem, OXLPProblem):
    solver.create_objective(problem)

# Solve and analyze
status = solver.solve(problem)

if status == OXSolutionStatus.OPTIMAL:
    for i, solution in enumerate(solver):
        print(f"Solution {i+1}: {solution.decision_variable_values}")
        print(f"Objective: {solution.objective_function_value}")

# Access solver statistics
logs = solver.get_solver_logs()

Warning

OR-Tools CP-SAT requires integer coefficients for all constraints and objectives. When using float coefficients, the equalizeDenominators parameter must be enabled to perform automatic conversion, or an OXception will be raised during constraint creation.

Note

This implementation is optimized for discrete optimization problems. For continuous optimization or large-scale linear programming, consider using the Gurobi solver interface which may provide better performance for those problem types.

class SolutionLimiter(max_solution_count: int, solver: OXORToolsSolverInterface, prb: OXCSPProblem)[source]

Bases: CpSolverSolutionCallback

Callback class to limit the number of solutions found.

This class extends CpSolverSolutionCallback to control the number of solutions collected during the solving process.

_solution_count

Current number of solutions found.

Type:

int

_max_solution_count

Maximum number of solutions to collect.

Type:

int

_solver

Reference to the solver interface.

Type:

OXORToolsSolverInterface

_problem

The problem being solved.

Type:

OXCSPProblem

__init__(max_solution_count: int, solver: OXORToolsSolverInterface, prb: OXCSPProblem)[source]

Initialize the solution limiter callback.

Parameters:
on_solution_callback()[source]

Callback method called when a solution is found.

This method creates an OXSolverSolution object with the current solution values and adds it to the solver’s solution list.

Raises:

OXception – If an unsupported special constraint type is encountered.

__init__(**kwargs)[source]

Initialize the OR-Tools solver interface.

Parameters:

**kwargs – Solver parameters. Supported parameters: - equalizeDenominators (bool): Use denominator equalization for float handling. - solutionCount (int): Maximum number of solutions to find. - maxTime (int): Maximum solving time in seconds.

create_objective(prb: OXLPProblem)[source]

Create the objective function in the OR-Tools model.

Parameters:

prb (OXLPProblem) – The linear programming problem containing the objective function.

Raises:

OXception – If no objective function is specified or if float weights are used without denominator equalization enabled.

create_special_constraints(prb: OXCSPProblem)[source]

Create all special constraints from the problem.

Parameters:

prb (OXCSPProblem) – The problem containing special constraints.

Raises:

OXception – If an unsupported special constraint type is encountered.

get_solver_logs() List[str | List[str]] | None[source]

Get solver logs and debugging information.

Returns:

Currently not implemented, returns None.

Return type:

Optional[LogsType]

solve(prb: OXCSPProblem) OXSolutionStatus[source]

Solve the optimization problem using OR-Tools CP-SAT solver.

Parameters:

prb (OXCSPProblem) – The problem to solve.

Returns:

The status of the solution process.

Return type:

OXSolutionStatus

Raises:

OXception – If the solver returns an unexpected status.

class solvers.ortools.OXORToolsSolverInterface(**kwargs)[source]

Bases: OXSolverInterface

Concrete implementation of OptiX solver interface using Google OR-Tools CP-SAT solver.

This class provides a comprehensive bridge between OptiX’s problem modeling framework and Google’s OR-Tools Constraint Programming solver. It handles the complete lifecycle of problem solving from variable and constraint creation through solution extraction and analysis.

The implementation leverages OR-Tools’ CP-SAT solver, which excels at discrete optimization problems including constraint satisfaction, integer programming, and mixed-integer programming. The class automatically handles type conversions, constraint translations, and solution callbacks to provide seamless integration with OptiX workflows.

Key Capabilities:
  • Variable Management: Automatic creation and mapping of boolean and integer variables

  • Constraint Translation: Comprehensive support for linear and special constraint types

  • Multi-Solution Handling: Configurable solution enumeration with callback system

  • Parameter Configuration: Flexible solver parameter management for performance tuning

  • Solution Analysis: Complete solution data extraction including constraint violations

Solver Parameters:

The class accepts various initialization parameters to customize solver behavior:

  • equalizeDenominators (bool): When True, enables automatic conversion of float coefficients to integers using common denominator calculation. This allows OR-Tools to handle fractional weights that would otherwise be rejected. Default: False

  • solutionCount (int): Maximum number of solutions to collect during enumeration. Higher values enable finding multiple feasible solutions but increase solving time. Default: 1

  • maxTime (int): Maximum solving time in seconds before termination. Prevents infinite solving on difficult instances. Default: 600 seconds (10 minutes)

_model

The underlying OR-Tools CP-SAT model instance that stores all variables, constraints, and objectives for the optimization problem.

Type:

CpModel

_var_mapping

Bidirectional mapping from OptiX variable UUIDs to their corresponding OR-Tools variable objects for efficient lookup during solving.

Type:

Dict[str, IntVar|BoolVar]

_constraint_mapping

Mapping from OptiX constraint UUIDs to OR-Tools constraint objects for tracking and solution analysis purposes.

Type:

Dict[str, Constraint]

_constraint_expr_mapping

Mapping from constraint UUIDs to their mathematical expressions for solution value calculation.

Type:

Dict[str, LinearExpr]

Type Support:
  • Boolean Variables: Automatically detected from 0-1 bounds, mapped to BoolVar

  • Integer Variables: Bounded integer variables with custom ranges, mapped to IntVar

  • Linear Expressions: Sum of variables with integer or float coefficients

  • Special Constraints: Non-linear relationships handled through CP-SAT primitives

Example

Comprehensive solver setup and configuration:

# Create solver with advanced configuration
solver = OXORToolsSolverInterface(
    equalizeDenominators=True,  # Handle fractional coefficients
    solutionCount=10,           # Find up to 10 solutions
    maxTime=1800               # 30-minute time limit
)

# Setup problem
solver.create_variables(problem)
solver.create_constraints(problem)
solver.create_special_constraints(problem)

if isinstance(problem, OXLPProblem):
    solver.create_objective(problem)

# Solve and analyze
status = solver.solve(problem)

if status == OXSolutionStatus.OPTIMAL:
    for i, solution in enumerate(solver):
        print(f"Solution {i+1}: {solution.decision_variable_values}")
        print(f"Objective: {solution.objective_function_value}")

# Access solver statistics
logs = solver.get_solver_logs()

Warning

OR-Tools CP-SAT requires integer coefficients for all constraints and objectives. When using float coefficients, the equalizeDenominators parameter must be enabled to perform automatic conversion, or an OXception will be raised during constraint creation.

Note

This implementation is optimized for discrete optimization problems. For continuous optimization or large-scale linear programming, consider using the Gurobi solver interface which may provide better performance for those problem types.

__init__(**kwargs)[source]

Initialize the OR-Tools solver interface.

Parameters:

**kwargs – Solver parameters. Supported parameters: - equalizeDenominators (bool): Use denominator equalization for float handling. - solutionCount (int): Maximum number of solutions to find. - maxTime (int): Maximum solving time in seconds.

create_special_constraints(prb: OXCSPProblem)[source]

Create all special constraints from the problem.

Parameters:

prb (OXCSPProblem) – The problem containing special constraints.

Raises:

OXception – If an unsupported special constraint type is encountered.

create_objective(prb: OXLPProblem)[source]

Create the objective function in the OR-Tools model.

Parameters:

prb (OXLPProblem) – The linear programming problem containing the objective function.

Raises:

OXception – If no objective function is specified or if float weights are used without denominator equalization enabled.

class SolutionLimiter(max_solution_count: int, solver: OXORToolsSolverInterface, prb: OXCSPProblem)[source]

Bases: CpSolverSolutionCallback

Callback class to limit the number of solutions found.

This class extends CpSolverSolutionCallback to control the number of solutions collected during the solving process.

_solution_count

Current number of solutions found.

Type:

int

_max_solution_count

Maximum number of solutions to collect.

Type:

int

_solver

Reference to the solver interface.

Type:

OXORToolsSolverInterface

_problem

The problem being solved.

Type:

OXCSPProblem

__init__(max_solution_count: int, solver: OXORToolsSolverInterface, prb: OXCSPProblem)[source]

Initialize the solution limiter callback.

Parameters:
on_solution_callback()[source]

Callback method called when a solution is found.

This method creates an OXSolverSolution object with the current solution values and adds it to the solver’s solution list.

Raises:

OXception – If an unsupported special constraint type is encountered.

solve(prb: OXCSPProblem) OXSolutionStatus[source]

Solve the optimization problem using OR-Tools CP-SAT solver.

Parameters:

prb (OXCSPProblem) – The problem to solve.

Returns:

The status of the solution process.

Return type:

OXSolutionStatus

Raises:

OXception – If the solver returns an unexpected status.

get_solver_logs() List[str | List[str]] | None[source]

Get solver logs and debugging information.

Returns:

Currently not implemented, returns None.

Return type:

Optional[LogsType]

Gurobi Solver

Gurobi Solver Integration Module

This module provides Gurobi commercial solver integration for the OptiX mathematical optimization framework. It implements the Gurobi-specific solver interface that enables high-performance optimization for linear programming, goal programming, and constraint satisfaction problems using Gurobi’s advanced optimization engine.

The module is organized around the following key components:

Architecture:
  • Solver Interface: Gurobi-specific implementation of OptiX solver interface

  • Variable Translation: Automatic conversion of OptiX variables to Gurobi format

  • Constraint Handling: Support for all OptiX constraint types and operators

  • Solution Extraction: Comprehensive solution status and value retrieval

Key Features:
  • High-performance commercial optimization engine integration

  • Support for binary, integer, and continuous variable types

  • Advanced constraint handling including goal programming

  • Configurable solver parameters and optimization settings

  • Robust solution status detection and error handling

Solver Capabilities:
  • Linear Programming (LP): Standard optimization with linear constraints

  • Goal Programming (GP): Multi-objective optimization with deviation variables

  • Constraint Satisfaction (CSP): Feasibility problems without optimization

  • Mixed-Integer Programming: Support for both continuous and integer variables

Usage:

The Gurobi solver is typically accessed through OptiX’s unified solver factory:

from solvers.OXSolverFactory import solve
from problem import OXLPProblem

# Create your optimization problem
problem = OXLPProblem()
# ... configure variables, constraints, objective ...

# Solve using Gurobi
status = solve(problem, 'Gurobi', use_continuous=True)
Requirements:
  • Gurobi optimization software and valid license

  • gurobipy Python package

  • OptiX framework core components

Notes

  • Gurobi requires a valid license for operation

  • Performance characteristics may vary based on problem size and type

  • Advanced Gurobi parameters can be configured through solver settings

class solvers.gurobi.OXGurobiSolverInterface(**kwargs)[source]

Bases: OXSolverInterface

Gurobi-specific implementation of the OptiX solver interface.

This class provides a concrete implementation of the OXSolverInterface for the Gurobi optimization solver. It handles the translation between OptiX’s abstract problem representation and Gurobi’s specific API calls, variable types, and constraint formats.

The interface supports both continuous and integer optimization modes, with automatic handling of variable bounds, constraint operators, and objective function setup. Special support is provided for goal programming with positive and negative deviation variables.

_model

The underlying Gurobi model instance

Type:

gp.Model

_var_mapping

Maps OptiX variable IDs to Gurobi variable objects

Type:

dict

_constraint_mapping

Maps OptiX constraint IDs to Gurobi constraint objects

Type:

dict

_constraint_expr_mapping

Maps constraint IDs to their Gurobi expressions

Type:

dict

Parameters:
  • use_continuous (bool) – Whether to use continuous variables instead of integers

  • equalizeDenominators (bool) – Whether to normalize fractional coefficients

Example

Direct usage of the Gurobi interface:

solver = OXGurobiSolverInterface(use_continuous=True)

# The solver is typically used through the factory pattern
# but can be used directly for advanced Gurobi-specific features

solver.create_variables(problem.variables)
solver.create_constraints(problem.constraints)
solver.create_objective(problem)

status = solver.solve(problem)
if status == OXSolutionStatus.OPTIMAL:
    solution = solver.get_solutions()[0]
__init__(**kwargs)[source]

Initialize the Gurobi solver interface with configuration parameters.

Creates a new Gurobi model instance and initializes internal mappings for variables, constraints, and constraint expressions. Configuration parameters are passed to the parent OXSolverInterface class.

Parameters:

**kwargs – Configuration parameters including: use_continuous (bool): Use continuous variables instead of integers equalizeDenominators (bool): Normalize fractional coefficients

Note

The Gurobi model is created with the name “OptiX Model” and uses default Gurobi settings unless modified through solver parameters.

create_objective(prb: OXLPProblem)[source]

Create and configure the objective function in the Gurobi model.

Translates the OptiX objective function to Gurobi format, handling both minimization and maximization objectives. Supports continuous and integer coefficient modes with automatic goal programming objective creation.

Parameters:

prb (OXLPProblem) – Problem instance with objective function definition

Raises:
  • OXception – If no objective function is specified

  • OXException – If float weights are used in integer mode without proper configuration

Note

  • For goal programming problems, the objective is automatically created

  • Fractional coefficients require equalizeDenominators parameter in integer mode

  • Objective type (minimize/maximize) is preserved from the problem definition

create_special_constraints(prb: OXCSPProblem)[source]

Create special non-linear constraints for constraint satisfaction problems.

This method is intended for handling special constraints that cannot be expressed as standard linear constraints (e.g., multiplication, division, modulo, conditional constraints). Currently not implemented for Gurobi.

Parameters:

prb (OXCSPProblem) – Constraint satisfaction problem with special constraints

Note

Implementation is pending for advanced constraint types that require special handling in the Gurobi solver.

get_solver_logs() List[str | List[str]] | None[source]

Retrieve solver execution logs and diagnostic information.

Returns detailed logs from the Gurobi solver execution including performance metrics, iteration details, and diagnostic messages. Currently not implemented.

Returns:

Solver logs if available, None otherwise

Return type:

Optional[LogsType]

Note

Implementation is pending for comprehensive log extraction from the Gurobi solver instance.

solve(prb: OXCSPProblem) OXSolutionStatus[source]

Solve the optimization problem using Gurobi solver.

Executes the Gurobi optimization process and extracts solution information including variable values, constraint evaluations, and objective function value. Creates a comprehensive solution object for optimal solutions.

Parameters:

prb (OXCSPProblem) – Problem instance to solve

Returns:

Status of the optimization process:
  • OPTIMAL: Solution found successfully

  • INFEASIBLE: No feasible solution exists

  • UNBOUNDED: Problem is unbounded

  • ERROR: Solver encountered an error or indeterminate status

Return type:

OXSolutionStatus

Note

  • Solution details are stored in the _solutions list for optimal solutions

  • Constraint values include left-hand side, operator, and right-hand side

  • Objective function value is included for linear programming problems

class solvers.gurobi.OXGurobiSolverInterface(**kwargs)[source]

Bases: OXSolverInterface

Gurobi-specific implementation of the OptiX solver interface.

This class provides a concrete implementation of the OXSolverInterface for the Gurobi optimization solver. It handles the translation between OptiX’s abstract problem representation and Gurobi’s specific API calls, variable types, and constraint formats.

The interface supports both continuous and integer optimization modes, with automatic handling of variable bounds, constraint operators, and objective function setup. Special support is provided for goal programming with positive and negative deviation variables.

_model

The underlying Gurobi model instance

Type:

gp.Model

_var_mapping

Maps OptiX variable IDs to Gurobi variable objects

Type:

dict

_constraint_mapping

Maps OptiX constraint IDs to Gurobi constraint objects

Type:

dict

_constraint_expr_mapping

Maps constraint IDs to their Gurobi expressions

Type:

dict

Parameters:
  • use_continuous (bool) – Whether to use continuous variables instead of integers

  • equalizeDenominators (bool) – Whether to normalize fractional coefficients

Example

Direct usage of the Gurobi interface:

solver = OXGurobiSolverInterface(use_continuous=True)

# The solver is typically used through the factory pattern
# but can be used directly for advanced Gurobi-specific features

solver.create_variables(problem.variables)
solver.create_constraints(problem.constraints)
solver.create_objective(problem)

status = solver.solve(problem)
if status == OXSolutionStatus.OPTIMAL:
    solution = solver.get_solutions()[0]
__init__(**kwargs)[source]

Initialize the Gurobi solver interface with configuration parameters.

Creates a new Gurobi model instance and initializes internal mappings for variables, constraints, and constraint expressions. Configuration parameters are passed to the parent OXSolverInterface class.

Parameters:

**kwargs – Configuration parameters including: use_continuous (bool): Use continuous variables instead of integers equalizeDenominators (bool): Normalize fractional coefficients

Note

The Gurobi model is created with the name “OptiX Model” and uses default Gurobi settings unless modified through solver parameters.

create_special_constraints(prb: OXCSPProblem)[source]

Create special non-linear constraints for constraint satisfaction problems.

This method is intended for handling special constraints that cannot be expressed as standard linear constraints (e.g., multiplication, division, modulo, conditional constraints). Currently not implemented for Gurobi.

Parameters:

prb (OXCSPProblem) – Constraint satisfaction problem with special constraints

Note

Implementation is pending for advanced constraint types that require special handling in the Gurobi solver.

create_objective(prb: OXLPProblem)[source]

Create and configure the objective function in the Gurobi model.

Translates the OptiX objective function to Gurobi format, handling both minimization and maximization objectives. Supports continuous and integer coefficient modes with automatic goal programming objective creation.

Parameters:

prb (OXLPProblem) – Problem instance with objective function definition

Raises:
  • OXception – If no objective function is specified

  • OXException – If float weights are used in integer mode without proper configuration

Note

  • For goal programming problems, the objective is automatically created

  • Fractional coefficients require equalizeDenominators parameter in integer mode

  • Objective type (minimize/maximize) is preserved from the problem definition

solve(prb: OXCSPProblem) OXSolutionStatus[source]

Solve the optimization problem using Gurobi solver.

Executes the Gurobi optimization process and extracts solution information including variable values, constraint evaluations, and objective function value. Creates a comprehensive solution object for optimal solutions.

Parameters:

prb (OXCSPProblem) – Problem instance to solve

Returns:

Status of the optimization process:
  • OPTIMAL: Solution found successfully

  • INFEASIBLE: No feasible solution exists

  • UNBOUNDED: Problem is unbounded

  • ERROR: Solver encountered an error or indeterminate status

Return type:

OXSolutionStatus

Note

  • Solution details are stored in the _solutions list for optimal solutions

  • Constraint values include left-hand side, operator, and right-hand side

  • Objective function value is included for linear programming problems

get_solver_logs() List[str | List[str]] | None[source]

Retrieve solver execution logs and diagnostic information.

Returns detailed logs from the Gurobi solver execution including performance metrics, iteration details, and diagnostic messages. Currently not implemented.

Returns:

Solver logs if available, None otherwise

Return type:

Optional[LogsType]

Note

Implementation is pending for comprehensive log extraction from the Gurobi solver instance.

Solution Management

Examples

Basic Solving

from problem import OXLPProblem, ObjectiveType
from constraints import RelationalOperators
from solvers import solve

# Create problem
problem = OXLPProblem()
problem.create_decision_variable("x", "Variable X", 0, 10)
problem.create_decision_variable("y", "Variable Y", 0, 10)

# Add constraint
problem.create_constraint(
    variables=[var.id for var in problem.variables],
    weights=[1, 1],
    operator=RelationalOperators.LESS_THAN_EQUAL,
    value=15
)

# Set objective
problem.create_objective_function(
    variables=[var.id for var in problem.variables],
    weights=[3, 2],
    objective_type=ObjectiveType.MAXIMIZE
)

# Solve with OR-Tools
status, solution = solve(problem, 'ORTools')
print(f"Status: {status}")

if solution:
    for sol in solution:
        print(f"Objective value: {sol.objective_value}")
        sol.print_solution_for(problem)

# Solve with Gurobi
try:
    status, solution = solve(problem, 'Gurobi')
    print(f"Gurobi Status: {status}")
except Exception as e:
    print(f"Gurobi not available: {e}")

Solver Comparison

import time
from solvers import solve

def compare_solvers(problem, solvers=['ORTools', 'Gurobi']):
    """Compare performance of different solvers on the same problem."""
    results = {}

    for solver_name in solvers:
        try:
            start_time = time.time()
            status, solution = solve(problem, solver_name)
            solve_time = time.time() - start_time

            results[solver_name] = {
                'status': status,
                'solve_time': solve_time,
                'objective_value': solution[0].objective_value if solution else None
            }

            print(f"{solver_name}:")
            print(f"  Status: {status}")
            print(f"  Time: {solve_time:.4f} seconds")
            if solution:
                print(f"  Objective: {solution[0].objective_value}")
            print()

        except Exception as e:
            print(f"{solver_name} failed: {e}")
            results[solver_name] = {'error': str(e)}

    return results

# Usage
results = compare_solvers(problem)

Custom Solver Implementation

from solvers import OXSolverInterface, OXSolverSolution, OXSolutionStatus
import random

class RandomSolverInterface(OXSolverInterface):
    """A simple random solver for demonstration purposes."""

    def __init__(self):
        super().__init__()
        self.solutions = []

    def solve(self, problem):
        """Solve the problem using random sampling."""
        self.solutions = []

        # Simple random search (not optimal, just for demo)
        best_objective = float('-inf') if problem.objective_function.objective_type == ObjectiveType.MAXIMIZE else float('inf')
        best_values = {}

        for _ in range(1000):  # 1000 random samples
            values = {}
            objective_value = 0

            # Generate random values for each variable
            for variable in problem.variables:
                random_value = random.uniform(variable.lower_bound, variable.upper_bound)
                values[variable.id] = random_value

            # Check constraints (simplified)
            feasible = True
            for constraint in problem.constraints:
                constraint_value = sum(
                    constraint.weights[i] * values[constraint.variables[i]]
                    for i in range(len(constraint.variables))
                )

                if constraint.operator == RelationalOperators.LESS_THAN_EQUAL:
                    if constraint_value > constraint.value:
                        feasible = False
                        break
                elif constraint.operator == RelationalOperators.GREATER_THAN_EQUAL:
                    if constraint_value < constraint.value:
                        feasible = False
                        break
                elif constraint.operator == RelationalOperators.EQUAL:
                    if abs(constraint_value - constraint.value) > 1e-6:
                        feasible = False
                        break

            if feasible:
                # Calculate objective value
                objective_value = sum(
                    problem.objective_function.weights[i] * values[problem.objective_function.variables[i]]
                    for i in range(len(problem.objective_function.variables))
                )

                # Check if this is the best solution so far
                is_better = False
                if problem.objective_function.objective_type == ObjectiveType.MAXIMIZE:
                    is_better = objective_value > best_objective
                else:
                    is_better = objective_value < best_objective

                if is_better:
                    best_objective = objective_value
                    best_values = values.copy()

        # Create solution
        if best_values:
            solution = OXSolverSolution(
                objective_value=best_objective,
                variable_values=best_values,
                status=OXSolutionStatus.OPTIMAL
            )
            self.solutions = [solution]
            return OXSolutionStatus.OPTIMAL
        else:
            return OXSolutionStatus.INFEASIBLE

    def get_solution(self):
        """Return the best solution found."""
        return self.solutions

# Custom solvers would need to be registered through the solver factory
# This is an example of implementing a custom solver interface

Advanced Solver Configuration

from solvers.ortools import OXORToolsSolverInterface
from solvers.gurobi import OXGurobiSolverInterface

# Configure OR-Tools solver
ortools_solver = OXORToolsSolverInterface()
ortools_solver.set_time_limit(300)  # 5 minutes
ortools_solver.set_num_threads(4)

# Configure Gurobi solver (if available)
try:
    gurobi_solver = OXGurobiSolverInterface()
    gurobi_solver.set_parameter('TimeLimit', 300)
    gurobi_solver.set_parameter('Threads', 4)
    gurobi_solver.set_parameter('MIPGap', 0.01)  # 1% optimality gap
except ImportError:
    print("Gurobi not available")

# Solve with configured solvers
status = ortools_solver.solve(problem)
ortools_solutions = ortools_solver.get_solution()

Parallel Solving

import concurrent.futures
import time

def solve_parallel(problem, solvers=['ORTools', 'Gurobi'], timeout=300):
    """Solve the same problem with multiple solvers in parallel."""

    def solve_with_solver(solver_name):
        try:
            start_time = time.time()
            status, solution = solve(problem, solver_name)
            solve_time = time.time() - start_time

            return {
                'solver': solver_name,
                'status': status,
                'solution': solution,
                'time': solve_time
            }
        except Exception as e:
            return {
                'solver': solver_name,
                'error': str(e),
                'time': None
            }

    # Use ThreadPoolExecutor for parallel execution
    with concurrent.futures.ThreadPoolExecutor(max_workers=len(solvers)) as executor:
        # Submit all solver tasks
        future_to_solver = {
            executor.submit(solve_with_solver, solver): solver
            for solver in solvers
        }

        results = []

        # Collect results as they complete
        for future in concurrent.futures.as_completed(future_to_solver, timeout=timeout):
            try:
                result = future.result()
                results.append(result)
                print(f"Completed: {result['solver']} in {result.get('time', 'N/A')} seconds")
            except Exception as e:
                solver = future_to_solver[future]
                results.append({
                    'solver': solver,
                    'error': str(e),
                    'time': None
                })

    return results

# Usage
parallel_results = solve_parallel(problem)

# Find the best result
best_result = None
for result in parallel_results:
    if 'error' not in result and result['solution']:
        if best_result is None or result['time'] < best_result['time']:
            best_result = result

if best_result:
    print(f"Best solver: {best_result['solver']} ({best_result['time']:.4f}s)")

Solution Analysis

def analyze_solution(solution, problem):
    """Analyze and validate a solution."""

    if not solution:
        print("No solution available")
        return

    sol = solution[0]  # Get first solution

    print("=== Solution Analysis ===")
    print(f"Objective Value: {sol.objective_value}")
    print(f"Status: {sol.status}")
    print()

    print("Variable Values:")
    for var_id, value in sol.variable_values.items():
        variable = next((v for v in problem.variables if v.id == var_id), None)
        if variable:
            print(f"  {variable.name}: {value:.6f}")
    print()

    # Validate constraints
    print("Constraint Validation:")
    all_satisfied = True

    for i, constraint in enumerate(problem.constraints):
        constraint_value = sum(
            constraint.weights[j] * sol.variable_values[constraint.variables[j]]
            for j in range(len(constraint.variables))
        )

        satisfied = False
        if constraint.operator == RelationalOperators.LESS_THAN_EQUAL:
            satisfied = constraint_value <= constraint.value + 1e-6
            op_str = "<="
        elif constraint.operator == RelationalOperators.GREATER_THAN_EQUAL:
            satisfied = constraint_value >= constraint.value - 1e-6
            op_str = ">="
        elif constraint.operator == RelationalOperators.EQUAL:
            satisfied = abs(constraint_value - constraint.value) <= 1e-6
            op_str = "=="

        status_icon = "✅" if satisfied else "❌"
        print(f"  Constraint {i+1}: {constraint_value:.6f} {op_str} {constraint.value} {status_icon}")

        if not satisfied:
            all_satisfied = False

    print(f"\nAll constraints satisfied: {'✅' if all_satisfied else '❌'}")

    # Calculate objective value manually to verify
    if hasattr(problem, 'objective_function') and problem.objective_function:
        manual_objective = sum(
            problem.objective_function.weights[i] * sol.variable_values[problem.objective_function.variables[i]]
            for i in range(len(problem.objective_function.variables))
        )
        print(f"Manual objective calculation: {manual_objective:.6f}")
        print(f"Solver objective value: {sol.objective_value:.6f}")
        print(f"Difference: {abs(manual_objective - sol.objective_value):.8f}")

# Usage
status, solution = solve(problem, 'ORTools')
analyze_solution(solution, problem)

Multi-Scenario Solving

The solve_all_scenarios function enables comprehensive scenario-based optimization analysis:

from problem import OXLPProblem, ObjectiveType
from constraints import RelationalOperators
from data import OXData
from solvers import solve_all_scenarios

# Create problem with scenario-based data
problem = OXLPProblem()

# Create decision variables
x = problem.create_decision_variable("production_x", "Production of X", 0, 100)
y = problem.create_decision_variable("production_y", "Production of Y", 0, 100)

# Create data object with scenarios
demand_data = OXData()
demand_data.demand = 100        # Default scenario
demand_data.price = 5.0

# Create scenarios for different market conditions
demand_data.create_scenario("High_Demand", demand=150, price=6.0)
demand_data.create_scenario("Low_Demand", demand=75, price=4.5)
demand_data.create_scenario("Peak_Season", demand=200, price=7.0)

# Add data to problem database
problem.db.add_object(demand_data)

# Create constraints using scenario data
problem.create_constraint(
    variables=[x.id, y.id],
    weights=[1, 1],
    operator=RelationalOperators.LESS_THAN_EQUAL,
    value=demand_data.demand,
    description="Total production must not exceed demand"
)

# Create objective function using scenario data
problem.create_objective_function(
    variables=[x.id, y.id],
    weights=[demand_data.price, 3.0],
    objective_type=ObjectiveType.MAXIMIZE,
    description="Maximize revenue"
)

# Solve across all scenarios
scenario_results = solve_all_scenarios(problem, 'ORTools', maxTime=300)

print(f"Solved {len(scenario_results)} scenarios")
print(f"Scenarios: {list(scenario_results.keys())}")

# Analyze results across scenarios
best_scenario = None
best_value = float('-inf')

for scenario_name, result in scenario_results.items():
    print(f"\n=== Scenario: {scenario_name} ===")

    if result['status'] == OXSolutionStatus.OPTIMAL:
        solution = result['solution']
        print(f"Status: Optimal")
        print(f"Objective Value: {solution.objective_value:.2f}")
        print(f"Production X: {solution.variable_values[x.id]:.2f}")
        print(f"Production Y: {solution.variable_values[y.id]:.2f}")

        # Track best scenario
        if solution.objective_value > best_value:
            best_value = solution.objective_value
            best_scenario = scenario_name
    else:
        print(f"Status: {result['status']}")

if best_scenario:
    print(f"\nBest performing scenario: {best_scenario} (${best_value:.2f})")

Advanced Multi-Scenario Analysis

from problem import OXLPProblem
from data import OXData
from solvers import solve_all_scenarios
import statistics

def comprehensive_scenario_analysis(problem, solver='ORTools'):
    """Perform comprehensive multi-scenario optimization analysis."""

    # Solve all scenarios
    results = solve_all_scenarios(problem, solver, maxTime=600)

    # Collect statistics
    optimal_scenarios = []
    objective_values = []

    for scenario_name, result in results.items():
        if result['status'] == OXSolutionStatus.OPTIMAL:
            optimal_scenarios.append(scenario_name)
            objective_values.append(result['solution'].objective_value)

    if not objective_values:
        print("No optimal solutions found across scenarios")
        return

    # Statistical analysis
    print("=== Multi-Scenario Analysis ===")
    print(f"Total scenarios: {len(results)}")
    print(f"Optimal scenarios: {len(optimal_scenarios)}")
    print(f"Success rate: {len(optimal_scenarios)/len(results)*100:.1f}%")
    print()

    print("=== Objective Value Statistics ===")
    print(f"Best value: {max(objective_values):.2f}")
    print(f"Worst value: {min(objective_values):.2f}")
    print(f"Average value: {statistics.mean(objective_values):.2f}")
    print(f"Median value: {statistics.median(objective_values):.2f}")
    print(f"Standard deviation: {statistics.stdev(objective_values):.2f}")
    print()

    # Scenario ranking
    scenario_ranking = []
    for scenario_name, result in results.items():
        if result['status'] == OXSolutionStatus.OPTIMAL:
            scenario_ranking.append((scenario_name, result['solution'].objective_value))

    scenario_ranking.sort(key=lambda x: x[1], reverse=True)

    print("=== Scenario Ranking ===")
    for i, (scenario, value) in enumerate(scenario_ranking, 1):
        print(f"{i:2d}. {scenario:<20}: ${value:8.2f}")

    # Sensitivity analysis
    if len(objective_values) > 1:
        value_range = max(objective_values) - min(objective_values)
        cv = statistics.stdev(objective_values) / statistics.mean(objective_values)

        print(f"\n=== Sensitivity Analysis ===")
        print(f"Value range: ${value_range:.2f}")
        print(f"Coefficient of variation: {cv:.3f}")

        if cv > 0.2:
            print("⚠️  High sensitivity to scenario parameters")
        elif cv > 0.1:
            print("⚡ Moderate sensitivity to scenario parameters")
        else:
            print("✅ Low sensitivity to scenario parameters")

    return results

# Usage with complex multi-object scenarios
problem = OXLPProblem()

# Create variables
x = problem.create_decision_variable("x", "Variable X", 0, 50)
y = problem.create_decision_variable("y", "Variable Y", 0, 50)

# Create multiple data objects with coordinated scenarios
capacity_data = OXData()
capacity_data.max_capacity = 100
capacity_data.create_scenario("Expansion", max_capacity=150)
capacity_data.create_scenario("Recession", max_capacity=80)
capacity_data.create_scenario("Growth", max_capacity=120)

cost_data = OXData()
cost_data.unit_cost = 2.0
cost_data.create_scenario("Expansion", unit_cost=1.8)    # Lower costs during expansion
cost_data.create_scenario("Recession", unit_cost=2.5)   # Higher costs during recession
cost_data.create_scenario("Growth", unit_cost=2.2)      # Moderate cost increase

# Add to database
problem.db.add_object(capacity_data)
problem.db.add_object(cost_data)

# Create constraints and objectives using scenario data
problem.create_constraint([x.id, y.id], [1, 1], "<=", capacity_data.max_capacity)
problem.create_objective_function([x.id, y.id], [cost_data.unit_cost, 3.0], "maximize")

# Perform comprehensive analysis
analysis_results = comprehensive_scenario_analysis(problem, 'Gurobi')

Constraint-Based Scenarios

from constraints import RelationalOperators
from solvers import solve_all_scenarios

# Create problem with constraint scenarios
problem = OXLPProblem()

x = problem.create_decision_variable("x", "Production X", 0, 100)
y = problem.create_decision_variable("y", "Production Y", 0, 100)

# Create base constraint
resource_constraint = problem.create_constraint(
    variables=[x.id, y.id],
    weights=[2, 1],
    operator=RelationalOperators.LESS_THAN_EQUAL,
    value=200,
    description="Resource availability constraint"
)

# Add constraint scenarios for different resource conditions
resource_constraint.create_scenario(
    "Limited_Resources",
    rhs=150,
    description="Resource shortage scenario"
)

resource_constraint.create_scenario(
    "Abundant_Resources",
    rhs=300,
    description="Resource abundance scenario"
)

resource_constraint.create_scenario(
    "Emergency_Resources",
    rhs=100,
    description="Emergency resource rationing"
)

# Create objective
problem.create_objective_function(
    variables=[x.id, y.id],
    weights=[5, 4],
    objective_type=ObjectiveType.MAXIMIZE
)

# Solve across constraint scenarios
constraint_results = solve_all_scenarios(problem, 'ORTools')

# Analyze impact of resource availability
print("=== Resource Scenario Analysis ===")
for scenario_name, result in constraint_results.items():
    if result['status'] == OXSolutionStatus.OPTIMAL:
        solution = result['solution']
        total_production = solution.variable_values[x.id] + solution.variable_values[y.id]

        print(f"{scenario_name}:")
        print(f"  Objective: ${solution.objective_value:.2f}")
        print(f"  Total Production: {total_production:.2f} units")
        print(f"  X Production: {solution.variable_values[x.id]:.2f}")
        print(f"  Y Production: {solution.variable_values[y.id]:.2f}")
        print()

Mixed Data and Constraint Scenarios

from data import OXData
from solvers import solve_all_scenarios

# Complex scenario setup with both data and constraint scenarios
problem = OXLPProblem()

# Variables
x = problem.create_decision_variable("x", "Product X", 0, 100)
y = problem.create_decision_variable("y", "Product Y", 0, 100)

# Data object scenarios for market conditions
market_data = OXData()
market_data.price_x = 10.0
market_data.price_y = 8.0
market_data.create_scenario("Bull_Market", price_x=12.0, price_y=10.0)
market_data.create_scenario("Bear_Market", price_x=8.0, price_y=6.0)

problem.db.add_object(market_data)

# Constraint scenarios for operational conditions
capacity_constraint = problem.create_constraint(
    variables=[x.id, y.id],
    weights=[1, 1],
    operator=RelationalOperators.LESS_THAN_EQUAL,
    value=150,
    description="Production capacity"
)
capacity_constraint.create_scenario("Maintenance", rhs=100)
capacity_constraint.create_scenario("Overtime", rhs=200)

# Objective using data scenarios
problem.create_objective_function(
    variables=[x.id, y.id],
    weights=[market_data.price_x, market_data.price_y],
    objective_type=ObjectiveType.MAXIMIZE
)

# This will solve all combinations:
# - Default + Default, Bull_Market + Default, Bear_Market + Default
# - Default + Maintenance, Bull_Market + Maintenance, Bear_Market + Maintenance
# - Default + Overtime, Bull_Market + Overtime, Bear_Market + Overtime
mixed_results = solve_all_scenarios(problem, 'Gurobi', use_continuous=True)

print(f"Total scenario combinations solved: {len(mixed_results)}")

# Group results by data vs constraint scenarios
market_scenarios = {}
capacity_scenarios = {}

for scenario_name, result in mixed_results.items():
    if result['status'] == OXSolutionStatus.OPTIMAL:
        solution = result['solution']

        # Categorize scenarios
        if 'Market' in scenario_name:
            market_scenarios[scenario_name] = solution.objective_value
        elif scenario_name in ['Maintenance', 'Overtime']:
            capacity_scenarios[scenario_name] = solution.objective_value
        else:
            print(f"Default scenario value: ${solution.objective_value:.2f}")

print("\n=== Market Impact Analysis ===")
for scenario, value in market_scenarios.items():
    print(f"{scenario}: ${value:.2f}")

print("\n=== Capacity Impact Analysis ===")
for scenario, value in capacity_scenarios.items():
    print(f"{scenario}: ${value:.2f}")

See Also

  • Problem Module - Problem type definitions

  • ../tutorials/custom_solvers - Creating custom solver implementations

  • ../user_guide/solvers - Detailed solver configuration guide