PySCIPOpt 5.7.1__cp310-cp310-win_amd64.whl → 6.1.0__cp310-cp310-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pyscipopt/scip.pxi CHANGED
@@ -31,6 +31,7 @@ include "conshdlr.pxi"
31
31
  include "cutsel.pxi"
32
32
  include "event.pxi"
33
33
  include "heuristic.pxi"
34
+ include "iisfinder.pxi"
34
35
  include "presol.pxi"
35
36
  include "pricer.pxi"
36
37
  include "propagator.pxi"
@@ -41,9 +42,9 @@ include "nodesel.pxi"
41
42
  include "matrix.pxi"
42
43
 
43
44
  # recommended SCIP version; major version is required
44
- MAJOR = 9
45
- MINOR = 2
46
- PATCH = 4
45
+ MAJOR = 10
46
+ MINOR = 0
47
+ PATCH = 0
47
48
 
48
49
  # for external user functions use def; for functions used only inside the interface (starting with _) use cdef
49
50
  # todo: check whether this is currently done like this
@@ -207,65 +208,72 @@ cdef class PY_SCIP_HEURTIMING:
207
208
 
208
209
  EventNames = {}
209
210
  cdef class PY_SCIP_EVENTTYPE:
210
- DISABLED = SCIP_EVENTTYPE_DISABLED
211
- VARADDED = SCIP_EVENTTYPE_VARADDED
212
- VARDELETED = SCIP_EVENTTYPE_VARDELETED
213
- VARFIXED = SCIP_EVENTTYPE_VARFIXED
214
- VARUNLOCKED = SCIP_EVENTTYPE_VARUNLOCKED
215
- OBJCHANGED = SCIP_EVENTTYPE_OBJCHANGED
216
- GLBCHANGED = SCIP_EVENTTYPE_GLBCHANGED
217
- GUBCHANGED = SCIP_EVENTTYPE_GUBCHANGED
218
- LBTIGHTENED = SCIP_EVENTTYPE_LBTIGHTENED
219
- LBRELAXED = SCIP_EVENTTYPE_LBRELAXED
220
- UBTIGHTENED = SCIP_EVENTTYPE_UBTIGHTENED
221
- UBRELAXED = SCIP_EVENTTYPE_UBRELAXED
222
- GHOLEADDED = SCIP_EVENTTYPE_GHOLEADDED
223
- GHOLEREMOVED = SCIP_EVENTTYPE_GHOLEREMOVED
224
- LHOLEADDED = SCIP_EVENTTYPE_LHOLEADDED
225
- LHOLEREMOVED = SCIP_EVENTTYPE_LHOLEREMOVED
226
- IMPLADDED = SCIP_EVENTTYPE_IMPLADDED
227
- PRESOLVEROUND = SCIP_EVENTTYPE_PRESOLVEROUND
228
- NODEFOCUSED = SCIP_EVENTTYPE_NODEFOCUSED
229
- NODEFEASIBLE = SCIP_EVENTTYPE_NODEFEASIBLE
230
- NODEINFEASIBLE = SCIP_EVENTTYPE_NODEINFEASIBLE
231
- NODEBRANCHED = SCIP_EVENTTYPE_NODEBRANCHED
232
- NODEDELETE = SCIP_EVENTTYPE_NODEDELETE
233
- FIRSTLPSOLVED = SCIP_EVENTTYPE_FIRSTLPSOLVED
234
- LPSOLVED = SCIP_EVENTTYPE_LPSOLVED
235
- LPEVENT = SCIP_EVENTTYPE_LPEVENT
236
- POORSOLFOUND = SCIP_EVENTTYPE_POORSOLFOUND
237
- BESTSOLFOUND = SCIP_EVENTTYPE_BESTSOLFOUND
238
- ROWADDEDSEPA = SCIP_EVENTTYPE_ROWADDEDSEPA
239
- ROWDELETEDSEPA = SCIP_EVENTTYPE_ROWDELETEDSEPA
240
- ROWADDEDLP = SCIP_EVENTTYPE_ROWADDEDLP
241
- ROWDELETEDLP = SCIP_EVENTTYPE_ROWDELETEDLP
242
- ROWCOEFCHANGED = SCIP_EVENTTYPE_ROWCOEFCHANGED
243
- ROWCONSTCHANGED = SCIP_EVENTTYPE_ROWCONSTCHANGED
244
- ROWSIDECHANGED = SCIP_EVENTTYPE_ROWSIDECHANGED
245
- SYNC = SCIP_EVENTTYPE_SYNC
246
- GBDCHANGED = SCIP_EVENTTYPE_GBDCHANGED
247
- LBCHANGED = SCIP_EVENTTYPE_LBCHANGED
248
- UBCHANGED = SCIP_EVENTTYPE_UBCHANGED
249
- BOUNDTIGHTENED = SCIP_EVENTTYPE_BOUNDTIGHTENED
250
- BOUNDRELAXED = SCIP_EVENTTYPE_BOUNDRELAXED
251
- BOUNDCHANGED = SCIP_EVENTTYPE_BOUNDCHANGED
252
- GHOLECHANGED = SCIP_EVENTTYPE_GHOLECHANGED
253
- LHOLECHANGED = SCIP_EVENTTYPE_LHOLECHANGED
254
- HOLECHANGED = SCIP_EVENTTYPE_HOLECHANGED
255
- DOMCHANGED = SCIP_EVENTTYPE_DOMCHANGED
256
- VARCHANGED = SCIP_EVENTTYPE_VARCHANGED
257
- VAREVENT = SCIP_EVENTTYPE_VAREVENT
258
- NODESOLVED = SCIP_EVENTTYPE_NODESOLVED
259
- NODEEVENT = SCIP_EVENTTYPE_NODEEVENT
260
- SOLFOUND = SCIP_EVENTTYPE_SOLFOUND
261
- SOLEVENT = SCIP_EVENTTYPE_SOLEVENT
262
- ROWCHANGED = SCIP_EVENTTYPE_ROWCHANGED
263
- ROWEVENT = SCIP_EVENTTYPE_ROWEVENT
211
+ DISABLED = SCIP_EVENTTYPE_DISABLED
212
+ VARADDED = SCIP_EVENTTYPE_VARADDED
213
+ VARDELETED = SCIP_EVENTTYPE_VARDELETED
214
+ VARFIXED = SCIP_EVENTTYPE_VARFIXED
215
+ VARUNLOCKED = SCIP_EVENTTYPE_VARUNLOCKED
216
+ OBJCHANGED = SCIP_EVENTTYPE_OBJCHANGED
217
+ GLBCHANGED = SCIP_EVENTTYPE_GLBCHANGED
218
+ GUBCHANGED = SCIP_EVENTTYPE_GUBCHANGED
219
+ LBTIGHTENED = SCIP_EVENTTYPE_LBTIGHTENED
220
+ LBRELAXED = SCIP_EVENTTYPE_LBRELAXED
221
+ UBTIGHTENED = SCIP_EVENTTYPE_UBTIGHTENED
222
+ UBRELAXED = SCIP_EVENTTYPE_UBRELAXED
223
+ GHOLEADDED = SCIP_EVENTTYPE_GHOLEADDED
224
+ GHOLEREMOVED = SCIP_EVENTTYPE_GHOLEREMOVED
225
+ LHOLEADDED = SCIP_EVENTTYPE_LHOLEADDED
226
+ LHOLEREMOVED = SCIP_EVENTTYPE_LHOLEREMOVED
227
+ IMPLADDED = SCIP_EVENTTYPE_IMPLADDED
228
+ PRESOLVEROUND = SCIP_EVENTTYPE_PRESOLVEROUND
229
+ NODEFOCUSED = SCIP_EVENTTYPE_NODEFOCUSED
230
+ NODEFEASIBLE = SCIP_EVENTTYPE_NODEFEASIBLE
231
+ NODEINFEASIBLE = SCIP_EVENTTYPE_NODEINFEASIBLE
232
+ NODEBRANCHED = SCIP_EVENTTYPE_NODEBRANCHED
233
+ NODEDELETE = SCIP_EVENTTYPE_NODEDELETE
234
+ DUALBOUNDIMPROVED = SCIP_EVENTTYPE_DUALBOUNDIMPROVED
235
+ FIRSTLPSOLVED = SCIP_EVENTTYPE_FIRSTLPSOLVED
236
+ LPSOLVED = SCIP_EVENTTYPE_LPSOLVED
237
+ LPEVENT = SCIP_EVENTTYPE_LPEVENT
238
+ POORSOLFOUND = SCIP_EVENTTYPE_POORSOLFOUND
239
+ BESTSOLFOUND = SCIP_EVENTTYPE_BESTSOLFOUND
240
+ ROWADDEDSEPA = SCIP_EVENTTYPE_ROWADDEDSEPA
241
+ ROWDELETEDSEPA = SCIP_EVENTTYPE_ROWDELETEDSEPA
242
+ ROWADDEDLP = SCIP_EVENTTYPE_ROWADDEDLP
243
+ ROWDELETEDLP = SCIP_EVENTTYPE_ROWDELETEDLP
244
+ ROWCOEFCHANGED = SCIP_EVENTTYPE_ROWCOEFCHANGED
245
+ ROWCONSTCHANGED = SCIP_EVENTTYPE_ROWCONSTCHANGED
246
+ ROWSIDECHANGED = SCIP_EVENTTYPE_ROWSIDECHANGED
247
+ SYNC = SCIP_EVENTTYPE_SYNC
248
+ GBDCHANGED = SCIP_EVENTTYPE_GBDCHANGED
249
+ LBCHANGED = SCIP_EVENTTYPE_LBCHANGED
250
+ UBCHANGED = SCIP_EVENTTYPE_UBCHANGED
251
+ BOUNDTIGHTENED = SCIP_EVENTTYPE_BOUNDTIGHTENED
252
+ BOUNDRELAXED = SCIP_EVENTTYPE_BOUNDRELAXED
253
+ BOUNDCHANGED = SCIP_EVENTTYPE_BOUNDCHANGED
254
+ GHOLECHANGED = SCIP_EVENTTYPE_GHOLECHANGED
255
+ LHOLECHANGED = SCIP_EVENTTYPE_LHOLECHANGED
256
+ HOLECHANGED = SCIP_EVENTTYPE_HOLECHANGED
257
+ DOMCHANGED = SCIP_EVENTTYPE_DOMCHANGED
258
+ VARCHANGED = SCIP_EVENTTYPE_VARCHANGED
259
+ VAREVENT = SCIP_EVENTTYPE_VAREVENT
260
+ NODESOLVED = SCIP_EVENTTYPE_NODESOLVED
261
+ NODEEVENT = SCIP_EVENTTYPE_NODEEVENT
262
+ SOLFOUND = SCIP_EVENTTYPE_SOLFOUND
263
+ SOLEVENT = SCIP_EVENTTYPE_SOLEVENT
264
+ GAPUPDATED = SCIP_EVENTTYPE_GAPUPDATED
265
+ ROWCHANGED = SCIP_EVENTTYPE_ROWCHANGED
266
+ ROWEVENT = SCIP_EVENTTYPE_ROWEVENT
264
267
 
