CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pyscipopt

Python interface and modeling environment for SCIP optimization solver

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

plugins.mddocs/

Plugin Framework

Extensible plugin system for implementing custom optimization algorithms and components. PySCIPOpt's plugin framework allows users to extend SCIP's functionality with custom branching rules, constraint handlers, heuristics, cutting plane separators, and other algorithmic components.

Capabilities

Benders Decomposition

Custom Benders decomposition handlers for implementing decomposition algorithms and cut generation methods.

class Benders:
    """
    Benders decomposition handler for implementing custom decomposition methods.
    
    Benders decomposition separates optimization problems into master and
    subproblems, iteratively adding cuts to improve the master problem bounds.
    """
    
    def bendersinit(self, benders):
        """
        Initialize Benders decomposition.
        
        Parameters:
        - benders: Benders decomposition object
        """
    
    def bendersexit(self, benders):
        """
        Cleanup Benders decomposition.
        
        Parameters:
        - benders: Benders decomposition object
        """
    
    def bendersexec(self, benders, result):
        """
        Execute Benders decomposition algorithm.
        
        Parameters:
        - benders: Benders decomposition object
        - result: Dictionary to store result status
        
        Returns:
        Should set result["result"] to appropriate SCIP_RESULT value
        """

Benders Cut Generation

Custom Benders cut handlers for generating problem-specific cutting planes in decomposition methods.

class Benderscut:
    """
    Benders cut handler for custom cut generation in decomposition methods.
    
    Generates cuts based on dual information from Benders subproblems
    to strengthen the master problem formulation.
    """
    
    def benderscutexec(self, benderscut, result):
        """
        Execute Benders cut generation.
        
        Parameters:
        - benderscut: Benders cut object
        - result: Dictionary to store result and cut information
        
        Returns:
        Should set result["result"] and add cuts if generated
        """

Branching Rules

Custom branching strategies for controlling the branch-and-bound tree exploration.

class Branchrule:
    """
    Custom branching rule handler for implementing domain-specific
    branching strategies in branch-and-bound algorithms.
    """
    
    def branchinit(self, branchrule):
        """
        Initialize branching rule.
        
        Parameters:
        - branchrule: Branching rule object
        """
    
    def branchexit(self, branchrule):
        """
        Cleanup branching rule.
        
        Parameters:
        - branchrule: Branching rule object
        """
    
    def branchexeclp(self, branchrule, allowaddcons, result):
        """
        Execute branching on LP solution.
        
        Parameters:
        - branchrule: Branching rule object
        - allowaddcons (bool): Whether adding constraints is allowed
        - result: Dictionary to store branching result
        
        Returns:
        Should set result["result"] and create child nodes if branching
        """
    
    def branchexecext(self, branchrule, allowaddcons, result):
        """
        Execute branching on external candidates.
        
        Parameters:
        - branchrule: Branching rule object
        - allowaddcons (bool): Whether adding constraints is allowed
        - result: Dictionary to store branching result
        """
    
    def branchexecps(self, branchrule, allowaddcons, result):
        """
        Execute branching on pseudo solution.
        
        Parameters:
        - branchrule: Branching rule object
        - allowaddcons (bool): Whether adding constraints is allowed
        - result: Dictionary to store branching result
        """

Node Selection

Custom node selection strategies for controlling the order of node processing in the branch-and-bound tree.

class Nodesel:
    """
    Node selection handler for implementing custom strategies
    to choose which node to process next in branch-and-bound.
    """
    
    def nodeselinit(self, nodesel):
        """
        Initialize node selector.
        
        Parameters:
        - nodesel: Node selector object
        """
    
    def nodeselexit(self, nodesel):
        """
        Cleanup node selector.
        
        Parameters:
        - nodesel: Node selector object
        """
    
    def nodeselselect(self, nodesel, selnode, result):
        """
        Select next node to process.
        
        Parameters:
        - nodesel: Node selector object
        - selnode: List to store selected node
        - result: Dictionary to store selection result
        
        Should select the most promising node from the tree
        """
    
    def nodeselcomp(self, nodesel, node1, node2):
        """
        Compare two nodes for selection priority.
        
        Parameters:
        - nodesel: Node selector object
        - node1: First node to compare
        - node2: Second node to compare
        
        Returns:
        int: -1 if node1 has higher priority, 1 if node2, 0 if equal
        """

