<commit_before>from os.path import abspath
import sys

include "linexpr.pxi"
include "lp.pxi"

include "branchrule.pxi"
include "conshdlr.pxi"
include "heuristic.pxi"
include "presol.pxi"
include "pricer.pxi"
include "propagator.pxi"
include "sepa.pxi"

# 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

def scipErrorHandler(function):
    def wrapper(*args, **kwargs):
        return PY_SCIP_CALL(function(*args, **kwargs))
    return wrapper

# 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_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:
    STAGE_INIT         = SCIP_STAGE_INIT
    STAGE_PROBLEM      = SCIP_STAGE_PROBLEM
    STAGE_TRANSFORMING = SCIP_STAGE_TRANSFORMING
    STAGE_TRANSFORMED  = SCIP_STAGE_TRANSFORMED
    STAGE_INITPRESOLVE = SCIP_STAGE_INITPRESOLVE
    STAGE_PRESOLVING   = SCIP_STAGE_PRESOLVING
    STAGE_EXITPRESOLVE = SCIP_STAGE_EXITPRESOLVE
    STAGE_PRESOLVED    = SCIP_STAGE_PRESOLVED
    STAGE_INITSOLVE    = SCIP_STAGE_INITSOLVE
    STAGE_SOLVING      = SCIP_STAGE_SOLVING
    STAGE_SOLVED       = SCIP_STAGE_SOLVED
    STAGE_EXITSOLVE    = SCIP_STAGE_EXITSOLVE
    STAGE_FREETRANS    = SCIP_STAGE_FREETRANS
    STAGE_FREE         = SCIP_STAGE_FREE

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

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!')
    return rc

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 Variable(LinExpr):
    """Is a linear expression and has SCIP_VAR*"""
    cdef SCIP_VAR* var
    cdef readonly str name

    @staticmethod
    cdef create(SCIP_VAR* scipvar, str name):
        var = Variable()
        var.var = scipvar
        var.name = name
        return var

    def __init__(self):
        LinExpr.__init__(self, {(self,) : 1.0})

    def __hash__(self):
        return hash(id(self))

    def __richcmp__(self, other, op):
        if op == 0: # <
            return id(self) < id(other)
        elif op == 4: # > 
            return id(self) > id(other)
        else: # interpret variable as expression
            # would like to do this, but doesn't work :-\
            # return super(Variable, self).__richcmp__(self, other, op)
            return _expr_richcmp(self, other, op)

    def __repr__(self):
        return self.name

    def vtype(self):
        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):
        return SCIPvarIsOriginal(self.var)

    def isInLP(self):
        return SCIPvarIsInLP(self.var)

    def getCol(self):
        cdef SCIP_COL* scip_col
        scip_col = SCIPvarGetCol(self.var)
        return Column.create(scip_col)

