PySCIPOpt 6.0.0__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
@@ -1068,8 +1068,8 @@ cdef class Solution:
1068
1068
  """Base class holding a pointer to corresponding SCIP_SOL."""
1069
1069
 
1070
1070
  # We are raising an error here to avoid creating a solution without an associated model. See Issue #625
1071
- def __init__(self, raise_error = False):
1072
- if not raise_error:
1071
+ def __init__(self, raise_error = True):
1072
+ if raise_error:
1073
1073
  raise ValueError("To create a solution you should use the createSol method of the Model class.")
1074
1074
 
1075
1075
  @staticmethod
@@ -1093,35 +1093,23 @@ cdef class Solution:
1093
1093
  """
1094
1094
  if scip == NULL:
1095
1095
  raise Warning("cannot create Solution with SCIP* == NULL")
1096
- sol = Solution(True)
1096
+ sol = Solution(raise_error=False)
1097
1097
  sol.sol = scip_sol
1098
1098
  sol.scip = scip
1099
1099
  return sol
1100
1100
 
1101
- def __getitem__(self, expr: Union[Expr, MatrixExpr]):
1102
- if isinstance(expr, MatrixExpr):
1103
- result = np.zeros(expr.shape, dtype=np.float64)
1104
- for idx in np.ndindex(expr.shape):
1105
- result[idx] = self.__getitem__(expr[idx])
1106
- return result
1107
-
1108
- # fast track for Variable
1109
- cdef SCIP_Real coeff
1110
- cdef _VarArray wrapper
1111
- if isinstance(expr, Variable):
1112
- wrapper = _VarArray(expr)
1113
- self._checkStage("SCIPgetSolVal")
1114
- return SCIPgetSolVal(self.scip, self.sol, wrapper.ptr[0])
1115
- 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
+ )
1116
1110
 
1117
- def _evaluate(self, term):
1118
1111
  self._checkStage("SCIPgetSolVal")
1119
- result = 1
1120
- cdef _VarArray wrapper
1121
- wrapper = _VarArray(term.vartuple)
1122
- for i in range(len(term.vartuple)):
1123
- result *= SCIPgetSolVal(self.scip, self.sol, wrapper.ptr[i])
1124
- return result
1112
+ return expr._evaluate(self)
1125
1113
 
1126
1114
  def __setitem__(self, Variable var, value):
1127
1115
  PY_SCIP_CALL(SCIPsetSolVal(self.scip, self.sol, var.scip_var, value))
@@ -1565,6 +1553,8 @@ cdef class Variable(Expr):
1565
1553
 
1566
1554
  property name:
1567
1555
  def __get__(self):
1556
+ if self.scip_var == NULL:
1557
+ return ""
1568
1558
  cname = bytes( SCIPvarGetName(self.scip_var) )
1569
1559
  return cname.decode('utf-8')
1570
1560
 
@@ -1870,6 +1860,16 @@ cdef class Variable(Expr):
1870
1860
  """
1871
1861
  return SCIPvarIsDeletable(self.scip_var)
1872
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
+
1873
1873
  def getNLocksDown(self):
1874
1874
  """
1875
1875
  Returns the number of locks for rounding down.
@@ -2213,9 +2213,14 @@ cdef class Constraint:
2213
2213
 
2214
2214
  property name:
2215
2215
  def __get__(self):
2216
+ if self.scip_cons == NULL:
2217
+ return ""
2216
2218
  cname = bytes( SCIPconsGetName(self.scip_cons) )
2217
2219
  return cname.decode('utf-8')
2218
2220
 
2221
+ def ptr(self):
2222
+ return <size_t>(self.scip_cons)
2223
+
2219
2224
  def __repr__(self):
2220
2225
  return self.name
2221
2226
 
@@ -2780,7 +2785,7 @@ cdef class IIS:
2780
2785
  ##
2781
2786
  cdef class Model:
2782
2787
 
2783
- 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):
2784
2789
  """
2785
2790
  Main class holding a pointer to SCIP for managing most interactions
2786
2791
 
@@ -2797,7 +2802,7 @@ cdef class Model:
2797
2802
  globalcopy : bool, optional
2798
2803
  whether to create a global or a local copy (default True)