Constraint Handlers

Custom constraint types with propagation, separation, and enforcement methods.

class Conshdlr:
    """
    Constraint handler for implementing custom constraint types
    with specialized propagation and separation algorithms.
    """
    
    def consinit(self, constraints):
        """
        Initialize constraint handler.
        
        Parameters:
        - constraints: List of constraints handled by this handler  
        """
    
    def consexit(self, constraints):
        """
        Cleanup constraint handler.
        
        Parameters:
        - constraints: List of constraints
        """
    
    def constrans(self, sourceconstraints, targetconstraints):
        """
        Transform constraints during problem transformation.
        
        Parameters:
        - sourceconstraints: Original constraints
        - targetconstraints: Transformed constraints
        """
    
    def consenfolp(self, constraints, nusefulconss, solinfeasible, result):
        """
        Enforce constraints for LP solution.
        
        Parameters:
        - constraints: List of constraints to enforce
        - nusefulconss (int): Number of useful constraints
        - solinfeasible (bool): Whether solution is already known infeasible  
        - result: Dictionary to store enforcement result
        """
    
    def consenfops(self, constraints, nusefulconss, solinfeasible, objinfeasible, result):
        """
        Enforce constraints for pseudo solution.
        
        Parameters:
        - constraints: List of constraints to enforce
        - nusefulconss (int): Number of useful constraints
        - solinfeasible (bool): Whether solution is already known infeasible
        - objinfeasible (bool): Whether solution is objectively infeasible
        - result: Dictionary to store enforcement result
        """
    
    def conscheck(self, constraints, solution, checkintegrality, checklprows, printreason, result):
        """
        Check feasibility of solution.
        
        Parameters:
        - constraints: List of constraints to check
        - solution: Solution to check
        - checkintegrality (bool): Check integrality constraints
        - checklprows (bool): Check LP row constraints
        - printreason (bool): Print infeasibility reasons
        - result: Dictionary to store check result
        """
    
    def consprop(self, constraints, nusefulconss, result):
        """
        Propagate constraints (domain reduction).
        
        Parameters:
        - constraints: List of constraints to propagate
        - nusefulconss (int): Number of useful constraints
        - result: Dictionary to store propagation result
        """
    
    def conssepalp(self, constraints, nusefulconss, result):
        """
        Separate constraints for LP solution.
        
        Parameters:
        - constraints: List of constraints for separation
        - nusefulconss (int): Number of useful constraints
        - result: Dictionary to store separation result
        """
    
    def conssepasol(self, constraints, nusefulconss, solution, result):
        """
        Separate constraints for given solution.
        
        Parameters:
        - constraints: List of constraints for separation
        - nusefulconss (int): Number of useful constraints
        - solution: Solution to separate
        - result: Dictionary to store separation result
        """

Event Handlers

Event handling system for monitoring solver progress and implementing custom callbacks.

class Eventhdlr:
    """
    Event handler for monitoring solver events and implementing
    custom callbacks during the optimization process.
    """
    
    def eventinit(self, eventhdlr):
        """
        Initialize event handler.
        
        Parameters:
        - eventhdlr: Event handler object
        """
    
    def eventexit(self, eventhdlr):
        """
        Cleanup event handler.
        
        Parameters:
        - eventhdlr: Event handler object
        """
    
    def eventexec(self, eventhdlr, event):
        """
        Execute event handling.
        
        Parameters:
        - eventhdlr: Event handler object
        - event: Event object containing event information
        
        Event types include:
        - Variable events: bound changes, domain changes, addition/deletion
        - Node events: focus, unfocus, branch, solution found
        - LP events: solved, objective change
        - Solution events: found, improved
        """