cdef class Constraint:
    cdef SCIP_CONS* cons
    cdef readonly str name
    cdef public object data #storage for python user

    @staticmethod
    cdef create(SCIP_CONS* scipcons, str name):
        cons = Constraint()
        cons.cons = scipcons
        cons.name = name
        return cons

    def __init__(self, name=None):
        self.name = name

    def __repr__(self):
        return self.name

    def isOriginal(self):
        return SCIPconsIsOriginal(self.cons)

    def isInitial(self):
        return SCIPconsIsInitial(self.cons)

    def isSeparated(self):
        return SCIPconsIsSeparated(self.cons)

    def isEnforced(self):
        return SCIPconsIsEnforced(self.cons)

    def isChecked(self):
        return SCIPconsIsChecked(self.cons)

    def isPropagated(self):
        return SCIPconsIsPropagated(self.cons)

    def isLocal(self):
        return SCIPconsIsLocal(self.cons)

    def isModifiable(self):
        return SCIPconsIsModifiable(self.cons)

    def isDynamic(self):
        return SCIPconsIsDynamic(self.cons)

    def isRemovable(self):
        return SCIPconsIsRemovable(self.cons)

    def isStickingAtNode(self):
        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

    def __init__(self, problemName='model', defaultPlugins=True):
        """
        Keyword arguments:
        problemName -- the name of the problem (default 'model')
        defaultPlugins -- use default plugins? (default True)
        """
        self.create()
        self._bestSol = None
        if defaultPlugins:
            self.includeDefaultPlugins()
        self.createProbBasic(problemName)

    def __del__(self):
        self.freeTransform()
        self.freeProb()
        self.free()

    @scipErrorHandler
    def create(self):
        return SCIPcreate(&self._scip)

    @scipErrorHandler
    def includeDefaultPlugins(self):
        return SCIPincludeDefaultPlugins(self._scip)

    @scipErrorHandler
    def createProbBasic(self, problemName='model'):
        n = str_conversion(problemName)
        return SCIPcreateProbBasic(self._scip, n)

    @scipErrorHandler
    def free(self):
        return SCIPfree(&self._scip)

    @scipErrorHandler
    def freeProb(self):
        return SCIPfreeProb(self._scip)

    @scipErrorHandler
    def freeTransform(self):
        return SCIPfreeTransform(self._scip)

    def printVersion(self):
        SCIPprintVersion(self._scip, NULL)

    def getTotalTime(self):
        return SCIPgetTotalTime(self._scip)

    def getSolvingTime(self):
        return SCIPgetSolvingTime(self._scip)

    def getReadingTime(self):
        return SCIPgetReadingTime(self._scip)

    def getPresolvingTime(self):
        return SCIPgetPresolvingTime(self._scip)

    def infinity(self):
        """Retrieve 'infinity' value."""
        return SCIPinfinity(self._scip)

    def epsilon(self):
        """Return epsilon for e.g. equality checks"""
        return SCIPepsilon(self._scip)

    def feastol(self):
        """Return feasibility tolerance"""
        return SCIPfeastol(self._scip)


    # Objective function

    def setMinimize(self):
        """Set the objective sense to maximization."""
        PY_SCIP_CALL(SCIPsetObjsense(self._scip, SCIP_OBJSENSE_MINIMIZE))

    def setMaximize(self):
        """Set the objective sense to minimization."""
        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.

        Keyword arguments:
        objlimit -- limit on the objective function
        """
        PY_SCIP_CALL(SCIPsetObjlimit(self._scip, objlimit))

    def setObjective(self, coeffs, sense = 'minimize'):
        """Establish the objective function, either as a variable dictionary or as a linear expression.

        Keyword arguments:
        coeffs -- the coefficients
        sense -- the objective sense (default 'minimize')
        """
        cdef SCIP_Real coeff
        if isinstance(coeffs, LinExpr):
            # transform linear expression into variable dictionary
            terms = coeffs.terms
            coeffs = {t[0]:c for t, c in terms.items() if c != 0.0}
        elif coeffs == 0:
            coeffs = {}
        for k in coeffs:
            var = <Variable>k
            PY_SCIP_CALL(SCIPchgVarObj(self._scip, var.var, <SCIP_Real>coeffs[k]))
        if sense == "minimize":
            self.setMinimize()
        elif sense == "maximize":
            self.setMaximize()
        else:
            raise Warning("unrecognized objective sense")

    # Setting parameters
    def setPresolve(self, setting):
        """Set presolving parameter settings.

        Keyword arguments:
        setting -- the parameter settings
        """
        PY_SCIP_CALL(SCIPsetPresolving(self._scip, setting, True))

    # Write original problem to file
    def writeProblem(self, filename='origprob.cip'):
        """Write original problem to a file.

        Keyword arguments:
        filename -- the name of the file to be used (default 'origprob.cip')
        """
        if filename.find('.') < 0:
            filename = filename + '.cip'
            ext = str_conversion('cip')
        else:
            ext = str_conversion(filename.split('.')[1])
        fn = str_conversion(filename)
        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.

        Keyword arguments:
        name -- the name of the variable (default '')
        vtype -- the typ of the variable (default 'C')
        lb -- the lower bound of the variable (default 0.0)
        ub -- the upper bound of the variable (default None)
        obj -- the objective value of the variable (default 0.0)
        pricedVar -- is the variable a pricing candidate? (default False)
        """
        cname = str_conversion(name)
        if ub is None:
            ub = 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, name)
        PY_SCIP_CALL(SCIPreleaseVar(self._scip, &scip_var))
        return pyVar

    def releaseVar(self, Variable var):
        """Release the variable.

        Keyword arguments:
        var -- the variable
        """
        PY_SCIP_CALL(SCIPreleaseVar(self._scip, &var.var))

    def getTransformedVar(self, Variable var):
        """Retrieve the transformed variable.

        Keyword arguments:
        var -- the variable
        """
        cdef SCIP_VAR* _tvar
        PY_SCIP_CALL(SCIPtransformVar(self._scip, var.var, &_tvar))
        return Variable.create(_tvar, SCIPvarGetName(_tvar).decode("utf-8"))

    def chgVarLb(self, Variable var, lb=None):
        """Changes the lower bound of the specified variable.

        Keyword arguments:
        var -- the variable
        lb -- the lower bound (default None)
        """
        if lb is None:
           lb = -SCIPinfinity(self._scip)
        PY_SCIP_CALL(SCIPchgVarLb(self._scip, var.var, lb))

    def chgVarUb(self, Variable var, ub=None):
        """Changes the upper bound of the specified variable.

        Keyword arguments:
        var -- the variable
        ub -- the upper bound (default None)
        """
        if ub is None:
           ub = SCIPinfinity(self._scip)
        PY_SCIP_CALL(SCIPchgVarLb(self._scip, var.var, ub))

    def chgVarType(self, Variable var, vtype):
        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 ', var.name)

    def getVars(self, transformed=False):
        """Retrieve all variables.

        Keyword arguments:
        transformed -- get transformed variables instead of original
        """
        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)

        for i in range(_nvars):
            _var = _vars[i]
            name = SCIPvarGetName(_var).decode("utf-8")
            vars.append(Variable.create(_var, name))

        return vars


    # Constraint functions
    # . By default the lhs is set to 0.0.
    # If the lhs is to be unbounded, then you set lhs to None.
    # By default the rhs is unbounded.
    def addCons(self, coeffs, lhs=0.0, rhs=None, name="cons",
                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.

        Keyword arguments:
        coeffs -- list of coefficients
        lhs -- the left hand side (default 0.0)
        rhs -- the right hand side (default None)
        name -- the name of the constraint (default 'cons')
        initial -- should the LP relaxation of constraint be in the initial LP? (default True)
        separate -- should the constraint be separated during LP processing? (default True)
        enforce -- should the constraint be enforced during node processing? (default True)
        check -- should the constraint be checked for feasibility? (default True)
        propagate -- should the constraint be propagated during node processing? (default True)
        local -- is the constraint only valid locally? (default False)
        modifiable -- is the constraint modifiable (subject to column generation)? (default False)
        dynamic -- is the constraint subject to aging? (default False)
        removable -- hould the relaxation be removed from the LP due to aging or cleanup? (default False)
        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 False)
        """
        if isinstance(coeffs, LinCons):
            kwargs = dict(lhs=lhs, rhs=rhs, name=name,
                          initial=initial, separate=separate, enforce=enforce,
                          check=check, propagate=propagate, local=local,
                          modifiable=modifiable, dynamic=dynamic,
                          removable=removable, stickingatnode=stickingatnode)
            deg = coeffs.expr.degree()
            if deg <= 1:
                return self._addLinCons(coeffs, **kwargs)
            elif deg <= 2:
                return self._addQuadCons(coeffs, **kwargs)
            else:
                raise NotImplementedError('Constraints of degree %d!' % deg)

        if lhs is None:
            lhs = -SCIPinfinity(self._scip)
        if rhs is None:
            rhs = SCIPinfinity(self._scip)
        cdef SCIP_CONS* scip_cons
        PY_SCIP_CALL(SCIPcreateConsLinear(self._scip, &scip_cons, str_conversion(name), 0, NULL, NULL, lhs, rhs,
            initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode))

        for k in coeffs:
            var = <Variable>k
            PY_SCIP_CALL(SCIPaddCoefLinear(self._scip, scip_cons, var.var, <SCIP_Real>coeffs[k]))

        PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
        PyCons = Constraint.create(scip_cons, name)

        PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))

        return PyCons

    def _addLinCons(self, LinCons lincons, **kwargs):
        """Add object of class LinCons."""
        assert isinstance(lincons, LinCons)
        kwargs['lhs'], kwargs['rhs'] = lincons.lb, lincons.ub
        terms = lincons.expr.terms
        assert lincons.expr.degree() <= 1
        assert terms[()] == 0.0
        coeffs = {t[0]:c for t, c in terms.items() if c != 0.0}

        return self.addCons(coeffs, **kwargs)

    def _addQuadCons(self, LinCons quadcons, **kwargs):
        kwargs['lhs'] = -SCIPinfinity(self._scip) if quadcons.lb is None else quadcons.lb
        kwargs['rhs'] =  SCIPinfinity(self._scip) if quadcons.ub is None else quadcons.ub
        terms = quadcons.expr.terms
        assert quadcons.expr.degree() <= 2
        assert terms[()] == 0.0

        name = str_conversion("quadcons") # TODO

        cdef SCIP_CONS* scip_cons
        PY_SCIP_CALL(SCIPcreateConsQuadratic(
            self._scip, &scip_cons, 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) == 0: # constant
                assert c == 0.0
            elif 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))

        return Constraint.create(scip_cons, SCIPconsGetName(scip_cons).decode("utf-8"))

    def addConsCoeff(self, Constraint cons, Variable var, coeff):
        """Add coefficient to the linear constraint (if non-zero).

        Keyword arguments:
        cons -- the constraint
        coeff -- the coefficient
        """
        PY_SCIP_CALL(SCIPaddCoefLinear(self._scip, cons.cons, var.var, coeff))

    def addConsSOS1(self, vars, weights=None, name="SOS1cons",
                initial=True, separate=True, enforce=True, check=True,
                propagate=True, local=False, dynamic=False,
                removable=False, stickingatnode=False):
        """Add an SOS1 constraint.

        Keyword arguments:
        vars -- list of variables to be included
        weights -- list of weights (default None)
        name -- the name of the constraint (default 'SOS1cons')
        initial -- should the LP relaxation of constraint be in the initial LP? (default True)
        separate -- should the constraint be separated during LP processing? (default True)
        enforce -- should the constraint be enforced during node processing? (default True)
        check -- should the constraint be checked for feasibility? (default True)
        propagate -- should the constraint be propagated during node processing? (default True)
        local -- is the constraint only valid locally? (default False)
        dynamic -- is the constraint subject to aging? (default False)
        removable -- hould the relaxation be removed from the LP due to aging or cleanup? (default False)
        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 False)
        """
        cdef SCIP_CONS* scip_cons
        cdef int _nvars

        PY_SCIP_CALL(SCIPcreateConsSOS1(self._scip, &scip_cons, str_conversion(name), 0, NULL, NULL,
            initial, separate, enforce, check, propagate, local, dynamic, removable, stickingatnode))

        if weights is None:
            for v in vars:
                var = <Variable>v
                PY_SCIP_CALL(SCIPappendVarSOS1(self._scip, scip_cons, var.var))
        else:
            nvars = len(vars)
            for i in range(nvars):
                var = <Variable>vars[i]
                PY_SCIP_CALL(SCIPaddVarSOS1(self._scip, scip_cons, var.var, weights[i]))

        PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
        return Constraint.create(scip_cons, name)

    def addConsSOS2(self, vars, weights=None, name="SOS2cons",
                initial=True, separate=True, enforce=True, check=True,
                propagate=True, local=False, dynamic=False,
                removable=False, stickingatnode=False):
        """Add an SOS2 constraint.

        Keyword arguments:
        vars -- list of variables to be included
        weights -- list of weights (default None)
        name -- the name of the constraint (default 'SOS2cons')
        initial -- should the LP relaxation of constraint be in the initial LP? (default True)
        separate -- should the constraint be separated during LP processing? (default True)
        enforce -- should the constraint be enforced during node processing? (default True)
        check -- should the constraint be checked for feasibility? (default True)
        propagate -- should the constraint be propagated during node processing? (default True)
        local -- is the constraint only valid locally? (default False)
        dynamic -- is the constraint subject to aging? (default False)
        removable -- hould the relaxation be removed from the LP due to aging or cleanup? (default False)
        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 False)
        """
        cdef SCIP_CONS* scip_cons
        cdef int _nvars

        PY_SCIP_CALL(SCIPcreateConsSOS2(self._scip, &scip_cons, str_conversion(name), 0, NULL, NULL,
            initial, separate, enforce, check, propagate, local, dynamic, removable, stickingatnode))

        if weights is None:
            for v in vars:
                var = <Variable>v
                PY_SCIP_CALL(SCIPappendVarSOS2(self._scip, scip_cons, var.var))
        else:
            nvars = len(vars)
            for i in range(nvars):
                var = <Variable>vars[i]
                PY_SCIP_CALL(SCIPaddVarSOS2(self._scip, scip_cons, var.var, weights[i]))

        PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
        return Constraint.create(scip_cons, name)


    def addPyCons(self, Constraint cons):
        """Adds a customly created cons.

        Keyword arguments:
        cons -- the Python constraint
        """
        PY_SCIP_CALL(SCIPaddCons(self._scip, cons.cons))

    def addVarSOS1(self, Constraint cons, Variable var, weight):
        """Add variable to SOS1 constraint.

        Keyword arguments:
        cons -- the SOS1 constraint
        vars -- the variable
        weight -- the weight
        """
        PY_SCIP_CALL(SCIPaddVarSOS1(self._scip, cons.cons, var.var, weight))

    def appendVarSOS1(self, Constraint cons, Variable var):
        """Append variable to SOS1 constraint.

        Keyword arguments:
        cons -- the SOS1 constraint
        vars -- the variable
        """
        PY_SCIP_CALL(SCIPappendVarSOS1(self._scip, cons.cons, var.var))

    def addVarSOS2(self, Constraint cons, Variable var, weight):
        """Add variable to SOS2 constraint.

        Keyword arguments:
        cons -- the SOS2 constraint
        vars -- the variable
        weight -- the weight
        """
        PY_SCIP_CALL(SCIPaddVarSOS2(self._scip, cons.cons, var.var, weight))

    def appendVarSOS2(self, Constraint cons, Variable var):
        """Append variable to SOS2 constraint.

        Keyword arguments:
        cons -- the SOS2 constraint
        vars -- the variable
        """
        PY_SCIP_CALL(SCIPappendVarSOS2(self._scip, cons.cons, var.var))

    def getTransformedCons(self, Constraint cons):
        """Retrieve transformed constraint.

        Keyword arguments:
        cons -- the constraint
        """
        cdef SCIP_CONS* transcons
        PY_SCIP_CALL(SCIPgetTransformedCons(self._scip, cons.cons, &transcons))

        return Constraint.create(transcons, SCIPconsGetName(transcons).decode("utf-8"))

    def getConss(self):
        """Retrieve all constraints."""
        cdef SCIP_CONS** _conss
        cdef SCIP_CONS* _cons
        cdef int _nconss
        conss = []

        _conss = SCIPgetConss(self._scip)
        _nconss = SCIPgetNConss(self._scip)

        for i in range(_nconss):
            _cons = _conss[i]
            conss.append(Constraint.create(_cons, SCIPconsGetName(_cons).decode("utf-8")))

        return conss

    def getDualsolLinear(self, Constraint cons):
        """Retrieve the dual solution to a linear constraint.

        Keyword arguments:
        cons -- the linear constraint
        """
        return SCIPgetDualsolLinear(self._scip, cons.cons)

    def getDualfarkasLinear(self, Constraint cons):
        """Retrieve the dual farkas value to a linear constraint.

        Keyword arguments:
        cons -- the linear constraint
        """
        return SCIPgetDualfarkasLinear(self._scip, cons.cons)

    def optimize(self):
        """Optimize the problem."""
        PY_SCIP_CALL(SCIPsolve(self._scip))
        self._bestSol = Solution.create(SCIPgetBestSol(self._scip))

    def includePricer(self, Pricer pricer, name, desc, priority=1, delay=True):
        """Include a pricer.

        Keyword arguments:
        pricer -- the pricer
        name -- the name
        desc -- the description
        priority -- priority of the variable pricer
        delay -- should the pricer be delayed until no other pricers or already
                 existing problem variables with negative reduced costs are found?
        """
        n = str_conversion(name)
        d = str_conversion(desc)
        PY_SCIP_CALL(SCIPincludePricer(self._scip, n, d,
                                            priority, delay,
                                            PyPricerCopy, PyPricerFree, PyPricerInit, PyPricerExit, PyPricerInitsol, PyPricerExitsol, PyPricerRedcost, PyPricerFarkas,
                                            <SCIP_PRICERDATA*>pricer))
        cdef SCIP_PRICER* scip_pricer
        scip_pricer = SCIPfindPricer(self._scip, n)
        PY_SCIP_CALL(SCIPactivatePricer(self._scip, scip_pricer))
        pricer.model = self


    def includeConshdlr(self, Conshdlr conshdlr, name, desc, sepapriority, enfopriority, chckpriority, sepafreq, propfreq, eagerfreq,
                        maxprerounds, delaysepa, delayprop, needscons, proptiming=SCIP_PROPTIMING_AFTERLPNODE, presoltiming=SCIP_PRESOLTIMING_FAST):
        """Include a constraint handler

        Keyword arguments:
        name -- name of constraint handler<commit_msg>add default values to `includeConshdlr`<commit_after>from os.path import abspath