2799
2804
  enablepricing : bool, optional
2800
- whether to enable pricing in copy (default False)
2805
+ whether to enable pricing in copy (default True)
2801
2806
  createscip : bool, optional
2802
2807
  initialize the Model object and creates a SCIP instance (default True)
2803
2808
  threadsafe : bool, optional
@@ -2813,6 +2818,7 @@ cdef class Model:
2813
2818
 
2814
2819
  self._freescip = True
2815
2820
  self._modelvars = {}
2821
+ self._modelconss = {}
2816
2822
  self._generated_event_handlers_count = 0
2817
2823
  self._benders_subproblems = [] # Keep references to Benders subproblem Models
2818
2824
  self._iis = NULL
@@ -2909,6 +2915,16 @@ cdef class Model:
2909
2915
  # Clear the references to allow Python GC to clean up the Model objects
2910
2916
  self._benders_subproblems = []
2911
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
+
2912
2928
  PY_SCIP_CALL( SCIPfree(&self._scip) )
2913
2929
 
2914
2930
  def __hash__(self):
@@ -2941,6 +2957,24 @@ cdef class Model:
2941
2957
  model._benders_subproblems = [] # Initialize Benders subproblems list
2942
2958
  return model
2943
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
+
2944
2978
  @property
2945
2979
  def _freescip(self):
2946
2980
  """
@@ -3048,11 +3082,20 @@ cdef class Model:
3048
3082
  SCIP_STAGE_SOLVED]:
3049
3083
  raise Warning("method cannot be called in stage %i." % self.getStage())
3050
3084
 
3051
- self._modelvars = {
3052
- var: value
3053
- for var, value in self._modelvars.items()
3054
- if value.isOriginal()
3055
- }
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
+
3056
3099
  PY_SCIP_CALL(SCIPfreeTransform(self._scip))
3057
3100
 
3058
3101
  def version(self):
@@ -3361,6 +3404,16 @@ cdef class Model:
3361
3404
  """
3362
3405
  return SCIPgetNStrongbranchLPIterations(self._scip)
3363
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
+
3364
3417
  def cutoffNode(self, Node node):
3365
3418
  """
3366
3419
  marks node and whole subtree to be cut off from the branch and bound tree.
@@ -3569,6 +3622,62 @@ cdef class Model:
3569
3622
  """
3570
3623
  return SCIPisFeasIntegral(self._scip, value)
3571
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
+
3572
3681
  def isEQ(self, val1, val2):
3573
3682
  """
3574
3683
  Checks, if values are in range of epsilon.
@@ -3981,6 +4090,17 @@ cdef class Model:
3981
4090
  """
3982
4091
  PY_SCIP_CALL(SCIPsetObjIntegral(self._scip))
3983
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
+
3984
4104
  def getLocalEstimate(self, original = False):
3985
4105
  """
3986
4106
  Gets estimate of best primal solution w.r.t. original or transformed problem contained in current subtree.
@@ -4238,11 +4358,7 @@ cdef class Model:
4238
4358
  else:
4239
4359
  PY_SCIP_CALL(SCIPaddVar(self._scip, scip_var))
4240
4360
 
4241
- pyVar = Variable.create(scip_var)
4242
-
4243
- # store variable in the model to avoid creating new python variable objects in getVars()
4244
- assert not pyVar.ptr() in self._modelvars
4245
- self._modelvars[pyVar.ptr()] = pyVar
4361
+ pyVar = self._getOrCreateVar(scip_var)
4246
4362
 
4247
4363
  #setting the variable data
4248
4364
  SCIPvarSetData(scip_var, <SCIP_VARDATA*>pyVar)
@@ -4372,8 +4488,7 @@ cdef class Model:
4372
4488
  """
4373
4489
  cdef SCIP_VAR* _tvar
4374
4490
  PY_SCIP_CALL(SCIPgetTransformedVar(self._scip, var.scip_var, &_tvar))
4375
-
4376
- return Variable.create(_tvar)
4491
+ return self._getOrCreateVar(_tvar)
4377
4492
 
4378
4493
  def addVarLocks(self, Variable var, int nlocksdown, int nlocksup):
4379
4494
  """
@@ -4449,11 +4564,72 @@ cdef class Model:
4449
4564
 
4450
4565
  """
