Skip to content

Commit d915f2e

Browse files
authored
Reader plugin (#566)
* add reader plugin * fix bug * add assert for write method * delete comment * Update CHANGELOG.md
1 parent cc3289e commit d915f2e

File tree

5 files changed

+174
-1
lines changed

5 files changed

+174
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44
### Added
5+
- Interface to include custom reader plugins
6+
- New test for reader plugin
57
### Fixed
68
- revert change from #543 to fix #570 (closing file descriptors)
79
- use correct offset value when updating the objective function

src/pyscipopt/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from pyscipopt.scip import Presol
2020
from pyscipopt.scip import Pricer
2121
from pyscipopt.scip import Prop
22+
from pyscipopt.scip import Reader
2223
from pyscipopt.scip import Sepa
2324
from pyscipopt.scip import LP
2425
from pyscipopt.scip import Expr

src/pyscipopt/reader.pxi

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
##@file reader.pxi
2+
#@brief Base class of the Reader Plugin
3+
cdef class Reader:
4+
cdef public Model model
5+
cdef public str name
6+
7+
def readerfree(self):
8+
'''calls destructor and frees memory of reader'''
9+
pass
10+
11+
def readerread(self, filename):
12+
'''calls read method of reader'''
13+
return {}
14+
15+
def readerwrite(self, file, name, transformed, objsense, objscale, objoffset, binvars, intvars,
16+
implvars, contvars, fixedvars, startnvars, conss, maxnconss, startnconss, genericnames):
17+
'''calls write method of reader'''
18+
return {}
19+
20+
21+
cdef SCIP_RETCODE PyReaderCopy (SCIP* scip, SCIP_READER* reader) with gil:
22+
return SCIP_OKAY
23+
24+
cdef SCIP_RETCODE PyReaderFree (SCIP* scip, SCIP_READER* reader) with gil:
25+
cdef SCIP_READERDATA* readerdata
26+
readerdata = SCIPreaderGetData(reader)
27+
PyReader = <Reader>readerdata
28+
PyReader.readerfree()
29+
Py_DECREF(PyReader)
30+
return SCIP_OKAY
31+
32+
cdef SCIP_RETCODE PyReaderRead (SCIP* scip, SCIP_READER* reader, const char* filename, SCIP_RESULT* result) with gil:
33+
cdef SCIP_READERDATA* readerdata
34+
readerdata = SCIPreaderGetData(reader)
35+
PyReader = <Reader>readerdata
36+
PyFilename = filename.decode('utf-8')
37+
result_dict = PyReader.readerread(PyFilename)
38+
result[0] = result_dict.get("result", <SCIP_RESULT>result[0])
39+
return SCIP_OKAY
40+
41+
cdef SCIP_RETCODE PyReaderWrite (SCIP* scip, SCIP_READER* reader, FILE* file,
42+
const char* name, SCIP_PROBDATA* probdata, SCIP_Bool transformed,
43+
SCIP_OBJSENSE objsense, SCIP_Real objscale, SCIP_Real objoffset,
44+
SCIP_VAR** vars, int nvars, int nbinvars, int nintvars, int nimplvars, int ncontvars,
45+
SCIP_VAR** fixedvars, int nfixedvars, int startnvars,
46+
SCIP_CONS** conss, int nconss, int maxnconss, int startnconss,
47+
SCIP_Bool genericnames, SCIP_RESULT* result) with gil:
48+
cdef SCIP_READERDATA* readerdata
49+
readerdata = SCIPreaderGetData(reader)
50+
cdef int fd = fileno(file)
51+
PyFile = os.fdopen(fd, "w", closefd=False)
52+
PyName = name.decode('utf-8')
53+
PyBinVars = [Variable.create(vars[i]) for i in range(nbinvars)]
54+
PyIntVars = [Variable.create(vars[i]) for i in range(nbinvars, nintvars)]
55+
PyImplVars = [Variable.create(vars[i]) for i in range(nintvars, nimplvars)]
56+
PyContVars = [Variable.create(vars[i]) for i in range(nimplvars, ncontvars)]
57+
PyFixedVars = [Variable.create(fixedvars[i]) for i in range(nfixedvars)]
58+
PyConss = [Constraint.create(conss[i]) for i in range(nconss)]
59+
PyReader = <Reader>readerdata
60+
result_dict = PyReader.readerwrite(PyFile, PyName, transformed, objsense, objscale, objoffset,
61+
PyBinVars, PyIntVars, PyImplVars, PyContVars, PyFixedVars, startnvars,
62+
PyConss, maxnconss, startnconss, genericnames)
63+
result[0] = result_dict.get("result", <SCIP_RESULT>result[0])
64+
return SCIP_OKAY

src/pyscipopt/scip.pyx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
import weakref
44
from os.path import abspath
55
from os.path import splitext
6+
import os
67
import sys
78
import warnings
89

910
cimport cython
1011
from cpython cimport Py_INCREF, Py_DECREF
1112
from cpython.pycapsule cimport PyCapsule_New, PyCapsule_IsValid, PyCapsule_GetPointer
1213
from libc.stdlib cimport malloc, free
13-
from libc.stdio cimport fdopen
14+
from libc.stdio cimport fdopen, fclose
15+
from posix.stdio cimport fileno
1416

1517
from collections.abc import Iterable
1618
from itertools import repeat
@@ -28,6 +30,7 @@ include "presol.pxi"
2830
include "pricer.pxi"
2931
include "propagator.pxi"
3032
include "sepa.pxi"
33+
include "reader.pxi"
3134
include "relax.pxi"
3235
include "nodesel.pxi"
3336

@@ -3671,6 +3674,24 @@ cdef class Model:
36713674
sepa.name = name
36723675
Py_INCREF(sepa)
36733676

3677+
def includeReader(self, Reader reader, name, desc, ext):
3678+
"""Include a reader
3679+
3680+
:param Reader reader: reader
3681+
:param name: name of reader
3682+
:param desc: description of reader
3683+
:param ext: file extension of reader
3684+
3685+
"""
3686+
n = str_conversion(name)
3687+
d = str_conversion(desc)
3688+
e = str_conversion(ext)
3689+
PY_SCIP_CALL(SCIPincludeReader(self._scip, n, d, e, PyReaderCopy, PyReaderFree,
3690+
PyReaderRead, PyReaderWrite, <SCIP_READERDATA*>reader))
3691+
reader.model = <Model>weakref.proxy(self)
3692+
reader.name = name
3693+
Py_INCREF(reader)
3694+
36743695
def includeProp(self, Prop prop, name, desc, presolpriority, presolmaxrounds,
36753696
proptiming, presoltiming=SCIP_PRESOLTIMING_FAST, priority=1, freq=1, delay=True):
36763697
"""Include a propagator.

tests/test_reader.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import pytest
2+
import os
3+
4+
from pyscipopt import Model, quicksum, Reader, SCIP_RESULT
5+
6+
class SudokuReader(Reader):
7+
8+
def readerread(self, filename):
9+
with open(filename, "r") as f:
10+
input = f.readline().split()
11+
12+
for i in range(len(input)):
13+
input[i] = int(input[i])
14+
15+
x = {}
16+
for i in range(9):
17+
for j in range(9):
18+
for k in range(9):
19+
name = str(i)+','+str(j)+','+str(k)
20+
x[i,j,k] = self.model.addVar(name, vtype='B')
21+
22+
# fill in initial values
23+
for i in range(9):
24+
for j in range(9):
25+
if input[j + 9*i] != 0:
26+
self.model.addCons(x[i,j,input[j + 9*i]-1] == 1)
27+
28+
# only one digit in every field
29+
for i in range(9):
30+
for j in range(9):
31+
self.model.addCons(quicksum(x[i,j,k] for k in range(9)) == 1)
32+
33+
# set up row and column constraints
34+
for ind in range(9):
35+
for k in range(9):
36+
self.model.addCons(quicksum(x[ind,j,k] for j in range(9)) == 1)
37+
self.model.addCons(quicksum(x[i,ind,k] for i in range(9)) == 1)
38+
39+
# set up square constraints
40+
for row in range(3):
41+
for col in range(3):
42+
for k in range(9):
43+
self.model.addCons(quicksum(x[i+3*row, j+3*col, k] for i in range(3) for j in range(3)) == 1)
44+
45+
return {"result": SCIP_RESULT.SUCCESS}
46+
47+
def readerwrite(self, file, name, transformed, objsense, objscale, objoffset, binvars, intvars,
48+
implvars, contvars, fixedvars, startnvars, conss, maxnconss, startnconss, genericnames):
49+
with file as f:
50+
f.write(name)
51+
52+
return {"result": SCIP_RESULT.SUCCESS}
53+
54+
55+
def createFile(filename):
56+
with open(filename, "w") as f:
57+
f.write("5 3 0 0 7 0 0 0 0 6 0 0 1 9 5 0 0 0 0 9 8 0 0 0 0 6 0 8 0 0 0 6 0 0 0 3 4 0 0 8 0 3 0 0 1 7 0 0 0 2 0 0 0 6 0 6 0 0 0 0 2 8 0 0 0 0 4 1 9 0 0 5 0 0 0 0 8 0 0 7 9")
58+
59+
def deleteFile(filename):
60+
os.remove(filename)
61+
62+
def test():
63+
createFile("tmp.sod")
64+
65+
m = Model("soduko")
66+
reader = SudokuReader()
67+
68+
m.includeReader(reader, "sodreader", "PyReader for soduko problem", "sod")
69+
70+
m.readProblem("tmp.sod")
71+
72+
m.optimize()
73+
74+
deleteFile("tmp.sod")
75+
76+
m.writeProblem("model.sod")
77+
with open("model.sod", "r") as f:
78+
input = f.readline()
79+
assert input == "soduko"
80+
81+
deleteFile("model.sod")
82+
83+
84+
if __name__ == "__main__":
85+
test()

0 commit comments

Comments
 (0)