Skip to content

Commit 967fb45

Browse files
committed
correct handling of purely linear and bilinear terms
1 parent d8468f7 commit 967fb45

2 files changed

Lines changed: 41 additions & 31 deletions

File tree

src/pyscipopt/scip.pxi

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5887,13 +5887,6 @@ cdef class Model:
58875887

58885888
PyCons = Constraint.create(scip_cons)
58895889

5890-
# Store the original polynomial expression on the constraint so that
5891-
# helpers such as getTermsQuadratic can reconstruct full linear terms
5892-
# even if SCIP's internal quadratic representation does not expose
5893-
# all linear coefficients explicitly.
5894-
if PyCons.data is None:
5895-
PyCons.data = quadcons.expr
5896-
58975890
return PyCons
58985891

58995892
def _createConsNonlinear(self, cons, **kwargs):
@@ -8310,14 +8303,13 @@ cdef class Model:
83108303
Triples ``(var1, var2, coef)`` for terms of the form
83118304
``coef * var1 * var2`` with ``var1 != var2``.
83128305
quadterms : list of tuple
8313-
Triples ``(var, sqrcoef, lincoef)`` corresponding to diagonal
8314-
quadratic terms of the form ``sqrcoef * var**2`` and the linear
8315-
coefficient ``lincoef`` associated with the same variable when it
8316-
also appears linearly in the quadratic part.
8306+
Triples ``(var, sqrcoef, lincoef)`` for variables that appear in
8307+
quadratic or bilinear terms. ``sqrcoef`` is the coefficient of
8308+
``var**2``, and ``lincoef`` is the linear coefficient of ``var``
8309+
if it also appears linearly.
83178310
linterms : list of tuple
8318-
Pairs ``(var, coef)`` for all variables with a nonzero linear
8319-
coefficient in the constraint, including variables that also
8320-
appear in quadratic or bilinear terms.
8311+
Pairs ``(var, coef)`` for purely linear variables, i.e.,
8312+
variables that do not participate in any quadratic or bilinear term.
83218313
83228314
"""
83238315
cdef SCIP_EXPR* expr
@@ -8336,6 +8328,7 @@ cdef class Model:
83368328
cdef int nbilinterms
83378329

83388330
# quadratic terms
8331+
cdef SCIP_EXPR* quadexpr
83398332
cdef SCIP_EXPR* sqrexpr
83408333
cdef SCIP_Real sqrcoef
83418334
cdef int nquadterms
@@ -8353,28 +8346,44 @@ cdef class Model:
83538346

83548347
linterms = []
83558348
bilinterms = []
8356-
quadterms = []
83578349

8350+
# Purely linear terms (variables not in any quadratic/bilinear term)
83588351
for termidx in range(nlinvars):
83598352
var = self._getOrCreateVar(SCIPgetVarExprVar(linexprs[termidx]))
83608353
linterms.append((var, lincoefs[termidx]))
83618354

8355+
# Collect quadratic terms in a dict so we can merge entries for the same variable.
8356+
quaddict = {} # var.ptr() -> [var, sqrcoef, lincoef]
8357+
83628358
for termidx in range(nbilinterms):
83638359
SCIPexprGetQuadraticBilinTerm(expr, termidx, &bilinterm1, &bilinterm2, &bilincoef, NULL, NULL)
83648360
scipvar1 = SCIPgetVarExprVar(bilinterm1)
83658361
scipvar2 = SCIPgetVarExprVar(bilinterm2)
83668362
var1 = self._getOrCreateVar(scipvar1)
83678363
var2 = self._getOrCreateVar(scipvar2)
83688364
if scipvar1 != scipvar2:
8369-
bilinterms.append((var1,var2,bilincoef))
8365+
bilinterms.append((var1, var2, bilincoef))
83708366
else:
8371-
quadterms.append((var1,bilincoef,0.0))
8367+
# Squared term reported as bilinear var*var
8368+
key = var1.ptr()
8369+
if key in quaddict:
8370+
quaddict[key][1] += bilincoef
8371+
else:
8372+
quaddict[key] = [var1, bilincoef, 0.0]
8373+
8374+
# Also collect linear coefficients from the quadratic terms
83728375
for termidx in range(nquadterms):
8373-
SCIPexprGetQuadraticQuadTerm(expr, termidx, NULL, &lincoef, &sqrcoef, NULL, NULL, &sqrexpr)
8374-
if sqrexpr == NULL:
8375-
continue
8376-
var = self._getOrCreateVar(SCIPgetVarExprVar(sqrexpr))
8377-
quadterms.append((var,sqrcoef,lincoef))
8376+
SCIPexprGetQuadraticQuadTerm(expr, termidx, &quadexpr, &lincoef, &sqrcoef, NULL, NULL, &sqrexpr)
8377+
scipvar1 = SCIPgetVarExprVar(quadexpr)
8378+
var = self._getOrCreateVar(scipvar1)
8379+
key = var.ptr()
8380+
if key in quaddict:
8381+
quaddict[key][1] += sqrcoef
8382+
quaddict[key][2] += lincoef
8383+
else:
8384+
quaddict[key] = [var, sqrcoef, lincoef]
8385+
8386+
quadterms = [(entry[0], entry[1], entry[2]) for entry in quaddict.values()]
83788387

83798388
return (bilinterms, quadterms, linterms)
83808389

tests/test_nonlinear.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -311,18 +311,19 @@ def test_quad_coeffs_mixed_linear_and_quadratic():
311311

312312
bilinterms, quadterms, linterms = scip.getTermsQuadratic(cons)
313313

314-
# Linear part: getTermsQuadratic must report all linear coefficients,
315-
# including those of variables that also appear quadratically or
316-
# bilinearly.
314+
# linterms contains only purely linear variables (not in any quadratic/bilinear term)
317315
lin_only = {v.name: c for (v, c) in linterms}
318316
assert lin_only["var4"] == 8
319-
assert lin_only["var3"] == 4
320-
assert lin_only["var2"] == -5
321-
# var1 has no linear component and must not appear in linterms
322-
assert "var1" not in lin_only or lin_only.get("var1", 0.0) == 0.0
317+
assert len(linterms) == 1 # only var4 is purely linear
323318

324-
# For completeness, checking if the coefficients from reconstructing the full linear
325-
# coefficients from both linterms and quadterms match
319+
# quadterms contains all variables that appear in quadratic/bilinear terms,
320+
# with both their squared coefficient and linear coefficient
321+
quad_dict = {v.name: (sqrcoef, lincoef) for v, sqrcoef, lincoef in quadterms}
322+
assert quad_dict["var3"] == (6.0, 4.0) # 6*var3^2 + 4*var3
323+
assert quad_dict["var1"] == (-3.0, 0.0) # -3*var1^2, no linear term
324+
assert quad_dict["var2"] == (0.0, -5.0) # -5*var2, no squared term
325+
326+
# Verify we can reconstruct all linear coefficients by combining linterms and quadterms
326327
full_lin = {}
327328
for v, c in linterms:
328329
full_lin[v.name] = full_lin.get(v.name, 0.0) + c

0 commit comments

Comments
 (0)