4451
4566
  cdef SCIP_Bool deleted
4452
- if var.ptr() in self._modelvars:
4453
- del self._modelvars[var.ptr()]
4567
+ del self._modelvars[var.ptr()]
4454
4568
  PY_SCIP_CALL(SCIPdelVar(self._scip, var.scip_var, &deleted))
4569
+ # Invalidate pointer after deletion. See issue #604.
4570
+ var.scip_var = NULL
4455
4571
  return deleted
4456
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
+
4457
4633
  def tightenVarLb(self, Variable var, lb, force=False):
4458
4634
  """
4459
4635
  Tighten the lower bound in preprocessing or current node, if the bound is tighter.
@@ -4729,17 +4905,7 @@ cdef class Model:
4729
4905
  nvars = SCIPgetNOrigVars(self._scip)
4730
4906
 
4731
4907
  for i in range(nvars):
4732
- ptr = <size_t>(_vars[i])
4733
-
4734
- # check whether the corresponding variable exists already
4735
- if ptr in self._modelvars:
4736
- vars.append(self._modelvars[ptr])
4737
- else:
4738
- # create a new variable
4739
- var = Variable.create(_vars[i])
4740
- assert var.ptr() == ptr
4741
- self._modelvars[ptr] = var
4742
- vars.append(var)
4908
+ vars.append(self._getOrCreateVar(_vars[i]))
4743
4909
 
4744
4910
  return vars
4745
4911
 
@@ -6013,7 +6179,7 @@ cdef class Model:
6013
6179
  Parameters
6014
6180
  ----------
6015
6181
  cons : ExprCons
6016
- The expression constraint that is not yet an actual constraint
6182
+ the constraint expression to add to the model (e.g., x + y <= 5)
6017
6183
  name : str, optional
6018
6184
  the name of the constraint, generic name if empty (Default value = "")
6019
6185
  initial : bool, optional
@@ -6063,7 +6229,7 @@ cdef class Model:
6063
6229
  scip_cons = (<Constraint>pycons_initial).scip_cons
6064
6230
 
6065
6231
  PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
6066
- pycons = Constraint.create(scip_cons)
6232
+ pycons = self._getOrCreateCons(scip_cons)
6067
6233
  PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
6068
6234
 
6069
6235
  return pycons
@@ -6385,7 +6551,7 @@ cdef class Model:
6385
6551
  PY_SCIP_CALL(SCIPaddConsElemDisjunction(self._scip,disj_cons, (<Constraint>pycons).scip_cons))
6386
6552
  PY_SCIP_CALL(SCIPreleaseCons(self._scip, &(<Constraint>pycons).scip_cons))
6387
6553
  PY_SCIP_CALL(SCIPaddCons(self._scip, disj_cons))
6388
- PyCons = Constraint.create(disj_cons)
6554
+ PyCons = self._getOrCreateCons(disj_cons)
6389
6555
  PY_SCIP_CALL(SCIPreleaseCons(self._scip, &disj_cons))
6390
6556
 
6391
6557
  return PyCons
@@ -6471,20 +6637,11 @@ cdef class Model:
6471
6637
 
6472
6638
  vars = []
6473
6639
  for i in range(nvars):
6474
- ptr = <size_t>(_vars[i])
6475
- # check whether the corresponding variable exists already
6476
- if ptr in self._modelvars:
6477
- vars.append(self._modelvars[ptr])
6478
- else:
6479
- # create a new variable
6480
- var = Variable.create(_vars[i])
6481
- assert var.ptr() == ptr
6482
- self._modelvars[ptr] = var
6483
- vars.append(var)
6640
+ vars.append(self._getOrCreateVar(_vars[i]))
6484
6641
 
6485
6642
  free(_vars)
6486
6643
  return vars
6487
-
6644
+
6488
6645
  def getConsVals(self, Constraint constraint):
6489
6646
  """
6490
6647
  Returns the value array of an arbitrary SCIP constraint that can be represented as a single linear constraint.
@@ -6560,16 +6717,7 @@ cdef class Model:
6560
6717
 
6561
6718
  vars = []
6562
6719
  for i in range(nvars):