265
268
  cdef class PY_SCIP_LOCKTYPE:
266
269
  MODEL = SCIP_LOCKTYPE_MODEL
267
270
  CONFLICT = SCIP_LOCKTYPE_CONFLICT
268
271
 
272
+ cdef class PY_SCIP_IMPLINTTYPE:
273
+ NONE = SCIP_IMPLINTTYPE_NONE
274
+ WEAK = SCIP_IMPLINTTYPE_WEAK
275
+ STRONG = SCIP_IMPLINTTYPE_STRONG
276
+
269
277
  cdef class PY_SCIP_LPSOLSTAT:
270
278
  NOTSOLVED = SCIP_LPSOLSTAT_NOTSOLVED
271
279
  OPTIMAL = SCIP_LPSOLSTAT_OPTIMAL
@@ -626,6 +634,31 @@ cdef class Column:
626
634
  return (self.__class__ == other.__class__
627
635
  and self.scip_col == (<Column>other).scip_col)
628
636
 
637
+ cdef class ColumnExact:
638
+ """Base class holding a pointer to corresponding SCIP_COLEXACT."""
639
+
640
+ @staticmethod
641
+ cdef create(SCIP_COLEXACT* scipcolexact):
642
+ """
643
+ Main method for creating a ColumnExact class. Is used instead of __init__.
644
+
645
+ Parameters
646
+ ----------
647
+ scipcolexact : SCIP_COLEXACT*
648
+ A pointer to the SCIP_COLEXACT
649
+
650
+ Returns
651
+ -------
652
+ col : ColumnExact
653
+ The Python representative of the SCIP_COLEXACT
654
+
655
+ """
656
+ if scipcolexact == NULL:
657
+ raise Warning("cannot create ColumnExact with SCIP_COLEXACT* == NULL")
658
+ col = ColumnExact()
659
+ col.scip_col_exact = scipcolexact
660
+ return col
661
+
629
662
  cdef class Row:
630
663
  """Base class holding a pointer to corresponding SCIP_ROW."""
631
664
 
@@ -910,6 +943,31 @@ cdef class Row:
910
943
  return (self.__class__ == other.__class__
911
944
  and self.scip_row == (<Row>other).scip_row)
912
945
 
946
+ cdef class RowExact:
947
+ """Base class holding a pointer to corresponding SCIP_ROW."""
948
+
949
+ @staticmethod
950
+ cdef create(SCIP_ROWEXACT* sciprowexact):
951
+ """
952
+ Main method for creating a RowExact class. Is used instead of __init__.
953
+
954
+ Parameters
955
+ ----------
956
+ sciprow : SCIP_ROWEXACT*
957
+ A pointer to the SCIP_ROWEXACT
958
+
959
+ Returns
960
+ -------
961
+ row : Row
962
+ The Python representative of the SCIP_ROWEXACT
963
+
964
+ """
965
+ if sciprowexact == NULL:
966
+ raise Warning("cannot create Row with SCIP_ROWEXACT* == NULL")
967
+ row_exact = RowExact()
968
+ row_exact.scip_row_exact = sciprowexact
969
+ return row_exact
970
+
913
971
  cdef class NLRow:
914
972
  """Base class holding a pointer to corresponding SCIP_NLROW."""
915
973
 
@@ -1010,8 +1068,8 @@ cdef class Solution:
1010
1068
  """Base class holding a pointer to corresponding SCIP_SOL."""
1011
1069
 
1012
1070
  # We are raising an error here to avoid creating a solution without an associated model. See Issue #625
1013
- def __init__(self, raise_error = False):
1014
- if not raise_error:
1071
+ def __init__(self, raise_error = True):
1072
+ if raise_error:
1015
1073
  raise ValueError("To create a solution you should use the createSol method of the Model class.")
1016
1074
 
1017
1075
  @staticmethod
@@ -1035,35 +1093,23 @@ cdef class Solution:
1035
1093
  """
1036
1094
  if scip == NULL:
1037
1095
  raise Warning("cannot create Solution with SCIP* == NULL")
1038
- sol = Solution(True)
1096
+ sol = Solution(raise_error=False)
1039
1097
  sol.sol = scip_sol
1040
1098
  sol.scip = scip
1041
1099
  return sol
1042
1100
 
1043
- def __getitem__(self, expr: Union[Expr, MatrixExpr]):
1044
- if isinstance(expr, MatrixExpr):
1045
- result = np.zeros(expr.shape, dtype=np.float64)
1046
- for idx in np.ndindex(expr.shape):
1047
- result[idx] = self.__getitem__(expr[idx])
1048
- return result
1049
-
1050
- # fast track for Variable
1051
- cdef SCIP_Real coeff
1052
- cdef _VarArray wrapper
1053
- if isinstance(expr, Variable):
1054
- wrapper = _VarArray(expr)
1055
- self._checkStage("SCIPgetSolVal")
1056
- return SCIPgetSolVal(self.scip, self.sol, wrapper.ptr[0])
1057
- return sum(self._evaluate(term)*coeff for term, coeff in expr.terms.items() if coeff != 0)
1101
+ def __getitem__(
1102
+ self,
1103
+ expr: Union[Expr, GenExpr, MatrixExpr],
1104
+ ) -> Union[float, np.ndarray]:
1105
+ if not isinstance(expr, (Expr, GenExpr, MatrixExpr)):
1106
+ raise TypeError(
1107
+ "Argument 'expr' has incorrect type, expected 'Expr', 'GenExpr', or "
1108
+ f"'MatrixExpr', got {type(expr).__name__!r}"
1109
+ )
1058
1110
 
1059
- def _evaluate(self, term):
1060
1111
  self._checkStage("SCIPgetSolVal")
1061
- result = 1
1062
- cdef _VarArray wrapper
1063
- wrapper = _VarArray(term.vartuple)
1064
- for i in range(len(term.vartuple)):
1065
- result *= SCIPgetSolVal(self.scip, self.sol, wrapper.ptr[i])
1066
- return result
1112
+ return expr._evaluate(self)
1067
1113
 
1068
1114
  def __setitem__(self, Variable var, value):
1069
1115
  PY_SCIP_CALL(SCIPsetSolVal(self.scip, self.sol, var.scip_var, value))
@@ -1507,6 +1553,8 @@ cdef class Variable(Expr):
1507
1553
 
1508
1554
  property name:
1509
1555
  def __get__(self):
1556
+ if self.scip_var == NULL:
1557
+ return ""
1510
1558
  cname = bytes( SCIPvarGetName(self.scip_var) )
1511
1559
  return cname.decode('utf-8')
1512
1560
 
@@ -1519,7 +1567,7 @@ cdef class Variable(Expr):
1519
1567
 
1520
1568
  def vtype(self):
1521
1569
  """
1522
- Retrieve the variables type (BINARY, INTEGER, IMPLINT or CONTINUOUS)
1570
+ Retrieve the variables type (BINARY, INTEGER, CONTINUOUS, or IMPLINT)
1523
1571
 
1524
1572
  Returns
1525
1573
  -------
@@ -1534,8 +1582,58 @@ cdef class Variable(Expr):
1534
1582
  return "INTEGER"
1535
1583
  elif vartype == SCIP_VARTYPE_CONTINUOUS:
1536
1584
  return "CONTINUOUS"
1537
- elif vartype == SCIP_VARTYPE_IMPLINT:
1585
+ elif vartype == SCIP_DEPRECATED_VARTYPE_IMPLINT:
1538
1586
  return "IMPLINT"
1587
+
1588
+ def isBinary(self):
1589
+ """
1590
+ Returns whether variable is of BINARY type.
1591
+
1592
+ Returns
1593
+ -------
1594
+ bool
1595
+ """
1596
+ return SCIPvarIsBinary(self.scip_var)
1597
+
1598
+ def isIntegral(self):
1599
+ """
1600
+ Returns whether variable is of INTEGER type.
1601
+
1602
+ Returns
1603
+ -------
1604
+ bool
1605
+ """
1606
+ return SCIPvarIsIntegral(self.scip_var)
1607
+
1608
+ def isImpliedIntegral(self):
1609
+ """
1610
+ Returns whether variable is implied integral (weakly or strongly).
1611
+
1612
+ Returns
1613
+ -------
1614
+ bool
1615
+ """
1616
+ return SCIPvarIsImpliedIntegral(self.scip_var)
1617
+
1618
+ def isNonImpliedIntegral(self):
1619
+ """
1620
+ Returns TRUE if the variable is integral, but not implied integral..
1621
+
1622
+ Returns
1623
+ -------
1624
+ bool
1625
+ """
1626
+ return SCIPvarIsNonimpliedIntegral(self.scip_var)
1627
+
1628
+ def getImplType(self):
1629
+ """
1630
+ Returns the implied integral type of the variable
1631
+
1632
+ Returns
1633
+ -------
1634
+ PY_SCIP_IMPLINTTYPE
1635
+ """
1636
+ return SCIPvarGetImplType(self.scip_var)
1539
1637
 
1540
1638
  def getStatus(self):
1541
1639
  """
@@ -1762,6 +1860,16 @@ cdef class Variable(Expr):
1762
1860
  """
1763
1861
  return SCIPvarIsDeletable(self.scip_var)
1764
1862
 
1863
+ def isActive(self):
1864
+ """
1865
+ Returns whether variable is an active (neither fixed nor aggregated) variable.
1866
+
1867
+ Returns
1868
+ -------
1869
+ boolean
1870
+ """
1871
+ return SCIPvarIsActive(self.scip_var)
1872
+
1765
1873
  def getNLocksDown(self):
