Skip to content

CplexEngine class

Bases: Engine

Concrete engine implementation using IBM CPLEX optimizer.

This class provides an interface for formulating and solving linear, integer, and nonlinear optimization models using the CPLEX solver.

Source code in pyorlib/engines/cplex/cplex_engine.py
class CplexEngine(Engine):
    """
    Concrete engine implementation using IBM CPLEX optimizer.

    This class provides an interface for formulating and solving linear,
    integer, and nonlinear optimization models using the CPLEX solver.
    """

    class _Variable(Variable):
        """
        Represents a CPLEX variable in an optimization model.

        The `CplexVariable` class is a concrete implementation of the abstract `Variable` class.
        It represents a variable that is compatible with the CPLEX solver.
        """

        # Strict class attributes.
        __slots__ = ["_cplex_var"]

        @property
        def name(self) -> str:
            return str(self._cplex_var.name)

        @property
        def lower_bound(self) -> float:
            lb = self._cplex_var.lb
            return -inf if lb <= -1e20 else float(lb)

        @property
        def upper_bound(self) -> float:
            ub = self._cplex_var.ub
            return inf if ub >= 1e20 else float(ub)

        @property
        def value(self) -> float:
            try:
                return float(self._cplex_var.solution_value)
            except DOcplexException:
                return -0.0

        @property
        def raw(self) -> Any:
            return self._cplex_var

        def __init__(
            self,
            name: str,
            solver: cpx.Model,
            value_type: ValueType,
            lower_bound: float = 0,
            upper_bound: float = inf,
        ):
            """
            Initializes a new `CplexVariable` object with the specified attributes and creates a corresponding CPLEX
            variable in the specified CPLEX solver.
            :param name: The name of the variable.
            :param solver: A reference to the CPLEX solver.
            :param value_type: An enumeration representing the type of the variable's value.
            :param lower_bound: The lower bound of the variable. Default is 0.
            :param upper_bound: The upper bound of the variable. Default is infinity.
            """
            # Calls the super init method and its validations
            super().__init__(name=name, value_type=value_type, lower_bound=lower_bound, upper_bound=upper_bound)

            # Applies new validations
            if solver is None:
                raise CplexException("The 'solver' argument cannot be None.")

            # Creates the CPLEX variable according to the value type
            cplex_var: Var | None

            if self.value_type == ValueType.BINARY:
                cplex_var = solver.binary_var(name=name)
            elif self.value_type == ValueType.INTEGER:
                cplex_var = solver.integer_var(name=name, lb=lower_bound, ub=upper_bound)
            elif self.value_type == ValueType.CONTINUOUS:
                cplex_var = solver.continuous_var(name=name, lb=lower_bound, ub=upper_bound)
            else:
                raise CplexException("Unknown ValueType.")

            # Applies new validations
            if cplex_var is None:
                raise CplexException("Failed to create the CPLEX variable.")

            # Instance attributes
            self._cplex_var: Var = cplex_var
            """ A Cplex.Var object representing the variable in the CPLEX solver. """

    @property
    def name(self) -> str:  # pragma: no cover
        return "CPLEX Engine"

    @property
    def constraints(self) -> List[Element]:
        return [Expression(expression=constraint) for constraint in self._solver.iter_constraints()]

    @property
    def objective_value(self) -> float | None:
        if self.solution_status in [SolutionStatus.OPTIMAL, SolutionStatus.FEASIBLE]:
            return float(self._solver.objective_value)
        return None

    @property
    def objective_expr(self) -> Element | None:
        objective = self._solver.get_objective_expr()
        return Expression(expression=objective) if objective is not None else None

    @property
    def solution_status(self) -> SolutionStatus:  # pragma: no cover
        if self._solver.solve_details is None:
            return SolutionStatus.NOT_SOLVED

        # Status Codes
        optimal_codes = [1, 5, 15, 17, 19, 20, 101, 102, 115, 121, 123, 125, 129, 130, 301]
        feasible_codes = [14, 16, 18, 23, 30, 120, 124, 127]
        infeasible_codes = [3, 103]

        if self._solver.solve_details.status_code in optimal_codes:
            return SolutionStatus.OPTIMAL
        elif self._solver.solve_details.status_code in feasible_codes:
            return SolutionStatus.FEASIBLE
        elif self._solver.solve_details.status_code in infeasible_codes:
            return SolutionStatus.INFEASIBLE
        else:
            StdOutLogger.error(action="Error code: ", msg=f"{self._solver.solve_details.status_code}")
            return SolutionStatus.ERROR

    def __init__(self, solver: cpx.Model | None = None):
        """
        Initialize a CPLEX engine instance.
        :param solver: A CPLEX solver object. If None, a new solver will be
            instantiated using CPLEX's default settings. Allows custom
            configuration of the solver before passing to the engine.
        """

        # Instance attributes
        self._solver: cpx.Model = solver if solver else cpx.Model(log_output=False)
        """ A reference to the CPLEX solver. """

        if self._solver is None or not isinstance(self._solver, cpx.Model):
            raise CplexException("The CPLEX solver must be an instance of cpx.Model")

    def add_variable(
        self,
        name: str,
        value_type: ValueType,
        lower_bound: float = 0,
        upper_bound: float = inf,
    ) -> Variable:
        return CplexEngine._Variable(
            name=name,
            solver=self._solver,
            value_type=value_type,
            lower_bound=lower_bound,
            upper_bound=upper_bound,
        )

    def add_constraint(self, expression: Element) -> Element:
        self._solver.add_constraint(ct=expression.raw)
        return expression

    def set_objective(self, opt_type: OptimizationType, expression: Element) -> Element:
        if opt_type == OptimizationType.MINIMIZE:
            self._solver.minimize(expr=expression.raw)
        elif opt_type == OptimizationType.MAXIMIZE:
            self._solver.maximize(expr=expression.raw)
        else:
            raise CplexException("Optimization type not supported.")
        return expression

    def solve(self) -> None:
        self._solver.solve()