import sys

include "linexpr.pxi"
include "lp.pxi"

include "branchrule.pxi"
include "conshdlr.pxi"
include "heuristic.pxi"
include "presol.pxi"
include "pricer.pxi"
include "propagator.pxi"
include "sepa.pxi"

# 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

def scipErrorHandler(function):
    def wrapper(*args, **kwargs):
        return PY_SCIP_CALL(function(*args, **kwargs))
    return wrapper

# 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_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:
    STAGE_INIT         = SCIP_STAGE_INIT
    STAGE_PROBLEM      = SCIP_STAGE_PROBLEM
    STAGE_TRANSFORMING = SCIP_STAGE_TRANSFORMING
    STAGE_TRANSFORMED  = SCIP_STAGE_TRANSFORMED
    STAGE_INITPRESOLVE = SCIP_STAGE_INITPRESOLVE
    STAGE_PRESOLVING   = SCIP_STAGE_PRESOLVING
    STAGE_EXITPRESOLVE = SCIP_STAGE_EXITPRESOLVE
    STAGE_PRESOLVED    = SCIP_STAGE_PRESOLVED
    STAGE_INITSOLVE    = SCIP_STAGE_INITSOLVE
    STAGE_SOLVING      = SCIP_STAGE_SOLVING
    STAGE_SOLVED       = SCIP_STAGE_SOLVED
    STAGE_EXITSOLVE    = SCIP_STAGE_EXITSOLVE
    STAGE_FREETRANS    = SCIP_STAGE_FREETRANS
    STAGE_FREE         = SCIP_STAGE_FREE

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

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!')
    return rc

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 Variable(LinExpr):
    """Is a linear expression and has SCIP_VAR*"""
    cdef SCIP_VAR* var
    cdef readonly str name

    @staticmethod
    cdef create(SCIP_VAR* scipvar, str name):
        var = Variable()
        var.var = scipvar
        var.name = name
        return var

    def __init__(self):
        LinExpr.__init__(self, {(self,) : 1.0})

    def __hash__(self):
        return hash(id(self))

    def __richcmp__(self, other, op):
        if op == 0: # <
            return id(self) < id(other)
        elif op == 4: # > 
            return id(self) > id(other)
        else: # interpret variable as expression
            # would like to do this, but doesn't work :-\
            # return super(Variable, self).__richcmp__(self, other, op)
            return _expr_richcmp(self, other, op)

    def __repr__(self):
        return self.name

    def vtype(self):
        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):
        return SCIPvarIsOriginal(self.var)

    def isInLP(self):
        return SCIPvarIsInLP(self.var)

    def getCol(self):
        cdef SCIP_COL* scip_col
        scip_col = SCIPvarGetCol(self.var)
        return Column.create(scip_col)