1766
1874
  """
1767
1875
  Returns the number of locks for rounding down.
@@ -2105,9 +2213,14 @@ cdef class Constraint:
2105
2213
 
2106
2214
  property name:
2107
2215
  def __get__(self):
2216
+ if self.scip_cons == NULL:
2217
+ return ""
2108
2218
  cname = bytes( SCIPconsGetName(self.scip_cons) )
2109
2219
  return cname.decode('utf-8')
2110
2220
 
2221
+ def ptr(self):
2222
+ return <size_t>(self.scip_cons)
2223
+
2111
2224
  def __repr__(self):
2112
2225
  return self.name
2113
2226
 
@@ -2555,6 +2668,115 @@ cdef class _VarArray:
2555
2668
  if self.ptr != NULL:
2556
2669
  free(self.ptr)
2557
2670
 
2671
+ cdef class IIS:
2672
+
2673
+ @staticmethod
2674
+ cdef create(SCIP_IIS* scip_iis):
2675
+ """
2676
+ Main method for creating an IIS class.
2677
+
2678
+ Parameters
2679
+ ----------
2680
+ scip : SCIP_IIS*
2681
+ A pointer to the SCIP_IIS
2682
+
2683
+ Returns
2684
+ -------
2685
+ sol : IIS
2686
+ The Python representative of the IIS
2687
+
2688
+ """
2689
+ iis = IIS()
2690
+ iis._iis = scip_iis
2691
+ return iis
2692
+
2693
+ def getTime(self):
2694
+ """
2695
+ Retrieve the solving time of the IIS.
2696
+
2697
+ Returns
2698
+ -------
2699
+ float
2700
+ """
2701
+ return SCIPiisGetTime(self._iis)
2702
+
2703
+ def isSubscipIrreducible(self):
2704
+ """
2705
+ Returns whether the IIS is irreducible.
2706
+
2707
+ Returns
2708
+ -------
2709
+ bool
2710
+ """
2711
+ return SCIPiisIsSubscipIrreducible(self._iis)
2712
+
2713
+ def isSubscipInfeasible(self):
2714
+ """
2715
+ Returns whether the IIS is infeasible.
2716
+
2717
+ Returns
2718
+ -------
2719
+ bool
2720
+ """
2721
+ return SCIPiisIsSubscipInfeasible(self._iis)
2722
+
2723
+ def setSubscipIrreducible(self, irreducible):
2724
+ """
2725
+ Sets the flag that states whether the IIS subscip is irreducible.
2726
+
2727
+ Parameters
2728
+ ----------
2729
+ irreducible : bool
2730
+ the value to set the irreducible flag to
2731
+ """
2732
+
2733
+ SCIPiisSetSubscipIrreducible(self._iis, irreducible)
2734
+
2735
+ def setSubscipInfeasible(self, infeasible):
2736
+ """
2737
+ Sets the flag that states whether the IIS subscip is infeasible.
2738
+
2739
+ Parameters
2740
+ ----------
2741
+ infeasible : bool
2742
+ the value to set the infeasible flag to
2743
+ """
2744
+
2745
+ SCIPiisSetSubscipInfeasible(self._iis, infeasible)
2746
+
2747
+ def getNNodes(self):
2748
+ """
2749
+ Gets number of nodes in the IIS solve.
2750
+
2751
+ Returns
2752
+ -------
2753
+ int
2754
+
2755
+ """
2756
+ return SCIPiisGetNNodes(self._iis)
2757
+
2758
+ def getSubscip(self):
2759
+ """
2760
+ Get the subscip of an IIS.
2761
+
2762
+ Returns
2763
+ -------
2764
+ Model
2765
+ """
2766
+ cdef SCIP* subscip
2767
+
2768
+ subscip = SCIPiisGetSubscip(self._iis)
2769
+ model = Model.create(subscip)
2770
+ return model
2771
+
2772
+ def greedyMakeIrreducible(self):
2773
+ """
2774
+ Perform the greedy deletion algorithm with singleton batches to obtain an irreducible infeasible subsystem (IIS)
2775
+ """
2776
+
2777
+ PY_SCIP_CALL(SCIPiisGreedyMakeIrreducible(self._iis))
2778
+
2779
+
2558
2780
  # - remove create(), includeDefaultPlugins(), createProbBasic() methods
2559
2781
  # - replace free() by "destructor"
2560
2782
  # - interface SCIPfreeProb()
@@ -2563,7 +2785,7 @@ cdef class _VarArray:
2563
2785
  ##
2564
2786
  cdef class Model:
2565
2787
 
2566
- def __init__(self, problemName='model', defaultPlugins=True, Model sourceModel=None, origcopy=False, globalcopy=True, enablepricing=False, createscip=True, threadsafe=False):
2788
+ def __init__(self, problemName='model', defaultPlugins=True, Model sourceModel=None, origcopy=False, globalcopy=True, enablepricing=True, createscip=True, threadsafe=False):
2567
2789
  """
2568
2790
  Main class holding a pointer to SCIP for managing most interactions
2569
2791
 
@@ -2580,7 +2802,7 @@ cdef class Model:
2580
2802
  globalcopy : bool, optional
2581
2803
  whether to create a global or a local copy (default True)
2582
2804
  enablepricing : bool, optional
2583
- whether to enable pricing in copy (default False)
2805
+ whether to enable pricing in copy (default True)
2584
2806
  createscip : bool, optional
2585
2807
  initialize the Model object and creates a SCIP instance (default True)
2586
2808
  threadsafe : bool, optional
@@ -2596,8 +2818,10 @@ cdef class Model:
2596
2818
 
2597
2819
  self._freescip = True
2598
2820
  self._modelvars = {}
2821
+ self._modelconss = {}
2599
2822
  self._generated_event_handlers_count = 0
2600
2823
  self._benders_subproblems = [] # Keep references to Benders subproblem Models
2824
+ self._iis = NULL
2601
2825
 
2602
2826
  if not createscip:
2603
2827
  # if no SCIP instance should be created, then an empty Model object is created.
@@ -2691,6 +2915,16 @@ cdef class Model:
2691
2915
  # Clear the references to allow Python GC to clean up the Model objects
2692
2916
  self._benders_subproblems = []
2693
2917
 
2918
+ # Invalidate all variable and constraint pointers before freeing SCIP. See issue #604.
2919
+ if self._modelvars:
2920
+ for var in self._modelvars.values():
2921
+ (<Variable>var).scip_var = NULL
2922
+ self._modelvars = {}
2923
+ if self._modelconss:
2924
+ for cons in self._modelconss.values():
2925
+ (<Constraint>cons).scip_cons = NULL
2926
+ self._modelconss = {}
2927
+
2694
2928
  PY_SCIP_CALL( SCIPfree(&self._scip) )
2695
2929
 
2696
2930
  def __hash__(self):
@@ -2723,6 +2957,24 @@ cdef class Model:
2723
2957
  model._benders_subproblems = [] # Initialize Benders subproblems list
2724
2958
  return model
2725
2959
 
2960
+ cdef _getOrCreateCons(self, SCIP_CONS* scip_cons):
2961
+ """Get existing Constraint wrapper or create and track a new one."""
2962
+ cdef size_t ptr = <size_t>scip_cons
2963
+ if ptr in self._modelconss:
2964
+ return self._modelconss[ptr]
2965
+ pyCons = Constraint.create(scip_cons)
2966
+ self._modelconss[ptr] = pyCons
2967
+ return pyCons
2968
+
2969
+ cdef _getOrCreateVar(self, SCIP_VAR* scip_var):
2970
+ """Get existing Variable wrapper or create and track a new one."""
2971
+ cdef size_t ptr = <size_t>scip_var
2972
+ if ptr in self._modelvars:
2973
+ return self._modelvars[ptr]
2974
+ pyVar = Variable.create(scip_var)
2975
+ self._modelvars[ptr] = pyVar
2976
+ return pyVar
2977
+
2726
2978
  @property
2727
2979
  def _freescip(self):
2728
2980
  """
@@ -2830,11 +3082,20 @@ cdef class Model:
2830
3082
  SCIP_STAGE_SOLVED]:
2831
3083
  raise Warning("method cannot be called in stage %i." % self.getStage())
2832
3084
 
2833
- self._modelvars = {
2834
- var: value
2835
- for var, value in self._modelvars.items()
2836
- if value.isOriginal()
2837
- }
3085
+ # Invalidate transformed variables. See issue #604.
3086
+ origvars = {ptr: var for ptr, var in self._modelvars.items() if var.isOriginal()}
3087
+ for ptr, var in self._modelvars.items():
3088
+ if ptr not in origvars:
3089
+ (<Variable>var).scip_var = NULL
3090
+ self._modelvars = origvars
3091
+
3092
+ # Invalidate transformed constraints. See issue #604.
3093
+ origconss = {ptr: cons for ptr, cons in self._modelconss.items() if cons.isOriginal()}
3094
+ for ptr, cons in self._modelconss.items():
3095
+ if ptr not in origconss:
3096
+ (<Constraint>cons).scip_cons = NULL
3097
+ self._modelconss = origconss
3098
+
2838
3099
  PY_SCIP_CALL(SCIPfreeTransform(self._scip))
2839
3100
 
2840
3101
  def version(self):
@@ -3143,6 +3404,16 @@ cdef class Model:
3143
3404
  """
3144
3405
  return SCIPgetNStrongbranchLPIterations(self._scip)
3145
3406
 
3407
+ def getPrimalDualIntegral(self):
3408
+ """
3409
+ Recomputes and returns the primal dual gap stored in the stats
3410
+
3411
+ Returns
3412
+ ------
3413
+ float
3414
+ """
3415
+ return SCIPgetPrimalDualIntegral(self._scip)
3416
+
3146
3417
  def cutoffNode(self, Node node):
3147
3418
  """
3148
3419
  marks node and whole subtree to be cut off from the branch and bound tree.
@@ -3351,6 +3622,62 @@ cdef class Model:
3351
3622
  """
3352
3623
  return SCIPisFeasIntegral(self._scip, value)
3353
3624
 
3625
+ def isIntegral(self, value):
3626
+ """
3627
+ Returns whether value is integral within epsilon tolerance.
3628
+
3629
+ Parameters
3630
+ ----------
3631
+ value : float
3632
+ value to check
3633
+
3634
+ Returns
3635
+ -------
3636
+ bool
3637
+
3638
+ """
3639
+ return SCIPisIntegral(self._scip, value)
3640
+
3641
+ def adjustedVarLb(self, Variable var, lb):
3642
+ """
3643
+ Returns the adjusted (i.e. rounded, if the given variable is of integral type) lower bound value;
3644
+ does not change the bounds of the variable.
3645
+
3646
+ Parameters
3647
+ ----------
3648
+ var : Variable
3649
+ variable for which the bound is adjusted
3650
+ lb : float
3651
+ lower bound value to adjust
3652
+
3653
+ Returns
3654
+ -------
3655
+ float
3656
+ adjusted lower bound
3657
+
3658
+ """
3659
+ return SCIPadjustedVarLb(self._scip, var.scip_var, lb)
3660
+
3661
+ def adjustedVarUb(self, Variable var, ub):
3662
+ """
3663
+ Returns the adjusted (i.e. rounded, if the given variable is of integral type) upper bound value;
3664
+ does not change the bounds of the variable.
3665
+
3666
+ Parameters
3667
+ ----------
3668
+ var : Variable
3669
+ variable for which the bound is adjusted
3670
+ ub : float
3671
+ upper bound value to adjust
3672
+
3673
+ Returns
3674
+ -------
3675
+ float
3676
+ adjusted upper bound
3677
+
3678
+ """
3679
+ return SCIPadjustedVarUb(self._scip, var.scip_var, ub)
3680
+
3354
3681
  def isEQ(self, val1, val2):
