forked from scipopt/PySCIPOpt
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathscip.pyx
2379 lines (1919 loc) · 94.3 KB
/
scip.pyx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import weakref
from os.path import abspath
import sys
import warnings
from cpython cimport Py_INCREF, Py_DECREF
from libc.stdlib cimport malloc, free
from libc.stdio cimport fdopen
include "expr.pxi"
include "lp.pxi"
include "branchrule.pxi"
include "conshdlr.pxi"
include "event.pxi"
include "heuristic.pxi"
include "presol.pxi"
include "pricer.pxi"
include "propagator.pxi"
include "sepa.pxi"
# recommended SCIP version; major version is required
MAJOR = 5
MINOR = 0
PATCH = 1
# for external user functions use def; for functions used only inside the interface (starting with _) use cdef
# todo: check whether this is currently done like this
if sys.version_info >= (3, 0):
str_conversion = lambda x:bytes(x,'utf-8')
else:
str_conversion = lambda x:x
# Mapping the SCIP_RESULT enum to a python class
# This is required to return SCIP_RESULT in the python code
# In __init__.py this is imported as SCIP_RESULT to keep the
# original naming scheme using capital letters
cdef class PY_SCIP_RESULT:
DIDNOTRUN = SCIP_DIDNOTRUN
DELAYED = SCIP_DELAYED
DIDNOTFIND = SCIP_DIDNOTFIND
FEASIBLE = SCIP_FEASIBLE
INFEASIBLE = SCIP_INFEASIBLE
UNBOUNDED = SCIP_UNBOUNDED
CUTOFF = SCIP_CUTOFF
SEPARATED = SCIP_SEPARATED
NEWROUND = SCIP_NEWROUND
REDUCEDDOM = SCIP_REDUCEDDOM
CONSADDED = SCIP_CONSADDED
CONSCHANGED = SCIP_CONSCHANGED
BRANCHED = SCIP_BRANCHED
SOLVELP = SCIP_SOLVELP
FOUNDSOL = SCIP_FOUNDSOL
SUSPENDED = SCIP_SUSPENDED
SUCCESS = SCIP_SUCCESS
cdef class PY_SCIP_PARAMSETTING:
DEFAULT = SCIP_PARAMSETTING_DEFAULT
AGGRESSIVE = SCIP_PARAMSETTING_AGGRESSIVE
FAST = SCIP_PARAMSETTING_FAST
OFF = SCIP_PARAMSETTING_OFF
cdef class PY_SCIP_PARAMEMPHASIS:
DEFAULT = SCIP_PARAMEMPHASIS_DEFAULT
CPSOLVER = SCIP_PARAMEMPHASIS_CPSOLVER
EASYCIP = SCIP_PARAMEMPHASIS_EASYCIP
FEASIBILITY = SCIP_PARAMEMPHASIS_FEASIBILITY
HARDLP = SCIP_PARAMEMPHASIS_HARDLP
OPTIMALITY = SCIP_PARAMEMPHASIS_OPTIMALITY
COUNTER = SCIP_PARAMEMPHASIS_COUNTER
PHASEFEAS = SCIP_PARAMEMPHASIS_PHASEFEAS
PHASEIMPROVE = SCIP_PARAMEMPHASIS_PHASEIMPROVE
PHASEPROOF = SCIP_PARAMEMPHASIS_PHASEPROOF
cdef class PY_SCIP_STATUS:
UNKNOWN = SCIP_STATUS_UNKNOWN
USERINTERRUPT = SCIP_STATUS_USERINTERRUPT
NODELIMIT = SCIP_STATUS_NODELIMIT
TOTALNODELIMIT = SCIP_STATUS_TOTALNODELIMIT
STALLNODELIMIT = SCIP_STATUS_STALLNODELIMIT
TIMELIMIT = SCIP_STATUS_TIMELIMIT
MEMLIMIT = SCIP_STATUS_MEMLIMIT
GAPLIMIT = SCIP_STATUS_GAPLIMIT
SOLLIMIT = SCIP_STATUS_SOLLIMIT
BESTSOLLIMIT = SCIP_STATUS_BESTSOLLIMIT
RESTARTLIMIT = SCIP_STATUS_RESTARTLIMIT
OPTIMAL = SCIP_STATUS_OPTIMAL
INFEASIBLE = SCIP_STATUS_INFEASIBLE
UNBOUNDED = SCIP_STATUS_UNBOUNDED
INFORUNBD = SCIP_STATUS_INFORUNBD
cdef class PY_SCIP_STAGE:
INIT = SCIP_STAGE_INIT
PROBLEM = SCIP_STAGE_PROBLEM
TRANSFORMING = SCIP_STAGE_TRANSFORMING
TRANSFORMED = SCIP_STAGE_TRANSFORMED
INITPRESOLVE = SCIP_STAGE_INITPRESOLVE
PRESOLVING = SCIP_STAGE_PRESOLVING
EXITPRESOLVE = SCIP_STAGE_EXITPRESOLVE
PRESOLVED = SCIP_STAGE_PRESOLVED
INITSOLVE = SCIP_STAGE_INITSOLVE
SOLVING = SCIP_STAGE_SOLVING
SOLVED = SCIP_STAGE_SOLVED
EXITSOLVE = SCIP_STAGE_EXITSOLVE
FREETRANS = SCIP_STAGE_FREETRANS
FREE = SCIP_STAGE_FREE
cdef class PY_SCIP_NODETYPE:
FOCUSNODE = SCIP_NODETYPE_FOCUSNODE
PROBINGNODE = SCIP_NODETYPE_PROBINGNODE
SIBLING = SCIP_NODETYPE_SIBLING
CHILD = SCIP_NODETYPE_CHILD
LEAF = SCIP_NODETYPE_LEAF
DEADEND = SCIP_NODETYPE_DEADEND
JUNCTION = SCIP_NODETYPE_JUNCTION
PSEUDOFORK = SCIP_NODETYPE_PSEUDOFORK
FORK = SCIP_NODETYPE_FORK
SUBROOT = SCIP_NODETYPE_SUBROOT
REFOCUSNODE = SCIP_NODETYPE_REFOCUSNODE
cdef class PY_SCIP_PROPTIMING:
BEFORELP = SCIP_PROPTIMING_BEFORELP
DURINGLPLOOP = SCIP_PROPTIMING_DURINGLPLOOP
AFTERLPLOOP = SCIP_PROPTIMING_AFTERLPLOOP
AFTERLPNODE = SCIP_PROPTIMING_AFTERLPNODE
cdef class PY_SCIP_PRESOLTIMING:
NONE = SCIP_PRESOLTIMING_NONE
FAST = SCIP_PRESOLTIMING_FAST
MEDIUM = SCIP_PRESOLTIMING_MEDIUM
EXHAUSTIVE = SCIP_PRESOLTIMING_EXHAUSTIVE
cdef class PY_SCIP_HEURTIMING:
BEFORENODE = SCIP_HEURTIMING_BEFORENODE
DURINGLPLOOP = SCIP_HEURTIMING_DURINGLPLOOP
AFTERLPLOOP = SCIP_HEURTIMING_AFTERLPLOOP
AFTERLPNODE = SCIP_HEURTIMING_AFTERLPNODE
AFTERPSEUDONODE = SCIP_HEURTIMING_AFTERPSEUDONODE
AFTERLPPLUNGE = SCIP_HEURTIMING_AFTERLPPLUNGE
AFTERPSEUDOPLUNGE = SCIP_HEURTIMING_AFTERPSEUDOPLUNGE
DURINGPRICINGLOOP = SCIP_HEURTIMING_DURINGPRICINGLOOP
BEFOREPRESOL = SCIP_HEURTIMING_BEFOREPRESOL
DURINGPRESOLLOOP = SCIP_HEURTIMING_DURINGPRESOLLOOP
AFTERPROPLOOP = SCIP_HEURTIMING_AFTERPROPLOOP
cdef class PY_SCIP_EVENTTYPE:
DISABLED = SCIP_EVENTTYPE_DISABLED
VARADDED = SCIP_EVENTTYPE_VARADDED
VARDELETED = SCIP_EVENTTYPE_VARDELETED
VARFIXED = SCIP_EVENTTYPE_VARFIXED
VARUNLOCKED = SCIP_EVENTTYPE_VARUNLOCKED
OBJCHANGED = SCIP_EVENTTYPE_OBJCHANGED
GLBCHANGED = SCIP_EVENTTYPE_GLBCHANGED
GUBCHANGED = SCIP_EVENTTYPE_GUBCHANGED
LBTIGHTENED = SCIP_EVENTTYPE_LBTIGHTENED
LBRELAXED = SCIP_EVENTTYPE_LBRELAXED
UBTIGHTENED = SCIP_EVENTTYPE_UBTIGHTENED
UBRELAXED = SCIP_EVENTTYPE_UBRELAXED
GHOLEADDED = SCIP_EVENTTYPE_GHOLEADDED
GHOLEREMOVED = SCIP_EVENTTYPE_GHOLEREMOVED
LHOLEADDED = SCIP_EVENTTYPE_LHOLEADDED
LHOLEREMOVED = SCIP_EVENTTYPE_LHOLEREMOVED
IMPLADDED = SCIP_EVENTTYPE_IMPLADDED
PRESOLVEROUND = SCIP_EVENTTYPE_PRESOLVEROUND
NODEFOCUSED = SCIP_EVENTTYPE_NODEFOCUSED
NODEFEASIBLE = SCIP_EVENTTYPE_NODEFEASIBLE
NODEINFEASIBLE = SCIP_EVENTTYPE_NODEINFEASIBLE
NODEBRANCHED = SCIP_EVENTTYPE_NODEBRANCHED
FIRSTLPSOLVED = SCIP_EVENTTYPE_FIRSTLPSOLVED
LPSOLVED = SCIP_EVENTTYPE_LPSOLVED
LPEVENT = SCIP_EVENTTYPE_LPEVENT
POORSOLFOUND = SCIP_EVENTTYPE_POORSOLFOUND
BESTSOLFOUND = SCIP_EVENTTYPE_BESTSOLFOUND
ROWADDEDSEPA = SCIP_EVENTTYPE_ROWADDEDSEPA
ROWDELETEDSEPA = SCIP_EVENTTYPE_ROWDELETEDSEPA
ROWADDEDLP = SCIP_EVENTTYPE_ROWADDEDLP
ROWDELETEDLP = SCIP_EVENTTYPE_ROWDELETEDLP
ROWCOEFCHANGED = SCIP_EVENTTYPE_ROWCOEFCHANGED
ROWCONSTCHANGED = SCIP_EVENTTYPE_ROWCONSTCHANGED
ROWSIDECHANGED = SCIP_EVENTTYPE_ROWSIDECHANGED
SYNC = SCIP_EVENTTYPE_SYNC
def PY_SCIP_CALL(SCIP_RETCODE rc):
if rc == SCIP_OKAY:
pass
elif rc == SCIP_ERROR:
raise Exception('SCIP: unspecified error!')
elif rc == SCIP_NOMEMORY:
raise MemoryError('SCIP: insufficient memory error!')
elif rc == SCIP_READERROR:
raise IOError('SCIP: read error!')
elif rc == SCIP_WRITEERROR:
raise IOError('SCIP: write error!')
elif rc == SCIP_NOFILE:
raise IOError('SCIP: file not found error!')
elif rc == SCIP_FILECREATEERROR:
raise IOError('SCIP: cannot create file!')
elif rc == SCIP_LPERROR:
raise Exception('SCIP: error in LP solver!')
elif rc == SCIP_NOPROBLEM:
raise Exception('SCIP: no problem exists!')
elif rc == SCIP_INVALIDCALL:
raise Exception('SCIP: method cannot be called at this time'
+ ' in solution process!')
elif rc == SCIP_INVALIDDATA:
raise Exception('SCIP: error in input data!')
elif rc == SCIP_INVALIDRESULT:
raise Exception('SCIP: method returned an invalid result code!')
elif rc == SCIP_PLUGINNOTFOUND:
raise Exception('SCIP: a required plugin was not found !')
elif rc == SCIP_PARAMETERUNKNOWN:
raise KeyError('SCIP: the parameter with the given name was not found!')
elif rc == SCIP_PARAMETERWRONGTYPE:
raise LookupError('SCIP: the parameter is not of the expected type!')
elif rc == SCIP_PARAMETERWRONGVAL:
raise ValueError('SCIP: the value is invalid for the given parameter!')
elif rc == SCIP_KEYALREADYEXISTING:
raise KeyError('SCIP: the given key is already existing in table!')
elif rc == SCIP_MAXDEPTHLEVEL:
raise Exception('SCIP: maximal branching depth level exceeded!')
else:
raise Exception('SCIP: unknown return code!')
cdef class Event:
cdef SCIP_EVENT* event
@staticmethod
cdef create(SCIP_EVENT* scip_event):
event = Event()
event.event = scip_event
return event
def getType(self):
return SCIPeventGetType(self.event)
def __repr__(self):
return self.getType()
cdef class Column:
"""Base class holding a pointer to corresponding SCIP_COL"""
cdef SCIP_COL* col
@staticmethod
cdef create(SCIP_COL* scip_col):
col = Column()
col.col = scip_col
return col
cdef class Row:
"""Base class holding a pointer to corresponding SCIP_ROW"""
cdef SCIP_ROW* row
@staticmethod
cdef create(SCIP_ROW* scip_row):
row = Row()
row.row = scip_row
return row
cdef class Solution:
"""Base class holding a pointer to corresponding SCIP_SOL"""
cdef SCIP_SOL* sol
@staticmethod
cdef create(SCIP_SOL* scip_sol):
sol = Solution()
sol.sol = scip_sol
return sol
cdef class Node:
"""Base class holding a pointer to corresponding SCIP_NODE"""
cdef SCIP_NODE* node
@staticmethod
cdef create(SCIP_NODE* scip_node):
node = Node()
node.node = scip_node
return node
def getParent(self):
"""Retrieve parent node."""
return Node.create(SCIPnodeGetParent(self.node))
def getNumber(self):
"""Retrieve number of node."""
return SCIPnodeGetNumber(self.node)
def getDepth(self):
"""Retrieve depth of node."""
return SCIPnodeGetDepth(self.node)
def getType(self):
"""Retrieve type of node."""
return SCIPnodeGetType(self.node)
def getLowerbound(self):
"""Retrieve lower bound of node."""
return SCIPnodeGetLowerbound(self.node)
def getEstimate(self):
"""Retrieve the estimated value of the best feasible solution in subtree of the node"""
return SCIPnodeGetEstimate(self.node)
def getNAddedConss(self):
"""Retrieve number of added constraints at this node"""
return SCIPnodeGetNAddedConss(self.node)
def isActive(self):
"""Is the node in the path to the current node?"""
return SCIPnodeIsActive(self.node)
def isPropagatedAgain(self):
"""Is the node marked to be propagated again?"""
return SCIPnodeIsPropagatedAgain(self.node)
cdef class Variable(Expr):
"""Is a linear expression and has SCIP_VAR*"""
cdef SCIP_VAR* var
@staticmethod
cdef create(SCIP_VAR* scipvar):
var = Variable()
var.var = scipvar
Expr.__init__(var, {Term(var) : 1.0})
return var
property name:
def __get__(self):
cname = bytes( SCIPvarGetName(self.var) )
return cname.decode('utf-8')
def ptr(self):
""" """
return <size_t>(self.var)
def __repr__(self):
return self.name
def vtype(self):
"""Retrieve the variables type (BINARY, INTEGER or CONTINUOUS)"""
vartype = SCIPvarGetType(self.var)
if vartype == SCIP_VARTYPE_BINARY:
return "BINARY"
elif vartype == SCIP_VARTYPE_INTEGER:
return "INTEGER"
elif vartype == SCIP_VARTYPE_CONTINUOUS or vartype == SCIP_VARTYPE_IMPLINT:
return "CONTINUOUS"
def isOriginal(self):
"""Retrieve whether the variable belongs to the original problem"""
return SCIPvarIsOriginal(self.var)
def isInLP(self):
"""Retrieve whether the variable is a COLUMN variable that is member of the current LP"""
return SCIPvarIsInLP(self.var)
def getCol(self):
"""Retrieve column of COLUMN variable"""
cdef SCIP_COL* scip_col
scip_col = SCIPvarGetCol(self.var)
return Column.create(scip_col)
def getLbOriginal(self):
"""Retrieve original lower bound of variable"""
return SCIPvarGetLbOriginal(self.var)
def getUbOriginal(self):
"""Retrieve original upper bound of variable"""
return SCIPvarGetUbOriginal(self.var)
def getLbGlobal(self):
"""Retrieve global lower bound of variable"""
return SCIPvarGetLbGlobal(self.var)
def getUbGlobal(self):
"""Retrieve global upper bound of variable"""
return SCIPvarGetUbGlobal(self.var)
def getLbLocal(self):
"""Retrieve current lower bound of variable"""
return SCIPvarGetLbLocal(self.var)
def getUbLocal(self):
"""Retrieve current upper bound of variable"""
return SCIPvarGetUbLocal(self.var)
def getObj(self):
"""Retrieve current objective value of variable"""
return SCIPvarGetObj(self.var)
def getLPSol(self):
"""Retrieve the current LP solution value of variable"""
return SCIPvarGetLPSol(self.var)
cdef class Constraint:
cdef SCIP_CONS* cons
cdef public object data #storage for python user
@staticmethod
cdef create(SCIP_CONS* scipcons):
if scipcons == NULL:
raise Warning("cannot create Constraint with SCIP_CONS* == NULL")
cons = Constraint()
cons.cons = scipcons
return cons
property name:
def __get__(self):
cname = bytes( SCIPconsGetName(self.cons) )
return cname.decode('utf-8')
def __repr__(self):
return self.name
def isOriginal(self):
"""Retrieve whether the constraint belongs to the original problem"""
return SCIPconsIsOriginal(self.cons)
def isInitial(self):
"""Retrieve True if the relaxation of the constraint should be in the initial LP"""
return SCIPconsIsInitial(self.cons)
def isSeparated(self):
"""Retrieve True if constraint should be separated during LP processing"""
return SCIPconsIsSeparated(self.cons)
def isEnforced(self):
"""Retrieve True if constraint should be enforced during node processing"""
return SCIPconsIsEnforced(self.cons)
def isChecked(self):
"""Retrieve True if conestraint should be checked for feasibility"""
return SCIPconsIsChecked(self.cons)
def isPropagated(self):
"""Retrieve True if constraint should be propagated during node processing"""
return SCIPconsIsPropagated(self.cons)
def isLocal(self):
"""Retrieve True if constraint is only locally valid or not added to any (sub)problem"""
return SCIPconsIsLocal(self.cons)
def isModifiable(self):
"""Retrieve True if constraint is modifiable (subject to column generation)"""
return SCIPconsIsModifiable(self.cons)
def isDynamic(self):
"""Retrieve True if constraint is subject to aging"""
return SCIPconsIsDynamic(self.cons)
def isRemovable(self):
"""Retrieve True if constraint's relaxation should be removed from the LP due to aging or cleanup"""
return SCIPconsIsRemovable(self.cons)
def isStickingAtNode(self):
"""Retrieve True if constraint is only locally valid or not added to any (sub)problem"""
return SCIPconsIsStickingAtNode(self.cons)
# - remove create(), includeDefaultPlugins(), createProbBasic() methods
# - replace free() by "destructor"
# - interface SCIPfreeProb()
cdef class Model:
cdef SCIP* _scip
# store best solution to get the solution values easier
cdef Solution _bestSol
# can be used to store problem data
cdef public object data
# make Model weak referentiable
cdef object __weakref__
def __init__(self, problemName='model', defaultPlugins=True):
"""
:param problemName: name of the problem (default 'model')
:param defaultPlugins: use default plugins? (default True)
"""
if self.version() < MAJOR:
raise Exception("linked SCIP is not compatible to this version of PySCIPOpt - use at least version", MAJOR)
if self.version() < MAJOR + MINOR/10.0 + PATCH/100.0:
warnings.warn("linked SCIP {} is not recommended for this version of PySCIPOpt - use version {}.{}.{}".format(self.version(), MAJOR, MINOR, PATCH))
self.create()
self._bestSol = None
if defaultPlugins:
self.includeDefaultPlugins()
self.createProbBasic(problemName)
def __dealloc__(self):
# call C function directly, because we can no longer call this object's methods, according to
# http://docs.cython.org/src/reference/extension_types.html#finalization-dealloc
PY_SCIP_CALL( SCIPfree(&self._scip) )
def create(self):
"""Create a new SCIP instance"""
PY_SCIP_CALL(SCIPcreate(&self._scip))
def includeDefaultPlugins(self):
"""Includes all default plug-ins into SCIP"""
PY_SCIP_CALL(SCIPincludeDefaultPlugins(self._scip))
def createProbBasic(self, problemName='model'):
"""Create new problem iinstance with given name
:param problemName: name of model or problem (Default value = 'model')
"""
n = str_conversion(problemName)
PY_SCIP_CALL(SCIPcreateProbBasic(self._scip, n))
def freeProb(self):
"""Frees problem and solution process data"""
PY_SCIP_CALL(SCIPfreeProb(self._scip))
def freeTransform(self):
"""Frees all solution process data including presolving and transformed problem, only original problem is kept"""
PY_SCIP_CALL(SCIPfreeTransform(self._scip))
def version(self):
"""Retrieve SCIP version"""
return SCIPversion()
def printVersion(self):
"""Print version, copyright information and compile mode"""
SCIPprintVersion(self._scip, NULL)
def getProbName(self):
"""Retrieve problem name"""
return bytes(SCIPgetProbName(self._scip)).decode('UTF-8')
def getTotalTime(self):
"""Retrieve the current total SCIP time in seconds, i.e. the total time since the SCIP instance has been created"""
return SCIPgetTotalTime(self._scip)
def getSolvingTime(self):
"""Retrieve the current solving time in seconds"""
return SCIPgetSolvingTime(self._scip)
def getReadingTime(self):
"""Retrieve the current reading time in seconds"""
return SCIPgetReadingTime(self._scip)
def getPresolvingTime(self):
"""Retrieve the curernt presolving time in seconds"""
return SCIPgetPresolvingTime(self._scip)
def getNNodes(self):
"""Retrieve the total number of processed nodes."""
return SCIPgetNNodes(self._scip)
def getCurrentNode(self):
"""Retrieve current node."""
return Node.create(SCIPgetCurrentNode(self._scip))
def getGap(self):
"""Retrieve the gap, i.e. |(primalbound - dualbound)/min(|primalbound|,|dualbound|)|."""
return SCIPgetGap(self._scip)
def getDepth(self):
"""Retrieve the depth of the current node"""
return SCIPgetDepth(self._scip)
def infinity(self):
"""Retrieve SCIP's infinity value"""
return SCIPinfinity(self._scip)
def epsilon(self):
"""Retrieve epsilon for e.g. equality checks"""
return SCIPepsilon(self._scip)
def feastol(self):
"""Retrieve feasibility tolerance"""
return SCIPfeastol(self._scip)
# Objective function
def setMinimize(self):
"""Set the objective sense to minimization."""
PY_SCIP_CALL(SCIPsetObjsense(self._scip, SCIP_OBJSENSE_MINIMIZE))
def setMaximize(self):
"""Set the objective sense to maximization."""
PY_SCIP_CALL(SCIPsetObjsense(self._scip, SCIP_OBJSENSE_MAXIMIZE))
def setObjlimit(self, objlimit):
"""Set a limit on the objective function.
Only solutions with objective value better than this limit are accepted.
:param objlimit: limit on the objective function
"""
PY_SCIP_CALL(SCIPsetObjlimit(self._scip, objlimit))
def setObjective(self, coeffs, sense = 'minimize', clear = 'true'):
"""Establish the objective function as a linear expression.
:param coeffs: the coefficients
:param sense: the objective sense (Default value = 'minimize')
:param clear: set all other variables objective coefficient to zero (Default value = 'true')
"""
cdef SCIP_VAR** _vars
cdef int _nvars
assert isinstance(coeffs, Expr)
if coeffs.degree() > 1:
raise ValueError("Nonlinear objective functions are not supported!")
if coeffs[CONST] != 0.0:
self.addObjoffset(coeffs[CONST])
if clear:
# clear existing objective function
_vars = SCIPgetOrigVars(self._scip)
_nvars = SCIPgetNOrigVars(self._scip)
for i in range(_nvars):
PY_SCIP_CALL(SCIPchgVarObj(self._scip, _vars[i], 0.0))
for term, coef in coeffs.terms.items():
# avoid CONST term of Expr
if term != CONST:
assert len(term) == 1
var = <Variable>term[0]
PY_SCIP_CALL(SCIPchgVarObj(self._scip, var.var, coef))
if sense == "minimize":
self.setMinimize()
elif sense == "maximize":
self.setMaximize()
else:
raise Warning("unrecognized optimization sense: %s" % sense)
def getObjective(self):
"""Retrieve objective function as Expr"""
variables = self.getVars()
objective = Expr()
for var in variables:
coeff = var.getObj()
if coeff != 0:
objective += coeff * var
objective.normalize()
return objective
def addObjoffset(self, offset, solutions = False):
"""Add constant offset to objective
:param offset: offset to add
:param solutions: add offset also to existing solutions (Default value = False)
"""
if solutions:
PY_SCIP_CALL(SCIPaddObjoffset(self._scip, offset))
else:
PY_SCIP_CALL(SCIPaddOrigObjoffset(self._scip, offset))
def getObjoffset(self, original = True):
"""Retrieve constant objective offset
:param original: offset of original or transformed problem (Default value = True)
"""
if original:
return SCIPgetOrigObjoffset(self._scip)
else:
return SCIPgetTransObjoffset(self._scip)
# Setting parameters
def setPresolve(self, setting):
"""Set presolving parameter settings.
:param setting: the parameter settings (SCIP_PARAMSETTING)
"""
PY_SCIP_CALL(SCIPsetPresolving(self._scip, setting, True))
def setSeparating(self, setting):
"""Set separating parameter settings.
:param setting: the parameter settings (SCIP_PARAMSETTING)
"""
PY_SCIP_CALL(SCIPsetSeparating(self._scip, setting, True))
def setHeuristics(self, setting):
"""Set heuristics parameter settings.
:param setting: the parameter setting (SCIP_PARAMSETTING)
"""
PY_SCIP_CALL(SCIPsetHeuristics(self._scip, setting, True))
def disablePropagation(self, onlyroot=False):
"""Disables propagation in SCIP to avoid modifying the original problem during transformation.
:param onlyroot: use propagation when root processing is finished (Default value = False)
"""
self.setIntParam("propagating/maxroundsroot", 0)
if not onlyroot:
self.setIntParam("propagating/maxrounds", 0)
# Write original problem to file
def writeProblem(self, filename='origprob.cip', trans=False):
"""Write original problem to a file.
:param filename: the name of the file to be used (Default value = 'origprob.cip')
:param trans: indicates whether the transformed problem is written to file (Default value = False)
"""
if filename.find('.') < 0:
filename = filename + '.cip'
ext = str_conversion('cip')
else:
ext = str_conversion(filename.split('.')[1])
fn = str_conversion(filename)
if trans:
PY_SCIP_CALL(SCIPwriteTransProblem(self._scip, fn, ext, False))
else:
PY_SCIP_CALL(SCIPwriteOrigProblem(self._scip, fn, ext, False))
print('wrote original problem to file ' + filename)
# Variable Functions
def addVar(self, name='', vtype='C', lb=0.0, ub=None, obj=0.0, pricedVar = False):
"""Create a new variable.
:param name: name of the variable, generic if empty (Default value = '')
:param vtype: type of the variable (Default value = 'C')
:param lb: lower bound of the variable, use None for -infinity (Default value = 0.0)
:param ub: upper bound of the variable, use None for +infinity (Default value = None)
:param obj: objective value of variable (Default value = 0.0)
:param pricedVar: is the variable a pricing candidate? (Default value = False)
"""
# replace empty name with generic one
if name == '':
name = 'x'+str(SCIPgetNVars(self._scip)+1)
cname = str_conversion(name)
if ub is None:
ub = SCIPinfinity(self._scip)
if lb is None:
lb = -SCIPinfinity(self._scip)
cdef SCIP_VAR* scip_var
if vtype in ['C', 'CONTINUOUS']:
PY_SCIP_CALL(SCIPcreateVarBasic(self._scip, &scip_var, cname, lb, ub, obj, SCIP_VARTYPE_CONTINUOUS))
elif vtype in ['B', 'BINARY']:
lb = 0.0
ub = 1.0
PY_SCIP_CALL(SCIPcreateVarBasic(self._scip, &scip_var, cname, lb, ub, obj, SCIP_VARTYPE_BINARY))
elif vtype in ['I', 'INTEGER']:
PY_SCIP_CALL(SCIPcreateVarBasic(self._scip, &scip_var, cname, lb, ub, obj, SCIP_VARTYPE_INTEGER))
else:
raise Warning("unrecognized variable type")
if pricedVar:
PY_SCIP_CALL(SCIPaddPricedVar(self._scip, scip_var, 1.0))
else:
PY_SCIP_CALL(SCIPaddVar(self._scip, scip_var))
pyVar = Variable.create(scip_var)
PY_SCIP_CALL(SCIPreleaseVar(self._scip, &scip_var))
return pyVar
def releaseVar(self, Variable var):
"""Release the variable.
:param Variable var: variable to be released
"""
PY_SCIP_CALL(SCIPreleaseVar(self._scip, &var.var))
def getTransformedVar(self, Variable var):
"""Retrieve the transformed variable.
:param Variable var: original variable to get the transformed of
"""
cdef SCIP_VAR* _tvar
PY_SCIP_CALL(SCIPtransformVar(self._scip, var.var, &_tvar))
return Variable.create(_tvar)
def addVarLocks(self, Variable var, nlocksdown, nlocksup):
"""adds given values to lock numbers of variable for rounding
:param Variable var: variable to adjust the locks for
:param nlocksdown: new number of down locks
:param nlocksup: new number of up locks
"""
PY_SCIP_CALL(SCIPaddVarLocks(self._scip, var.var, nlocksdown, nlocksup))
def chgVarLb(self, Variable var, lb):
"""Changes the lower bound of the specified variable.
:param Variable var: variable to change bound of
:param lb: new lower bound (set to None for -infinity)
"""
if lb is None:
lb = -SCIPinfinity(self._scip)
PY_SCIP_CALL(SCIPchgVarLb(self._scip, var.var, lb))
def chgVarUb(self, Variable var, ub):
"""Changes the upper bound of the specified variable.
:param Variable var: variable to change bound of
:param ub: new upper bound (set to None for +infinity)
"""
if ub is None:
ub = SCIPinfinity(self._scip)
PY_SCIP_CALL(SCIPchgVarUb(self._scip, var.var, ub))
def chgVarType(self, Variable var, vtype):
"""Changes the type of a variable
:param Variable var: variable to change type of
:param vtype: new variable type
"""
cdef SCIP_Bool infeasible
if vtype in ['C', 'CONTINUOUS']:
PY_SCIP_CALL(SCIPchgVarType(self._scip, var.var, SCIP_VARTYPE_CONTINUOUS, &infeasible))
elif vtype in ['B', 'BINARY']:
PY_SCIP_CALL(SCIPchgVarType(self._scip, var.var, SCIP_VARTYPE_BINARY, &infeasible))
elif vtype in ['I', 'INTEGER']:
PY_SCIP_CALL(SCIPchgVarType(self._scip, var.var, SCIP_VARTYPE_INTEGER, &infeasible))
else:
raise Warning("unrecognized variable type")
if infeasible:
print('could not change variable type of variable %s' % var)
def getVars(self, transformed=False):
"""Retrieve all variables.
:param transformed: get transformed variables instead of original (Default value = False)
"""
cdef SCIP_VAR** _vars
cdef SCIP_VAR* _var
cdef int _nvars
vars = []
if transformed:
_vars = SCIPgetVars(self._scip)
_nvars = SCIPgetNVars(self._scip)
else:
_vars = SCIPgetOrigVars(self._scip)
_nvars = SCIPgetNOrigVars(self._scip)
return [Variable.create(_vars[i]) for i in range(_nvars)]
# Constraint functions
def addCons(self, cons, name='', initial=True, separate=True,
enforce=True, check=True, propagate=True, local=False,
modifiable=False, dynamic=False, removable=False,
stickingatnode=False):
"""Add a linear or quadratic constraint.
:param cons: list of coefficients
:param name: the name of the constraint, generic name if empty (Default value = '')
:param initial: should the LP relaxation of constraint be in the initial LP? (Default value = True)
:param separate: should the constraint be separated during LP processing? (Default value = True)
:param enforce: should the constraint be enforced during node processing? (Default value = True)
:param check: should the constraint be checked during for feasibility? (Default value = True)
:param propagate: should the constraint be propagated during node processing? (Default value = True)
:param local: is the constraint only valid locally? (Default value = False)
:param modifiable: is the constraint modifiable (subject to column generation)? (Default value = False)
:param dynamic: is the constraint subject to aging? (Default value = False)
:param removable: should the relaxation be removed from the LP due to aging or cleanup? (Default value = False)
:param stickingatnode: should the constraint always be kept at the node where it was added, even if it may be moved to a more global node? (Default value = False)
"""
assert isinstance(cons, ExprCons)
# replace empty name with generic one
if name == '':
name = 'c'+str(SCIPgetNConss(self._scip)+1)
kwargs = dict(name=name, initial=initial, separate=separate,
enforce=enforce, check=check,
propagate=propagate, local=local,
modifiable=modifiable, dynamic=dynamic,
removable=removable,
stickingatnode=stickingatnode)
kwargs['lhs'] = -SCIPinfinity(self._scip) if cons.lhs is None else cons.lhs
kwargs['rhs'] = SCIPinfinity(self._scip) if cons.rhs is None else cons.rhs
deg = cons.expr.degree()
if deg <= 1:
return self._addLinCons(cons, **kwargs)
elif deg <= 2:
return self._addQuadCons(cons, **kwargs)
elif deg == float('inf'): # general nonlinear
return self._addGenNonlinearCons(cons, **kwargs)
else:
return self._addNonlinearCons(cons, **kwargs)
def _addLinCons(self, ExprCons lincons, **kwargs):
assert isinstance(lincons, ExprCons)
assert lincons.expr.degree() <= 1
terms = lincons.expr.terms
cdef SCIP_CONS* scip_cons
PY_SCIP_CALL(SCIPcreateConsLinear(
self._scip, &scip_cons, str_conversion(kwargs['name']), 0, NULL, NULL,
kwargs['lhs'], kwargs['rhs'], kwargs['initial'],
kwargs['separate'], kwargs['enforce'], kwargs['check'],
kwargs['propagate'], kwargs['local'], kwargs['modifiable'],
kwargs['dynamic'], kwargs['removable'], kwargs['stickingatnode']))
for key, coeff in terms.items():
var = <Variable>key[0]
PY_SCIP_CALL(SCIPaddCoefLinear(self._scip, scip_cons, var.var, <SCIP_Real>coeff))
PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
PyCons = Constraint.create(scip_cons)
PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
return PyCons
def _addQuadCons(self, ExprCons quadcons, **kwargs):
terms = quadcons.expr.terms
assert quadcons.expr.degree() <= 2
cdef SCIP_CONS* scip_cons
PY_SCIP_CALL(SCIPcreateConsQuadratic(
self._scip, &scip_cons, str_conversion(kwargs['name']),
0, NULL, NULL, # linear
0, NULL, NULL, NULL, # quadratc
kwargs['lhs'], kwargs['rhs'],
kwargs['initial'], kwargs['separate'], kwargs['enforce'],
kwargs['check'], kwargs['propagate'], kwargs['local'],
kwargs['modifiable'], kwargs['dynamic'], kwargs['removable']))
for v, c in terms.items():
if len(v) == 1: # linear
var = <Variable>v[0]
PY_SCIP_CALL(SCIPaddLinearVarQuadratic(self._scip, scip_cons, var.var, c))
else: # quadratic
assert len(v) == 2, 'term: %s' % v
var1, var2 = <Variable>v[0], <Variable>v[1]
PY_SCIP_CALL(SCIPaddBilinTermQuadratic(self._scip, scip_cons, var1.var, var2.var, c))
PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
PyCons = Constraint.create(scip_cons)
PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))
return PyCons
def _addNonlinearCons(self, ExprCons cons, **kwargs):
cdef SCIP_EXPR* expr
cdef SCIP_EXPR** varexprs
cdef SCIP_EXPRDATA_MONOMIAL** monomials
cdef int* idxs
cdef SCIP_EXPRTREE* exprtree
cdef SCIP_VAR** vars
cdef SCIP_CONS* scip_cons
terms = cons.expr.terms
# collect variables
variables = {var.ptr():var for term in terms for var in term}
variables = list(variables.values())
varindex = {var.ptr():idx for (idx,var) in enumerate(variables)}
# create variable expressions
varexprs = <SCIP_EXPR**> malloc(len(varindex) * sizeof(SCIP_EXPR*))
for idx in varindex.values():
PY_SCIP_CALL( SCIPexprCreate(SCIPblkmem(self._scip), &expr, SCIP_EXPR_VARIDX, <int>idx) )
varexprs[idx] = expr
# create monomials for terms
monomials = <SCIP_EXPRDATA_MONOMIAL**> malloc(len(terms) * sizeof(SCIP_EXPRDATA_MONOMIAL*))
for i, (term, coef) in enumerate(terms.items()):
idxs = <int*> malloc(len(term) * sizeof(int))
for j, var in enumerate(term):
idxs[j] = varindex[var.ptr()]
PY_SCIP_CALL( SCIPexprCreateMonomial(SCIPblkmem(self._scip), &monomials[i], <SCIP_Real>coef, <int>len(term), idxs, NULL) )
free(idxs)
# create polynomial from monomials
PY_SCIP_CALL( SCIPexprCreatePolynomial(SCIPblkmem(self._scip), &expr,
<int>len(varindex), varexprs,
<int>len(terms), monomials, 0.0, <SCIP_Bool>True) )
# create expression tree
PY_SCIP_CALL( SCIPexprtreeCreate(SCIPblkmem(self._scip), &exprtree, expr, <int>len(variables), 0, NULL) )
vars = <SCIP_VAR**> malloc(len(variables) * sizeof(SCIP_VAR*))
for idx, var in enumerate(variables): # same as varindex
vars[idx] = (<Variable>var).var
PY_SCIP_CALL( SCIPexprtreeSetVars(exprtree, <int>len(variables), vars) )
# create nonlinear constraint for exprtree
PY_SCIP_CALL( SCIPcreateConsNonlinear(
self._scip, &scip_cons, str_conversion(kwargs['name']),
0, NULL, NULL, # linear
1, &exprtree, NULL, # nonlinear