Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased
### Added
- Interface to include custom reader plugins
- New test for reader plugin
### Fixed
- revert change from #543 to fix #570 (closing file descriptors)
- use correct offset value when updating the objective function
Expand Down
1 change: 1 addition & 0 deletions src/pyscipopt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from pyscipopt.scip import Presol
from pyscipopt.scip import Pricer
from pyscipopt.scip import Prop
from pyscipopt.scip import Reader
from pyscipopt.scip import Sepa
from pyscipopt.scip import LP
from pyscipopt.scip import Expr
Expand Down
64 changes: 64 additions & 0 deletions src/pyscipopt/reader.pxi
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
##@file reader.pxi
#@brief Base class of the Reader Plugin
cdef class Reader:
cdef public Model model
cdef public str name

def readerfree(self):
'''calls destructor and frees memory of reader'''
pass

def readerread(self, filename):
'''calls read method of reader'''
return {}

def readerwrite(self, file, name, transformed, objsense, objscale, objoffset, binvars, intvars,
implvars, contvars, fixedvars, startnvars, conss, maxnconss, startnconss, genericnames):
'''calls write method of reader'''
return {}


cdef SCIP_RETCODE PyReaderCopy (SCIP* scip, SCIP_READER* reader) with gil:
return SCIP_OKAY

cdef SCIP_RETCODE PyReaderFree (SCIP* scip, SCIP_READER* reader) with gil:
cdef SCIP_READERDATA* readerdata
readerdata = SCIPreaderGetData(reader)
PyReader = <Reader>readerdata
PyReader.readerfree()
Py_DECREF(PyReader)
return SCIP_OKAY

cdef SCIP_RETCODE PyReaderRead (SCIP* scip, SCIP_READER* reader, const char* filename, SCIP_RESULT* result) with gil:
cdef SCIP_READERDATA* readerdata
readerdata = SCIPreaderGetData(reader)
PyReader = <Reader>readerdata
PyFilename = filename.decode('utf-8')
result_dict = PyReader.readerread(PyFilename)
result[0] = result_dict.get("result", <SCIP_RESULT>result[0])
return SCIP_OKAY

cdef SCIP_RETCODE PyReaderWrite (SCIP* scip, SCIP_READER* reader, FILE* file,
const char* name, SCIP_PROBDATA* probdata, SCIP_Bool transformed,
SCIP_OBJSENSE objsense, SCIP_Real objscale, SCIP_Real objoffset,
SCIP_VAR** vars, int nvars, int nbinvars, int nintvars, int nimplvars, int ncontvars,
SCIP_VAR** fixedvars, int nfixedvars, int startnvars,
SCIP_CONS** conss, int nconss, int maxnconss, int startnconss,
SCIP_Bool genericnames, SCIP_RESULT* result) with gil:
cdef SCIP_READERDATA* readerdata
readerdata = SCIPreaderGetData(reader)
cdef int fd = fileno(file)
PyFile = os.fdopen(fd, "w", closefd=False)
PyName = name.decode('utf-8')
PyBinVars = [Variable.create(vars[i]) for i in range(nbinvars)]
PyIntVars = [Variable.create(vars[i]) for i in range(nbinvars, nintvars)]
PyImplVars = [Variable.create(vars[i]) for i in range(nintvars, nimplvars)]
PyContVars = [Variable.create(vars[i]) for i in range(nimplvars, ncontvars)]
PyFixedVars = [Variable.create(fixedvars[i]) for i in range(nfixedvars)]
PyConss = [Constraint.create(conss[i]) for i in range(nconss)]
PyReader = <Reader>readerdata
result_dict = PyReader.readerwrite(PyFile, PyName, transformed, objsense, objscale, objoffset,
PyBinVars, PyIntVars, PyImplVars, PyContVars, PyFixedVars, startnvars,
PyConss, maxnconss, startnconss, genericnames)
result[0] = result_dict.get("result", <SCIP_RESULT>result[0])
return SCIP_OKAY
23 changes: 22 additions & 1 deletion src/pyscipopt/scip.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
import weakref
from os.path import abspath
from os.path import splitext
import os
import sys
import warnings