3355
3682
  """
3356
3683
  Checks, if values are in range of epsilon.
@@ -3763,6 +4090,17 @@ cdef class Model:
3763
4090
  """
3764
4091
  PY_SCIP_CALL(SCIPsetObjIntegral(self._scip))
3765
4092
 
4093
+ def isObjIntegral(self):
4094
+ """
4095
+ Returns whether the objective function is integral.
4096
+
4097
+ Returns
4098
+ -------
4099
+ bool
4100
+
4101
+ """
4102
+ return SCIPisObjIntegral(self._scip)
4103
+
3766
4104
  def getLocalEstimate(self, original = False):
3767
4105
  """
3768
4106
  Gets estimate of best primal solution w.r.t. original or transformed problem contained in current subtree.
@@ -4008,7 +4346,7 @@ cdef class Model:
4008
4346
  elif vtype in ['I', 'INTEGER']:
4009
4347
  PY_SCIP_CALL(SCIPcreateVarBasic(self._scip, &scip_var, cname, lb, ub, obj, SCIP_VARTYPE_INTEGER))
4010
4348
  elif vtype in ['M', 'IMPLINT']:
4011
- PY_SCIP_CALL(SCIPcreateVarBasic(self._scip, &scip_var, cname, lb, ub, obj, SCIP_VARTYPE_IMPLINT))
4349
+ PY_SCIP_CALL(SCIPcreateVarBasic(self._scip, &scip_var, cname, lb, ub, obj, SCIP_DEPRECATED_VARTYPE_IMPLINT))
4012
4350
  else:
4013
4351
  raise Warning("unrecognized variable type")
4014
4352
 
@@ -4020,11 +4358,7 @@ cdef class Model:
4020
4358
  else:
4021
4359
  PY_SCIP_CALL(SCIPaddVar(self._scip, scip_var))
4022
4360
 
4023
- pyVar = Variable.create(scip_var)
4024
-
4025
- # store variable in the model to avoid creating new python variable objects in getVars()
4026
- assert not pyVar.ptr() in self._modelvars
4027
- self._modelvars[pyVar.ptr()] = pyVar
4361
+ pyVar = self._getOrCreateVar(scip_var)
4028
4362
 
4029
4363
  #setting the variable data
4030
4364
  SCIPvarSetData(scip_var, <SCIP_VARDATA*>pyVar)
@@ -4154,8 +4488,7 @@ cdef class Model:
4154
4488
  """
4155
4489
  cdef SCIP_VAR* _tvar
4156
4490
  PY_SCIP_CALL(SCIPgetTransformedVar(self._scip, var.scip_var, &_tvar))
4157
-
4158
- return Variable.create(_tvar)
4491
+ return self._getOrCreateVar(_tvar)
4159
4492
 
4160
4493
  def addVarLocks(self, Variable var, int nlocksdown, int nlocksup):
4161
4494
  """
@@ -4231,11 +4564,72 @@ cdef class Model:
4231
4564
 
4232
4565
  """
4233
4566
  cdef SCIP_Bool deleted
4234
- if var.ptr() in self._modelvars:
4235
- del self._modelvars[var.ptr()]
4567
+ del self._modelvars[var.ptr()]
4236
4568
  PY_SCIP_CALL(SCIPdelVar(self._scip, var.scip_var, &deleted))
4569
+ # Invalidate pointer after deletion. See issue #604.
4570
+ var.scip_var = NULL
4237
4571
  return deleted
4238
4572
 
4573
+ def aggregateVars(self, Variable varx, Variable vary, coefx=1.0, coefy=-1.0, rhs=0.0):
4574
+ """
4575
+ Aggregate two variables by adding an aggregation constraint.
4576
+
4577
+ The aggregation is defined by the linear equation:
4578
+
4579
+ coefx * varx + coefy * vary = rhs
4580
+
4581
+ After aggregation, varx becomes a redundant variable and vary remains active.
4582
+ The aggregation effectively substitutes varx with: (rhs - coefy * vary) / coefx
4583
+
4584
+ This method can only be called during presolving.
4585
+
4586
+ Parameters
4587
+ ----------
4588
+ varx : Variable
4589
+ variable to be aggregated (will become redundant)
4590
+ vary : Variable
4591
+ variable to aggregate with (will remain active)
4592
+ coefx : float, optional
4593
+ coefficient for varx in the aggregation equation (default: 1.0)
4594
+ coefy : float, optional
4595
+ coefficient for vary in the aggregation equation (default: -1.0)
4596
+ rhs : float, optional
4597
+ right-hand side of the aggregation equation (default: 0.0)
4598
+
4599
+ Returns
4600
+ -------
4601
+ infeasible : bool
4602
+ whether the aggregation is infeasible (e.g., bounds are incompatible)
4603
+ redundant : bool
4604
+ whether the aggregation makes varx redundant
4605
+ aggregated : bool
4606
+ whether the aggregation was actually performed
4607
+
4608
+ Examples
4609
+ --------
4610
+ To express x = y (i.e., 1*x + (-1)*y = 0):
4611
+
4612
+ infeas, redun, aggr = model.aggregateVars(x, y, 1.0, -1.0, 0.0)
4613
+
4614
+ To express x = 5 - y (i.e., 1*x + 1*y = 5):
4615
+
4616
+ infeas, redun, aggr = model.aggregateVars(x, y, 1.0, 1.0, 5.0)
4617
+
4618
+ """
4619
+ cdef SCIP_Bool infeasible
4620
+ cdef SCIP_Bool redundant
4621
+ cdef SCIP_Bool aggregated
4622
+ PY_SCIP_CALL(SCIPaggregateVars(self._scip,
4623
+ varx.scip_var,
4624
+ vary.scip_var,
4625
+ coefx,
4626
+ coefy,
4627
+ rhs,
4628
+ &infeasible,
4629
+ &redundant,
4630
+ &aggregated))
4631
+ return infeasible, redundant, aggregated
4632
+
4239
4633
  def tightenVarLb(self, Variable var, lb, force=False):
4240
4634
  """
4241
4635
  Tighten the lower bound in preprocessing or current node, if the bound is tighter.
@@ -4456,7 +4850,7 @@ cdef class Model:
4456
4850
  elif vtype in ['I', 'INTEGER']:
4457
4851
  PY_SCIP_CALL(SCIPchgVarType(self._scip, var.scip_var, SCIP_VARTYPE_INTEGER, &infeasible))
4458
4852
  elif vtype in ['M', 'IMPLINT']:
4459
- PY_SCIP_CALL(SCIPchgVarType(self._scip, var.scip_var, SCIP_VARTYPE_IMPLINT, &infeasible))
4853
+ PY_SCIP_CALL(SCIPchgVarType(self._scip, var.scip_var, SCIP_DEPRECATED_VARTYPE_IMPLINT, &infeasible))
4460
4854
  else:
4461
4855
  raise Warning("unrecognized variable type")
4462
4856
  if infeasible:
@@ -4511,17 +4905,7 @@ cdef class Model:
4511
4905
  nvars = SCIPgetNOrigVars(self._scip)
4512
4906
 
4513
4907
  for i in range(nvars):
4514
- ptr = <size_t>(_vars[i])
4515
-
4516
- # check whether the corresponding variable exists already
4517
- if ptr in self._modelvars:
4518
- vars.append(self._modelvars[ptr])
4519
- else:
4520
- # create a new variable
4521
- var = Variable.create(_vars[i])
4522
- assert var.ptr() == ptr
4523
- self._modelvars[ptr] = var
4524
- vars.append(var)
4908
+ vars.append(self._getOrCreateVar(_vars[i]))
4525
4909
 
4526
4910
  return vars
4527
4911
 
@@ -5795,7 +6179,7 @@ cdef class Model:
5795
6179
  Parameters
5796
6180
  ----------
5797
6181
  cons : ExprCons
5798
- The expression constraint that is not yet an actual constraint
6182
+ the constraint expression to add to the model (e.g., x + y <= 5)
5799
6183
  name : str, optional
5800
6184
  the name of the constraint, generic name if empty (Default value = "")
5801
6185
  initial : bool, optional
@@ -5845,7 +6229,7 @@ cdef class Model:
5845
6229
  scip_cons = (<Constraint>pycons_initial).scip_cons
5846
6230
 
5847
6231
  PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
5848
- pycons = Constraint.create(scip_cons)
6232
+ pycons = self._getOrCreateCons(scip_cons)
5849
6233
  PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
5850
6234
 
5851
6235
  return pycons
@@ -6167,7 +6551,7 @@ cdef class Model:
6167
6551
  PY_SCIP_CALL(SCIPaddConsElemDisjunction(self._scip,disj_cons, (<Constraint>pycons).scip_cons))
6168
6552
  PY_SCIP_CALL(SCIPreleaseCons(self._scip, &(<Constraint>pycons).scip_cons))
6169
6553
  PY_SCIP_CALL(SCIPaddCons(self._scip, disj_cons))
6170
- PyCons = Constraint.create(disj_cons)
6554
+ PyCons = self._getOrCreateCons(disj_cons)
6171
6555
  PY_SCIP_CALL(SCIPreleaseCons(self._scip, &disj_cons))
6172
6556
 
6173
6557
  return PyCons
@@ -6253,20 +6637,11 @@ cdef class Model:
6253
6637
 
6254
6638
  vars = []
6255
6639
  for i in range(nvars):
6256
- ptr = <size_t>(_vars[i])
6257
- # check whether the corresponding variable exists already
6258
- if ptr in self._modelvars:
6259
- vars.append(self._modelvars[ptr])
6260
- else:
6261
- # create a new variable
6262
- var = Variable.create(_vars[i])
6263
- assert var.ptr() == ptr
6264
- self._modelvars[ptr] = var
6265
- vars.append(var)
6640
+ vars.append(self._getOrCreateVar(_vars[i]))
6266
6641
 
6267
6642
  free(_vars)
6268
6643
  return vars
6269
-
6644
+
6270
6645
  def getConsVals(self, Constraint constraint):
6271
6646
  """
6272
6647
  Returns the value array of an arbitrary SCIP constraint that can be represented as a single linear constraint.
@@ -6342,16 +6717,7 @@ cdef class Model:
6342
6717
 
6343
6718
  vars = []
6344
6719
  for i in range(nvars):