Primal Heuristics

Custom heuristic algorithms for finding feasible solutions.

class Heur:
    """
    Primal heuristic handler for implementing custom algorithms
    to find feasible solutions during the optimization process.
    """
    
    def heurinit(self, heur):
        """
        Initialize heuristic.
        
        Parameters:
        - heur: Heuristic object
        """
    
    def heurexit(self, heur):
        """
        Cleanup heuristic.
        
        Parameters:
        - heur: Heuristic object
        """
    
    def heurexec(self, heur, heurtiming, nodeinfeasible, result):
        """
        Execute heuristic algorithm.
        
        Parameters:
        - heur: Heuristic object
        - heurtiming: Timing information (when heuristic is called)
        - nodeinfeasible (bool): Whether current node is infeasible
        - result: Dictionary to store heuristic result
        
        Timing values:
        - BEFORENODE: Before processing node
        - DURINGLPLOOP: During LP solving loop
        - AFTERLPLOOP: After LP solving loop
        - AFTERNODE: After processing node
        """

Presolving Methods

Custom presolving routines for problem simplification and preprocessing.

class Presol:
    """
    Presolving handler for implementing custom problem simplification
    and preprocessing routines before the main optimization begins.
    """
    
    def presolinit(self, presol):
        """
        Initialize presolving method.
        
        Parameters:  
        - presol: Presolving object
        """
    
    def presolexit(self, presol):
        """
        Cleanup presolving method.
        
        Parameters:
        - presol: Presolving object
        """
    
    def presolexec(self, presol, presolvinground, presoltiming, nnewfixedvars, 
                   nnewaggrvars, nnewchgvartypes, nnewchgbds, nnewholes,
                   nnewdelconss, nnewaddconss, nnewupgdconss, nnewchgcoefs,
                   nnewchgsides, nfixedvars, naggrvars, nchgvartypes, 
                   nchgbds, naddholes, ndelconss, naddconss, nupgdconss,
                   nchgcoefs, nchgsides, result):
        """
        Execute presolving routine.
        
        Parameters:
        - presol: Presolving object
        - presolvinground (int): Current presolving round
        - presoltiming: Timing information
        - Various counters for tracking changes made
        - result: Dictionary to store presolving result
        
        Should modify problem and update counters for changes made
        """

Variable Pricers

Column generation algorithms for adding variables dynamically during optimization.

class Pricer:
    """
    Variable pricer for implementing column generation algorithms
    that add variables dynamically during the optimization process.
    """
    
    def pricerinit(self, pricer):
        """
        Initialize pricer.
        
        Parameters:
        - pricer: Pricer object
        """
    
    def pricerexit(self, pricer):
        """
        Cleanup pricer.
        
        Parameters:
        - pricer: Pricer object
        """
    
    def pricerredcost(self, pricer, result):
        """
        Perform reduced cost pricing.
        
        Parameters:
        - pricer: Pricer object
        - result: Dictionary to store pricing result
        
        Should add variables with negative reduced cost
        """
    
    def pricerfarkas(self, pricer, result):
        """
        Perform Farkas pricing for infeasible LP.
        
        Parameters:
        - pricer: Pricer object
        - result: Dictionary to store pricing result
        
        Should add variables to prove infeasibility
        """

Propagators

Custom domain propagation algorithms for tightening variable bounds.

class Prop:
    """
    Propagator handler for implementing custom domain propagation
    algorithms that tighten variable bounds and domains.
    """
    
    def propinit(self, prop):
        """
        Initialize propagator.
        
        Parameters:
        - prop: Propagator object
        """
    
    def propexit(self, prop):
        """
        Cleanup propagator.
        
        Parameters:
        - prop: Propagator object
        """
    
    def propexec(self, prop, proptiming, result):
        """
        Execute propagation algorithm.
        
        Parameters:
        - prop: Propagator object
        - proptiming: When propagation is called
        - result: Dictionary to store propagation result
        
        Timing values:
        - BEFORELP: Before LP solving
        - DURINGLPLOOP: During LP solving
        - AFTERLPLOOP: After LP solving  
        """
    
    def propresprop(self, prop, cutoff, result):
        """
        Resolve propagation after bound changes.
        
        Parameters:
        - prop: Propagator object
        - cutoff (bool): Whether cutoff occurred
        - result: Dictionary to store result
        """

