1from builtins import property
2from typing import List, Optional, Dict, Union, Tuple
3import numbers
4import mip
5from math import fabs
6import math
7
8
9class Column:
10    """A column contains all the non-zero entries of a variable in the
11    constraint matrix. To create a variable see
12    :meth:`~mip.Model.add_var`."""
13
14    __slots__ = ["constrs", "coeffs"]
15
16    def __init__(
17        self,
18        constrs=None,  # type : Optional[List["mip.Constr"]]
19        coeffs=None,  # type: Optional[List[numbers.Real]]
20    ):
21        self.constrs = constrs
22        self.coeffs = coeffs
23
24    def __str__(self) -> str:
25        res = "["
26        for k in range(len(self.constrs)):
27            res += "{} {}".format(self.coeffs[k], self.constrs[k].name)
28            if k < len(self.constrs) - 1:
29                res += ", "
30        res += "]"
31        return res
32
33
34class LinExpr:
35    """
36    Linear expressions are used to enter the objective function and the model \
37    constraints. These expressions are created using operators and variables.
38
39    Consider a model object m, the objective function of :code:`m` can be
40    specified as:
41
42    .. code:: python
43
44     m.objective = 10*x1 + 7*x4
45
46    In the example bellow, a constraint is added to the model
47
48    .. code:: python
49
50     m += xsum(3*x[i] i in range(n)) - xsum(x[i] i in range(m))
51
52    A constraint is just a linear expression with the addition of a sense (==,
53    <= or >=) and a right hand side, e.g.:
54
55    .. code:: python
56
57     m += x1 + x2 + x3 == 1
58
59    If used in intermediate calculations, the solved value of the linear
60    expression can be obtained with the ``x`` parameter, just as with
61    a ``Var``.
62
63    .. code:: python
64
65     a = 10*x1 + 7*x4
66     print(a.x)
67
68    """
69
70    __slots__ = ["__const", "__expr", "__sense"]
71
72    def __init__(
73        self,
74        variables: Optional[List["mip.Var"]] = None,
75        coeffs: Optional[List[numbers.Real]] = None,
76        const: numbers.Real = 0.0,
77        sense: str = "",
78    ):
79        self.__const = const
80        self.__expr = {}  # type: Dict[mip.Var, numbers.Real]
81        self.__sense = sense
82
83        if variables is not None and coeffs is not None:
84            if len(variables) != len(coeffs):
85                raise ValueError("Coefficients and variables must be same length.")
86            for i in range(len(coeffs)):
87                if abs(coeffs[i]) <= 1e-12:
88                    continue
89                self.add_var(variables[i], coeffs[i])
90
91    def __add__(
92        self,
93        other: Union["mip.Var", "mip.LinExpr", numbers.Real],
94    ) -> "mip.LinExpr":
95        if isinstance(other, numbers.Real) and fabs(other) < mip.EPS:
96            return self
97
98        result = self.copy()
99        if isinstance(other, Var):
100            result.add_var(other, 1)
101        elif isinstance(other, LinExpr):
102            result.add_expr(other)
103        elif isinstance(other, numbers.Real):
104            result.add_const(other)
105        else:
106            raise TypeError("type {} not supported".format(type(other)))
107        return result
108
109    def __radd__(
110        self,
111        other: Union["mip.Var", "mip.LinExpr", numbers.Real],
112    ) -> "mip.LinExpr":
113        return self.__add__(other)
114
115    def __iadd__(
116        self,
117        other: Union["mip.Var", "mip.LinExpr", numbers.Real],
118    ) -> "mip.LinExpr":
119        raise DeprecationWarning("Inplace operations are deprecated")
120
121    def __sub__(
122        self,
123        other: Union["mip.Var", "mip.LinExpr", numbers.Real],
124    ) -> "mip.LinExpr":
125        result = self.copy()
126        if isinstance(other, Var):
127            result.add_var(other, -1)
128        elif isinstance(other, LinExpr):
129            result.add_expr(other, -1)
130        elif isinstance(other, numbers.Real):
131            result.add_const(-other)
132        else:
133            raise TypeError("type {} not supported".format(type(other)))
134        return result
135
136    def __rsub__(
137        self,
138        other: Union["mip.Var", "mip.LinExpr", numbers.Real],
139    ) -> "mip.LinExpr":
140        return (-self).__add__(other)
141
142    def __isub__(
143        self,
144        other: Union["mip.Var", "mip.LinExpr", numbers.Real],
145    ) -> "mip.LinExpr":
146        raise DeprecationWarning("Inplace operations are deprecated")
147
148    def __mul__(self, other: numbers.Real) -> Union["mip.LinExpr", numbers.Real]:
149        if not isinstance(other, numbers.Real):
150            raise TypeError("Can not multiply with type {}".format(type(other)))
151        if isinstance(other, numbers.Real):
152            if fabs(other) < mip.EPS:
153                return other
154            if fabs(other - 1) < mip.EPS:
155                return self
156
157        result = self.copy()
158        result.__const *= other
159        for var in result.__expr.keys():
160            result.__expr[var] *= other
161        return result
162
163    def __rmul__(self, other: numbers.Real) -> "mip.LinExpr":
164        return self.__mul__(other)
165
166    def __imul__(self, other: numbers.Real) -> "mip.LinExpr":
167        raise DeprecationWarning("Inplace operations are deprecated")
168
169    def __truediv__(self, other: numbers.Real) -> "mip.LinExpr":
170        if not isinstance(other, numbers.Real):
171            raise TypeError("Can not divide with type {}".format(type(other)))
172        result = self.copy()
173        result.__const /= other
174        for var in result.__expr.keys():
175            result.__expr[var] /= other
176        return result
177
178    def __itruediv__(self, other: numbers.Real) -> "LinExpr":
179        raise DeprecationWarning("Inplace operations are deprecated")
180
181    def __neg__(self) -> "LinExpr":
182        return self.__mul__(-1)
183
184    def __str__(self) -> str:
185        result = []
186
187        if hasattr(self, "__sense"):
188            if self.__sense == mip.MINIMIZE:
189                result.append("Minimize ")
190            elif self.__sense == mip.MAXIMIZE:
191                result.append("Minimize ")
192
193        if self.__expr:
194            for var, coeff in self.__expr.items():
195                result.append("+ " if coeff >= 0 else "- ")
196                result.append(str(abs(coeff)) if abs(coeff) != 1 else "")
197                result.append("{var} ".format(**locals()))
198
199        if self.__sense:
200            if self.__sense == mip.EQUAL:
201                result.append(" = ")
202            if self.__sense == mip.LESS_OR_EQUAL:
203                result.append(" <= ")
204            if self.__sense == mip.GREATER_OR_EQUAL:
205                result.append(" >= ")
206            result.append(
207                str(abs(self.__const))
208                if self.__const < 0
209                else "- " + str(abs(self.__const))
210            )
211        elif self.__const != 0:
212            result.append(
213                "+ " + str(abs(self.__const))
214                if self.__const > 0
215                else "- " + str(abs(self.__const))
216            )
217
218        return "".join(result)
219
220    def __eq__(self, other) -> "LinExpr":
221        result = self - other
222        result.__sense = "="
223        return result
224
225    def __le__(
226        self,
227        other: Union["mip.Var", "LinExpr", numbers.Real],
228    ) -> "mip.LinExpr":
229        result = self - other
230        result.__sense = "<"
231        return result
232
233    def __ge__(
234        self,
235        other: Union["mip.Var", "LinExpr", numbers.Real],
236    ) -> "mip.LinExpr":
237        result = self - other
238        result.__sense = ">"
239        return result
240
241    def __len__(self):
242        return len(self.__expr)
243
244    def add_const(self, val: numbers.Real):
245        """adds a constant value to the linear expression, in the case of
246        a constraint this correspond to the right-hand-side
247
248        Args:
249            val(numbers.Real): a real number
250        """
251        self.__const += val
252
253    def add_expr(self, expr: "LinExpr", coeff: numbers.Real = 1):
254        """Extends a linear expression with the contents of another.
255
256        Args:
257            expr (LinExpr): another linear expression
258            coeff (numbers.Real): coefficient which will multiply the linear
259                expression added
260        """
261        self.__const += expr.const * coeff
262        for var, coeff_var in expr.expr.items():
263            self.add_var(var, coeff_var * coeff)
264
265    def add_term(
266        self,
267        term: Union["mip.Var", "mip.LinExpr", numbers.Real],
268        coeff: numbers.Real = 1,
269    ):
270        """Adds a term to the linear expression.
271
272        Args:
273            expr (Union[mip.Var, LinExpr, numbers.Real]) : can be a
274                variable, another linear expression or a real number.
275
276            coeff (numbers.Real) : coefficient which will multiply the added
277                term
278
279        """
280        if isinstance(term, Var):
281            self.add_var(term, coeff)
282        elif isinstance(term, LinExpr):
283            self.add_expr(term, coeff)
284        elif isinstance(term, numbers.Real):
285            self.add_const(term)
286        else:
287            raise TypeError("type {} not supported".format(type(term)))
288
289    def add_var(self, var: "mip.Var", coeff: numbers.Real = 1):
290        """Adds a variable with a coefficient to the linear expression.
291
292        Args:
293            var (mip.Var) : a variable
294            coeff (numbers.Real) : coefficient which the variable will be added
295        """
296        if var in self.__expr:
297            if -mip.EPS <= self.__expr[var] + coeff <= mip.EPS:
298                del self.__expr[var]
299            else:
300                self.__expr[var] += coeff
301        else:
302            self.__expr[var] = coeff
303
304    def set_expr(self: "LinExpr", expr: Dict["mip.Var", numbers.Real]):
305        """Sets terms of the linear expression
306
307        Args:
308            expr(Dict[mip.Var, numbers.Real]) : dictionary mapping variables to
309                their coefficients in the linear expression.
310        """
311
312        self.__expr = expr
313
314    def copy(self) -> "mip.LinExpr":
315        copy = LinExpr()
316        copy.__const = self.__const
317        copy.__expr = self.__expr.copy()
318        copy.__sense = self.__sense
319        return copy
320
321    def equals(self, other: "mip.LinExpr") -> bool:
322        """returns true if a linear expression equals to another,
323        false otherwise"""
324        if self.__sense != other.__sense:
325            return False
326        if len(self.__expr) != len(other.__expr):
327            return False
328        if abs(self.__const - other.__const) >= 1e-12:
329            return False
330        other_contents = {vr.idx: coef for vr, coef in other.__expr.items()}
331        for (v, c) in self.__expr.items():
332            if v.idx not in other_contents:
333                return False
334            oc = other_contents[v.idx]
335            if abs(c - oc) > 1e-12:
336                return False
337        return True
338
339    def __hash__(self):
340        hash_el = [v.idx for v in self.__expr.keys()]
341        for c in self.__expr.values():
342            hash_el.append(c)
343        hash_el.append(self.__const)
344        hash_el.append(self.__sense)
345        return hash(tuple(hash_el))
346
347    @property
348    def const(self) -> numbers.Real:
349        """constant part of the linear expression"""
350        return self.__const
351
352    @property
353    def expr(self) -> Dict["mip.Var", numbers.Real]:
354        """the non-constant part of the linear expression
355
356        Dictionary with pairs: (variable, coefficient) where coefficient
357        is a real number.
358
359        :rtype: Dict[mip.Var, numbers.Real]
360        """
361        return self.__expr
362
363    @property
364    def sense(self) -> str:
365        """sense of the linear expression
366
367        sense can be EQUAL("="), LESS_OR_EQUAL("<"), GREATER_OR_EQUAL(">") or
368        empty ("") if this is an affine expression, such as the objective
369        function
370        """
371        return self.__sense
372
373    @sense.setter
374    def sense(self, value):
375        """sense of the linear expression
376
377        sense can be EQUAL("="), LESS_OR_EQUAL("<"), GREATER_OR_EQUAL(">") or
378        empty ("") if this is an affine expression, such as the objective
379        function
380        """
381        self.__sense = value
382
383    @property
384    def violation(self) -> Optional[numbers.Real]:
385        """Amount that current solution violates this constraint
386
387        If a solution is available, than this property indicates how much
388        the current solution violates this constraint.
389        """
390        lhs = sum(coef * var.x for (var, coef) in self.__expr.items())
391        rhs = -self.const
392        viol = None
393        if self.sense == "=":
394            viol = abs(lhs - rhs)
395        elif self.sense == "<":
396            viol = max(lhs - rhs, 0.0)
397        elif self.sense == ">":
398            viol = max(rhs - lhs, 0.0)
399        else:
400            raise ValueError("Invalid sense {}".format(self.sense))
401
402        return viol
403
404    @property
405    def x(self) -> Optional[numbers.Real]:
406        """Value of this linear expression in the solution. None
407        is returned if no solution is available."""
408        x = self.__const
409        for var, coef in self.__expr.items():
410            var_x = var.x
411            if var_x is None:
412                return None
413            x += var_x * coef
414        return x
415
416    def __float__(self):
417        x = self.x
418        return math.nan if x is None else float(x)
419
420    @property
421    def model(self) -> Optional["mip.Model"]:
422        """Model which this LinExpr refers to, None if no variables are
423        involved.
424
425        :rtype: Optional[mip.Model]
426        """
427        if not self.expr:
428            return None
429
430        return next(iter(self.expr)).model
431
432
433class Constr:
434    """A row (constraint) in the constraint matrix.
435
436    A constraint is a specific :class:`~LinExpr` that includes a
437    sense (<, > or == or less-or-equal, greater-or-equal and equal,
438    respectively) and a right-hand-side constant value. Constraints can be
439    added to the model using the overloaded operator :code:`+=` or using
440    the method :meth:`~mip.Model.add_constr` of the
441    :class:`~mip.Model` class:
442
443    .. code:: python
444
445      m += 3*x1 + 4*x2 <= 5
446
447    summation expressions are also supported:
448
449    .. code:: python
450
451      m += xsum(x[i] for i in range(n)) == 1
452    """
453
454    __slots__ = ["__model", "idx", "__priority"]
455
456    def __init__(
457        self,
458        model: "mip.Model",
459        idx: int,
460        priority: "mip.constants.ConstraintPriority" = None,
461    ):
462        self.__model = model
463        self.idx = idx
464        self.__priority = priority
465
466    def __hash__(self) -> int:
467        return self.idx
468
469    def __str__(self) -> str:
470        if self.name:
471            res = self.name + ":"
472        else:
473            res = "constr({}): ".format(self.idx + 1)
474        line = ""
475        len_line = 0
476        for (var, val) in self.expr.expr.items():
477            astr = " {:+} {}".format(val, var.name)
478            len_line += len(astr)
479            line += astr
480
481            if len_line > 75:
482                line += "\n\t"
483                len_line = 0
484        res += line
485        rhs = self.expr.const * -1.0
486        if self.expr.sense == "=":
487            res += " = {}".format(rhs)
488        elif self.expr.sense == "<":
489            res += " <= {}".format(rhs)
490        elif self.expr.sense == ">":
491            res += " >= {}".format(rhs)
492        else:
493            raise ValueError("Invalid sense {}".format(self.expr.sense))
494
495        return res
496
497    @property
498    def rhs(self) -> numbers.Real:
499        """The right-hand-side (constant value) of the linear constraint."""
500        return self.__model.solver.constr_get_rhs(self.idx)
501
502    @rhs.setter
503    def rhs(self, rhs: numbers.Real):
504        self.__model.solver.constr_set_rhs(self.idx, rhs)
505
506    @property
507    def slack(self) -> Optional[numbers.Real]:
508        """Value of the slack in this constraint in the optimal
509        solution. Available only if the formulation was solved.
510        """
511        return self.__model.solver.constr_get_slack(self)
512
513    @property
514    def pi(self) -> Optional[numbers.Real]:
515        """Value for the dual variable of this constraint in the optimal
516        solution of a linear programming :class:`~mip.Model`. Only
517        available if a pure linear programming problem was solved (only
518        continuous variables).
519        """
520        return self.__model.solver.constr_get_pi(self)
521
522    @property
523    def expr(self) -> LinExpr:
524        """Linear expression that defines the constraint.
525
526        :rtype: mip.LinExpr"""
527        return self.__model.solver.constr_get_expr(self)
528
529    @expr.setter
530    def expr(self, value: LinExpr):
531        self.__model.solver.constr_set_expr(self, value)
532
533    @property
534    def name(self) -> str:
535        """constraint name"""
536        return self.__model.solver.constr_get_name(self.idx)
537
538    @property
539    def priority(self) -> mip.constants.ConstraintPriority:
540        """priority value"""
541        return self.__priority
542
543    @priority.setter
544    def priority(self, priority: mip.constants.ConstraintPriority):
545        self.__priority = priority
546
547
548class Var:
549    """Decision variable of the :class:`~mip.Model`. The creation of
550    variables is performed calling the :meth:`~mip.Model.add_var`."""
551
552    __slots__ = ["__model", "idx"]
553
554    def __init__(self, model: "mip.Model", idx: int):
555        self.__model = model
556        self.idx = idx
557
558    def __hash__(self) -> int:
559        return self.idx
560
561    def __add__(
562        self, other: Union["mip.Var", LinExpr, numbers.Real]
563    ) -> Union["mip.Var", LinExpr]:
564        if isinstance(other, Var):
565            return LinExpr([self, other], [1, 1])
566        if isinstance(other, LinExpr):
567            return other.__add__(self)
568        if isinstance(other, numbers.Real):
569            if fabs(other) < mip.EPS:
570                return self
571            return LinExpr([self], [1], other)
572
573        raise TypeError("type {} not supported".format(type(other)))
574
575    def __radd__(
576        self, other: Union["mip.Var", LinExpr, numbers.Real]
577    ) -> Union["mip.Var", LinExpr]:
578        return self.__add__(other)
579
580    def __sub__(
581        self, other: Union["mip.Var", LinExpr, numbers.Real]
582    ) -> Union["mip.Var", LinExpr]:
583        if isinstance(other, Var):
584            return LinExpr([self, other], [1, -1])
585        elif isinstance(other, LinExpr):
586            return (-other).__add__(self)
587        elif isinstance(other, numbers.Real):
588            if fabs(other) < mip.EPS:
589                return self
590            return LinExpr([self], [1], -other)
591        else:
592            raise TypeError("type {} not supported".format(type(other)))
593
594    def __rsub__(
595        self, other: Union["mip.Var", LinExpr, numbers.Real]
596    ) -> Union["mip.Var", LinExpr]:
597        if isinstance(other, Var):
598            return LinExpr([self, other], [-1, 1])
599        elif isinstance(other, LinExpr):
600            return other.__sub__(self)
601        elif isinstance(other, numbers.Real):
602            return LinExpr([self], [-1], other)
603        else:
604            raise TypeError("type {} not supported".format(type(other)))
605
606    def __mul__(self, other: numbers.Real) -> Union["mip.Var", numbers.Real, LinExpr]:
607        if not isinstance(other, numbers.Real):
608            raise TypeError("Can not multiply with type {}".format(type(other)))
609        if fabs(other) < mip.EPS:
610            return other
611        if fabs(other - 1) < mip.EPS:
612            return self
613        return LinExpr([self], [other])
614
615    def __rmul__(self, other: numbers.Real) -> Union["mip.Var", numbers.Real, LinExpr]:
616        return self.__mul__(other)
617
618    def __truediv__(
619        self, other: numbers.Real
620    ) -> Union["mip.Var", numbers.Real, LinExpr]:
621        if not isinstance(other, numbers.Real):
622            raise TypeError("Can not divide with type {}".format(type(other)))
623        return self.__mul__(1.0 / other)
624
625    def __neg__(self) -> LinExpr:
626        return LinExpr([self], [-1.0])
627
628    def __eq__(self, other) -> LinExpr:
629        if isinstance(other, Var):
630            return LinExpr([self, other], [1, -1], sense="=")
631        elif isinstance(other, LinExpr):
632            return other == self
633        elif isinstance(other, numbers.Real):
634            if other != 0:
635                return LinExpr([self], [1], -1 * other, sense="=")
636            return LinExpr([self], [1], sense="=")
637        else:
638            raise TypeError("type {} not supported".format(type(other)))
639
640    def __le__(self, other: Union["mip.Var", LinExpr, numbers.Real]) -> LinExpr:
641        if isinstance(other, Var):
642            return LinExpr([self, other], [1, -1], sense="<")
643        elif isinstance(other, LinExpr):
644            return other >= self
645        elif isinstance(other, numbers.Real):
646            if other != 0:
647                return LinExpr([self], [1], -1 * other, sense="<")
648            return LinExpr([self], [1], sense="<")
649        else:
650            raise TypeError("type {} not supported".format(type(other)))
651
652    def __ge__(self, other: Union["mip.Var", LinExpr, numbers.Real]) -> LinExpr:
653        if isinstance(other, Var):
654            return LinExpr([self, other], [1, -1], sense=">")
655        elif isinstance(other, LinExpr):
656            return other <= self
657        elif isinstance(other, numbers.Real):
658            if other != 0:
659                return LinExpr([self], [1], -1 * other, sense=">")
660            return LinExpr([self], [1], sense=">")
661        else:
662            raise TypeError("type {} not supported".format(type(other)))
663
664    @property
665    def name(self) -> str:
666        """Variable name."""
667        return self.__model.solver.var_get_name(self.idx)
668
669    def __str__(self) -> str:
670        return self.name
671
672    @property
673    def lb(self) -> numbers.Real:
674        """Variable lower bound."""
675        return self.__model.solver.var_get_lb(self)
676
677    @lb.setter
678    def lb(self, value: numbers.Real):
679        self.__model.solver.var_set_lb(self, value)
680
681    @property
682    def ub(self) -> numbers.Real:
683        """Variable upper bound."""
684        return self.__model.solver.var_get_ub(self)
685
686    @ub.setter
687    def ub(self, value: numbers.Real):
688        self.__model.solver.var_set_ub(self, value)
689
690    @property
691    def obj(self) -> numbers.Real:
692        """Coefficient of variable in the objective function."""
693        return self.__model.solver.var_get_obj(self)
694
695    @obj.setter
696    def obj(self, value: numbers.Real):
697        self.__model.solver.var_set_obj(self, value)
698
699    @property
700    def var_type(self) -> str:
701        """Variable type, ('B') BINARY, ('C') CONTINUOUS and ('I') INTEGER."""
702        return self.__model.solver.var_get_var_type(self)
703
704    @var_type.setter
705    def var_type(self, value: str):
706        if value not in (mip.BINARY, mip.CONTINUOUS, mip.INTEGER):
707            raise ValueError(
708                "Expected one of {}, but got {}".format(
709                    (mip.BINARY, mip.CONTINUOUS, mip.INTEGER), value
710                )
711            )
712        self.__model.solver.var_set_var_type(self, value)
713
714    @property
715    def column(self) -> Column:
716        """Variable coefficients in constraints.
717
718        :rtype: mip.Column
719        """
720        return self.__model.solver.var_get_column(self)
721
722    @column.setter
723    def column(self, value: Column):
724        self.__model.solver.var_set_column(self, value)
725
726    @property
727    def rc(self) -> Optional[numbers.Real]:
728        """Reduced cost, only available after a linear programming model (only
729        continuous variables) is optimized. Note that None is returned if no
730        optimum solution is available"""
731
732        return self.__model.solver.var_get_rc(self)
733
734    @property
735    def x(self) -> Optional[numbers.Real]:
736        """Value of this variable in the solution. Note that None is returned
737        if no solution is not available."""
738        return self.__model.solver.var_get_x(self)
739
740    def xi(self, i: int) -> Optional[numbers.Real]:
741        """Value for this variable in the :math:`i`-th solution from the solution
742        pool. Note that None is returned if the solution is not available."""
743        if self.__model.status in [
744            mip.OptimizationStatus.OPTIMAL,
745            mip.OptimizationStatus.FEASIBLE,
746        ]:
747            return self.__model.solver.var_get_xi(self, i)
748        return None
749
750    def __float__(self):
751        x = self.x
752        return math.nan if x is None else float(x)
753
754    @property
755    def model(self) -> "mip.Model":
756        """Model which this variable refers to.
757
758        :rtype: mip.Model
759        """
760        return self.__model
761
762
763class ConflictGraph:
764
765    """A conflict graph stores conflicts between incompatible assignments in
766    binary variables.
767
768    For example, if there is a constraint :math:`x_1 + x_2 \leq 1` then
769    there is a conflict between :math:`x_1 = 1` and :math:`x_2 = 1`. We can state
770    that :math:`x_1` and :math:`x_2` are conflicting. Conflicts can also involve the complement
771    of a binary variable. For example, if there is a constraint :math:`x_1 \leq
772    x_2` then there is a conflict between :math:`x_1 = 1` and :math:`x_2 = 0`.
773    We now can state that :math:`x_1` and :math:`\lnot x_2` are conflicting."""
774
775    __slots__ = ["model"]
776
777    def __init__(self, model: "mip.Model"):
778        self.model = model
779        self.model.solver.update_conflict_graph()
780
781    @property
782    def density(self) -> float:
783        return self.model.solver.cgraph_density()
784
785    def conflicting(
786        self,
787        e1: Union["mip.LinExpr", "mip.Var"],
788        e2: Union["mip.LinExpr", "mip.Var"],
789    ) -> bool:
790        """Checks if two assignments of binary variables are in conflict.
791
792        Args:
793            e1 (Union[mip.LinExpr, mip.Var]): binary variable, if assignment to be
794                tested is the assignment to one, or a linear expression like x == 0
795                to indicate that conflict with the complement of the variable
796                should be tested.
797
798            e2 (Union[mip.LinExpr, mip.Var]): binary variable, if assignment to be
799                tested is the assignment to one, or a linear expression like x == 0
800                to indicate that conflict with the complement of the variable
801                should be tested.
802        """
803        if not isinstance(e1, (mip.LinExpr, mip.Var)):
804            raise TypeError("type {} not supported".format(type(e1)))
805        if not isinstance(e2, (mip.LinExpr, mip.Var)):
806            raise TypeError("type {} not supported".format(type(e2)))
807
808        return e1.model.solver.conflicting(e1, e2)
809
810    def conflicting_assignments(
811        self, v: Union["mip.LinExpr", "mip.Var"]
812    ) -> Tuple[List["mip.Var"], List["mip.Var"]]:
813        """Returns from the conflict graph all assignments conflicting with one
814        specific assignment.
815
816        Args:
817            v (Union[mip.Var, mip.LinExpr]): binary variable, if assignment to be
818                tested is the assignment to one or a linear expression like x == 0
819                to indicate the complement.
820
821        :rtype: Tuple[List[mip.Var], List[mip.Var]]
822
823        Returns:
824            Returns a tuple with two lists. The first one indicates variables
825            whose conflict occurs when setting them to one. The second list
826            includes variable whose conflict occurs when setting them to zero.
827        """
828        if not isinstance(v, (mip.LinExpr, mip.Var)):
829            raise TypeError("type {} not supported".format(type(v)))
830
831        return self.model.solver.conflicting_nodes(v)
832