6345
- ptr = <size_t>(_vars[i])
6346
- # check whether the corresponding variable exists already
6347
- if ptr in self._modelvars:
6348
- vars.append(self._modelvars[ptr])
6349
- else:
6350
- # create a new variable
6351
- var = Variable.create(_vars[i])
6352
- assert var.ptr() == ptr
6353
- self._modelvars[ptr] = var
6354
- vars.append(var)
6720
+ vars.append(self._getOrCreateVar(_vars[i]))
6355
6721
 
6356
6722
  return vars
6357
6723
 
@@ -6369,22 +6735,10 @@ cdef class Model:
6369
6735
  Variable
6370
6736
 
6371
6737
  """
6372
-
6373
6738
  cdef SCIP_VAR* _resultant
6374
6739
 
6375
6740
  _resultant = SCIPgetResultantAnd(self._scip, and_cons.scip_cons)
6376
-
6377
- ptr = <size_t>(_resultant)
6378
- # check whether the corresponding variable exists already
6379
- if ptr not in self._modelvars:
6380
- # create a new variable
6381
- resultant = Variable.create(_resultant)
6382
- assert resultant.ptr() == ptr
6383
- self._modelvars[ptr] = resultant
6384
- else:
6385
- resultant = self._modelvars[ptr]
6386
-
6387
- return resultant
6741
+ return self._getOrCreateVar(_resultant)
6388
6742
 
6389
6743
  def isAndConsSorted(self, Constraint and_cons):
6390
6744
  """
@@ -6415,38 +6769,6 @@ cdef class Model:
6415
6769
  """
6416
6770
 
6417
6771
  PY_SCIP_CALL(SCIPsortAndCons(self._scip, and_cons.scip_cons))
6418
-
6419
- def chgAndConsCheckFlagWhenUpgr(self, Constraint cons, flag):
6420
- """
6421
- when 'upgrading' the given AND-constraint, should the check flag for the upgraded
6422
- constraint be set to TRUE, even if the check flag of this AND-constraint is set to FALSE?
6423
-
6424
- Parameters
6425
- ----------
6426
- cons : Constraint
6427
- The AND constraint to change.
6428
- flag : bool
6429
- The new value for the check flag.
6430
-
6431
- """
6432
-
6433
- PY_SCIP_CALL(SCIPchgAndConsCheckFlagWhenUpgr(self._scip, cons.scip_cons, flag))
6434
-
6435
- def chgAndConsRemovableFlagWhenUpgr(self, Constraint cons, flag):
6436
- """
6437
- when 'upgrading' the given AND-constraint, should the removable flag for the upgraded
6438
- constraint be set to TRUE, even if the removable flag of this AND-constraint is set to FALSE?
6439
-
6440
- Parameters
6441
- ----------
6442
- cons : Constraint
6443
- The AND constraint to change.
6444
- flag : bool
6445
- The new value for the removable flag.
6446
-
6447
- """
6448
-
6449
- PY_SCIP_CALL(SCIPchgAndConsRemovableFlagWhenUpgr(self._scip, cons.scip_cons, flag))
6450
6772
 
6451
6773
  def printCons(self, Constraint constraint):
6452
6774
  """
@@ -6505,7 +6827,10 @@ cdef class Model:
6505
6827
  else:
6506
6828
  raise NotImplementedError("Adding coefficients to %s constraints is not implemented." % constype)
6507
6829
 
6508
- def addConsNode(self, Node node, Constraint cons, Node validnode=None):
6830
+ def addConsNode(self, Node node, ExprCons cons, Node validnode=None, name='',
6831
+ initial=True, separate=True, enforce=True, check=True,
6832
+ propagate=True, local=True, dynamic=False, removable=True,
6833
+ stickingatnode=True):
6509
6834
  """
6510
6835
  Add a constraint to the given node.
6511
6836
 
@@ -6513,35 +6838,120 @@ cdef class Model:
6513
6838
  ----------
6514
6839
  node : Node
6515
6840
  node at which the constraint will be added
6516
- cons : Constraint
6517
- the constraint to add to the node
6841
+ cons : ExprCons
6842
+ the constraint expression to add to the node (e.g., x + y <= 5)
6518
6843
  validnode : Node or None, optional
6519
6844
  more global node where cons is also valid. (Default=None)
6845
+ name : str, optional
6846
+ name of the constraint (Default value = '')
6847
+ initial : bool, optional
6848
+ should the LP relaxation of constraint be in the initial LP? (Default value = True)
6849
+ separate : bool, optional
6850
+ should the constraint be separated during LP processing? (Default value = True)
6851
+ enforce : bool, optional
6852
+ should the constraint be enforced during node processing? (Default value = True)
6853
+ check : bool, optional
6854
+ should the constraint be checked for feasibility? (Default value = True)
6855
+ propagate : bool, optional
6856
+ should the constraint be propagated during node processing? (Default value = True)
6857
+ local : bool, optional
6858
+ is the constraint only valid locally? (Default value = True)
6859
+ dynamic : bool, optional
6860
+ is the constraint subject to aging? (Default value = False)
6861
+ removable : bool, optional
6862
+ should the relaxation be removed from the LP due to aging or cleanup? (Default value = True)
6863
+ stickingatnode : bool, optional
6864
+ should the constraint always be kept at the node where it was added? (Default value = True)
6865
+
6866
+ Returns
6867
+ -------
6868
+ Constraint
6869
+ The added Constraint object.
6520
6870
 
6521
6871
  """
6872
+ assert isinstance(cons, ExprCons), "given constraint is not ExprCons but %s" % cons.__class__.__name__
6873
+
6874
+ cdef SCIP_CONS* scip_cons
6875
+
6876
+ kwargs = dict(name=name, initial=initial, separate=separate,
6877
+ enforce=enforce, check=check, propagate=propagate,
6878
+ local=local, modifiable=False, dynamic=dynamic,
6879
+ removable=removable, stickingatnode=stickingatnode)
6880
+ pycons_initial = self.createConsFromExpr(cons, **kwargs)
6881
+ scip_cons = (<Constraint>pycons_initial).scip_cons
6882
+
6522
6883
  if isinstance(validnode, Node):
6523
- PY_SCIP_CALL(SCIPaddConsNode(self._scip, node.scip_node, cons.scip_cons, validnode.scip_node))
6884
+ PY_SCIP_CALL(SCIPaddConsNode(self._scip, node.scip_node, scip_cons, validnode.scip_node))
6524
6885
  else:
6525
- PY_SCIP_CALL(SCIPaddConsNode(self._scip, node.scip_node, cons.scip_cons, NULL))
6526
- Py_INCREF(cons)
6886
+ PY_SCIP_CALL(SCIPaddConsNode(self._scip, node.scip_node, scip_cons, NULL))
6887
+
6888
+ pycons = Constraint.create(scip_cons)
6889
+ pycons.data = (<Constraint>pycons_initial).data
6890
+ PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
6891
+
6892
+ return pycons
6527
6893
 
6528
- def addConsLocal(self, Constraint cons, Node validnode=None):
6894
+ def addConsLocal(self, ExprCons cons, Node validnode=None, name='',
6895
+ initial=True, separate=True, enforce=True, check=True,
6896
+ propagate=True, local=True, dynamic=False, removable=True,
6897
+ stickingatnode=True):
6529
6898
  """
6530
6899
  Add a constraint to the current node.
6531
6900
 
6532
6901
  Parameters
6533
6902
  ----------
6534
- cons : Constraint
6535
- the constraint to add to the current node
6903
+ cons : ExprCons
6904
+ the constraint expression to add to the current node (e.g., x + y <= 5)
6536
6905
  validnode : Node or None, optional
6537
6906
  more global node where cons is also valid. (Default=None)
6907
+ name : str, optional
6908
+ name of the constraint (Default value = '')
6909
+ initial : bool, optional
6910
+ should the LP relaxation of constraint be in the initial LP? (Default value = True)
6911
+ separate : bool, optional
6912
+ should the constraint be separated during LP processing? (Default value = True)
6913
+ enforce : bool, optional
6914
+ should the constraint be enforced during node processing? (Default value = True)
6915
+ check : bool, optional
6916
+ should the constraint be checked for feasibility? (Default value = True)
6917
+ propagate : bool, optional
6918
+ should the constraint be propagated during node processing? (Default value = True)
6919
+ local : bool, optional
6920
+ is the constraint only valid locally? (Default value = True)
6921
+ dynamic : bool, optional
6922
+ is the constraint subject to aging? (Default value = False)
6923
+ removable : bool, optional
6924
+ should the relaxation be removed from the LP due to aging or cleanup? (Default value = True)
6925
+ stickingatnode : bool, optional
6926
+ should the constraint always be kept at the node where it was added? (Default value = True)
6927
+
6928
+ Returns
6929
+ -------
6930
+ Constraint
6931
+ The added Constraint object.
6538
6932
 
6539
6933
  """
6934
+ assert isinstance(cons, ExprCons), "given constraint is not ExprCons but %s" % cons.__class__.__name__
6935
+
6936
+ cdef SCIP_CONS* scip_cons
6937
+
6938
+ kwargs = dict(name=name, initial=initial, separate=separate,
6939
+ enforce=enforce, check=check, propagate=propagate,
6940
+ local=local, modifiable=False, dynamic=dynamic,
6941
+ removable=removable, stickingatnode=stickingatnode)
6942
+ pycons_initial = self.createConsFromExpr(cons, **kwargs)
6943
+ scip_cons = (<Constraint>pycons_initial).scip_cons
6944
+
6540
6945
  if isinstance(validnode, Node):
6541
- PY_SCIP_CALL(SCIPaddConsLocal(self._scip, cons.scip_cons, validnode.scip_node))
6946
+ PY_SCIP_CALL(SCIPaddConsLocal(self._scip, scip_cons, validnode.scip_node))
6542
6947
  else:
6543
- PY_SCIP_CALL(SCIPaddConsLocal(self._scip, cons.scip_cons, NULL))
6544
- Py_INCREF(cons)
6948
+ PY_SCIP_CALL(SCIPaddConsLocal(self._scip, scip_cons, NULL))
6949
+
6950
+ pycons = Constraint.create(scip_cons)
6951
+ pycons.data = (<Constraint>pycons_initial).data
6952
+ PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
6953
+
6954
+ return pycons
6545
6955
 
