Python interface and modeling environment for SCIP optimization solver
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Comprehensive parameter system for fine-tuning solver behavior, setting limits, configuring algorithms, and accessing solver statistics. PySCIPOpt provides extensive control over SCIP's optimization process through hundreds of parameters.
Core functions for getting, setting, and managing solver parameters.
def setParam(self, name, value):
"""
Set a solver parameter.
Parameters:
- name (str): Parameter name (hierarchical with '/' separators)
- value: Parameter value (type depends on parameter)
Examples:
- "limits/time": float (time limit in seconds)
- "limits/nodes": int (node limit)
- "numerics/feastol": float (feasibility tolerance)
- "branching/scorefunc": str (scoring function)
"""
def getParam(self, name):
"""
Get current value of a solver parameter.
Parameters:
- name (str): Parameter name
Returns:
Parameter value (int, float, str, or bool depending on parameter type)
"""
def getParams(self):
"""
Get all parameters and their current values.
Returns:
dict: Dictionary mapping parameter names to values
"""
def setParams(self, params):
"""
Set multiple parameters at once.
Parameters:
- params (dict): Dictionary mapping parameter names to values
"""
def resetParam(self, name):
"""
Reset parameter to its default value.
Parameters:
- name (str): Parameter name to reset
"""
def resetParams(self):
"""Reset all parameters to their default values."""
def readParams(self, file):
"""
Read parameters from file.
Parameters:
- file (str): Path to parameter file (.set format)
"""
def writeParams(self, filename='param.set', comments=True, changed=True):
"""
Write parameters to file.
Parameters:
- filename (str): Output filename
- comments (bool): Include parameter descriptions as comments
- changed (bool): Only write parameters that differ from defaults
"""Predefined parameter configurations for different solving strategies.
def setEmphasis(self, emphasis, quiet=True):
"""
Set parameter emphasis for different solving strategies.
Parameters:
- emphasis: Emphasis setting from SCIP_PARAMEMPHASIS
'DEFAULT': Balanced default settings
'CPSOLVER': Settings for constraint programming
'EASYCIP': Settings for easy instances
'FEASIBILITY': Focus on finding feasible solutions quickly
'HARDLP': Settings for hard linear programming relaxations
'OPTIMALITY': Focus on proving optimality
'COUNTER': Settings for adversarial/counter examples
'PHASEFEAS': Phase 1: focus on feasibility
'PHASEIMPROVE': Phase 2: improve solution quality
'PHASEPROOF': Phase 3: prove optimality
- quiet (bool): Suppress output during emphasis setting
"""
# Parameter emphasis constants
SCIP_PARAMEMPHASIS = {
'DEFAULT', 'CPSOLVER', 'EASYCIP', 'FEASIBILITY',
'HARDLP', 'OPTIMALITY', 'COUNTER', 'PHASEFEAS',
'PHASEIMPROVE', 'PHASEPROOF'
}Parameters controlling computational resource usage.
# Time limits
def setParam(self, "limits/time", seconds):
"""Set time limit in seconds (float)"""
def setParam(self, "limits/abortfactor", factor):
"""Set abort factor for early termination (float, default: 1e-6)"""
# Node limits
def setParam(self, "limits/nodes", count):
"""Set maximum number of branch-and-bound nodes (int, default: -1 = unlimited)"""
def setParam(self, "limits/totalnodes", count):
"""Set total node limit across all runs (int)"""
def setParam(self, "limits/stallnodes", count):
"""Set stalling node limit (int, default: -1)"""
# Memory limits
def setParam(self, "limits/memory", megabytes):
"""Set memory limit in megabytes (float, default: 8796093022208.0)"""
# Solution limits
def setParam(self, "limits/solutions", count):
"""Set maximum number of solutions to find (int, default: -1)"""
def setParam(self, "limits/bestsol", count):
"""Set limit on number of best solutions (int, default: -1)"""
# Gap limits
def setParam(self, "limits/gap", gap):
"""Set relative gap limit for termination (float, default: 0.0)"""
def setParam(self, "limits/absgap", gap):
"""Set absolute gap limit for termination (float, default: 0.0)"""Parameters controlling numerical precision and feasibility checking.
# Feasibility tolerances
def setParam(self, "numerics/feastol", tolerance):
"""Set feasibility tolerance (float, default: 1e-6)"""
def setParam(self, "numerics/lpfeastol", tolerance):
"""Set LP feasibility tolerance (float, default: 1e-6)"""
def setParam(self, "numerics/dualfeastol", tolerance):
"""Set dual feasibility tolerance (float, default: 1e-7)"""
# Optimality tolerances
def setParam(self, "numerics/epsilon", tolerance):
"""Set epsilon for equality comparisons (float, default: 1e-9)"""
def setParam(self, "numerics/sumepsilon", tolerance):
"""Set epsilon for sum comparisons (float, default: 1e-6)"""
# Integrality tolerance
def setParam(self, "numerics/integralityTol", tolerance):
"""Set integrality tolerance (float, default: 1e-6)"""
# Infinity value
def setParam(self, "numerics/infinity", value):
"""Set infinity value (float, default: 1e20)"""Parameters controlling branching strategies and variable selection.
# Branching rules
def setParam(self, "branching/scorefunc", function):
"""
Set branching score function (str):
- 's': sum of scores
- 'p': product of scores
- 'q': quotient of scores
"""
def setParam(self, "branching/scorefactor", factor):
"""Set branching score factor (float, default: 0.167)"""
def setParam(self, "branching/preferbinary", prefer):
"""Prefer branching on binary variables (bool, default: False)"""
# Variable selection
def setParam(self, "branching/relpscost/conflictweight", weight):
"""Set conflict weight in reliable pseudocost branching (float)"""
def setParam(self, "branching/relpscost/minreliable", count):
"""Set minimum reliable count (int, default: 1)"""Parameters controlling cutting plane generation and separation.
# General cutting settings
def setParam(self, "separating/maxrounds", rounds):
"""Set maximum separation rounds per node (int, default: -1)"""
def setParam(self, "separating/maxroundsroot", rounds):
"""Set maximum separation rounds at root (int, default: -1)"""
def setParam(self, "separating/maxcuts", cuts):
"""Set maximum cuts per separation round (int, default: 100)"""
def setParam(self, "separating/maxcutsroot", cuts):
"""Set maximum cuts per round at root (int, default: 2000)"""
# Specific cut types
def setParam(self, "separating/gomory/freq", frequency):
"""Set Gomory cut frequency (int, default: 10)"""
def setParam(self, "separating/clique/freq", frequency):
"""Set clique cut frequency (int, default: 0)"""
def setParam(self, "separating/knapsackcover/freq", frequency):
"""Set knapsack cover cut frequency (int, default: 0)"""Parameters controlling problem preprocessing and simplification.
# Presolving rounds
def setParam(self, "presolving/maxrounds", rounds):
"""Set maximum presolving rounds (int, default: -1)"""
def setParam(self, "presolving/maxrestarts", restarts):
"""Set maximum presolving restarts (int, default: -1)"""
# Component presolvers
def setParam(self, "presolving/components/maxintvars", count):
"""Set maximum integer variables for component detection (int)"""
def setParam(self, "presolving/dualfix/enabled", enabled):
"""Enable dual fixing presolver (bool, default: True)"""
def setParam(self, "presolving/domcol/enabled", enabled):
"""Enable dominated columns presolver (bool, default: True)"""Parameters controlling primal heuristics and solution finding strategies.
# General heuristic settings
def setParam(self, "heuristics/emphasis", emphasis):
"""
Set heuristic emphasis:
- 'DEFAULT': balanced approach
- 'AGGRESSIVE': more heuristic calls
- 'FAST': fewer heuristic calls
- 'OFF': disable heuristics
"""
# Specific heuristics
def setParam(self, "heuristics/rounding/freq", frequency):
"""Set simple rounding frequency (int, default: 1)"""
def setParam(self, "heuristics/shifting/freq", frequency):
"""Set shifting heuristic frequency (int, default: 10)"""
def setParam(self, "heuristics/localbranching/freq", frequency):
"""Set local branching frequency (int, default: -1)"""
def setParam(self, "heuristics/diving/maxdepth", depth):
"""Set maximum diving depth (int, default: -1)"""Parameters for controlling the linear programming solver interface.
# LP solver selection
def setParam(self, "lp/solver", solver):
"""
Set LP solver (str):
- 'soplex': SoPlex (default)
- 'cplex': IBM CPLEX
- 'gurobi': Gurobi
- 'clp': COIN-OR CLP
"""
# LP solving parameters
def setParam(self, "lp/pricing", pricing):
"""
Set LP pricing strategy (str):
- 'lpi': LP interface default
- 'auto': automatic
- 'dantzig': Dantzig rule
- 'partial': partial pricing
- 'steep': steepest edge
- 'quicksteep': quick steepest edge
- 'devex': devex
"""
def setParam(self, "lp/iterlim", limit):
"""Set LP iteration limit (int, default: -1)"""
def setParam(self, "lp/rootiterlim", limit):
"""Set root LP iteration limit (int, default: -1)"""Parameters controlling solver output and logging.
# Verbosity levels
def setParam(self, "display/verblevel", level):
"""
Set verbosity level (int):
- 0: quiet (errors only)
- 1: normal (default)
- 2: verbose
- 3: full
- 4: debug
- 5: trace
"""
# Display columns
def setParam(self, "display/freq", frequency):
"""Set display frequency for node processing (int, default: 100)"""
def setParam(self, "display/headerfreq", frequency):
"""Set header display frequency (int, default: 15)"""
# Statistics output
def setParam(self, "display/statistics", enabled):
"""Enable statistics display (bool, default: True)"""Functions to access detailed solving statistics and performance information.
def getTotalTime(self):
"""Get total solving time including reading and presolving (float)"""
def getSolvingTime(self):
"""Get pure solving time excluding presolving (float)"""
def getReadingTime(self):
"""Get time spent reading problem files (float)"""
def getPresolvingTime(self):
"""Get time spent in presolving (float)"""
def getNNodes(self):
"""Get number of processed branch-and-bound nodes (int)"""
def getNTotalNodes(self):
"""Get total number of created nodes (int)"""
def getNRuns(self):
"""Get number of runs performed (int)"""
def getGap(self):
"""Get current optimality gap as percentage (float)"""
def getDepth(self):
"""Get current depth in branch-and-bound tree (int)"""
def getMaxDepth(self):
"""Get maximum depth reached (int)"""
def getDualboundRoot(self):
"""Get dual bound at root node (float)"""
def getPrimalboundRoot(self):
"""Get primal bound at root node (float)"""
def getNSols(self):
"""Get number of feasible solutions found (int)"""
def getNSolsFound(self):
"""Get total number of solutions found including infeasible (int)"""
def getNLimSolsFound(self):
"""Get number of solutions found respecting objective limit (int)"""from pyscipopt import Model
model = Model("parameter_example")
# Set time limit to 60 seconds
model.setParam("limits/time", 60.0)
# Set node limit
model.setParam("limits/nodes", 1000)
# Set gap tolerance to 1%
model.setParam("limits/gap", 0.01)
# Set verbosity level
model.setParam("display/verblevel", 2) # Verbose output
# Check current parameter values
print(f"Time limit: {model.getParam('limits/time')}")
print(f"Gap tolerance: {model.getParam('limits/gap')}")
print(f"Verbosity: {model.getParam('display/verblevel')}")from pyscipopt import Model
model = Model("multiple_params")
# Set multiple parameters at once
params = {
"limits/time": 300.0, # 5 minutes
"limits/gap": 0.005, # 0.5% gap
"limits/nodes": 10000, # Node limit
"numerics/feastol": 1e-7, # Tighter feasibility tolerance
"branching/scorefunc": 'p', # Product scoring
"separating/maxrounds": 5, # Limit cutting rounds
"display/verblevel": 1 # Normal verbosity
}
model.setParams(params)
# Verify settings
current_params = model.getParams()
for param, value in params.items():
print(f"{param}: {current_params[param]}")from pyscipopt import Model, SCIP_PARAMEMPHASIS
model = Model("emphasis_example")
# Configure for feasibility finding
model.setEmphasis(SCIP_PARAMEMPHASIS.FEASIBILITY)
# Add problem...
x = model.addVar(name="x", vtype="I", lb=0, ub=100)
y = model.addVar(name="y", vtype="I", lb=0, ub=100)
model.addCons(3*x + 2*y <= 150)
model.addCons(x + 4*y <= 200)
model.addCons(2*x + y >= 50)
model.setObjective(x + y, "maximize")
# Solve with feasibility emphasis
model.optimize()
if model.getNSols() > 0:
print("Feasible solution found quickly!")
# Switch emphasis to optimality proving
model.setEmphasis(SCIP_PARAMEMPHASIS.OPTIMALITY)
# Continue solving for better bounds
model.optimize()from pyscipopt import Model
# Configuration for hard mixed-integer programming
def configure_for_hard_MIP(model):
"""Configure parameters for difficult MIP instances"""
# Increase time and node limits
model.setParam("limits/time", 3600.0) # 1 hour
model.setParam("limits/nodes", 1000000) # 1M nodes
# Tighter tolerances
model.setParam("numerics/feastol", 1e-8)
model.setParam("numerics/integralityTol", 1e-8)
# Aggressive cutting planes
model.setParam("separating/maxroundsroot", 100)
model.setParam("separating/maxcutsroot", 5000)
# More thorough presolving
model.setParam("presolving/maxrounds", 20)
# Strong branching for better tree exploration
model.setParam("branching/scorefunc", 'p')
# More aggressive heuristics
model.setParam("heuristics/emphasis", "AGGRESSIVE")
# Configuration for quick feasibility checking
def configure_for_quick_feasibility(model):
"""Configure for finding feasible solutions quickly"""
# Short time limit
model.setParam("limits/time", 60.0)
# Looser tolerances
model.setParam("numerics/feastol", 1e-5)
# Minimal cutting planes
model.setParam("separating/maxrounds", 1)
# Quick presolving
model.setParam("presolving/maxrounds", 3)
# Focus on heuristics
model.setParam("heuristics/emphasis", "AGGRESSIVE")
model.setParam("heuristics/rounding/freq", 1)
model.setParam("heuristics/shifting/freq", 1)
# Usage
model = Model("specialized_config")
# Choose configuration based on problem characteristics
problem_is_hard = True
if problem_is_hard:
configure_for_hard_MIP(model)
else:
configure_for_quick_feasibility(model)
# Add problem and solve...from pyscipopt import Model
model = Model("param_files")
# Configure parameters
model.setParam("limits/time", 300.0)
model.setParam("limits/gap", 0.01)
model.setParam("display/verblevel", 2)
# Save current parameters to file
model.writeParams("my_settings.set", comments=True, changed=True)
# Create new model and load parameters
model2 = Model("param_load_test")
model2.readParams("my_settings.set")
# Verify parameters were loaded
print(f"Loaded time limit: {model2.getParam('limits/time')}")
print(f"Loaded gap: {model2.getParam('limits/gap')}")from pyscipopt import Model
import time
model = Model("performance_monitoring")
# Add a complex problem
n = 50
x = [[model.addVar(name=f"x_{i}_{j}", vtype="B")
for j in range(n)] for i in range(n)]
# Assignment constraints
for i in range(n):
model.addCons(quicksum(x[i][j] for j in range(n)) == 1)
for j in range(n):
model.addCons(quicksum(x[i][j] for i in range(n)) == 1)
# Objective with random costs
import random
random.seed(42)
costs = [[random.randint(1, 100) for j in range(n)] for i in range(n)]
model.setObjective(quicksum(costs[i][j] * x[i][j]
for i in range(n) for j in range(n)), "minimize")
# Set parameters for monitoring
model.setParam("limits/time", 120.0)
model.setParam("display/freq", 50) # Display every 50 nodes
# Solve and monitor
start_time = time.time()
model.optimize()
wall_time = time.time() - start_time
# Print detailed statistics
print("\n=== Solving Statistics ===")
print(f"Status: {model.getStatus()}")
print(f"Wall time: {wall_time:.2f} seconds")
print(f"SCIP total time: {model.getTotalTime():.2f} seconds")
print(f"SCIP solving time: {model.getSolvingTime():.2f} seconds")
print(f"Presolving time: {model.getPresolvingTime():.2f} seconds")
print(f"Nodes processed: {model.getNNodes()}")
print(f"Total nodes: {model.getNTotalNodes()}")
print(f"Maximum depth: {model.getMaxDepth()}")
print(f"Solutions found: {model.getNSols()}")
print(f"Gap: {model.getGap():.4%}")
if model.getStatus() == 'optimal':
print(f"Optimal value: {model.getObjVal()}")
elif model.getNSols() > 0:
print(f"Best value found: {model.getObjVal()}")from pyscipopt import Model, Eventhdlr, SCIP_EVENTTYPE
class ParameterAdjuster(Eventhdlr):
"""Adjust parameters dynamically during solving"""
def __init__(self):
self.nodes_processed = 0
def eventexec(self, eventhdlr, event):
"""Adjust parameters based on solving progress"""
if event.getType() == SCIP_EVENTTYPE.NODEBRANCHED:
self.nodes_processed += 1
# After 1000 nodes, switch to faster cutting
if self.nodes_processed == 1000:
self.model.setParam("separating/maxrounds", 2)
print("Switched to faster cutting after 1000 nodes")
# After 5000 nodes, reduce heuristic frequency
elif self.nodes_processed == 5000:
self.model.setParam("heuristics/rounding/freq", 10)
self.model.setParam("heuristics/shifting/freq", 20)
print("Reduced heuristic frequency after 5000 nodes")
# Usage
model = Model("dynamic_params")
# Set up problem...
n = 20
x = [model.addVar(name=f"x_{i}", vtype="I", lb=0, ub=10) for i in range(n)]
model.addCons(quicksum(x[i] for i in range(n)) <= n*5)
model.setObjective(quicksum((i+1)*x[i] for i in range(n)), "maximize")
# Include parameter adjuster
adjuster = ParameterAdjuster()
model.includeEventhdlr(adjuster, "ParamAdjuster", "Adjust parameters dynamically")
# Catch node events
model.catchEvent(SCIP_EVENTTYPE.NODEBRANCHED, adjuster)
model.optimize()Install with Tessl CLI
npx tessl i tessl/pypi-pyscipopt