6563
- ptr = <size_t>(_vars[i])
6564
- # check whether the corresponding variable exists already
6565
- if ptr in self._modelvars:
6566
- vars.append(self._modelvars[ptr])
6567
- else:
6568
- # create a new variable
6569
- var = Variable.create(_vars[i])
6570
- assert var.ptr() == ptr
6571
- self._modelvars[ptr] = var
6572
- vars.append(var)
6720
+ vars.append(self._getOrCreateVar(_vars[i]))
6573
6721
 
6574
6722
  return vars
6575
6723
 
@@ -6587,22 +6735,10 @@ cdef class Model:
6587
6735
  Variable
6588
6736
 
6589
6737
  """
6590
-
6591
6738
  cdef SCIP_VAR* _resultant
6592
6739
 
6593
6740
  _resultant = SCIPgetResultantAnd(self._scip, and_cons.scip_cons)
6594
-
6595
- ptr = <size_t>(_resultant)
6596
- # check whether the corresponding variable exists already
6597
- if ptr not in self._modelvars:
6598
- # create a new variable
6599
- resultant = Variable.create(_resultant)
6600
- assert resultant.ptr() == ptr
6601
- self._modelvars[ptr] = resultant
6602
- else:
6603
- resultant = self._modelvars[ptr]
6604
-
6605
- return resultant
6741
+ return self._getOrCreateVar(_resultant)
6606
6742
 
6607
6743
  def isAndConsSorted(self, Constraint and_cons):
6608
6744
  """
@@ -6691,7 +6827,10 @@ cdef class Model:
6691
6827
  else:
6692
6828
  raise NotImplementedError("Adding coefficients to %s constraints is not implemented." % constype)
6693
6829
 
6694
- 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):
6695
6834
  """
6696
6835
  Add a constraint to the given node.
6697
6836
 
@@ -6699,35 +6838,120 @@ cdef class Model:
6699
6838
  ----------
6700
6839
  node : Node
6701
6840
  node at which the constraint will be added
6702
- cons : Constraint
6703
- the constraint to add to the node
6841
+ cons : ExprCons
6842
+ the constraint expression to add to the node (e.g., x + y <= 5)
6704
6843
  validnode : Node or None, optional
6705
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.
6706
6870
 
6707
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
+
6708
6883
  if isinstance(validnode, Node):
6709
- 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))
6710
6885
  else:
6711
- PY_SCIP_CALL(SCIPaddConsNode(self._scip, node.scip_node, cons.scip_cons, NULL))
6712
- 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))
6713
6891
 
6714
- def addConsLocal(self, Constraint cons, Node validnode=None):
6892
+ return pycons
6893
+
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):
6715
6898
  """
6716
6899
  Add a constraint to the current node.
6717
6900
 
6718
6901
  Parameters
6719
6902
  ----------
6720
- cons : Constraint
6721
- 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)
6722
6905
  validnode : Node or None, optional
6723
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.
6724
6932
 
6725
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
+
6726
6945
  if isinstance(validnode, Node):
6727
- PY_SCIP_CALL(SCIPaddConsLocal(self._scip, cons.scip_cons, validnode.scip_node))
6946
+ PY_SCIP_CALL(SCIPaddConsLocal(self._scip, scip_cons, validnode.scip_node))
6728
6947
  else:
6729
- PY_SCIP_CALL(SCIPaddConsLocal(self._scip, cons.scip_cons, NULL))
6730
- 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
6731
6955
 