Cutting Plane Separators

Custom cutting plane generation for strengthening LP relaxations.

class Sepa:
    """
    Separator handler for implementing custom cutting plane generation
    algorithms to strengthen LP relaxations.
    """
    
    def sepainit(self, sepa):
        """
        Initialize separator.
        
        Parameters:
        - sepa: Separator object
        """
    
    def sepaexit(self, sepa):
        """
        Cleanup separator.
        
        Parameters:
        - sepa: Separator object
        """
    
    def sepaexeclp(self, sepa, result):
        """
        Execute separation for LP solution.
        
        Parameters:
        - sepa: Separator object
        - result: Dictionary to store separation result
        
        Should add cutting planes that cut off current LP solution
        """
    
    def sepaexecsol(self, sepa, solution, result):
        """
        Execute separation for given solution.
        
        Parameters:
        - sepa: Separator object
        - solution: Solution to separate
        - result: Dictionary to store separation result
        """

File Readers

Custom file format readers for specialized problem formats.

class Reader:
    """
    File reader handler for implementing custom file format parsers
    to read optimization problems from specialized formats.
    """
    
    def readerread(self, reader, filename, result):
        """
        Read problem from file.
        
        Parameters:
        - reader: Reader object
        - filename (str): Path to file to read
        - result: Dictionary to store reading result
        
        Should parse file and create corresponding SCIP problem
        """
    
    def readerwrite(self, reader, file, name, transformed, objsense, objscale,
                    objoffset, binvars, intvars, implvars, contvars, fixedvars,
                    startnvars, conss, maxnconss, startnconss, genericnames, result):
        """
        Write problem to file.
        
        Parameters:
        - reader: Reader object
        - file: File handle for writing
        - Various problem components to write
        - result: Dictionary to store writing result
        """

Linear Programming Interface

Access to the LP relaxation and linear programming solver interface for advanced LP operations.

class LP:
    """
    Linear programming interface providing access to LP relaxation,
    dual values, basis information, and advanced LP solver operations.
    """
    
    def getNRows(self):
        """
        Get number of rows in current LP.
        
        Returns:
        int: Number of LP rows
        """
    
    def getNCols(self):
        """
        Get number of columns in current LP.
        
        Returns:
        int: Number of LP columns
        """
    
    def getSolstat(self):
        """
        Get LP solution status.
        
        Returns:
        SCIP_LPSOLSTAT: LP solution status
        """
    
    def getObjval(self):
        """
        Get LP objective value.
        
        Returns:
        float: LP objective value
        """
    
    def getColsData(self):
        """
        Get LP column data.
        
        Returns:
        tuple: Column data including lower bounds, upper bounds, objective coefficients
        """
    
    def getRowsData(self):
        """
        Get LP row data.
        
        Returns:
        tuple: Row data including left-hand sides, right-hand sides, row matrix
        """
    
    def getPrimalSol(self):
        """
        Get primal LP solution.
        
        Returns:
        list: Primal solution values for LP columns
        """
    
    def getDualSol(self):
        """
        Get dual LP solution.
        
        Returns:
        list: Dual solution values for LP rows
        """
    
    def getRedcostSol(self):
        """
        Get reduced cost solution.
        
        Returns:
        list: Reduced costs for LP columns
        """

Usage Examples

Custom Branching Rule

from pyscipopt import Model, Branchrule, SCIP_RESULT