cimport cython
from cpython cimport Py_INCREF, Py_DECREF
from cpython.pycapsule cimport PyCapsule_New, PyCapsule_IsValid, PyCapsule_GetPointer
from libc.stdlib cimport malloc, free
from libc.stdio cimport fdopen
from libc.stdio cimport fdopen, fclose
from posix.stdio cimport fileno

from collections.abc import Iterable
from itertools import repeat
Expand All @@ -28,6 +30,7 @@ include "presol.pxi"
include "pricer.pxi"
include "propagator.pxi"
include "sepa.pxi"
include "reader.pxi"
include "relax.pxi"
include "nodesel.pxi"

Expand Down Expand Up @@ -3671,6 +3674,24 @@ cdef class Model:
sepa.name = name
Py_INCREF(sepa)

def includeReader(self, Reader reader, name, desc, ext):
"""Include a reader

:param Reader reader: reader
:param name: name of reader
:param desc: description of reader
:param ext: file extension of reader

"""
n = str_conversion(name)
d = str_conversion(desc)
e = str_conversion(ext)
PY_SCIP_CALL(SCIPincludeReader(self._scip, n, d, e, PyReaderCopy, PyReaderFree,
PyReaderRead, PyReaderWrite, <SCIP_READERDATA*>reader))
reader.model = <Model>weakref.proxy(self)
reader.name = name
Py_INCREF(reader)

def includeProp(self, Prop prop, name, desc, presolpriority, presolmaxrounds,
proptiming, presoltiming=SCIP_PRESOLTIMING_FAST, priority=1, freq=1, delay=True):
"""Include a propagator.
Expand Down
85 changes: 85 additions & 0 deletions tests/test_reader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import pytest
import os

from pyscipopt import Model, quicksum, Reader, SCIP_RESULT

class SudokuReader(Reader):

def readerread(self, filename):
with open(filename, "r") as f:
input = f.readline().split()

for i in range(len(input)):
input[i] = int(input[i])

x = {}
for i in range(9):
for j in range(9):
for k in range(9):
name = str(i)+','+str(j)+','+str(k)
x[i,j,k] = self.model.addVar(name, vtype='B')

# fill in initial values
for i in range(9):
for j in range(9):
if input[j + 9*i] != 0:
self.model.addCons(x[i,j,input[j + 9*i]-1] == 1)

# only one digit in every field
for i in range(9):
for j in range(9):
self.model.addCons(quicksum(x[i,j,k] for k in range(9)) == 1)

# set up row and column constraints
for ind in range(9):
for k in range(9):
self.model.addCons(quicksum(x[ind,j,k] for j in range(9)) == 1)
self.model.addCons(quicksum(x[i,ind,k] for i in range(9)) == 1)

# set up square constraints
for row in range(3):
for col in range(3):
for k in range(9):
self.model.addCons(quicksum(x[i+3*row, j+3*col, k] for i in range(3) for j in range(3)) == 1)

return {"result": SCIP_RESULT.SUCCESS}

def readerwrite(self, file, name, transformed, objsense, objscale, objoffset, binvars, intvars,
implvars, contvars, fixedvars, startnvars, conss, maxnconss, startnconss, genericnames):
with file as f:
f.write(name)

return {"result": SCIP_RESULT.SUCCESS}


def createFile(filename):
with open(filename, "w") as f:
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")

def deleteFile(filename):
os.remove(filename)

def test():
createFile("tmp.sod")

m = Model("soduko")
reader = SudokuReader()

m.includeReader(reader, "sodreader", "PyReader for soduko problem", "sod")

m.readProblem("tmp.sod")

m.optimize()

deleteFile("tmp.sod")

m.writeProblem("model.sod")
with open("model.sod", "r") as f:
input = f.readline()
assert input == "soduko"

deleteFile("model.sod")


if __name__ == "__main__":
test()