cdef class Constraint:
    cdef SCIP_CONS* cons
    cdef readonly str name
    cdef public object data #storage for python user

    @staticmethod
    cdef create(SCIP_CONS* scipcons, str name):
        cons = Constraint()
        cons.cons = scipcons
        cons.name = name
        return cons

    def __init__(self, name=None):
        self.name = name

    def __repr__(self):
        return self.name

    def isOriginal(self):
        return SCIPconsIsOriginal(self.cons)

    def isInitial(self):
        return SCIPconsIsInitial(self.cons)

    def isSeparated(self):
        return SCIPconsIsSeparated(self.cons)

    def isEnforced(self):
        return SCIPconsIsEnforced(self.cons)

    def isChecked(self):
        return SCIPconsIsChecked(self.cons)

    def isPropagated(self):
        return SCIPconsIsPropagated(self.cons)

    def isLocal(self):
        return SCIPconsIsLocal(self.cons)

    def isModifiable(self):
        return SCIPconsIsModifiable(self.cons)

    def isDynamic(self):
        return SCIPconsIsDynamic(self.cons)

    def isRemovable(self):
        return SCIPconsIsRemovable(self.cons)

    def isStickingAtNode(self):
        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

    def __init__(self, problemName='model', defaultPlugins=True):
        """
        Keyword arguments:
        problemName -- the name of the problem (default 'model')
        defaultPlugins -- use default plugins? (default True)
        """
        self.create()
        self._bestSol = None
        if defaultPlugins:
            self.includeDefaultPlugins()
        self.createProbBasic(problemName)

    def __del__(self):
        self.freeTransform()
        self.freeProb()
        self.free()

    @scipErrorHandler
    def create(self):
        return SCIPcreate(&self._scip)

    @scipErrorHandler
    def includeDefaultPlugins(self):
        return SCIPincludeDefaultPlugins(self._scip)

    @scipErrorHandler
    def createProbBasic(self, problemName='model'):
        n = str_conversion(problemName)
        return SCIPcreateProbBasic(self._scip, n)

    @scipErrorHandler
    def free(self):
        return SCIPfree(&self._scip)

    @scipErrorHandler
    def freeProb(self):
        return SCIPfreeProb(self._scip)

    @scipErrorHandler
    def freeTransform(self):
        return SCIPfreeTransform(self._scip)

    def printVersion(self):
        SCIPprintVersion(self._scip, NULL)

    def getTotalTime(self):
        return SCIPgetTotalTime(self._scip)

    def getSolvingTime(self):
        return SCIPgetSolvingTime(self._scip)

    def getReadingTime(self):
        return SCIPgetReadingTime(self._scip)

    def getPresolvingTime(self):
        return SCIPgetPresolvingTime(self._scip)

    def infinity(self):
        """Retrieve 'infinity' value."""
        return SCIPinfinity(self._scip)

    def epsilon(self):
        """Return epsilon for e.g. equality checks"""
        return SCIPepsilon(self._scip)

    def feastol(self):
        """Return feasibility tolerance"""
        return SCIPfeastol(self._scip)


    # Objective function

    def setMinimize(self):
        """Set the objective sense to maximization."""
        PY_SCIP_CALL(SCIPsetObjsense(self._scip, SCIP_OBJSENSE_MINIMIZE))

    def setMaximize(self):
        """Set the objective sense to minimization."""
        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.

        Keyword arguments:
        objlimit -- limit on the objective function
        """
        PY_SCIP_CALL(SCIPsetObjlimit(self._scip, objlimit))

    def setObjective(self, coeffs, sense = 'minimize'):
        """Establish the objective function, either as a variable dictionary or as a linear expression.

        Keyword arguments:
        coeffs -- the coefficients
        sense -- the objective sense (default 'minimize')
        """
        cdef SCIP_Real coeff
        if isinstance(coeffs, LinExpr):
            # transform linear expression into variable dictionary
            terms = coeffs.terms
            coeffs = {t[0]:c for t, c in terms.items() if c != 0.0}
        elif coeffs == 0:
            coeffs = {}
        for k in coeffs:
            var = <Variable>k
            PY_SCIP_CALL(SCIPchgVarObj(self._scip, var.var, <SCIP_Real>coeffs[k]))
        if sense == "minimize":
            self.setMinimize()
        elif sense == "maximize":
            self.setMaximize()
        else:
            raise Warning("unrecognized objective sense")

    # Setting parameters
    def setPresolve(self, setting):
        """Set presolving parameter settings.

        Keyword arguments:
        setting -- the parameter settings
        """
        PY_SCIP_CALL(SCIPsetPresolving(self._scip, setting, True))

    # Write original problem to file
    def writeProblem(self, filename='origprob.cip'):
        """Write original problem to a file.

        Keyword arguments:
        filename -- the name of the file to be used (default 'origprob.cip')
        """
        if filename.find('.') < 0:
            filename = filename + '.cip'
            ext = str_conversion('cip')
        else:
            ext = str_conversion(filename.split('.')[1])
        fn = str_conversion(filename)
        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.

        Keyword arguments:
        name -- the name of the variable (default '')
        vtype -- the typ of the variable (default 'C')
        lb -- the lower bound of the variable (default 0.0)
        ub -- the upper bound of the variable (default None)
        obj -- the objective value of the variable (default 0.0)
        pricedVar -- is the variable a pricing candidate? (default False)
        """
        cname = str_conversion(name)
        if ub is None:
            ub = 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, name)
        PY_SCIP_CALL(SCIPreleaseVar(self._scip, &scip_var))
        return pyVar

    def releaseVar(self, Variable var):
        """Release the variable.

        Keyword arguments:
        var -- the variable
        """
        PY_SCIP_CALL(SCIPreleaseVar(self._scip, &var.var))

    def getTransformedVar(self, Variable var):
        """Retrieve the transformed variable.

        Keyword arguments:
        var -- the variable
        """
        cdef SCIP_VAR* _tvar
        PY_SCIP_CALL(SCIPtransformVar(self._scip, var.var, &_tvar))
        return Variable.create(_tvar, SCIPvarGetName(_tvar).decode("utf-8"))

    def chgVarLb(self, Variable var, lb=None):
        """Changes the lower bound of the specified variable.

        Keyword arguments:
        var -- the variable
        lb -- the lower bound (default None)
        """
        if lb is None:
           lb = -SCIPinfinity(self._scip)
        PY_SCIP_CALL(SCIPchgVarLb(self._scip, var.var, lb))

    def chgVarUb(self, Variable var, ub=None):
        """Changes the upper bound of the specified variable.

        Keyword arguments:
        var -- the variable
        ub -- the upper bound (default None)
        """
        if ub is None:
           ub = SCIPinfinity(self._scip)
        PY_SCIP_CALL(SCIPchgVarLb(self._scip, var.var, ub))

    def chgVarType(self, Variable var, vtype):
        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 ', var.name)

    def getVars(self, transformed=False):
        """Retrieve all variables.

        Keyword arguments:
        transformed -- get transformed variables instead of original
        """
        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)

        for i in range(_nvars):
            _var = _vars[i]
            name = SCIPvarGetName(_var).decode("utf-8")
            vars.append(Variable.create(_var, name))

        return vars


    # Constraint functions
    # . By default the lhs is set to 0.0.
    # If the lhs is to be unbounded, then you set lhs to None.
    # By default the rhs is unbounded.
    def addCons(self, coeffs, lhs=0.0, rhs=None, name="cons",
                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.

        Keyword arguments:
        coeffs -- list of coefficients
        lhs -- the left hand side (default 0.0)
        rhs -- the right hand side (default None)
        name -- the name of the constraint (default 'cons')
        initial -- should the LP relaxation of constraint be in the initial LP? (default True)
        separate -- should the constraint be separated during LP processing? (default True)
        enforce -- should the constraint be enforced during node processing? (default True)
        check -- should the constraint be checked for feasibility? (default True)
        propagate -- should the constraint be propagated during node processing? (default True)
        local -- is the constraint only valid locally? (default False)
        modifiable -- is the constraint modifiable (subject to column generation)? (default False)
        dynamic -- is the constraint subject to aging? (default False)
        removable -- hould the relaxation be removed from the LP due to aging or cleanup? (default False)
        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 False)
        """
        if isinstance(coeffs, LinCons):
            kwargs = dict(lhs=lhs, rhs=rhs, name=name,
                          initial=initial, separate=separate, enforce=enforce,
                          check=check, propagate=propagate, local=local,
                          modifiable=modifiable, dynamic=dynamic,
                          removable=removable, stickingatnode=stickingatnode)
            deg = coeffs.expr.degree()
            if deg <= 1:
                return self._addLinCons(coeffs, **kwargs)
            elif deg <= 2:
                return self._addQuadCons(coeffs, **kwargs)
            else:
                raise NotImplementedError('Constraints of degree %d!' % deg)

        if lhs is None:
            lhs = -SCIPinfinity(self._scip)
        if rhs is None:
            rhs = SCIPinfinity(self._scip)
        cdef SCIP_CONS* scip_cons
        PY_SCIP_CALL(SCIPcreateConsLinear(self._scip, &scip_cons, str_conversion(name), 0, NULL, NULL, lhs, rhs,
            initial, separate, enforce, check, propagate, local, modifiable, dynamic, removable, stickingatnode))

        for k in coeffs:
            var = <Variable>k
            PY_SCIP_CALL(SCIPaddCoefLinear(self._scip, scip_cons, var.var, <SCIP_Real>coeffs[k]))

        PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
        PyCons = Constraint.create(scip_cons, name)

        PY_SCIP_CALL(SCIPreleaseCons(self._scip, &scip_cons))

        return PyCons

    def _addLinCons(self, LinCons lincons, **kwargs):
        """Add object of class LinCons."""
        assert isinstance(lincons, LinCons)
        kwargs['lhs'], kwargs['rhs'] = lincons.lb, lincons.ub
        terms = lincons.expr.terms
        assert lincons.expr.degree() <= 1
        assert terms[()] == 0.0
        coeffs = {t[0]:c for t, c in terms.items() if c != 0.0}

        return self.addCons(coeffs, **kwargs)

    def _addQuadCons(self, LinCons quadcons, **kwargs):
        kwargs['lhs'] = -SCIPinfinity(self._scip) if quadcons.lb is None else quadcons.lb
        kwargs['rhs'] =  SCIPinfinity(self._scip) if quadcons.ub is None else quadcons.ub
        terms = quadcons.expr.terms
        assert quadcons.expr.degree() <= 2
        assert terms[()] == 0.0

        name = str_conversion("quadcons") # TODO

        cdef SCIP_CONS* scip_cons
        PY_SCIP_CALL(SCIPcreateConsQuadratic(
            self._scip, &scip_cons, 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) == 0: # constant
                assert c == 0.0
            elif 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))

        return Constraint.create(scip_cons, SCIPconsGetName(scip_cons).decode("utf-8"))

    def addConsCoeff(self, Constraint cons, Variable var, coeff):
        """Add coefficient to the linear constraint (if non-zero).

        Keyword arguments:
        cons -- the constraint
        coeff -- the coefficient
        """
        PY_SCIP_CALL(SCIPaddCoefLinear(self._scip, cons.cons, var.var, coeff))

    def addConsSOS1(self, vars, weights=None, name="SOS1cons",
                initial=True, separate=True, enforce=True, check=True,
                propagate=True, local=False, dynamic=False,
                removable=False, stickingatnode=False):
        """Add an SOS1 constraint.

        Keyword arguments:
        vars -- list of variables to be included
        weights -- list of weights (default None)
        name -- the name of the constraint (default 'SOS1cons')
        initial -- should the LP relaxation of constraint be in the initial LP? (default True)
        separate -- should the constraint be separated during LP processing? (default True)
        enforce -- should the constraint be enforced during node processing? (default True)
        check -- should the constraint be checked for feasibility? (default True)
        propagate -- should the constraint be propagated during node processing? (default True)
        local -- is the constraint only valid locally? (default False)
        dynamic -- is the constraint subject to aging? (default False)
        removable -- hould the relaxation be removed from the LP due to aging or cleanup? (default False)
        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 False)
        """
        cdef SCIP_CONS* scip_cons
        cdef int _nvars

        PY_SCIP_CALL(SCIPcreateConsSOS1(self._scip, &scip_cons, str_conversion(name), 0, NULL, NULL,
            initial, separate, enforce, check, propagate, local, dynamic, removable, stickingatnode))

        if weights is None:
            for v in vars:
                var = <Variable>v
                PY_SCIP_CALL(SCIPappendVarSOS1(self._scip, scip_cons, var.var))
        else:
            nvars = len(vars)
            for i in range(nvars):
                var = <Variable>vars[i]
                PY_SCIP_CALL(SCIPaddVarSOS1(self._scip, scip_cons, var.var, weights[i]))

        PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
        return Constraint.create(scip_cons, name)

    def addConsSOS2(self, vars, weights=None, name="SOS2cons",
                initial=True, separate=True, enforce=True, check=True,
                propagate=True, local=False, dynamic=False,
                removable=False, stickingatnode=False):
        """Add an SOS2 constraint.

        Keyword arguments:
        vars -- list of variables to be included
        weights -- list of weights (default None)
        name -- the name of the constraint (default 'SOS2cons')
        initial -- should the LP relaxation of constraint be in the initial LP? (default True)
        separate -- should the constraint be separated during LP processing? (default True)
        enforce -- should the constraint be enforced during node processing? (default True)
        check -- should the constraint be checked for feasibility? (default True)
        propagate -- should the constraint be propagated during node processing? (default True)
        local -- is the constraint only valid locally? (default False)
        dynamic -- is the constraint subject to aging? (default False)
        removable -- hould the relaxation be removed from the LP due to aging or cleanup? (default False)
        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 False)
        """
        cdef SCIP_CONS* scip_cons
        cdef int _nvars

        PY_SCIP_CALL(SCIPcreateConsSOS2(self._scip, &scip_cons, str_conversion(name), 0, NULL, NULL,
            initial, separate, enforce, check, propagate, local, dynamic, removable, stickingatnode))

        if weights is None:
            for v in vars:
                var = <Variable>v
                PY_SCIP_CALL(SCIPappendVarSOS2(self._scip, scip_cons, var.var))
        else:
            nvars = len(vars)
            for i in range(nvars):
                var = <Variable>vars[i]
                PY_SCIP_CALL(SCIPaddVarSOS2(self._scip, scip_cons, var.var, weights[i]))

        PY_SCIP_CALL(SCIPaddCons(self._scip, scip_cons))
        return Constraint.create(scip_cons, name)


    def addPyCons(self, Constraint cons):
        """Adds a customly created cons.

        Keyword arguments:
        cons -- the Python constraint
        """
        PY_SCIP_CALL(SCIPaddCons(self._scip, cons.cons))

    def addVarSOS1(self, Constraint cons, Variable var, weight):
        """Add variable to SOS1 constraint.

        Keyword arguments:
        cons -- the SOS1 constraint
        vars -- the variable
        weight -- the weight
        """
        PY_SCIP_CALL(SCIPaddVarSOS1(self._scip, cons.cons, var.var, weight))

    def appendVarSOS1(self, Constraint cons, Variable var):
        """Append variable to SOS1 constraint.

        Keyword arguments:
        cons -- the SOS1 constraint
        vars -- the variable
        """
        PY_SCIP_CALL(SCIPappendVarSOS1(self._scip, cons.cons, var.var))

    def addVarSOS2(self, Constraint cons, Variable var, weight):
        """Add variable to SOS2 constraint.

        Keyword arguments:
        cons -- the SOS2 constraint
        vars -- the variable
        weight -- the weight
        """
        PY_SCIP_CALL(SCIPaddVarSOS2(self._scip, cons.cons, var.var, weight))

    def appendVarSOS2(self, Constraint cons, Variable var):
        """Append variable to SOS2 constraint.

        Keyword arguments:
        cons -- the SOS2 constraint
        vars -- the variable
        """
        PY_SCIP_CALL(SCIPappendVarSOS2(self._scip, cons.cons, var.var))

    def getTransformedCons(self, Constraint cons):
        """Retrieve transformed constraint.

        Keyword arguments:
        cons -- the constraint
        """
        cdef SCIP_CONS* transcons
        PY_SCIP_CALL(SCIPgetTransformedCons(self._scip, cons.cons, &transcons))

        return Constraint.create(transcons, SCIPconsGetName(transcons).decode("utf-8"))

    def getConss(self):
        """Retrieve all constraints."""
        cdef SCIP_CONS** _conss
        cdef SCIP_CONS* _cons
        cdef int _nconss
        conss = []

        _conss = SCIPgetConss(self._scip)
        _nconss = SCIPgetNConss(self._scip)

        for i in range(_nconss):
            _cons = _conss[i]
            conss.append(Constraint.create(_cons, SCIPconsGetName(_cons).decode("utf-8")))

        return conss

    def getDualsolLinear(self, Constraint cons):
        """Retrieve the dual solution to a linear constraint.

        Keyword arguments:
        cons -- the linear constraint
        """
        return SCIPgetDualsolLinear(self._scip, cons.cons)

    def getDualfarkasLinear(self, Constraint cons):
        """Retrieve the dual farkas value to a linear constraint.

        Keyword arguments:
        cons -- the linear constraint
        """
        return SCIPgetDualfarkasLinear(self._scip, cons.cons)

    def optimize(self):
        """Optimize the problem."""
        PY_SCIP_CALL(SCIPsolve(self._scip))
        self._bestSol = Solution.create(SCIPgetBestSol(self._scip))

    def includePricer(self, Pricer pricer, name, desc, priority=1, delay=True):
        """Include a pricer.

        Keyword arguments:
        pricer -- the pricer
        name -- the name
        desc -- the description
        priority -- priority of the variable pricer
        delay -- should the pricer be delayed until no other pricers or already
                 existing problem variables with negative reduced costs are found?
        """
        n = str_conversion(name)
        d = str_conversion(desc)
        PY_SCIP_CALL(SCIPincludePricer(self._scip, n, d,
                                            priority, delay,
                                            PyPricerCopy, PyPricerFree, PyPricerInit, PyPricerExit, PyPricerInitsol, PyPricerExitsol, PyPricerRedcost, PyPricerFarkas,
                                            <SCIP_PRICERDATA*>pricer))
        cdef SCIP_PRICER* scip_pricer
        scip_pricer = SCIPfindPricer(self._scip, n)
        PY_SCIP_CALL(SCIPactivatePricer(self._scip, scip_pricer))
        pricer.model = self

    def includeConshdlr(self, Conshdlr conshdlr, name, desc, sepapriority=0,
                        enfopriority=0, chckpriority=0, sepafreq=-1, propfreq=-1,
                        eagerfreq=100, maxprerounds=-1, delaysepa=False,
                        delayprop=False, needscons=True,
                        proptiming=PY_SCIP_PROPTIMING.BEFORELP,
                        presoltiming=PY_SCIP_PRESOLTIMING.MEDIUM):
        """Include a constraint handler

        Keyword arguments:
        name -- name of constraint handler
{'fraction_non_alphanumeric': 0.08514114974011018, 'fraction_numerical': 0.003159139718011765, 'mean_word_length': 2.483061412542685, 'pattern_counts': {'":': 4, '<': 41, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '67388', 'n_tokens_mistral': 20213, 'n_tokens_neox': 19643, 'n_words': 5273}