class CustomBranchingRule(Branchrule):
    """Example: Most fractional branching rule"""
    
    def branchexeclp(self, branchrule, allowaddcons, result):
        """Branch on most fractional variable"""
        
        # Get LP solution
        lpsol = {}
        for var in self.model.getVars():
            if var.vtype() in ['B', 'I']:  # Binary or integer variables
                lpsol[var] = self.model.getVal(var)
        
        # Find most fractional variable
        most_fractional_var = None
        max_fractionality = 0
        
        for var, val in lpsol.items():
            fractionality = min(val - int(val), int(val) + 1 - val)
            if fractionality > max_fractionality:
                max_fractionality = fractionality
                most_fractional_var = var
        
        if most_fractional_var is not None and max_fractionality > 1e-6:
            # Create two child nodes
            val = lpsol[most_fractional_var]
            
            # Create down branch: var <= floor(val)
            down_node = self.model.createChild(1.0, 1.0)  # priority, estimate
            self.model.addConsNode(down_node, 
                                   most_fractional_var <= int(val))
            
            # Create up branch: var >= ceil(val) 
            up_node = self.model.createChild(1.0, 1.0)
            self.model.addConsNode(up_node,
                                   most_fractional_var >= int(val) + 1)
            
            result["result"] = SCIP_RESULT.BRANCHED
        else:
            result["result"] = SCIP_RESULT.DIDNOTRUN

# Usage
model = Model("branching_example")

# Add variables and constraints
x = model.addVar(name="x", vtype="I", lb=0, ub=10)
y = model.addVar(name="y", vtype="I", lb=0, ub=8)

model.addCons(2.5*x + 3.7*y <= 15.3)
model.addCons(1.2*x + 0.8*y >= 3.1)

model.setObjective(x + y, "maximize")

# Include custom branching rule
branching_rule = CustomBranchingRule()
model.includeBranchrule(branching_rule, "MostFractional", "Branch on most fractional variable", 
                        priority=100000, maxdepth=-1, maxbounddist=1.0)

model.optimize()

Custom Heuristic

from pyscipopt import Model, Heur, SCIP_RESULT, SCIP_HEURTIMING

class RoundingHeuristic(Heur):
    """Simple rounding heuristic for mixed-integer problems"""
    
    def heurexec(self, heur, heurtiming, nodeinfeasible, result):
        """Execute rounding heuristic"""
        
        if nodeinfeasible:
            result["result"] = SCIP_RESULT.DIDNOTRUN
            return
        
        # Get current LP solution
        lpsol = {}
        for var in self.model.getVars():
            lpsol[var] = self.model.getVal(var)
        
        # Create rounded solution
        rounded_sol = self.model.createSol(heur)
        
        for var in self.model.getVars():
            if var.vtype() in ['B', 'I']:
                # Round to nearest integer
                rounded_val = round(lpsol[var])
                # Respect bounds
                rounded_val = max(var.getLbLocal(), min(var.getUbLocal(), rounded_val))
                self.model.setSolVal(rounded_sol, var, rounded_val)
            else:
                # Keep continuous variables as is
                self.model.setSolVal(rounded_sol, var, lpsol[var])
        
        # Try to add solution
        feasible = self.model.checkSol(rounded_sol)
        if feasible:
            self.model.addSol(rounded_sol)
            result["result"] = SCIP_RESULT.FOUNDSOL
        else:
            result["result"] = SCIP_RESULT.DIDNOTFIND
        
        # Free solution
        self.model.freeSol(rounded_sol)

# Usage  
model = Model("heuristic_example")

# Create mixed-integer problem
x = [model.addVar(name=f"x_{i}", vtype="I", lb=0, ub=10) for i in range(5)]
y = [model.addVar(name=f"y_{i}", lb=0, ub=5) for i in range(3)]

# Add constraints
model.addCons(quicksum(2*x[i] for i in range(5)) + quicksum(y[j] for j in range(3)) <= 25)
model.addCons(quicksum(x[i] for i in range(5)) >= 3)

# Objective
model.setObjective(quicksum(x[i] for i in range(5)) + quicksum(3*y[j] for j in range(3)), "maximize")