6732
6956
  def addConsKnapsack(self, vars, weights, capacity, name="",
6733
6957
  initial=True, separate=True, enforce=True, check=True,
@@ -6794,9 +7018,9 @@ cdef class Model:
6794
7018
 
6795
7019
  PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
6796
7020
 
6797
- pyCons = Constraint.create(scip_cons)
7021
+ pyCons = self._getOrCreateCons(scip_cons)
6798
7022
  PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
6799
-
7023
+
6800
7024
  return pyCons
6801
7025
 
6802
7026
  def addConsSOS1(self, vars, weights=None, name="",
@@ -6862,7 +7086,7 @@ cdef class Model:
6862
7086
 
6863
7087
  PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
6864
7088
 
6865
- return Constraint.create(scip_cons)
7089
+ return self._getOrCreateCons(scip_cons)
6866
7090
 
6867
7091
  def addConsSOS2(self, vars, weights=None, name="",
6868
7092
  initial=True, separate=True, enforce=True, check=True,
@@ -6927,7 +7151,7 @@ cdef class Model:
6927
7151
 
6928
7152
  PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
6929
7153
 
6930
- return Constraint.create(scip_cons)
7154
+ return self._getOrCreateCons(scip_cons)
6931
7155
 
6932
7156
  def addConsAnd(self, vars, resvar, name="",
6933
7157
  initial=True, separate=True, enforce=True, check=True,
@@ -6988,7 +7212,7 @@ cdef class Model:
6988
7212
  initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode))
6989
7213
 
6990
7214
  PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
6991
- pyCons = Constraint.create(scip_cons)
7215
+ pyCons = self._getOrCreateCons(scip_cons)
6992
7216
  PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
6993
7217
 
6994
7218
  return pyCons
@@ -7052,7 +7276,7 @@ cdef class Model:
7052
7276
  initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode))
7053
7277
 
7054
7278
  PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
7055
- pyCons = Constraint.create(scip_cons)
7279
+ pyCons = self._getOrCreateCons(scip_cons)
7056
7280
  PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
7057
7281
 
7058
7282
  return pyCons
@@ -7115,7 +7339,7 @@ cdef class Model:
7115
7339
  initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode))
7116
7340
 
7117
7341
  PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
7118
- pyCons = Constraint.create(scip_cons)
7342
+ pyCons = self._getOrCreateCons(scip_cons)
7119
7343
  PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
7120
7344
 
7121
7345
  return pyCons
@@ -7200,7 +7424,7 @@ cdef class Model:
7200
7424
  PY_SCIP_CALL(SCIPaddVarCardinality(self._scip, scip_cons, scip_var, indvar, <SCIP_Real>weights[i]))
7201
7425
 
7202
7426
  PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
7203
- pyCons = Constraint.create(scip_cons)
7427
+ pyCons = self._getOrCreateCons(scip_cons)
7204
7428
 
7205
7429
  PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
7206
7430
 
@@ -7293,7 +7517,7 @@ cdef class Model:
7293
7517
  PY_SCIP_CALL(SCIPaddVarIndicator(self._scip, scip_cons, wrapper.ptr[0], <SCIP_Real>coeff))
7294
7518
 
7295
7519
  PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
7296
- pyCons = Constraint.create(scip_cons)
7520
+ pyCons = self._getOrCreateCons(scip_cons)
7297
7521
 
7298
7522
  PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
7299
7523
 
@@ -7483,7 +7707,7 @@ cdef class Model:
7483
7707
  cdef SCIP_CONS* lincons = SCIPgetLinearConsIndicator(cons.scip_cons)
7484
7708
  if lincons == NULL:
7485
7709
  return None
7486
- return Constraint.create(lincons)
7710
+ return self._getOrCreateCons(lincons)
7487
7711
 
7488
7712
  def getSlackVarIndicator(self, Constraint cons):
7489
7713
  """
@@ -7501,7 +7725,7 @@ cdef class Model:
7501
7725
 
7502
7726
  """
7503
7727
  cdef SCIP_VAR* var = SCIPgetSlackVarIndicator(cons.scip_cons)
7504
- return Variable.create(var)
7728
+ return self._getOrCreateVar(var)
7505
7729
 
7506
7730
  def addPyCons(self, Constraint cons):
7507
7731
  """
@@ -7945,7 +8169,7 @@ cdef class Model:
7945
8169
  """
7946
8170
  cdef SCIP_CONS* transcons
7947
8171
  PY_SCIP_CALL(SCIPgetTransformedCons(self._scip, cons.scip_cons, &transcons))
7948
- return Constraint.create(transcons)
8172
+ return self._getOrCreateCons(transcons)
7949
8173
 
7950
8174
  def isNLPConstructed(self):
7951
8175
  """
@@ -8085,8 +8309,16 @@ cdef class Model:
8085
8309
  Returns
8086
8310
  -------
8087
8311
  bilinterms : list of tuple
8312
+ Triples ``(var1, var2, coef)`` for terms of the form
8313
+ ``coef * var1 * var2`` with ``var1 != var2``.
8088
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.
8089
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.
8090
8322
 
8091
8323
  """