6546
6956
  def addConsKnapsack(self, vars, weights, capacity, name="",
6547
6957
  initial=True, separate=True, enforce=True, check=True,
@@ -6608,9 +7018,9 @@ cdef class Model:
6608
7018
 
6609
7019
  PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
6610
7020
 
6611
- pyCons = Constraint.create(scip_cons)
7021
+ pyCons = self._getOrCreateCons(scip_cons)
6612
7022
  PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
6613
-
7023
+
6614
7024
  return pyCons
6615
7025
 
6616
7026
  def addConsSOS1(self, vars, weights=None, name="",
@@ -6676,7 +7086,7 @@ cdef class Model:
6676
7086
 
6677
7087
  PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
6678
7088
 
6679
- return Constraint.create(scip_cons)
7089
+ return self._getOrCreateCons(scip_cons)
6680
7090
 
6681
7091
  def addConsSOS2(self, vars, weights=None, name="",
6682
7092
  initial=True, separate=True, enforce=True, check=True,
@@ -6741,7 +7151,7 @@ cdef class Model:
6741
7151
 
6742
7152
  PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
6743
7153
 
6744
- return Constraint.create(scip_cons)
7154
+ return self._getOrCreateCons(scip_cons)
6745
7155
 
6746
7156
  def addConsAnd(self, vars, resvar, name="",
6747
7157
  initial=True, separate=True, enforce=True, check=True,
@@ -6802,7 +7212,7 @@ cdef class Model:
6802
7212
  initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode))
6803
7213
 
6804
7214
  PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
6805
- pyCons = Constraint.create(scip_cons)
7215
+ pyCons = self._getOrCreateCons(scip_cons)
6806
7216
  PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
6807
7217
 
6808
7218
  return pyCons
@@ -6866,7 +7276,7 @@ cdef class Model:
6866
7276
  initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode))
6867
7277
 
6868
7278
  PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
6869
- pyCons = Constraint.create(scip_cons)
7279
+ pyCons = self._getOrCreateCons(scip_cons)
6870
7280
  PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
6871
7281
 
6872
7282
  return pyCons
@@ -6929,7 +7339,7 @@ cdef class Model:
6929
7339
  initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode))
6930
7340
 
6931
7341
  PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
6932
- pyCons = Constraint.create(scip_cons)
7342
+ pyCons = self._getOrCreateCons(scip_cons)
6933
7343
  PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
6934
7344
 
6935
7345
  return pyCons
@@ -7014,7 +7424,7 @@ cdef class Model:
7014
7424
  PY_SCIP_CALL(SCIPaddVarCardinality(self._scip, scip_cons, scip_var, indvar, <SCIP_Real>weights[i]))
7015
7425
 
7016
7426
  PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
7017
- pyCons = Constraint.create(scip_cons)
7427
+ pyCons = self._getOrCreateCons(scip_cons)
7018
7428
 
7019
7429
  PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
7020
7430
 
@@ -7107,7 +7517,7 @@ cdef class Model:
7107
7517
  PY_SCIP_CALL(SCIPaddVarIndicator(self._scip, scip_cons, wrapper.ptr[0], <SCIP_Real>coeff))
7108
7518
 
7109
7519
  PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
7110
- pyCons = Constraint.create(scip_cons)
7520
+ pyCons = self._getOrCreateCons(scip_cons)
7111
7521
 
7112
7522
  PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
7113
7523
 
@@ -7297,7 +7707,7 @@ cdef class Model:
7297
7707
  cdef SCIP_CONS* lincons = SCIPgetLinearConsIndicator(cons.scip_cons)
7298
7708
  if lincons == NULL:
7299
7709
  return None
7300
- return Constraint.create(lincons)
7710
+ return self._getOrCreateCons(lincons)
7301
7711
 
7302
7712
  def getSlackVarIndicator(self, Constraint cons):
7303
7713
  """
@@ -7315,7 +7725,7 @@ cdef class Model:
7315
7725
 
7316
7726
  """
7317
7727
  cdef SCIP_VAR* var = SCIPgetSlackVarIndicator(cons.scip_cons)
7318
- return Variable.create(var)
7728
+ return self._getOrCreateVar(var)
7319
7729
 
7320
7730
  def addPyCons(self, Constraint cons):
7321
7731
  """
@@ -7759,7 +8169,7 @@ cdef class Model:
7759
8169
  """
7760
8170
  cdef SCIP_CONS* transcons
7761
8171
  PY_SCIP_CALL(SCIPgetTransformedCons(self._scip, cons.scip_cons, &transcons))
7762
- return Constraint.create(transcons)
8172
+ return self._getOrCreateCons(transcons)
7763
8173
 
7764
8174
  def isNLPConstructed(self):
7765
8175
  """
@@ -7899,8 +8309,16 @@ cdef class Model:
7899
8309
  Returns
7900
8310
  -------
7901
8311
  bilinterms : list of tuple
8312
+ Triples ``(var1, var2, coef)`` for terms of the form
8313
+ ``coef * var1 * var2`` with ``var1 != var2``.
7902
8314
  quadterms : list of tuple
8315
+ Triples ``(var, sqrcoef, lincoef)`` for variables that appear in
8316
+ quadratic or bilinear terms. ``sqrcoef`` is the coefficient of
8317
+ ``var**2``, and ``lincoef`` is the linear coefficient of ``var``
8318
+ if it also appears linearly.
7903
8319
  linterms : list of tuple
8320
+ Pairs ``(var, coef)`` for purely linear variables, i.e.,
8321
+ variables that do not participate in any quadratic or bilinear term.
7904
8322
 
7905
8323
  """
7906
8324
  cdef SCIP_EXPR* expr
@@ -7919,6 +8337,7 @@ cdef class Model:
7919
8337
  cdef int nbilinterms
7920
8338
 
7921
8339
  # quadratic terms
8340
+ cdef SCIP_EXPR* quadexpr
7922
8341
  cdef SCIP_EXPR* sqrexpr
7923
8342
  cdef SCIP_Real sqrcoef
7924
8343
  cdef int nquadterms
@@ -7931,33 +8350,49 @@ cdef class Model:
7931
8350
  assert self.checkQuadraticNonlinear(cons), "constraint is not quadratic"
7932
8351
 
7933
8352
  expr = SCIPgetExprNonlinear(cons.scip_cons)
7934
- SCIPexprGetQuadraticData(expr, NULL, &nlinvars, &linexprs, &lincoefs, &nquadterms, &nbilinterms, NULL, NULL)
8353
+ SCIPexprGetQuadraticData(expr, NULL, &nlinvars, &linexprs, &lincoefs,
8354
+ &nquadterms, &nbilinterms, NULL, NULL)
7935
8355
 
7936
8356
  linterms = []
7937
8357
  bilinterms = []
7938
- quadterms = []
7939
8358
 
8359
+ # Purely linear terms (variables not in any quadratic/bilinear term)
7940
8360
  for termidx in range(nlinvars):
7941
- var = Variable.create(SCIPgetVarExprVar(linexprs[termidx]))
8361
+ var = self._getOrCreateVar(SCIPgetVarExprVar(linexprs[termidx]))
7942
8362
  linterms.append((var, lincoefs[termidx]))
7943
8363
 
8364
+ # Collect quadratic terms in a dict so we can merge entries for the same variable.
8365
+ quaddict = {} # var.ptr() -> [var, sqrcoef, lincoef]
8366
+
7944
8367
  for termidx in range(nbilinterms):
7945
8368
  SCIPexprGetQuadraticBilinTerm(expr, termidx, &bilinterm1, &bilinterm2, &bilincoef, NULL, NULL)
7946
8369
  scipvar1 = SCIPgetVarExprVar(bilinterm1)
7947
8370
  scipvar2 = SCIPgetVarExprVar(bilinterm2)
7948
- var1 = Variable.create(scipvar1)
7949
- var2 = Variable.create(scipvar2)
8371
+ var1 = self._getOrCreateVar(scipvar1)
8372
+ var2 = self._getOrCreateVar(scipvar2)
7950
8373
  if scipvar1 != scipvar2:
7951
- bilinterms.append((var1,var2,bilincoef))
8374
+ bilinterms.append((var1, var2, bilincoef))
7952
8375
  else:
7953
- quadterms.append((var1,bilincoef,0.0))
7954
-
8376
+ # Squared term reported as bilinear var*var
8377
+ key = var1.ptr()
8378
+ if key in quaddict:
8379
+ quaddict[key][1] += bilincoef
8380
+ else: # TODO: SCIP handles expr like x**2 appropriately, but PySCIPOpt requires this. Need to investigate why.
8381
+ quaddict[key] = [var1, bilincoef, 0.0]
8382
+
8383
+ # Also collect linear coefficients from the quadratic terms
7955
8384
  for termidx in range(nquadterms):
7956
- SCIPexprGetQuadraticQuadTerm(expr, termidx, NULL, &lincoef, &sqrcoef, NULL, NULL, &sqrexpr)
7957
- if sqrexpr == NULL:
7958
- continue
7959
- var = Variable.create(SCIPgetVarExprVar(sqrexpr))
7960
- quadterms.append((var,sqrcoef,lincoef))
8385
+ SCIPexprGetQuadraticQuadTerm(expr, termidx, &quadexpr, &lincoef, &sqrcoef, NULL, NULL, &sqrexpr)
8386
+ scipvar1 = SCIPgetVarExprVar(quadexpr)
8387
+ var = self._getOrCreateVar(scipvar1)
8388
+ key = var.ptr()
8389
+ if key in quaddict:
8390
+ quaddict[key][1] += sqrcoef
8391
+ quaddict[key][2] += lincoef
8392
+ else:
8393
+ quaddict[key] = [var, sqrcoef, lincoef]
8394
+
8395
+ quadterms = [tuple(entry) for entry in quaddict.values()]
7961
8396
 
7962
8397
  return (bilinterms, quadterms, linterms)
7963
8398
 
@@ -8016,7 +8451,7 @@ cdef class Model:
8016
8451
  conss = SCIPgetOrigConss(self._scip)
8017
8452
  nconss = SCIPgetNOrigConss(self._scip)
8018
8453
 
8019
- return [Constraint.create(conss[i]) for i in range(nconss)]
8454
+ return [self._getOrCreateCons(conss[i]) for i in range(nconss)]
8020
8455
 
8021
8456
  def getNConss(self, transformed=True):
8022
8457
  """
@@ -8047,7 +8482,10 @@ cdef class Model:
8047
8482
  constraint to be deleted
8048
8483
 
8049
8484
  """
8485
+ del self._modelconss[cons.ptr()]
8050
8486
  PY_SCIP_CALL(SCIPdelCons(self._scip, cons.scip_cons))
8487
+ # Remove from tracking and invalidate pointer. See issue #604.
8488
+ cons.scip_cons = NULL
8051
8489
 
8052
8490
  def delConsLocal(self, Constraint cons):