# Include custom heuristic
rounding_heur = RoundingHeuristic()
model.includeHeur(rounding_heur, "SimpleRounding", "Round fractional values to nearest integer",
                  'L', priority=1000, freq=1, freqofs=0, maxdepth=-1,
                  timingmask=SCIP_HEURTIMING.AFTERLPLOOP)

model.optimize()

Custom Constraint Handler

from pyscipopt import Model, Conshdlr, SCIP_RESULT

class AllDifferentConstraintHandler(Conshdlr):
    """All-different constraint: all variables must have different values"""
    
    def conscheck(self, constraints, solution, checkintegrality, checklprows, printreason, result):
        """Check if all variables have different values"""
        
        for cons in constraints:
            # Get variables from constraint (stored during constraint creation)
            variables = cons.data["variables"]
            
            # Get solution values
            values = [self.model.getSolVal(solution, var) for var in variables]
            
            # Check if all values are different (within tolerance)
            epsilon = 1e-6
            for i in range(len(values)):
                for j in range(i+1, len(values)):
                    if abs(values[i] - values[j]) < epsilon:
                        if printreason:
                            print(f"All-different violated: {variables[i].name} = {variables[j].name} = {values[i]}")
                        result["result"] = SCIP_RESULT.INFEASIBLE
                        return
        
        result["result"] = SCIP_RESULT.FEASIBLE
    
    def consenfolp(self, constraints, nusefulconss, solinfeasible, result):
        """Enforce all-different constraints for LP solution"""
        
        cuts_added = False
        
        for cons in constraints:
            variables = cons.data["variables"]
            
            # Get current LP values
            values = [(var, self.model.getVal(var)) for var in variables]
            
            # Find pairs of variables with same values
            epsilon = 1e-6
            for i in range(len(values)):
                for j in range(i+1, len(values)):
                    var1, val1 = values[i]
                    var2, val2 = values[j]
                    
                    if abs(val1 - val2) < epsilon:
                        # Add cut to separate this solution
                        # For integer variables: var1 - var2 >= 1 OR var2 - var1 >= 1
                        # Relaxed: |var1 - var2| >= 1
                        
                        # Add two cuts
                        cut1_expr = var1 - var2 >= 1
                        cut2_expr = var2 - var1 >= 1
                        
                        self.model.addCons(cut1_expr, name=f"alldiff_cut1_{var1.name}_{var2.name}")
                        self.model.addCons(cut2_expr, name=f"alldiff_cut2_{var1.name}_{var2.name}")
                        
                        cuts_added = True
        
        if cuts_added:
            result["result"] = SCIP_RESULT.SEPARATED
        else:
            result["result"] = SCIP_RESULT.FEASIBLE

# Usage
model = Model("alldiff_example")

# Create variables
n = 4
x = [model.addVar(name=f"x_{i}", vtype="I", lb=1, ub=n) for i in range(n)]

# Create all-different constraint handler
alldiff_handler = AllDifferentConstraintHandler()
model.includeConshdlr(alldiff_handler, "AllDifferent", "All variables must be different",
                      sepapriority=100, enfopriority=100, checkpriority=-100,
                      sepafreq=1, propfreq=1, eagerfreq=100, maxprerounds=-1,
                      delaysepa=False, delayprop=False, needscons=True)

# Add all-different constraint
alldiff_cons = model.createCons(alldiff_handler, "alldiff_constraint")
alldiff_cons.data = {"variables": x}
model.addCons(alldiff_cons)

# Additional constraints
model.addCons(quicksum(x[i] for i in range(n)) == 10)

# Objective
model.setObjective(quicksum((i+1)*x[i] for i in range(n)), "maximize")

model.optimize()

Install with Tessl CLI

npx tessl i tessl/pypi-pyscipopt

docs

core-model.md

expressions.md

index.md

math-functions.md

parameters.md

plugins.md

variables-constraints.md

tile.json