8092
8324
  cdef SCIP_EXPR* expr
@@ -8105,6 +8337,7 @@ cdef class Model:
8105
8337
  cdef int nbilinterms
8106
8338
 
8107
8339
  # quadratic terms
8340
+ cdef SCIP_EXPR* quadexpr
8108
8341
  cdef SCIP_EXPR* sqrexpr
8109
8342
  cdef SCIP_Real sqrcoef
8110
8343
  cdef int nquadterms
@@ -8117,33 +8350,49 @@ cdef class Model:
8117
8350
  assert self.checkQuadraticNonlinear(cons), "constraint is not quadratic"
8118
8351
 
8119
8352
  expr = SCIPgetExprNonlinear(cons.scip_cons)
8120
- SCIPexprGetQuadraticData(expr, NULL, &nlinvars, &linexprs, &lincoefs, &nquadterms, &nbilinterms, NULL, NULL)
8353
+ SCIPexprGetQuadraticData(expr, NULL, &nlinvars, &linexprs, &lincoefs,
8354
+ &nquadterms, &nbilinterms, NULL, NULL)
8121
8355
 
8122
8356
  linterms = []
8123
8357
  bilinterms = []
8124
- quadterms = []
8125
8358
 
8359
+ # Purely linear terms (variables not in any quadratic/bilinear term)
8126
8360
  for termidx in range(nlinvars):
8127
- var = Variable.create(SCIPgetVarExprVar(linexprs[termidx]))
8361
+ var = self._getOrCreateVar(SCIPgetVarExprVar(linexprs[termidx]))
8128
8362
  linterms.append((var, lincoefs[termidx]))
8129
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
+
8130
8367
  for termidx in range(nbilinterms):
8131
8368
  SCIPexprGetQuadraticBilinTerm(expr, termidx, &bilinterm1, &bilinterm2, &bilincoef, NULL, NULL)
8132
8369
  scipvar1 = SCIPgetVarExprVar(bilinterm1)
8133
8370
  scipvar2 = SCIPgetVarExprVar(bilinterm2)
8134
- var1 = Variable.create(scipvar1)
8135
- var2 = Variable.create(scipvar2)
8371
+ var1 = self._getOrCreateVar(scipvar1)
8372
+ var2 = self._getOrCreateVar(scipvar2)
8136
8373
  if scipvar1 != scipvar2:
8137
- bilinterms.append((var1,var2,bilincoef))
8374
+ bilinterms.append((var1, var2, bilincoef))
8138
8375
  else:
8139
- quadterms.append((var1,bilincoef,0.0))
8140
-
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
8141
8384
  for termidx in range(nquadterms):
8142
- SCIPexprGetQuadraticQuadTerm(expr, termidx, NULL, &lincoef, &sqrcoef, NULL, NULL, &sqrexpr)
8143
- if sqrexpr == NULL:
8144
- continue
8145
- var = Variable.create(SCIPgetVarExprVar(sqrexpr))
8146
- 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()]
8147
8396
 
8148
8397
  return (bilinterms, quadterms, linterms)
8149
8398
 
@@ -8202,7 +8451,7 @@ cdef class Model:
8202
8451
  conss = SCIPgetOrigConss(self._scip)
8203
8452
  nconss = SCIPgetNOrigConss(self._scip)
8204
8453
 
8205
- return [Constraint.create(conss[i]) for i in range(nconss)]
8454
+ return [self._getOrCreateCons(conss[i]) for i in range(nconss)]
8206
8455
 
8207
8456
  def getNConss(self, transformed=True):
8208
8457
  """
@@ -8233,7 +8482,10 @@ cdef class Model:
8233
8482
  constraint to be deleted
8234
8483
 
8235
8484
  """
8485
+ del self._modelconss[cons.ptr()]
8236
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
8237
8489
 
8238
8490
  def delConsLocal(self, Constraint cons):