8053
8491
  """
@@ -8647,11 +9085,8 @@ cdef class Model:
8647
9085
  PY_SCIP_CALL(SCIPgetBendersSubproblemVar(self._scip, _benders, var.scip_var, &_mappedvar, probnumber))
8648
9086
 
8649
9087
  if _mappedvar == NULL:
8650
- mappedvar = None
8651
- else:
8652
- mappedvar = Variable.create(_mappedvar)
8653
-
8654
- return mappedvar
9088
+ return None
9089
+ return self._getOrCreateVar(_mappedvar)
8655
9090
 
8656
9091
  def getBendersAuxiliaryVar(self, probnumber, Benders benders = None):
8657
9092
  """
@@ -8678,9 +9113,7 @@ cdef class Model:
8678
9113
  _benders = benders._benders
8679
9114
 
8680
9115
  _auxvar = SCIPbendersGetAuxiliaryVar(_benders, probnumber)
8681
- auxvar = Variable.create(_auxvar)
8682
-
8683
- return auxvar
9116
+ return self._getOrCreateVar(_auxvar)
8684
9117
 
8685
9118
  def checkBendersSubproblemOptimality(self, Solution solution, probnumber, Benders benders = None):
8686
9119
  """
@@ -9154,7 +9587,10 @@ cdef class Model:
9154
9587
  maxdepth : int, optional
9155
9588
  maximal depth level to call heuristic at (Default value = -1)
9156
9589
  timingmask : PY_SCIP_HEURTIMING, optional
9157
- positions in the node solving loop where heuristic should be executed
9590
+ positions in the node solvingreturn {
9591
+ 'result': SCIP_RESULT.SUCCESS,
9592
+ 'lowerbound': 10e4
9593
+ } loop where heuristic should be executed
9158
9594
  (Default value = SCIP_HEURTIMING_BEFORENODE)
9159
9595
  usessubscip : bool, optional
9160
9596
  does the heuristic use a secondary SCIP instance? (Default value = False)
@@ -9172,6 +9608,72 @@ cdef class Model:
9172
9608
  heur.model = <Model>weakref.proxy(self)
9173
9609
  heur.name = name
9174
9610
  Py_INCREF(heur)
9611
+
9612
+ def includeIISfinder(self, IISfinder iisfinder, name, desc, priority=10000, freq=1):
9613
+ """
9614
+ Include an IIS (Irreducible Infeasible Set) finder handler.
9615
+
9616
+ Parameters
9617
+ ----------
9618
+ iisfinder : IISfinder
9619
+ IIS finder
9620
+ name : str
9621
+ name of IIS finder
9622
+ desc : str
9623
+ description of IIS finder
9624
+ priority : int, optional
9625
+ priority of the IISfinder (#todo description)
9626
+ freq : int, optional
9627
+ frequency for calling IIS finder
9628
+
9629
+ """
9630
+ cdef SCIP_IISFINDER* scip_iisfinder
9631
+
9632
+ nam = str_conversion(name)
9633
+ des = str_conversion(desc)
9634
+
9635
+ iisfinder.iis = IIS()
9636
+
9637
+ PY_SCIP_CALL(SCIPincludeIISfinder(self._scip, nam, des, priority, PyiisfinderCopy, PyiisfinderFree,
9638
+ PyiisfinderExec, <SCIP_IISFINDERDATA*> iisfinder))
9639
+
9640
+ scip_iisfinder = SCIPfindIISfinder(self._scip, nam)
9641
+ iisfinder.name = name
9642
+ Py_INCREF(iisfinder)
9643
+ iisfinder.scip_iisfinder = scip_iisfinder
9644
+
9645
+ def generateIIS(self):
9646
+ """
9647
+ Generates an Irreducible Infeasible Subsystem (IIS) from the current
9648
+ problem.
9649
+
9650
+ Returns
9651
+ -------
9652
+ IIS
9653
+
9654
+ """
9655
+ cdef SCIP_IIS* _iis
9656
+
9657
+ PY_SCIP_CALL( SCIPgenerateIIS(self._scip) )
9658
+
9659
+ _iis = SCIPgetIIS(self._scip)
9660
+ return IIS.create(_iis)
9661
+
9662
+ def getIIS(self):
9663
+ """
9664
+ Get the IIS object.
9665
+ Note: Needs to be called after generateIIS, or after a single execution of the iisfinderExec.
9666
+
9667
+ Returns
9668
+ -------
9669
+ IIS
9670
+ """
9671
+ cdef SCIP_IIS* _iis
9672
+
9673
+ _iis = SCIPgetIIS(self._scip)
9674
+ assert _iis != NULL, "No IIS exists. You need to first call generateIIS() or run the iisfinderexec method of your custom IISfinder class."
9675
+
9676
+ return IIS.create(_iis)
9175
9677
 
9176
9678
  def includeRelax(self, Relax relax, name, desc, priority=10000, freq=1):
9177
9679
  """
@@ -9404,7 +9906,7 @@ cdef class Model:
9404
9906
  PY_SCIP_CALL(SCIPgetLPBranchCands(self._scip, &lpcands, &lpcandssol, &lpcandsfrac,
9405
9907
  &nlpcands, &npriolpcands, &nfracimplvars))
9406
9908
 
9407
- return ([Variable.create(lpcands[i]) for i in range(nlpcands)], [lpcandssol[i] for i in range(nlpcands)],
9909
+ return ([self._getOrCreateVar(lpcands[i]) for i in range(nlpcands)], [lpcandssol[i] for i in range(nlpcands)],
9408
9910
  [lpcandsfrac[i] for i in range(nlpcands)], nlpcands, npriolpcands, nfracimplvars)
9409
9911
 
9410
9912
  def getNLPBranchCands(self):
@@ -9441,7 +9943,7 @@ cdef class Model:
9441
9943
 
9442
9944
  PY_SCIP_CALL(SCIPgetPseudoBranchCands(self._scip, &pseudocands, &npseudocands, &npriopseudocands))
9443
9945
 
9444
- return ([Variable.create(pseudocands[i]) for i in range(npseudocands)], npseudocands, npriopseudocands)
9946
+ return ([self._getOrCreateVar(pseudocands[i]) for i in range(npseudocands)], npseudocands, npriopseudocands)
9445
9947
 
9446
9948
  def branchVar(self, Variable variable):
9447
9949
  """
@@ -10416,7 +10918,10 @@ cdef class Model:
10416
10918
  Solution or None
10417
10919
 
10418
10920
  """
10419
- self._bestSol = Solution.create(self._scip, SCIPgetBestSol(self._scip))
10921
+ cdef SCIP_SOL* _sol = SCIPgetBestSol(self._scip)
10922
+ if _sol == NULL:
10923
+ return None
10924
+ self._bestSol = Solution.create(self._scip, _sol)
10420
10925
  return self._bestSol
10421
10926
 
10422
10927
  def getSolObjVal(self, Solution sol, original=True):
@@ -10459,6 +10964,8 @@ cdef class Model:
10459
10964
  float
10460
10965
 
10461
10966
  """
10967
+ if sol is None or sol.sol == NULL:
10968
+ raise ValueError("Cannot get solution time: solution is None or NULL")
10462
10969
  return SCIPgetSolTime(self._scip, sol.sol)
10463
10970
 
10464
10971
  def getObjVal(self, original=True):
@@ -10492,20 +10999,26 @@ cdef class Model:
10492
10999
 
10493
11000
  return self.getSolObjVal(self._bestSol, original)
10494
11001
 
10495
- def getSolVal(self, Solution sol, Expr expr):
11002
+ def getSolVal(
11003
+ self,
11004
+ Solution sol,
11005
+ expr: Union[Expr, GenExpr, MatrixExpr],
11006
+ ) -> Union[float, np.ndarray]:
10496
11007
  """
10497
- Retrieve value of given variable or expression in the given solution or in
10498
- the LP/pseudo solution if sol == None
11008
+ Retrieve value of given variable or expression in the given solution.
10499
11009
 
10500
11010
  Parameters
10501
11011
  ----------
10502
11012
  sol : Solution
10503
- expr : Expr
10504
- polynomial expression to query the value of
11013
+ Solution to query the value from. If None, the current LP/pseudo solution is
11014
+ used.
11015
+
11016
+ expr : Expr, GenExpr, MatrixExpr
11017
+ Expression to query the value of.
10505
11018
 
10506
11019
  Returns
10507
11020
  -------
10508
- float
11021
+ float or np.ndarray
10509
11022
 
10510
11023
  Notes
10511
11024
  -----
@@ -10513,46 +11026,39 @@ cdef class Model:
10513
11026
 
10514
11027
  """
10515
11028
  # no need to create a NULL solution wrapper in case we have a variable
10516
- cdef _VarArray wrapper
10517
- if sol == None and isinstance(expr, Variable):
10518
- wrapper = _VarArray(expr)
10519
- return SCIPgetSolVal(self._scip, NULL, wrapper.ptr[0])
10520
- if sol == None:
10521
- sol = Solution.create(self._scip, NULL)
10522
- return sol[expr]
11029
+ return (sol or Solution.create(self._scip, NULL))[expr]
10523
11030
 
10524
- def getVal(self, expr: Union[Expr, MatrixExpr] ):
11031
+ def getVal(self, expr: Union[Expr, GenExpr, MatrixExpr]) -> Union[float, np.ndarray]:
10525
11032
  """
10526
11033
  Retrieve the value of the given variable or expression in the best known solution.
10527
11034
  Can only be called after solving is completed.
10528
11035
 
10529
11036
  Parameters
10530
11037
  ----------
10531
- expr : Expr ot MatrixExpr
10532
- polynomial expression to query the value of
11038
+ expr : Expr, GenExpr or MatrixExpr
11039
+ Expression to query the value of.
10533
11040
 
10534
11041
  Returns
10535
11042
  -------
10536
- float
11043
+ float or np.ndarray
10537
11044
 
10538
11045
  Notes
10539
11046
  -----
10540
11047
  A variable is also an expression.
10541
11048
 
10542
11049
  """
10543
- stage_check = SCIPgetStage(self._scip) not in [SCIP_STAGE_INIT, SCIP_STAGE_FREE]
10544
-
10545
- if not stage_check or self._bestSol.sol == NULL and SCIPgetStage(self._scip) != SCIP_STAGE_SOLVING:
11050
+ if SCIPgetStage(self._scip) in {SCIP_STAGE_INIT, SCIP_STAGE_FREE}:
10546
11051
  raise Warning("Method cannot be called in stage ", self.getStage())
10547
11052
 
10548
- if isinstance(expr, MatrixExpr):
10549
- result = np.empty(expr.shape, dtype=float)
10550
- for idx in np.ndindex(result.shape):
10551
- result[idx] = self.getSolVal(self._bestSol, expr[idx])
10552
- else:
10553
- result = self.getSolVal(self._bestSol, expr)
11053
+ # Ensure _bestSol is up-to-date (cheap pointer comparison)
11054
+ cdef SCIP_SOL* current_best_sol = SCIPgetBestSol(self._scip)
11055
+ if self._bestSol is None or self._bestSol.sol != current_best_sol:
11056
+ self._bestSol = Solution.create(self._scip, current_best_sol)
10554
11057
 
10555
- return result
11058
+ if self._bestSol.sol == NULL and SCIPgetStage(self._scip) != SCIP_STAGE_SOLVING:
11059
+ raise Warning("No solution available")
11060
+
11061
+ return self._bestSol[expr]
10556
11062
 
10557
11063
  def hasPrimalRay(self):
10558
11064
  """
@@ -10863,12 +11369,58 @@ cdef class Model:
10863
11369
 
10864
11370
  # Statistic Methods
10865
11371
 
10866
- def printStatistics(self):
10867
- """Print statistics."""
11372
+ def printStatistics(self, filename=None):
11373
+ """
11374
+ Print statistics.
11375
+
11376
+ Parameters
11377
+ ----------
11378
+ filename : str, optional
11379
+ name of the output file (Default = None)
11380
+
11381
+ """
11382
+
10868
11383
  user_locale = locale.getlocale(category=locale.LC_NUMERIC)
10869
11384
  locale.setlocale(locale.LC_NUMERIC, "C")
10870
11385
 
10871
- PY_SCIP_CALL(SCIPprintStatistics(self._scip, NULL))
11386
+ if not filename:
11387
+ PY_SCIP_CALL(SCIPprintStatistics(self._scip, NULL))
11388
+ else:
11389
+ with open(filename, "w") as f:
11390
+ cfile = fdopen(f.fileno(), "w")
11391
+ PY_SCIP_CALL(SCIPprintStatistics(self._scip, cfile))
11392
+
11393
+ locale.setlocale(locale.LC_NUMERIC,user_locale)
11394
+
11395
+ def printStatisticsJson(self, filename=None):
11396
+ """
11397
+ Print statistics in JSON format.
11398
+
11399
+ Parameters
11400
+ ----------
11401
+ filename : str, optional
11402
+ name of the output file (Default = None)
11403
+
11404
+ """
11405
+
11406
+ user_locale = locale.getlocale(category=locale.LC_NUMERIC)
11407
+ locale.setlocale(locale.LC_NUMERIC, "C")
11408
+
11409
+ if not filename:
11410
+ PY_SCIP_CALL(SCIPprintStatisticsJson(self._scip, NULL))
11411
+ else:
11412
+ with open(filename, "w") as f:
11413
+ cfile = fdopen(f.fileno(), "w")
11414
+ PY_SCIP_CALL(SCIPprintStatisticsJson(self._scip, cfile))
11415
+
11416
+ locale.setlocale(locale.LC_NUMERIC,user_locale)
11417
+
11418
+ def printStatisticsJson(self):
11419
+ """Print statistics in JSON format."""
11420
+ user_locale = locale.getlocale(category=locale.LC_NUMERIC)
11421
+ locale.setlocale(locale.LC_NUMERIC, "C")
11422
+
11423
+ PY_SCIP_CALL(SCIPprintStatisticsJson(self._scip, NULL))
10872
11424
 
10873
11425
  locale.setlocale(locale.LC_NUMERIC,user_locale)
10874
11426
 
@@ -10892,6 +11444,28 @@ cdef class Model:
10892
11444
  PY_SCIP_CALL(SCIPprintStatistics(self._scip, cfile))
10893
11445
 
10894
11446
  locale.setlocale(locale.LC_NUMERIC,user_locale)
11447
+
11448
+
11449
+ def writeStatisticsJson(self, filename="origprob.stats.json"):
11450
+ """
11451
+ Write statistics to a JSON file.
11452
+
11453
+ Parameters
11454
+ ----------
11455
+ filename : str, optional
11456
+ name of the output file (Default = "origprob.stats.json")
11457
+
11458
+ """
11459
+ user_locale = locale.getlocale(category=locale.LC_NUMERIC)
11460
+ locale.setlocale(locale.LC_NUMERIC, "C")
11461
+
11462
+ # use this doubled opening pattern to ensure that IOErrors are
11463
+ # triggered early and in Python not in C,Cython or SCIP.
11464
+ with open(filename, "w") as f:
11465
+ cfile = fdopen(f.fileno(), "w")
11466
+ PY_SCIP_CALL(SCIPprintStatisticsJson(self._scip, cfile))
11467
+
11468
+ locale.setlocale(locale.LC_NUMERIC,user_locale)
10895
11469
 
10896
11470
  def getNLPs(self):
10897
11471
  """
@@ -11300,12 +11874,12 @@ cdef class Model:
11300
11874
 
11301
11875
  def chgReoptObjective(self, coeffs, sense = 'minimize'):
11302
11876
  """
11303
- Establish the objective function as a linear expression.
11877
+ Change the objective function for reoptimization.
11304
11878
 
11305
11879
  Parameters
11306
11880
  ----------
11307
- coeffs : list of float
11308
- the coefficients
11881
+ coeffs : Expr
11882
+ the coefficients as a linear expression
11309
11883
  sense : str
11310
11884
  the objective sense (Default value = 'minimize')
11311
11885
 
@@ -11314,7 +11888,6 @@ cdef class Model:
11314
11888
  cdef int nvars
11315
11889
  cdef SCIP_Real* _coeffs
11316
11890
  cdef SCIP_OBJSENSE objsense
11317
- cdef SCIP_Real coef
11318
11891
  cdef int i
11319
11892
  cdef _VarArray wrapper
11320
11893
 
@@ -11332,24 +11905,27 @@ cdef class Model:
11332
11905
  if coeffs[CONST] != 0.0:
11333
11906
  raise ValueError("Constant offsets in objective are not supported!")
11334
11907
 
11335
- vars = SCIPgetOrigVars(self._scip)
11336
- nvars = SCIPgetNOrigVars(self._scip)
11337
- _coeffs = <SCIP_Real*> malloc(nvars * sizeof(SCIP_Real))
11908
+ nvars = len(coeffs.terms) - (CONST in coeffs.terms)
11338
11909
 
11339
- for i in range(nvars):
11340
- _coeffs[i] = 0.0
11910
+ if nvars == 0:
11911
+ PY_SCIP_CALL(SCIPchgReoptObjective(self._scip, objsense, NULL, NULL, 0))
11912
+ return
11913
+
11914
+ _coeffs = <SCIP_Real*> malloc(nvars * sizeof(SCIP_Real))
11915
+ vars = <SCIP_VAR**> malloc(nvars * sizeof(SCIP_VAR*))
11341
11916
 
11917
+ i = 0
11342
11918
  for term, coef in coeffs.terms.items():
11343
11919
  # avoid CONST term of Expr
11344
11920
  if term != CONST:
11345
- assert len(term) == 1
11346
- for i in range(nvars):
11347
- wrapper = _VarArray(term[0])
11348
- if vars[i] == wrapper.ptr[0]:
11349
- _coeffs[i] = coef
11921
+ wrapper = _VarArray(term[0])
11922
+ vars[i] = wrapper.ptr[0]
11923
+ _coeffs[i] = coef
11924
+ i += 1
11350
11925
 
11351
- PY_SCIP_CALL(SCIPchgReoptObjective(self._scip, objsense, vars, &_coeffs[0], nvars))
11926
+ PY_SCIP_CALL(SCIPchgReoptObjective(self._scip, objsense, vars, _coeffs, nvars))
11352
11927
 
11928
+ free(vars)
11353
11929
  free(_coeffs)
11354
11930
 
11355
11931
  def chgVarBranchPriority(self, Variable var, priority):
@@ -11562,6 +12138,65 @@ cdef class Model:
11562
12138
  """
11563
12139
  return SCIPgetTreesizeEstimation(self._scip)
11564
12140
 
12141
+
12142
+ # Exact SCIP methods
12143
+ def enableExactSolving(self, SCIP_Bool enable):
12144
+ """
12145
+ Enables or disables exact solving mode in SCIP.
12146
+
12147
+ Parameters
12148
+ ----------
12149
+ enable : SCIP_Bool
12150
+ Whether to enable exact solving mode (True) or disable it (False).
12151
+ """
12152
+
12153
+ PY_SCIP_CALL(SCIPenableExactSolving(self._scip, enable))
12154
+
12155
+ def isExact(self):
12156
+ """
12157
+ Returns whether exact solving mode is enabled in SCIP.
12158
+
12159
+ Returns
12160
+ -------
12161
+ bool
12162
+ """
12163
+
12164
+ return SCIPisExact(self._scip)
12165
+
12166
+ def allowNegSlackExact(self):
12167
+ """
12168
+ Returns whether negative slack is allowed in exact solving mode.
12169
+
12170
+ Returns
12171
+ -------
12172
+ bool
12173
+ """
12174
+
12175
+ return SCIPallowNegSlack(self._scip)
12176
+
12177
+ def branchLPExact(self):
12178
+ """
12179
+ Performs exact LP branching.
12180
+
12181
+ Returns
12182
+ -------
12183
+ SCIP_RESULT
12184
+ """
12185
+ cdef SCIP_RESULT result
12186
+ PY_SCIP_CALL(SCIPbranchLPExact(self._scip, &result))
12187
+ return result
12188
+
12189
+ def addRowExact(self, RowExact rowexact):
12190
+ """
12191
+ Adds an exact row to the LP.
12192
+
12193
+ Parameters
12194
+ ----------
12195
+ rowexact : RowExact
12196
+ The exact row to add.
12197
+ """
12198
+ PY_SCIP_CALL(SCIPaddRowExact(self._scip, rowexact.scip_row_exact))
12199
+
11565
12200
  def getBipartiteGraphRepresentation(self, prev_col_features=None, prev_edge_features=None, prev_row_features=None,
11566
12201
  static_only=False, suppress_warnings=False):
11567
12202
  """
@@ -11669,7 +12304,7 @@ cdef class Model:
11669
12304
  col_features[col_i][col_feature_map["integer"]] = 1
11670
12305
  elif vtype == SCIP_VARTYPE_CONTINUOUS:
11671
12306
  col_features[col_i][col_feature_map["continuous"]] = 1
11672
- elif vtype == SCIP_VARTYPE_IMPLINT:
12307
+ elif vtype == SCIP_DEPRECATED_VARTYPE_IMPLINT:
11673
12308
  col_features[col_i][col_feature_map["implicit_integer"]] = 1
11674
12309
  # Objective coefficient
11675
12310
  col_features[col_i][col_feature_map["obj_coef"]] = SCIPcolGetObj(cols[i])