Attributes

name property

name: str

constraints property

constraints: List[Element]

objective_value property

objective_value: float | None

objective_expr property

objective_expr: Element | None

solution_status property

solution_status: SolutionStatus

Functions

__init__

__init__(solver: Model | None = None)

Initialize a CPLEX engine instance.

PARAMETER DESCRIPTION
solver

A CPLEX solver object. If None, a new solver will be instantiated using CPLEX's default settings. Allows custom configuration of the solver before passing to the engine.

TYPE: Model | None DEFAULT: None

Source code in pyorlib/engines/cplex/cplex_engine.py
def __init__(self, solver: cpx.Model | None = None):
    """
    Initialize a CPLEX engine instance.
    :param solver: A CPLEX solver object. If None, a new solver will be
        instantiated using CPLEX's default settings. Allows custom
        configuration of the solver before passing to the engine.
    """

    # Instance attributes
    self._solver: cpx.Model = solver if solver else cpx.Model(log_output=False)
    """ A reference to the CPLEX solver. """

    if self._solver is None or not isinstance(self._solver, cpx.Model):
        raise CplexException("The CPLEX solver must be an instance of cpx.Model")

add_variable

add_variable(name: str, value_type: ValueType, lower_bound: float = 0, upper_bound: float = inf) -> Variable
Source code in pyorlib/engines/cplex/cplex_engine.py
def add_variable(
    self,
    name: str,
    value_type: ValueType,
    lower_bound: float = 0,
    upper_bound: float = inf,
) -> Variable:
    return CplexEngine._Variable(
        name=name,
        solver=self._solver,
        value_type=value_type,
        lower_bound=lower_bound,
        upper_bound=upper_bound,
    )

add_constraint

add_constraint(expression: Element) -> Element
Source code in pyorlib/engines/cplex/cplex_engine.py
def add_constraint(self, expression: Element) -> Element:
    self._solver.add_constraint(ct=expression.raw)
    return expression

set_objective

set_objective(opt_type: OptimizationType, expression: Element) -> Element
Source code in pyorlib/engines/cplex/cplex_engine.py
def set_objective(self, opt_type: OptimizationType, expression: Element) -> Element:
    if opt_type == OptimizationType.MINIMIZE:
        self._solver.minimize(expr=expression.raw)
    elif opt_type == OptimizationType.MAXIMIZE:
        self._solver.maximize(expr=expression.raw)
    else:
        raise CplexException("Optimization type not supported.")
    return expression

solve

solve() -> None
Source code in pyorlib/engines/cplex/cplex_engine.py
def solve(self) -> None:
    self._solver.solve()