8239
8491
  """
@@ -8833,11 +9085,8 @@ cdef class Model:
8833
9085
  PY_SCIP_CALL(SCIPgetBendersSubproblemVar(self._scip, _benders, var.scip_var, &_mappedvar, probnumber))
8834
9086
 
8835
9087
  if _mappedvar == NULL:
8836
- mappedvar = None
8837
- else:
8838
- mappedvar = Variable.create(_mappedvar)
8839
-
8840
- return mappedvar
9088
+ return None
9089
+ return self._getOrCreateVar(_mappedvar)
8841
9090
 
8842
9091
  def getBendersAuxiliaryVar(self, probnumber, Benders benders = None):
8843
9092
  """
@@ -8864,9 +9113,7 @@ cdef class Model:
8864
9113
  _benders = benders._benders
8865
9114
 
8866
9115
  _auxvar = SCIPbendersGetAuxiliaryVar(_benders, probnumber)
8867
- auxvar = Variable.create(_auxvar)
8868
-
8869
- return auxvar
9116
+ return self._getOrCreateVar(_auxvar)
8870
9117
 
8871
9118
  def checkBendersSubproblemOptimality(self, Solution solution, probnumber, Benders benders = None):
8872
9119
  """
@@ -9659,7 +9906,7 @@ cdef class Model:
9659
9906
  PY_SCIP_CALL(SCIPgetLPBranchCands(self._scip, &lpcands, &lpcandssol, &lpcandsfrac,
9660
9907
  &nlpcands, &npriolpcands, &nfracimplvars))
9661
9908
 
9662
- 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)],
9663
9910
  [lpcandsfrac[i] for i in range(nlpcands)], nlpcands, npriolpcands, nfracimplvars)
9664
9911
 
9665
9912
  def getNLPBranchCands(self):
@@ -9696,7 +9943,7 @@ cdef class Model:
9696
9943
 
9697
9944
  PY_SCIP_CALL(SCIPgetPseudoBranchCands(self._scip, &pseudocands, &npseudocands, &npriopseudocands))
9698
9945
 
9699
- 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)
9700
9947
 
9701
9948
  def branchVar(self, Variable variable):
9702
9949
  """
@@ -10671,7 +10918,10 @@ cdef class Model:
10671
10918
  Solution or None
10672
10919
 
10673
10920
  """
10674
- 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)
10675
10925
  return self._bestSol
10676
10926
 
10677
10927
  def getSolObjVal(self, Solution sol, original=True):
@@ -10714,6 +10964,8 @@ cdef class Model:
10714
10964
  float
10715
10965
 
10716
10966
  """
10967
+ if sol is None or sol.sol == NULL:
10968
+ raise ValueError("Cannot get solution time: solution is None or NULL")
10717
10969
  return SCIPgetSolTime(self._scip, sol.sol)
10718
10970
 
10719
10971
  def getObjVal(self, original=True):
@@ -10747,20 +10999,26 @@ cdef class Model:
10747
10999
 
10748
11000
  return self.getSolObjVal(self._bestSol, original)
10749
11001
 
10750
- 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]:
10751
11007
  """
10752
- Retrieve value of given variable or expression in the given solution or in
10753
- the LP/pseudo solution if sol == None
11008
+ Retrieve value of given variable or expression in the given solution.
10754
11009
 
10755
11010
  Parameters
10756
11011
  ----------
10757
11012
  sol : Solution
10758
- expr : Expr
10759
- 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.
10760
11018
 
10761
11019
  Returns
10762
11020
  -------
10763
- float
11021
+ float or np.ndarray
10764
11022
 
10765
11023
  Notes
10766
11024
  -----
@@ -10768,46 +11026,39 @@ cdef class Model:
10768
11026
 
10769
11027
  """
10770
11028
  # no need to create a NULL solution wrapper in case we have a variable
10771
- cdef _VarArray wrapper
10772
- if sol == None and isinstance(expr, Variable):
10773
- wrapper = _VarArray(expr)
10774
- return SCIPgetSolVal(self._scip, NULL, wrapper.ptr[0])
10775
- if sol == None:
10776
- sol = Solution.create(self._scip, NULL)
10777
- return sol[expr]
11029
+ return (sol or Solution.create(self._scip, NULL))[expr]
10778
11030
 
