From 2da8fa675495b67153ec5a8d57377db3def6aace Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Fri, 2 Oct 2026 23:31:42 +0200 Subject: [PATCH 01/24] Annotate straightforward PySCIPOpt stub APIs --- src/pyscipopt/recipes/infeasibilities.py | 8 +- src/pyscipopt/scip.pyi | 284 ++++++++++++----------- 2 files changed, 147 insertions(+), 145 deletions(-) diff --git a/src/pyscipopt/recipes/infeasibilities.py b/src/pyscipopt/recipes/infeasibilities.py index 2d50de7ab..d6d63684b 100644 --- a/src/pyscipopt/recipes/infeasibilities.py +++ b/src/pyscipopt/recipes/infeasibilities.py @@ -35,10 +35,10 @@ def get_infeasible_constraints(orig_model: Model, verbose: bool = False): model.optimize() n_infeasibilities_detected = 0 - for c in binary: - if model.isInfinity(model.getVal(binary[c])) or model.isGT(model.getVal(binary[c]), 0): + for name in binary: + if model.isInfinity(model.getVal(binary[name])) or model.isGT(model.getVal(binary[name]), 0): n_infeasibilities_detected += 1 - print("Constraint %s is causing an infeasibility." % c) + print("Constraint %s is causing an infeasibility." % name) if verbose: if n_infeasibilities_detected > 0: @@ -47,4 +47,4 @@ def get_infeasible_constraints(orig_model: Model, verbose: bool = False): else: print("Model is feasible.") - return n_infeasibilities_detected, aux \ No newline at end of file + return n_infeasibilities_detected, aux diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 316b13e9f..865106a62 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -105,11 +105,11 @@ class Benderscut: @disjoint_base class BoundChange: def __init__(self) -> None: ... - def getBoundchgtype(self) -> Incomplete: ... - def getBoundtype(self) -> Incomplete: ... - def getNewBound(self) -> Incomplete: ... - def getVar(self) -> Incomplete: ... - def isRedundant(self) -> Incomplete: ... + def getBoundchgtype(self) -> int: ... + def getBoundtype(self) -> int: ... + def getNewBound(self) -> float: ... + def getVar(self) -> Variable: ... + def isRedundant(self) -> bool: ... @disjoint_base class Branchrule: @@ -128,15 +128,15 @@ class Branchrule: class Column: data: Incomplete def __init__(self) -> None: ... - def getAge(self) -> Incomplete: ... - def getBasisStatus(self) -> Incomplete: ... - def getLPPos(self) -> Incomplete: ... - def getLb(self) -> Incomplete: ... - def getObjCoeff(self) -> Incomplete: ... - def getPrimsol(self) -> Incomplete: ... - def getUb(self) -> Incomplete: ... - def getVar(self) -> Incomplete: ... - def isIntegral(self) -> Incomplete: ... + def getAge(self) -> int: ... + def getBasisStatus(self) -> Literal["lower", "basic", "upper", "zero"]: ... + def getLPPos(self) -> int: ... + def getLb(self) -> float: ... + def getObjCoeff(self) -> float: ... + def getPrimsol(self) -> float: ... + def getUb(self) -> float: ... + def getVar(self) -> Variable: ... + def isIntegral(self) -> bool: ... def __eq__(self, other: object, /) -> bool: ... def __ge__(self, other: object, /) -> bool: ... def __gt__(self, other: object, /) -> bool: ... @@ -260,26 +260,26 @@ class Constant(GenExpr): @disjoint_base class Constraint: data: Incomplete - name: Incomplete + name: str def __init__(self) -> None: ... - def getConshdlrName(self) -> Incomplete: ... - def isActive(self) -> Incomplete: ... - def isChecked(self) -> Incomplete: ... - def isCumulative(self) -> Incomplete: ... - def isDynamic(self) -> Incomplete: ... - def isEnforced(self) -> Incomplete: ... - def isInitial(self) -> Incomplete: ... - def isKnapsack(self) -> Incomplete: ... - def isLinear(self) -> Incomplete: ... - def isLinearType(self) -> Incomplete: ... - def isLocal(self) -> Incomplete: ... - def isModifiable(self) -> Incomplete: ... - def isNonlinear(self) -> Incomplete: ... - def isOriginal(self) -> Incomplete: ... - def isPropagated(self) -> Incomplete: ... - def isRemovable(self) -> Incomplete: ... - def isSeparated(self) -> Incomplete: ... - def isStickingAtNode(self) -> Incomplete: ... + def getConshdlrName(self) -> str: ... + def isActive(self) -> bool: ... + def isChecked(self) -> bool: ... + def isCumulative(self) -> bool: ... + def isDynamic(self) -> bool: ... + def isEnforced(self) -> bool: ... + def isInitial(self) -> bool: ... + def isKnapsack(self) -> bool: ... + def isLinear(self) -> bool: ... + def isLinearType(self) -> bool: ... + def isLocal(self) -> bool: ... + def isModifiable(self) -> bool: ... + def isNonlinear(self) -> bool: ... + def isOriginal(self) -> bool: ... + def isPropagated(self) -> bool: ... + def isRemovable(self) -> bool: ... + def isSeparated(self) -> bool: ... + def isStickingAtNode(self) -> bool: ... def ptr(self) -> Incomplete: ... def __eq__(self, other: object, /) -> bool: ... def __ge__(self, other: object, /) -> bool: ... @@ -309,7 +309,7 @@ class Cutsel: @disjoint_base class DomainChanges: def __init__(self) -> None: ... - def getBoundchgs(self) -> Incomplete: ... + def getBoundchgs(self) -> list[BoundChange]: ... @disjoint_base class Event: @@ -317,13 +317,13 @@ class Event: name: Incomplete def __init__(self) -> None: ... def _getEventNames(self) -> Incomplete: ... - def getName(self) -> Incomplete: ... - def getNewBound(self) -> Incomplete: ... - def getNode(self) -> Incomplete: ... - def getOldBound(self) -> Incomplete: ... - def getRow(self) -> Incomplete: ... + def getName(self) -> str: ... + def getNewBound(self) -> float: ... + def getNode(self) -> Node | None: ... + def getOldBound(self) -> float: ... + def getRow(self) -> Row: ... def getType(self) -> Incomplete: ... - def getVar(self) -> Incomplete: ... + def getVar(self) -> Variable: ... def __eq__(self, other: object, /) -> bool: ... def __ge__(self, other: object, /) -> bool: ... def __gt__(self, other: object, /) -> bool: ... @@ -487,14 +487,14 @@ class Heur: @disjoint_base class IIS: def __init__(self) -> None: ... - def getNNodes(self) -> Incomplete: ... - def getSubscip(self) -> Incomplete: ... - def getTime(self) -> Incomplete: ... - def greedyMakeIrreducible(self) -> Incomplete: ... - def isSubscipInfeasible(self) -> Incomplete: ... - def isSubscipIrreducible(self) -> Incomplete: ... - def setSubscipInfeasible(self, infeasible: Incomplete) -> Incomplete: ... - def setSubscipIrreducible(self, irreducible: Incomplete) -> Incomplete: ... + def getNNodes(self) -> int: ... + def getSubscip(self) -> Model: ... + def getTime(self) -> float: ... + def greedyMakeIrreducible(self) -> None: ... + def isSubscipInfeasible(self) -> bool: ... + def isSubscipIrreducible(self) -> bool: ... + def setSubscipInfeasible(self, infeasible: bool) -> None: ... + def setSubscipIrreducible(self, irreducible: bool) -> None: ... @disjoint_base class IISfinder: @@ -506,8 +506,10 @@ class IISfinder: @disjoint_base class LP: - name: Incomplete - def __init__(self, name: Incomplete = ..., sense: Incomplete = ...) -> None: ... + name: str + def __init__( + self, name: str = "LP", sense: Literal["minimize", "maximize"] = "minimize" + ) -> None: ... def addCol( self, entries: Incomplete, @@ -541,36 +543,36 @@ class LP: def clear(self) -> Incomplete: ... def delCols(self, firstcol: Incomplete, lastcol: Incomplete) -> Incomplete: ... def delRows(self, firstrow: Incomplete, lastrow: Incomplete) -> Incomplete: ... - def getActivity(self) -> Incomplete: ... - def getBase(self) -> Incomplete: ... - def getBasisInds(self) -> Incomplete: ... + def getActivity(self) -> list[float]: ... + def getBase(self) -> tuple[list[int], list[int]]: ... + def getBasisInds(self) -> list[int]: ... def getBounds( - self, firstcol: Incomplete = ..., lastcol: Incomplete = ... - ) -> Incomplete: ... - def getDual(self) -> Incomplete: ... - def getDualRay(self) -> Incomplete: ... - def getIntParam(self, param: Incomplete) -> Incomplete: ... - def getNIterations(self) -> Incomplete: ... - def getObjVal(self) -> Incomplete: ... - def getPrimal(self) -> Incomplete: ... - def getPrimalRay(self) -> Incomplete: ... - def getRealParam(self, param: Incomplete) -> Incomplete: ... - def getRedcost(self) -> Incomplete: ... + self, firstcol: int = 0, lastcol: int | None = None + ) -> tuple[list[float], list[float]] | None: ... + def getDual(self) -> list[float]: ... + def getDualRay(self) -> list[float] | None: ... + def getIntParam(self, param: Incomplete) -> int: ... + def getNIterations(self) -> int: ... + def getObjVal(self) -> float: ... + def getPrimal(self) -> list[float]: ... + def getPrimalRay(self) -> list[float] | None: ... + def getRealParam(self, param: Incomplete) -> float: ... + def getRedcost(self) -> list[float]: ... def getSides( - self, firstrow: Incomplete = ..., lastrow: Incomplete = ... - ) -> Incomplete: ... - def infinity(self) -> Incomplete: ... - def isDualFeasible(self) -> Incomplete: ... - def isInfinity(self, val: Incomplete) -> Incomplete: ... - def isOptimal(self) -> Incomplete: ... - def isPrimalFeasible(self) -> Incomplete: ... - def ncols(self) -> Incomplete: ... - def nrows(self) -> Incomplete: ... + self, firstrow: int = 0, lastrow: int | None = None + ) -> tuple[list[float], list[float]] | None: ... + def infinity(self) -> float: ... + def isDualFeasible(self) -> bool: ... + def isInfinity(self, val: float) -> bool: ... + def isOptimal(self) -> bool: ... + def isPrimalFeasible(self) -> bool: ... + def ncols(self) -> int: ... + def nrows(self) -> int: ... def readLP(self, filename: Incomplete) -> Incomplete: ... def setBase(self, cstat: Incomplete, rstat: Incomplete) -> Incomplete: ... def setIntParam(self, param: Incomplete, value: Incomplete) -> Incomplete: ... def setRealParam(self, param: Incomplete, value: Incomplete) -> Incomplete: ... - def solve(self, dual: Incomplete = ...) -> Incomplete: ... + def solve(self, dual: bool = True) -> float: ... def writeLP(self, filename: Incomplete) -> Incomplete: ... class MatrixConstraint(np.ndarray): @@ -1306,7 +1308,7 @@ class Model: def getNSols(self) -> int: ... def getNSolsFound(self) -> int: ... def getNStrongbranchLPIterations(self) -> int: ... - def getPrimalDualIntegral(self) -> Incomplete: ... + def getPrimalDualIntegral(self) -> float: ... def getNTotalNodes(self) -> int: ... def getNVars(self, transformed: bool = True) -> int: ... def getNVarsAnd(self, and_cons: Constraint) -> int: ... @@ -1584,7 +1586,7 @@ class Model: def isNLPConstructed(self) -> bool: ... def isNegative(self, val: float) -> bool: ... def isObjChangedProbing(self) -> bool: ... - def isObjIntegral(self) -> Incomplete: ... + def isObjIntegral(self) -> bool: ... def isPositive(self, val: float) -> bool: ... def isReoptEnabled(self) -> bool: ... def isZero(self, value: float) -> bool: ... @@ -1791,13 +1793,13 @@ class Model: @disjoint_base class NLRow: data: Incomplete - name: Incomplete + name: str def __init__(self) -> None: ... - def getConstant(self) -> Incomplete: ... - def getDualsol(self) -> Incomplete: ... - def getLhs(self) -> Incomplete: ... - def getLinearTerms(self) -> Incomplete: ... - def getRhs(self) -> Incomplete: ... + def getConstant(self) -> float: ... + def getDualsol(self) -> float: ... + def getLhs(self) -> float: ... + def getLinearTerms(self) -> list[tuple[Variable, float]]: ... + def getRhs(self) -> float: ... def __eq__(self, other: object, /) -> bool: ... def __ge__(self, other: object, /) -> bool: ... def __gt__(self, other: object, /) -> bool: ... @@ -1810,20 +1812,20 @@ class NLRow: class Node: data: Incomplete def __init__(self) -> None: ... - def getAddedConss(self) -> Incomplete: ... - def getDepth(self) -> Incomplete: ... - def getDomchg(self) -> Incomplete: ... - def getEstimate(self) -> Incomplete: ... - def getLowerbound(self) -> Incomplete: ... - def getNAddedConss(self) -> Incomplete: ... - def getNDomchg(self) -> Incomplete: ... - def getNParentBranchings(self) -> Incomplete: ... - def getNumber(self) -> Incomplete: ... - def getParent(self) -> Incomplete: ... + def getAddedConss(self) -> list[Constraint]: ... + def getDepth(self) -> int: ... + def getDomchg(self) -> DomainChanges | None: ... + def getEstimate(self) -> float: ... + def getLowerbound(self) -> float: ... + def getNAddedConss(self) -> int: ... + def getNDomchg(self) -> tuple[int, int, int]: ... + def getNParentBranchings(self) -> int: ... + def getNumber(self) -> int: ... + def getParent(self) -> Node | None: ... def getParentBranchings(self) -> Incomplete: ... def getType(self) -> Incomplete: ... - def isActive(self) -> Incomplete: ... - def isPropagatedAgain(self) -> Incomplete: ... + def isActive(self) -> bool: ... + def isPropagatedAgain(self) -> bool: ... def __eq__(self, other: object, /) -> bool: ... def __ge__(self, other: object, /) -> bool: ... def __gt__(self, other: object, /) -> bool: ... @@ -2227,28 +2229,28 @@ class Relax: @disjoint_base class Row: data: Incomplete - name: Incomplete + name: str def __init__(self) -> None: ... - def getAge(self) -> Incomplete: ... - def getBasisStatus(self) -> Incomplete: ... - def getCols(self) -> Incomplete: ... - def getConsOriginConshdlrtype(self) -> Incomplete: ... - def getConstant(self) -> Incomplete: ... - def getDualfarkas(self) -> Incomplete: ... - def getDualsol(self) -> Incomplete: ... - def getLPPos(self) -> Incomplete: ... - def getLhs(self) -> Incomplete: ... - def getNLPNonz(self) -> Incomplete: ... - def getNNonz(self) -> Incomplete: ... - def getNorm(self) -> Incomplete: ... + def getAge(self) -> int: ... + def getBasisStatus(self) -> Literal["lower", "basic", "upper"]: ... + def getCols(self) -> list[Column]: ... + def getConsOriginConshdlrtype(self) -> str: ... + def getConstant(self) -> float: ... + def getDualfarkas(self) -> float: ... + def getDualsol(self) -> float: ... + def getLPPos(self) -> int: ... + def getLhs(self) -> float: ... + def getNLPNonz(self) -> int: ... + def getNNonz(self) -> int: ... + def getNorm(self) -> float: ... def getOrigintype(self) -> Incomplete: ... - def getRhs(self) -> Incomplete: ... - def getVals(self) -> Incomplete: ... - def isInGlobalCutpool(self) -> Incomplete: ... - def isIntegral(self) -> Incomplete: ... - def isLocal(self) -> Incomplete: ... - def isModifiable(self) -> Incomplete: ... - def isRemovable(self) -> Incomplete: ... + def getRhs(self) -> float: ... + def getVals(self) -> list[float]: ... + def isInGlobalCutpool(self) -> bool: ... + def isIntegral(self) -> bool: ... + def isLocal(self) -> bool: ... + def isModifiable(self) -> bool: ... + def isRemovable(self) -> bool: ... def __eq__(self, other: object, /) -> bool: ... def __ge__(self, other: object, /) -> bool: ... def __gt__(self, other: object, /) -> bool: ... @@ -2281,8 +2283,8 @@ class Solution: def __init__(self, raise_error: Incomplete = ...) -> None: ... def _checkStage(self, method: Incomplete) -> Incomplete: ... def getOrigin(self) -> Incomplete: ... - def retransform(self) -> Incomplete: ... - def translate(self, target: Incomplete) -> Incomplete: ... + def retransform(self) -> None: ... + def translate(self, target: Model) -> Solution: ... def __getitem__(self, index: Incomplete, /) -> Incomplete: ... def __setitem__(self, key: Incomplete, value: Incomplete, /) -> None: ... @@ -2393,40 +2395,40 @@ class VarExpr(GenExpr): @disjoint_base class Variable(Expr): data: Incomplete - name: Incomplete + name: str def __init__(self) -> None: ... - def getAvgSol(self) -> Incomplete: ... - def getCol(self) -> Incomplete: ... + def getAvgSol(self) -> float: ... + def getCol(self) -> Column: ... def getImplType(self) -> Incomplete: ... - def getIndex(self) -> Incomplete: ... - def getLPSol(self) -> Incomplete: ... - def getLbGlobal(self) -> Incomplete: ... - def getLbLocal(self) -> Incomplete: ... - def getLbOriginal(self) -> Incomplete: ... + def getIndex(self) -> int: ... + def getLPSol(self) -> float: ... + def getLbGlobal(self) -> float: ... + def getLbLocal(self) -> float: ... + def getLbOriginal(self) -> float: ... def getNBranchings(self, branchdir: Incomplete) -> Incomplete: ... def getNBranchingsCurrentRun(self, branchdir: Incomplete) -> Incomplete: ... - def getNLocksDown(self) -> Incomplete: ... + def getNLocksDown(self) -> int: ... def getNLocksDownType(self, locktype: Incomplete) -> Incomplete: ... - def getNLocksUp(self) -> Incomplete: ... + def getNLocksUp(self) -> int: ... def getNLocksUpType(self, locktype: Incomplete) -> Incomplete: ... - def getObj(self) -> Incomplete: ... + def getObj(self) -> float: ... def getStatus(self) -> Incomplete: ... - def getUbGlobal(self) -> Incomplete: ... - def getUbLocal(self) -> Incomplete: ... - def getUbOriginal(self) -> Incomplete: ... - def isActive(self) -> Incomplete: ... - def isBinary(self) -> Incomplete: ... - def isDeletable(self) -> Incomplete: ... - def isImpliedIntegral(self) -> Incomplete: ... - def isInLP(self) -> Incomplete: ... - def isIntegral(self) -> Incomplete: ... - def isNonImpliedIntegral(self) -> Incomplete: ... - def isOriginal(self) -> Incomplete: ... - def isRelaxationOnly(self) -> Incomplete: ... - def markRelaxationOnly(self) -> Incomplete: ... + def getUbGlobal(self) -> float: ... + def getUbLocal(self) -> float: ... + def getUbOriginal(self) -> float: ... + def isActive(self) -> bool: ... + def isBinary(self) -> bool: ... + def isDeletable(self) -> bool: ... + def isImpliedIntegral(self) -> bool: ... + def isInLP(self) -> bool: ... + def isIntegral(self) -> bool: ... + def isNonImpliedIntegral(self) -> bool: ... + def isOriginal(self) -> bool: ... + def isRelaxationOnly(self) -> bool: ... + def markRelaxationOnly(self) -> None: ... def ptr(self) -> Incomplete: ... def varMayRound(self, direction: Incomplete = ...) -> Incomplete: ... - def vtype(self) -> Incomplete: ... + def vtype(self) -> Literal["BINARY", "INTEGER", "CONTINUOUS", "IMPLINT"]: ... @disjoint_base class _VarArray: From 097dfbb3c373fadb891bd99203feafd55e712c3f Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Sat, 3 Oct 2026 00:02:10 +0200 Subject: [PATCH 02/24] Annotate constraint inputs and solution indexing --- src/pyscipopt/scip.pyi | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 865106a62..aa64b9308 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -692,7 +692,7 @@ class Model: def addCoefLogicor(self, logicor_cons: Constraint, var: Variable) -> None: ... def addCons( self, - cons: Incomplete, + cons: ExprCons, name: str = "", initial: bool = True, separate: bool = True, @@ -759,7 +759,7 @@ class Model: ) -> Constraint: ... def addConsDisjunction( self, - conss: Iterable[Incomplete], + conss: Iterable[ExprCons], name: str = "", initial: bool = True, relaxcons: Constraint | None = None, @@ -774,7 +774,7 @@ class Model: ) -> Constraint: ... def addConsIndicator( self, - cons: Incomplete, + cons: ExprCons, binvar: Variable | None = None, activeone: bool = True, name: str = "", @@ -807,7 +807,7 @@ class Model: ) -> Constraint: ... def addConsLocal( self, - cons: Incomplete, + cons: ExprCons, validnode: Node | None = None, name: str = "", initial: bool = True, @@ -838,7 +838,7 @@ class Model: def addConsNode( self, node: Node, - cons: Incomplete, + cons: ExprCons, validnode: Node | None = None, name: str = "", initial: bool = True, @@ -915,7 +915,7 @@ class Model: ) -> Constraint: ... def addConss( self, - conss: Iterable[Incomplete], + conss: Iterable[ExprCons] | MatrixExprCons, name: str | Iterable[str] = "", initial: bool | Iterable[bool] = True, separate: bool | Iterable[bool] = True, @@ -934,7 +934,7 @@ class Model: ) -> None: ... def addMatrixCons( self, - cons: Incomplete, + cons: ExprCons | MatrixExprCons, name: str | np.ndarray = "", initial: bool | np.ndarray = True, separate: bool | np.ndarray = True, @@ -949,7 +949,7 @@ class Model: ) -> Constraint | MatrixConstraint: ... def addMatrixConsDisjunction( self, - conss: Incomplete, + conss: Iterable[ExprCons | MatrixExprCons], name: str | np.ndarray = "", initial: bool | np.ndarray = True, relaxcons: None = None, @@ -958,10 +958,10 @@ class Model: local: bool | np.ndarray = False, modifiable: bool | np.ndarray = False, dynamic: bool | np.ndarray = False, - ) -> Incomplete: ... + ) -> Constraint | MatrixConstraint: ... def addMatrixConsIndicator( self, - cons: Incomplete, + cons: ExprCons | MatrixExprCons, binvar: Variable | MatrixVariable | None = None, activeone: bool | np.ndarray = True, name: str | np.ndarray = "", @@ -1111,7 +1111,7 @@ class Model: ) -> Constraint: ... def createConsFromExpr( self, - cons: Incomplete, + cons: ExprCons, name: str = "", initial: bool = True, separate: bool = True, @@ -2285,8 +2285,11 @@ class Solution: def getOrigin(self) -> Incomplete: ... def retransform(self) -> None: ... def translate(self, target: Model) -> Solution: ... - def __getitem__(self, index: Incomplete, /) -> Incomplete: ... - def __setitem__(self, key: Incomplete, value: Incomplete, /) -> None: ... + @overload + def __getitem__(self, index: Expr | GenExpr, /) -> float: ... + @overload + def __getitem__(self, index: MatrixExpr, /) -> np.ndarray: ... + def __setitem__(self, key: Variable, value: float, /) -> None: ... class Statistics: status: str From 2e214478c884eabfaf1d983a458a392e33d94e68 Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Sat, 3 Oct 2026 00:10:23 +0200 Subject: [PATCH 03/24] Annotate LP row and column mutation APIs --- src/pyscipopt/scip.pyi | 52 +++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index aa64b9308..525785748 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -512,37 +512,37 @@ class LP: ) -> None: ... def addCol( self, - entries: Incomplete, - obj: Incomplete = ..., - lb: Incomplete = ..., - ub: Incomplete = ..., - ) -> Incomplete: ... + entries: Sequence[tuple[int, float]], + obj: float = 0.0, + lb: float = 0.0, + ub: float | None = None, + ) -> None: ... def addCols( self, - entrieslist: Incomplete, - objs: Incomplete = ..., - lbs: Incomplete = ..., - ubs: Incomplete = ..., - ) -> Incomplete: ... + entrieslist: Sequence[Sequence[tuple[int, float]]], + objs: Sequence[float] | None = None, + lbs: Sequence[float] | None = None, + ubs: Sequence[float] | None = None, + ) -> None: ... def addRow( - self, entries: Incomplete, lhs: Incomplete = ..., rhs: Incomplete = ... - ) -> Incomplete: ... + self, + entries: Sequence[tuple[int, float]], + lhs: float = 0.0, + rhs: float | None = None, + ) -> None: ... def addRows( - self, entrieslist: Incomplete, lhss: Incomplete = ..., rhss: Incomplete = ... - ) -> Incomplete: ... - def chgBound( - self, col: Incomplete, lb: Incomplete, ub: Incomplete - ) -> Incomplete: ... - def chgCoef( - self, row: Incomplete, col: Incomplete, newval: Incomplete - ) -> Incomplete: ... - def chgObj(self, col: Incomplete, obj: Incomplete) -> Incomplete: ... - def chgSide( - self, row: Incomplete, lhs: Incomplete, rhs: Incomplete - ) -> Incomplete: ... + self, + entrieslist: Sequence[Sequence[tuple[int, float]]], + lhss: Sequence[float] | None = None, + rhss: Sequence[float] | None = None, + ) -> None: ... + def chgBound(self, col: int, lb: float, ub: float) -> None: ... + def chgCoef(self, row: int, col: int, newval: float) -> None: ... + def chgObj(self, col: int, obj: float) -> None: ... + def chgSide(self, row: int, lhs: float, rhs: float) -> None: ... def clear(self) -> Incomplete: ... - def delCols(self, firstcol: Incomplete, lastcol: Incomplete) -> Incomplete: ... - def delRows(self, firstrow: Incomplete, lastrow: Incomplete) -> Incomplete: ... + def delCols(self, firstcol: int, lastcol: int) -> None: ... + def delRows(self, firstrow: int, lastrow: int) -> None: ... def getActivity(self) -> list[float]: ... def getBase(self) -> tuple[list[int], list[int]]: ... def getBasisInds(self) -> list[int]: ... From daa35041027e8903e64e660474d1cebe7bafbc0f Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Sat, 3 Oct 2026 00:23:58 +0200 Subject: [PATCH 04/24] Annotate file operation paths and statistics reader --- src/pyscipopt/scip.pyi | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 525785748..032f3b638 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -1,9 +1,9 @@ import os from collections.abc import Iterable, Mapping, Sequence -from typing import Any, AnyStr, ClassVar, Literal, SupportsFloat, overload +from typing import Any, ClassVar, Literal, SupportsFloat, overload import numpy as np -from _typeshed import Incomplete +from _typeshed import Incomplete, StrOrBytesPath from typing_extensions import CapsuleType, Self, disjoint_base CONST: Term @@ -26,10 +26,10 @@ is_memory_freed: Incomplete print_memory_in_use: Incomplete quickprod: Incomplete quicksum: Incomplete -readStatistics: Incomplete str_conversion: Incomplete value_to_array: Incomplete +def readStatistics(filename: StrOrBytesPath) -> Statistics: ... @overload def exp(x: float | Expr | GenExpr) -> ExpExpr: ... @overload @@ -568,12 +568,12 @@ class LP: def isPrimalFeasible(self) -> bool: ... def ncols(self) -> int: ... def nrows(self) -> int: ... - def readLP(self, filename: Incomplete) -> Incomplete: ... + def readLP(self, filename: bytes | bytearray) -> None: ... def setBase(self, cstat: Incomplete, rstat: Incomplete) -> Incomplete: ... def setIntParam(self, param: Incomplete, value: Incomplete) -> Incomplete: ... def setRealParam(self, param: Incomplete, value: Incomplete) -> Incomplete: ... def solve(self, dual: bool = True) -> float: ... - def writeLP(self, filename: Incomplete) -> Incomplete: ... + def writeLP(self, filename: bytes | bytearray) -> None: ... class MatrixConstraint(np.ndarray): def getConshdlrName(self) -> Incomplete: ... @@ -1608,9 +1608,7 @@ class Model: def printSol( self, solution: Solution | None = None, write_zeros: bool = False ) -> None: ... - def printStatistics( - self, filename: str | os.PathLike[str] | None = None - ) -> None: ... + def printStatistics(self, filename: StrOrBytesPath | None = None) -> None: ... def printStatisticsJson(self) -> None: ... def printVersion(self) -> None: ... def propagateProbing(self, maxproprounds: int) -> tuple[bool, int]: ... @@ -1733,12 +1731,12 @@ class Model: def version(self) -> float: ... def writeBestSol( self, - filename: str | bytes | os.PathLike[AnyStr] = "origprob.sol", + filename: StrOrBytesPath = "origprob.sol", write_zeros: bool = False, ) -> None: ... def writeBestTransSol( self, - filename: str | bytes | os.PathLike[AnyStr] = "transprob.sol", + filename: StrOrBytesPath = "transprob.sol", write_zeros: bool = False, ) -> None: ... def writeLP(self, filename: str | os.PathLike[str] = "LP.lp") -> None: ... @@ -1759,7 +1757,7 @@ class Model: ) -> None: ... def writeProblem( self, - filename: str | os.PathLike[str] = "model.cip", + filename: str | os.PathLike[str] | None = "model.cip", trans: bool = False, genericnames: bool = False, verbose: bool = True, @@ -1767,19 +1765,17 @@ class Model: def writeSol( self, solution: Solution, - filename: str | bytes | os.PathLike[AnyStr] = "origprob.sol", + filename: StrOrBytesPath = "origprob.sol", write_zeros: bool = False, ) -> None: ... - def writeStatistics( - self, filename: str | bytes | os.PathLike[AnyStr] = "origprob.stats" - ) -> None: ... + def writeStatistics(self, filename: StrOrBytesPath = "origprob.stats") -> None: ... def writeStatisticsJson( - self, filename: str | os.PathLike[str] = "origprob.stats.json" + self, filename: StrOrBytesPath = "origprob.stats.json" ) -> None: ... def writeTransSol( self, solution: Solution, - filename: str | bytes | os.PathLike[AnyStr] = "transprob.sol", + filename: StrOrBytesPath = "transprob.sol", write_zeros: bool = False, ) -> None: ... def __eq__(self, other: object, /) -> bool: ... From d907df4737515af35736c90f44905eede5b987d4 Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Sat, 3 Oct 2026 00:26:11 +0200 Subject: [PATCH 05/24] Complete LP basis and parameter annotations --- src/pyscipopt/scip.pyi | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 032f3b638..cf50d3a2c 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -540,7 +540,7 @@ class LP: def chgCoef(self, row: int, col: int, newval: float) -> None: ... def chgObj(self, col: int, obj: float) -> None: ... def chgSide(self, row: int, lhs: float, rhs: float) -> None: ... - def clear(self) -> Incomplete: ... + def clear(self) -> None: ... def delCols(self, firstcol: int, lastcol: int) -> None: ... def delRows(self, firstrow: int, lastrow: int) -> None: ... def getActivity(self) -> list[float]: ... @@ -551,12 +551,12 @@ class LP: ) -> tuple[list[float], list[float]] | None: ... def getDual(self) -> list[float]: ... def getDualRay(self) -> list[float] | None: ... - def getIntParam(self, param: Incomplete) -> int: ... + def getIntParam(self, param: int) -> int: ... def getNIterations(self) -> int: ... def getObjVal(self) -> float: ... def getPrimal(self) -> list[float]: ... def getPrimalRay(self) -> list[float] | None: ... - def getRealParam(self, param: Incomplete) -> float: ... + def getRealParam(self, param: int) -> float: ... def getRedcost(self) -> list[float]: ... def getSides( self, firstrow: int = 0, lastrow: int | None = None @@ -569,9 +569,9 @@ class LP: def ncols(self) -> int: ... def nrows(self) -> int: ... def readLP(self, filename: bytes | bytearray) -> None: ... - def setBase(self, cstat: Incomplete, rstat: Incomplete) -> Incomplete: ... - def setIntParam(self, param: Incomplete, value: Incomplete) -> Incomplete: ... - def setRealParam(self, param: Incomplete, value: Incomplete) -> Incomplete: ... + def setBase(self, cstat: Sequence[int], rstat: Sequence[int]) -> None: ... + def setIntParam(self, param: int, value: int) -> None: ... + def setRealParam(self, param: int, value: float) -> None: ... def solve(self, dual: bool = True) -> float: ... def writeLP(self, filename: bytes | bytearray) -> None: ... From c6d5df9811dd9d70d28f4dee3b35a6b57e719368 Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Sat, 3 Oct 2026 13:13:25 +0200 Subject: [PATCH 06/24] Keep LP enum inputs incomplete --- src/pyscipopt/scip.pyi | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index cf50d3a2c..5436ad980 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -551,12 +551,12 @@ class LP: ) -> tuple[list[float], list[float]] | None: ... def getDual(self) -> list[float]: ... def getDualRay(self) -> list[float] | None: ... - def getIntParam(self, param: int) -> int: ... + def getIntParam(self, param: Incomplete) -> int: ... def getNIterations(self) -> int: ... def getObjVal(self) -> float: ... def getPrimal(self) -> list[float]: ... def getPrimalRay(self) -> list[float] | None: ... - def getRealParam(self, param: int) -> float: ... + def getRealParam(self, param: Incomplete) -> float: ... def getRedcost(self) -> list[float]: ... def getSides( self, firstrow: int = 0, lastrow: int | None = None @@ -569,9 +569,9 @@ class LP: def ncols(self) -> int: ... def nrows(self) -> int: ... def readLP(self, filename: bytes | bytearray) -> None: ... - def setBase(self, cstat: Sequence[int], rstat: Sequence[int]) -> None: ... - def setIntParam(self, param: int, value: int) -> None: ... - def setRealParam(self, param: int, value: float) -> None: ... + def setBase(self, cstat: Incomplete, rstat: Incomplete) -> None: ... + def setIntParam(self, param: Incomplete, value: int) -> None: ... + def setRealParam(self, param: Incomplete, value: float) -> None: ... def solve(self, dual: bool = True) -> float: ... def writeLP(self, filename: bytes | bytearray) -> None: ... From fcf108709fc89aba963d877d5b107d20fd8a1031 Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Sat, 3 Oct 2026 13:13:28 +0200 Subject: [PATCH 07/24] Annotate matrix variable getter array types --- src/pyscipopt/scip.pyi | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 5436ad980..011f7a605 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -4,6 +4,7 @@ from typing import Any, ClassVar, Literal, SupportsFloat, overload import numpy as np from _typeshed import Incomplete, StrOrBytesPath +from numpy.typing import NDArray from typing_extensions import CapsuleType, Self, disjoint_base CONST: Term @@ -647,20 +648,20 @@ class MatrixExprCons(np.ndarray): class MatrixGenExpr(MatrixExpr): ... class MatrixVariable(MatrixExpr): - def getAvgSol(self) -> Incomplete: ... - def getCol(self) -> Incomplete: ... - def getIndex(self) -> Incomplete: ... - def getLPSol(self) -> Incomplete: ... - def getLbGlobal(self) -> Incomplete: ... - def getLbLocal(self) -> Incomplete: ... - def getLbOriginal(self) -> Incomplete: ... - def getObj(self) -> Incomplete: ... - def getUbGlobal(self) -> Incomplete: ... - def getUbLocal(self) -> Incomplete: ... - def getUbOriginal(self) -> Incomplete: ... - def isInLP(self) -> Incomplete: ... + def getAvgSol(self) -> NDArray[np.float64]: ... + def getCol(self) -> NDArray[np.object_]: ... + def getIndex(self) -> NDArray[np.int_]: ... + def getLPSol(self) -> NDArray[np.float64]: ... + def getLbGlobal(self) -> NDArray[np.float64]: ... + def getLbLocal(self) -> NDArray[np.float64]: ... + def getLbOriginal(self) -> NDArray[np.float64]: ... + def getObj(self) -> NDArray[np.float64]: ... + def getUbGlobal(self) -> NDArray[np.float64]: ... + def getUbLocal(self) -> NDArray[np.float64]: ... + def getUbOriginal(self) -> NDArray[np.float64]: ... + def isInLP(self) -> NDArray[np.bool_]: ... def varMayRound(self, direction: Incomplete = ...) -> Incomplete: ... - def vtype(self) -> Incomplete: ... + def vtype(self) -> NDArray[np.object_]: ... def __pos__(self, /) -> MatrixExpr: ... # type: ignore[override] @disjoint_base From 6b36dbac07045cefbbceb5d34ed04c036ac42470 Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Sat, 3 Oct 2026 13:18:35 +0200 Subject: [PATCH 08/24] Annotate probing LP pricing controls and result --- src/pyscipopt/scip.pyi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 011f7a605..a33e73ff6 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -1688,10 +1688,10 @@ class Model: def solveProbingLP(self, itlim: int = -1) -> tuple[bool, bool]: ... def solveProbingLPWithPricing( self, - pretendroot: Incomplete = ..., - displayinfo: Incomplete = ..., - maxpricerounds: Incomplete = ..., - ) -> Incomplete: ... + pretendroot: bool = False, + displayinfo: bool = False, + maxpricerounds: int = -1, + ) -> tuple[bool, bool]: ... def sortAndCons(self, and_cons: Constraint) -> None: ... def startDive(self) -> None: ... def startProbing(self) -> None: ... From 5b45f846b78fdb67f34bc6190485952f60e04e87 Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Sat, 3 Oct 2026 13:22:40 +0200 Subject: [PATCH 09/24] Annotate variable metadata and object pointer returns --- src/pyscipopt/scip.pyi | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index a33e73ff6..4218b47dc 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -281,7 +281,7 @@ class Constraint: def isRemovable(self) -> bool: ... def isSeparated(self) -> bool: ... def isStickingAtNode(self) -> bool: ... - def ptr(self) -> Incomplete: ... + def ptr(self) -> int: ... def __eq__(self, other: object, /) -> bool: ... def __ge__(self, other: object, /) -> bool: ... def __gt__(self, other: object, /) -> bool: ... @@ -2405,14 +2405,18 @@ class Variable(Expr): def getLbGlobal(self) -> float: ... def getLbLocal(self) -> float: ... def getLbOriginal(self) -> float: ... - def getNBranchings(self, branchdir: Incomplete) -> Incomplete: ... - def getNBranchingsCurrentRun(self, branchdir: Incomplete) -> Incomplete: ... + def getNBranchings(self, branchdir: Incomplete) -> int: ... + def getNBranchingsCurrentRun(self, branchdir: Incomplete) -> int: ... def getNLocksDown(self) -> int: ... - def getNLocksDownType(self, locktype: Incomplete) -> Incomplete: ... + def getNLocksDownType(self, locktype: Incomplete) -> int: ... def getNLocksUp(self) -> int: ... - def getNLocksUpType(self, locktype: Incomplete) -> Incomplete: ... + def getNLocksUpType(self, locktype: Incomplete) -> int: ... def getObj(self) -> float: ... - def getStatus(self) -> Incomplete: ... + def getStatus( + self, + ) -> Literal[ + "ORIGINAL", "LOOSE", "COLUMN", "FIXED", "AGGREGATED", "MULTAGGR", "NEGATED" + ]: ... def getUbGlobal(self) -> float: ... def getUbLocal(self) -> float: ... def getUbOriginal(self) -> float: ... @@ -2426,8 +2430,8 @@ class Variable(Expr): def isOriginal(self) -> bool: ... def isRelaxationOnly(self) -> bool: ... def markRelaxationOnly(self) -> None: ... - def ptr(self) -> Incomplete: ... - def varMayRound(self, direction: Incomplete = ...) -> Incomplete: ... + def ptr(self) -> int: ... + def varMayRound(self, direction: Literal["up", "down"] = "down") -> bool: ... def vtype(self) -> Literal["BINARY", "INTEGER", "CONTINUOUS", "IMPLINT"]: ... @disjoint_base From 2f3036334fcb56e4bb584ef59ba285486c6c6fe3 Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Sat, 3 Oct 2026 13:23:05 +0200 Subject: [PATCH 10/24] Annotate solution construction and stage checks --- src/pyscipopt/scip.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 4218b47dc..e7ca1631a 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -2277,8 +2277,8 @@ class Sepa: @disjoint_base class Solution: data: Incomplete - def __init__(self, raise_error: Incomplete = ...) -> None: ... - def _checkStage(self, method: Incomplete) -> Incomplete: ... + def __init__(self, raise_error: bool = True) -> None: ... + def _checkStage(self, method: str) -> None: ... def getOrigin(self) -> Incomplete: ... def retransform(self) -> None: ... def translate(self, target: Model) -> Solution: ... From ea87208bdd3aed6ecaec3bf79d7436c2f88fb40c Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Sat, 3 Oct 2026 13:24:33 +0200 Subject: [PATCH 11/24] Annotate memory debugging helpers --- src/pyscipopt/scip.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index e7ca1631a..d822b989d 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -23,13 +23,13 @@ _core_sum: Incomplete buildGenExprObj: Incomplete expr_to_array: Incomplete expr_to_nodes: Incomplete -is_memory_freed: Incomplete -print_memory_in_use: Incomplete quickprod: Incomplete quicksum: Incomplete str_conversion: Incomplete value_to_array: Incomplete +def is_memory_freed() -> bool: ... +def print_memory_in_use() -> None: ... def readStatistics(filename: StrOrBytesPath) -> Statistics: ... @overload def exp(x: float | Expr | GenExpr) -> ExpExpr: ... From 6d81a26d6108756903aad2c66db8b9b249ae66b8 Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Sat, 3 Oct 2026 13:24:38 +0200 Subject: [PATCH 12/24] Annotate UTF-8 string conversion helper --- src/pyscipopt/scip.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index d822b989d..1dc1c15d2 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -25,12 +25,12 @@ expr_to_array: Incomplete expr_to_nodes: Incomplete quickprod: Incomplete quicksum: Incomplete -str_conversion: Incomplete value_to_array: Incomplete def is_memory_freed() -> bool: ... def print_memory_in_use() -> None: ... def readStatistics(filename: StrOrBytesPath) -> Statistics: ... +def str_conversion(x: str) -> bytes: ... @overload def exp(x: float | Expr | GenExpr) -> ExpExpr: ... @overload From 0739dab05c3b4cedcbf8334adad06b04a493e510 Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Sat, 3 Oct 2026 13:24:43 +0200 Subject: [PATCH 13/24] Annotate event and stage name cache helpers --- src/pyscipopt/scip.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 1dc1c15d2..762d8fd76 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -317,7 +317,7 @@ class Event: data: Incomplete name: Incomplete def __init__(self) -> None: ... - def _getEventNames(self) -> Incomplete: ... + def _getEventNames(self) -> None: ... def getName(self) -> str: ... def getNewBound(self) -> float: ... def getNode(self) -> Node | None: ... @@ -683,7 +683,7 @@ class Model: def _createConsLinear(self, lincons: Incomplete) -> Incomplete: ... def _createConsNonlinear(self, cons: Incomplete) -> Incomplete: ... def _createConsQuadratic(self, quadcons: Incomplete) -> Incomplete: ... - def _getStageNames(self) -> Incomplete: ... + def _getStageNames(self) -> None: ... def activateBenders(self, benders: Benders, nsubproblems: int) -> None: ... def addBendersSubproblem(self, benders: Benders, subproblem: Model) -> None: ... def addCoefKnapsack( From 74bfc5ff703fcf2163e8fd00d03e7575fe058add Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Sat, 3 Oct 2026 13:28:12 +0200 Subject: [PATCH 14/24] Annotate expression constructors and metadata methods --- src/pyscipopt/scip.pyi | 34 +++++++++++++++++----------------- stubs/allowlist | 4 ++++ 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 762d8fd76..48029bf81 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -252,7 +252,7 @@ class Conshdlr: @disjoint_base class Constant(GenExpr): number: Incomplete - def __init__(self, *args: Incomplete, **kwargs: Incomplete) -> None: ... + def __init__(self, number: SupportsFloat) -> None: ... def __neg__(self, /) -> Constant: ... def __pow__( # type: ignore[override] self, other: float | Constant, mod: Incomplete = ..., / @@ -381,8 +381,8 @@ class ExprLike: class Expr(ExprLike): terms: Incomplete def __init__(self, terms: Incomplete = ...) -> None: ... - def degree(self) -> Incomplete: ... - def normalize(self) -> Incomplete: ... + def degree(self) -> int: ... + def normalize(self) -> None: ... def __neg__(self, /) -> Expr: ... @overload def __add__(self, other: float | Expr, /) -> Expr: ... @@ -429,13 +429,13 @@ class Expr(ExprLike): @disjoint_base class ExprCons: - _lhs: Incomplete - _rhs: Incomplete - expr: Incomplete + _lhs: float | None + _rhs: float | None + expr: Expr | GenExpr def __init__( - self, expr: Incomplete, lhs: Incomplete = ..., rhs: Incomplete = ... + self, expr: Expr | GenExpr, lhs: float | None = None, rhs: float | None = None ) -> None: ... - def normalize(self) -> Incomplete: ... + def normalize(self) -> None: ... def __bool__(self) -> bool: ... def __ge__(self, other: float, /) -> ExprCons: ... def __le__(self, other: float, /) -> ExprCons: ... @@ -445,8 +445,8 @@ class GenExpr(ExprLike): _op: Incomplete children: Incomplete def __init__(self) -> None: ... - def degree(self) -> Incomplete: ... - def getOp(self) -> Incomplete: ... + def degree(self) -> float: ... + def getOp(self) -> str | None: ... def __neg__(self, /) -> ProdExpr | Constant: ... @overload def __add__(self, other: float | ExprLike, /) -> SumExpr: ... @@ -2127,7 +2127,7 @@ class PY_SCIP_STATUS: @disjoint_base class PowExpr(GenExpr): expo: Incomplete - def __init__(self, *args: Incomplete, **kwargs: Incomplete) -> None: ... + def __init__(self) -> None: ... def __neg__(self, /) -> ProdExpr: ... @disjoint_base @@ -2158,7 +2158,7 @@ class Pricer: @disjoint_base class ProdExpr(GenExpr): constant: Incomplete - def __init__(self, *args: Incomplete, **kwargs: Incomplete) -> None: ... + def __init__(self) -> None: ... def __neg__(self, /) -> ProdExpr: ... @disjoint_base @@ -2357,13 +2357,13 @@ class Statistics: @disjoint_base class SumExpr(GenExpr): constant: Incomplete - def __init__(self, *args: Incomplete, **kwargs: Incomplete) -> None: ... + def __init__(self) -> None: ... def __neg__(self, /) -> ProdExpr: ... @disjoint_base class Term: - vartuple: Incomplete - def __init__(self, *vartuple: Incomplete) -> None: ... + vartuple: tuple[Variable, ...] + def __init__(self, *vartuple: Variable) -> None: ... def __mul__(self, other: Term, /) -> Term: ... def __eq__(self, other: object, /) -> bool: ... def __ge__(self, other: object, /) -> bool: ... @@ -2376,7 +2376,7 @@ class Term: def __ne__(self, other: object, /) -> bool: ... class UnaryExpr(GenExpr): - def __init__(self, *args: Incomplete, **kwargs: Incomplete) -> None: ... + def __init__(self, op: str, expr: GenExpr) -> None: ... def __neg__(self, /) -> ProdExpr: ... class AbsExpr(UnaryExpr): ... @@ -2389,7 +2389,7 @@ class CosExpr(UnaryExpr): ... @disjoint_base class VarExpr(GenExpr): var: Incomplete - def __init__(self, *args: Incomplete, **kwargs: Incomplete) -> None: ... + def __init__(self, var: Expr) -> None: ... def __neg__(self, /) -> ProdExpr: ... @disjoint_base diff --git a/stubs/allowlist b/stubs/allowlist index dc849f74c..33d6a8c28 100644 --- a/stubs/allowlist +++ b/stubs/allowlist @@ -7,3 +7,7 @@ pyscipopt.scip.ExprLike.__rmul__ pyscipopt.scip.ExprLike.__rsub__ pyscipopt.scip.ExprLike.__rtruediv__ pyscipopt.scip.ExprLike.__sub__ +# Cython exposes generic signatures; these constructors reject arguments at runtime. +pyscipopt.scip.PowExpr.__init__ +pyscipopt.scip.ProdExpr.__init__ +pyscipopt.scip.SumExpr.__init__ From dd7697041dc9a68391aee9dab86f3c16ea6d2791 Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Sat, 3 Oct 2026 13:28:18 +0200 Subject: [PATCH 15/24] Annotate matrix expression evaluation result --- src/pyscipopt/scip.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 48029bf81..075e5e35e 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -593,7 +593,7 @@ class MatrixConstraint(np.ndarray): def isStickingAtNode(self) -> Incomplete: ... class MatrixExpr(np.ndarray): - def _evaluate(self, sol: Solution) -> Incomplete: ... + def _evaluate(self, sol: Solution) -> NDArray[np.object_]: ... def __array_ufunc__( self, ufunc: np.ufunc, From b9044470ad5618039345dcfdf2d769e7d5377703 Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Sat, 3 Oct 2026 13:32:37 +0200 Subject: [PATCH 16/24] Keep bound change and LP basis enum values incomplete --- src/pyscipopt/scip.pyi | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 075e5e35e..0fdc2c10f 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -106,8 +106,8 @@ class Benderscut: @disjoint_base class BoundChange: def __init__(self) -> None: ... - def getBoundchgtype(self) -> int: ... - def getBoundtype(self) -> int: ... + def getBoundchgtype(self) -> Incomplete: ... + def getBoundtype(self) -> Incomplete: ... def getNewBound(self) -> float: ... def getVar(self) -> Variable: ... def isRedundant(self) -> bool: ... @@ -545,7 +545,7 @@ class LP: def delCols(self, firstcol: int, lastcol: int) -> None: ... def delRows(self, firstrow: int, lastrow: int) -> None: ... def getActivity(self) -> list[float]: ... - def getBase(self) -> tuple[list[int], list[int]]: ... + def getBase(self) -> tuple[list[Incomplete], list[Incomplete]]: ... def getBasisInds(self) -> list[int]: ... def getBounds( self, firstcol: int = 0, lastcol: int | None = None From 77ded3129773717ac3285119a6a93aa7cc4a9198 Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Sat, 3 Oct 2026 13:33:27 +0200 Subject: [PATCH 17/24] Annotate parent branching collections and root absence --- src/pyscipopt/scip.pyi | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 0fdc2c10f..39acb0bda 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -1819,7 +1819,9 @@ class Node: def getNParentBranchings(self) -> int: ... def getNumber(self) -> int: ... def getParent(self) -> Node | None: ... - def getParentBranchings(self) -> Incomplete: ... + def getParentBranchings( + self, + ) -> tuple[list[Variable], list[float], list[Incomplete]] | None: ... def getType(self) -> Incomplete: ... def isActive(self) -> bool: ... def isPropagatedAgain(self) -> bool: ... From ac637cce1e083fc9b600b12b68a67f070be8abfb Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Sat, 3 Oct 2026 13:35:11 +0200 Subject: [PATCH 18/24] Annotate constraint factory inputs and outputs --- src/pyscipopt/scip.pyi | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 39acb0bda..f7218af83 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -679,10 +679,18 @@ class Model: createscip: bool = True, threadsafe: bool = False, ) -> None: ... - def _createConsGenNonlinear(self, cons: Incomplete) -> Incomplete: ... - def _createConsLinear(self, lincons: Incomplete) -> Incomplete: ... - def _createConsNonlinear(self, cons: Incomplete) -> Incomplete: ... - def _createConsQuadratic(self, quadcons: Incomplete) -> Incomplete: ... + def _createConsGenNonlinear( + self, cons: ExprCons, **kwargs: Incomplete + ) -> Constraint: ... + def _createConsLinear( + self, lincons: ExprCons, **kwargs: Incomplete + ) -> Constraint: ... + def _createConsNonlinear( + self, cons: ExprCons, **kwargs: Incomplete + ) -> Constraint: ... + def _createConsQuadratic( + self, quadcons: ExprCons, **kwargs: Incomplete + ) -> Constraint: ... def _getStageNames(self) -> None: ... def activateBenders(self, benders: Benders, nsubproblems: int) -> None: ... def addBendersSubproblem(self, benders: Benders, subproblem: Model) -> None: ... From f4cfdbf2eab63c741c86ca815770b22e4babbd16 Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Sat, 3 Oct 2026 13:41:52 +0200 Subject: [PATCH 19/24] Annotate expression numeric metadata fields --- src/pyscipopt/scip.pyi | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index f7218af83..0f4393645 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -251,7 +251,7 @@ class Conshdlr: @disjoint_base class Constant(GenExpr): - number: Incomplete + number: SupportsFloat def __init__(self, number: SupportsFloat) -> None: ... def __neg__(self, /) -> Constant: ... def __pow__( # type: ignore[override] @@ -380,7 +380,7 @@ class ExprLike: @disjoint_base class Expr(ExprLike): terms: Incomplete - def __init__(self, terms: Incomplete = ...) -> None: ... + def __init__(self, terms: Incomplete = None) -> None: ... def degree(self) -> int: ... def normalize(self) -> None: ... def __neg__(self, /) -> Expr: ... @@ -442,7 +442,7 @@ class ExprCons: @disjoint_base class GenExpr(ExprLike): - _op: Incomplete + _op: str | None children: Incomplete def __init__(self) -> None: ... def degree(self) -> float: ... @@ -2136,7 +2136,7 @@ class PY_SCIP_STATUS: @disjoint_base class PowExpr(GenExpr): - expo: Incomplete + expo: SupportsFloat def __init__(self) -> None: ... def __neg__(self, /) -> ProdExpr: ... @@ -2167,7 +2167,7 @@ class Pricer: @disjoint_base class ProdExpr(GenExpr): - constant: Incomplete + constant: SupportsFloat def __init__(self) -> None: ... def __neg__(self, /) -> ProdExpr: ... @@ -2366,7 +2366,7 @@ class Statistics: @disjoint_base class SumExpr(GenExpr): - constant: Incomplete + constant: SupportsFloat def __init__(self) -> None: ... def __neg__(self, /) -> ProdExpr: ... From 86f23ac44680d505a085f8c92e699902744612e7 Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Sat, 3 Oct 2026 13:54:52 +0200 Subject: [PATCH 20/24] Annotate nonlinear expression addition input --- src/pyscipopt/scip.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 0f4393645..6a39fd100 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -939,7 +939,7 @@ class Model: ) -> list[Constraint]: ... def addCut(self, cut: Row, forcecut: bool = False) -> bool: ... def addExprNonlinear( - self, cons: Constraint, expr: Incomplete, coef: float + self, cons: Constraint, expr: Expr | GenExpr, coef: float ) -> None: ... def addMatrixCons( self, From 4abc9303ada3d19207a1b17239bd74fc36d00c54 Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Tue, 13 Oct 2026 22:59:51 +0200 Subject: [PATCH 21/24] Enable unused ignore and deprecation checks in mypy --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 8c6c8d9fb..a9a1bedd8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,10 @@ Homepage = "https://github.com/SCIP-Interfaces/PySCIPOpt" norecursedirs = ["check"] testpaths = ["tests"] +[tool.mypy] +warn_unused_ignores = true +enable_error_code = ["deprecated"] + [tool.setuptools] include-package-data = false From 09700b9c1a935e170b626d76706d2a6dca52c774 Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Tue, 13 Oct 2026 22:59:51 +0200 Subject: [PATCH 22/24] Mark deprecated PySCIPOpt APIs in the stubs --- src/pyscipopt/scip.pyi | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index 6a39fd100..e8da17102 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -5,13 +5,13 @@ from typing import Any, ClassVar, Literal, SupportsFloat, overload import numpy as np from _typeshed import Incomplete, StrOrBytesPath from numpy.typing import NDArray -from typing_extensions import CapsuleType, Self, disjoint_base +from typing_extensions import CapsuleType, Self, deprecated, disjoint_base CONST: Term EventNames: dict MAJOR: int MINOR: int -Operator: Op +Operator: Op # type: ignore[deprecated] PATCH: int PY_SCIP_CALL: Incomplete StageNames: dict @@ -446,6 +446,7 @@ class GenExpr(ExprLike): children: Incomplete def __init__(self) -> None: ... def degree(self) -> float: ... + @deprecated("Deprecated since 6.3.0; use type(expr) instead.") def getOp(self) -> str | None: ... def __neg__(self, /) -> ProdExpr | Constant: ... @overload @@ -1188,6 +1189,9 @@ class Model: def flushRowExtensions(self, row: Row) -> None: ... def frac(self, value: float) -> float: ... def free(self) -> None: ... + @deprecated( + "Deprecated since 5.7.0; Benders subproblems are freed automatically when the master Model is deallocated." + ) def freeBendersSubproblems(self) -> None: ... def freeProb(self) -> None: ... def freeReoptSolve(self) -> None: ... @@ -1245,6 +1249,7 @@ class Model: def getDepth(self) -> int: ... def getDeterministicTime(self) -> float: ... def getFirstPrimalBound(self) -> float: ... + @deprecated("Use Model.getDualsolLinear(cons) instead.") def getDualMultiplier(self, cons: Constraint) -> float: ... def getDualSolVal( self, cons: Constraint, boundconstraint: bool = False @@ -1853,6 +1858,7 @@ class Nodesel: def nodeinitsol(self) -> Incomplete: ... def nodeselect(self) -> Incomplete: ... +@deprecated("Use type(expr) to check the expression type instead.") class Op: add: ClassVar[str] = ... const: ClassVar[str] = ... From a9cd772b981dcd04830d4daf8444e421807390c4 Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Tue, 13 Oct 2026 23:00:01 +0200 Subject: [PATCH 23/24] Annotate user data, model ownership, and matrix rounding --- src/pyscipopt/scip.pyi | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/pyscipopt/scip.pyi b/src/pyscipopt/scip.pyi index e8da17102..a29999dc1 100644 --- a/src/pyscipopt/scip.pyi +++ b/src/pyscipopt/scip.pyi @@ -127,7 +127,7 @@ class Branchrule: @disjoint_base class Column: - data: Incomplete + data: Any def __init__(self) -> None: ... def getAge(self) -> int: ... def getBasisStatus(self) -> Literal["lower", "basic", "upper", "zero"]: ... @@ -148,7 +148,7 @@ class Column: @disjoint_base class ColumnExact: - data: Incomplete + data: Any def __init__(self) -> None: ... @disjoint_base @@ -260,7 +260,7 @@ class Constant(GenExpr): @disjoint_base class Constraint: - data: Incomplete + data: Any name: str def __init__(self) -> None: ... def getConshdlrName(self) -> str: ... @@ -314,7 +314,7 @@ class DomainChanges: @disjoint_base class Event: - data: Incomplete + data: Any name: Incomplete def __init__(self) -> None: ... def _getEventNames(self) -> None: ... @@ -661,14 +661,16 @@ class MatrixVariable(MatrixExpr): def getUbLocal(self) -> NDArray[np.float64]: ... def getUbOriginal(self) -> NDArray[np.float64]: ... def isInLP(self) -> NDArray[np.bool_]: ... - def varMayRound(self, direction: Incomplete = ...) -> Incomplete: ... + def varMayRound( + self, direction: Literal["up", "down"] = "down" + ) -> NDArray[np.bool_]: ... def vtype(self) -> NDArray[np.object_]: ... def __pos__(self, /) -> MatrixExpr: ... # type: ignore[override] @disjoint_base class Model: - _freescip: Incomplete - data: Incomplete + _freescip: bool + data: Any def __init__( self, problemName: str = "model", @@ -1802,7 +1804,7 @@ class Model: @disjoint_base class NLRow: - data: Incomplete + data: Any name: str def __init__(self) -> None: ... def getConstant(self) -> float: ... @@ -1820,7 +1822,7 @@ class NLRow: @disjoint_base class Node: - data: Incomplete + data: Any def __init__(self) -> None: ... def getAddedConss(self) -> list[Constraint]: ... def getDepth(self) -> int: ... @@ -2241,7 +2243,7 @@ class Relax: @disjoint_base class Row: - data: Incomplete + data: Any name: str def __init__(self) -> None: ... def getAge(self) -> int: ... @@ -2274,7 +2276,7 @@ class Row: @disjoint_base class RowExact: - data: Incomplete + data: Any def __init__(self) -> None: ... @disjoint_base @@ -2292,7 +2294,7 @@ class Sepa: @disjoint_base class Solution: - data: Incomplete + data: Any def __init__(self, raise_error: bool = True) -> None: ... def _checkStage(self, method: str) -> None: ... def getOrigin(self) -> Incomplete: ... @@ -2410,7 +2412,7 @@ class VarExpr(GenExpr): @disjoint_base class Variable(Expr): - data: Incomplete + data: Any name: str def __init__(self) -> None: ... def getAvgSol(self) -> float: ... From 99430007a9aad7fdc25c77397f4795d328bf04dd Mon Sep 17 00:00:00 2001 From: Jonathan Berthias Date: Tue, 13 Oct 2026 23:07:05 +0200 Subject: [PATCH 24/24] Document expanded stub annotations in the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c19ccfa4f..aabcecf95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased ### Added +- Added more type annotations and deprecation markers to the stubs. ### Fixed ### Changed ### Removed