10779
- def getVal(self, expr: Union[Expr, MatrixExpr] ):
11031
+ def getVal(self, expr: Union[Expr, GenExpr, MatrixExpr]) -> Union[float, np.ndarray]:
10780
11032
  """
10781
11033
  Retrieve the value of the given variable or expression in the best known solution.
10782
11034
  Can only be called after solving is completed.
10783
11035
 
10784
11036
  Parameters
10785
11037
  ----------
10786
- expr : Expr ot MatrixExpr
10787
- polynomial expression to query the value of
11038
+ expr : Expr, GenExpr or MatrixExpr
11039
+ Expression to query the value of.
10788
11040
 
10789
11041
  Returns
10790
11042
  -------
10791
- float
11043
+ float or np.ndarray
10792
11044
 
10793
11045
  Notes
10794
11046
  -----
10795
11047
  A variable is also an expression.
10796
11048
 
10797
11049
  """
10798
- stage_check = SCIPgetStage(self._scip) not in [SCIP_STAGE_INIT, SCIP_STAGE_FREE]
10799
-
10800
- 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}:
10801
11051
  raise Warning("Method cannot be called in stage ", self.getStage())
10802
11052
 
10803
- if isinstance(expr, MatrixExpr):
10804
- result = np.empty(expr.shape, dtype=float)
10805
- for idx in np.ndindex(result.shape):
10806
- result[idx] = self.getSolVal(self._bestSol, expr[idx])
10807
- else:
10808
- 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)
10809
11057
 
10810
- 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]
10811
11062
 
10812
11063
  def hasPrimalRay(self):
10813
11064
  """
@@ -11623,12 +11874,12 @@ cdef class Model:
11623
11874
 
11624
11875
  def chgReoptObjective(self, coeffs, sense = 'minimize'):
11625
11876
  """
11626
- Establish the objective function as a linear expression.
11877
+ Change the objective function for reoptimization.
11627
11878
 
11628
11879
  Parameters
11629
11880
  ----------
11630
- coeffs : list of float
11631
- the coefficients
11881
+ coeffs : Expr
11882
+ the coefficients as a linear expression
11632
11883
  sense : str
11633
11884
  the objective sense (Default value = 'minimize')
11634
11885
 
@@ -11637,7 +11888,6 @@ cdef class Model:
11637
11888
  cdef int nvars
11638
11889
  cdef SCIP_Real* _coeffs
11639
11890
  cdef SCIP_OBJSENSE objsense
11640
- cdef SCIP_Real coef
11641
11891
  cdef int i
11642
11892
  cdef _VarArray wrapper
11643
11893
 
@@ -11655,24 +11905,27 @@ cdef class Model:
11655
11905
  if coeffs[CONST] != 0.0:
11656
11906
  raise ValueError("Constant offsets in objective are not supported!")
11657
11907
 
11658
- vars = SCIPgetOrigVars(self._scip)
11659
- nvars = SCIPgetNOrigVars(self._scip)
11660
- _coeffs = <SCIP_Real*> malloc(nvars * sizeof(SCIP_Real))
11908
+ nvars = len(coeffs.terms) - (CONST in coeffs.terms)
11661
11909
 
11662
- for i in range(nvars):
11663
- _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*))
11664
11916
 
11917
+ i = 0
11665
11918
  for term, coef in coeffs.terms.items():
11666
11919
  # avoid CONST term of Expr
11667
11920
  if term != CONST:
11668
- assert len(term) == 1
11669
- for i in range(nvars):
11670
- wrapper = _VarArray(term[0])
11671
- if vars[i] == wrapper.ptr[0]:
11672
- _coeffs[i] = coef
11921
+ wrapper = _VarArray(term[0])
11922
+ vars[i] = wrapper.ptr[0]
11923
+ _coeffs[i] = coef
11924
+ i += 1
11673
11925
 
11674
- PY_SCIP_CALL(SCIPchgReoptObjective(self._scip, objsense, vars, &_coeffs[0], nvars))
11926
+ PY_SCIP_CALL(SCIPchgReoptObjective(self._scip, objsense, vars, _coeffs, nvars))
11675
11927
 
11928
+ free(vars)
11676
11929
  free(_coeffs)
11677
11930
 
11678
11931
  def chgVarBranchPriority(self, Variable var, priority):