Repository navigation
Description
What version of OR-Tools and what language are you using?
Version: 9.15.6755 (reproduced against the published wheel; the affected function is byte-identical on the current main)
Language: Python
Which solver are you using (e.g. CP-SAT, Routing, GLOP, BOP, Gurobi)?
ortools.linear_solver.python.model_builder (ModelBuilder)
What operating system (Linux, Windows, ...)?
Linux (platform-independent — it's a proto-index bug in pure Python)
What did you do?
Called Model.add_enforced_linear_constraint:
from ortools.linear_solver.python import model_builder as mb
# (a) crash
m = mb.Model()
x = m.new_bool_var("x"); y = m.new_num_var(0.0, 10.0, "y")
m.add_enforced_linear_constraint(y, x, True, lb=0.0, ub=3.0, name="enf")
# -> process dies, SIGSEGV (exit 139)
# (b) silent corruption + wrong optimum
m = mb.Model()
yy = m.new_num_var(0.0, 10.0, "y"); w = m.new_num_var(0.0, 10.0, "w"); z = m.new_bool_var("z")
cap = m.add_linear_constraint(yy, ub=2.0, name="cap") # user constraint y <= 2
m.add_enforced_linear_constraint(w, z, True, ub=3.0, name="ind")
m.maximize(yy)
s = mb.Solver("sat"); print(s.solve(m), s.objective_value)
# -> OPTIMAL 3.0 (expected 2.0); cap.name is now "ind", cap.upper_bound is now 3.0What did you expect to see?
The indicator constraint stored in general_constraint, the unrelated cap constraint untouched, and the optimum 2.0.
What did you see instead?
Either a segfault or silent corruption of an unrelated constraint with a wrong optimum and no error.
Root cause. Model.add_enforced_linear_constraint and the BoundedLinearExpression branch of _add_enforced_linear_constraint_to_helper (ortools/linear_solver/python/model_builder.py) write the indicator constraint's name, bounds and terms through the non-enforced helper setters — set_constraint_name / set_constraint_lower_bound / set_constraint_upper_bound / add_terms_to_constraint — while passing the enforced constraint's index.
Enforced constraints live in MPModelProto.general_constraint[i]; those setters address MPModelProto.constraint[i]. The index spaces are disjoint, so the write lands on an unrelated linear constraint (destroying it) while the indicator is left empty — or, when no linear constraint exists at that index, mutable_constraint(i) reads out of bounds and the interpreter segfaults (the DCHECK is compiled out in the release wheel).
This is a lone divergence: the bool branch of the same helper already uses the set_enforced_* / add_terms_to_enforced_constraint variants, as do the EnforcedLinearConstraint property setters, and both the Java and C# ports do it correctly (ModelBuilder.java addEnforcedConstraintTerm + lin.setLowerBound, ModelBuilder.cs AddEnforcedConstraintTerm). Only the Python add_enforced_linear_constraint path uses the wrong setters.
I have a fix ready that routes every setter in the enforced paths through the set_enforced_* variants (mirroring the already-correct bool branch), plus regression tests for both the crash and the corruption case, and will open a PR referencing this issue.
Activity
fix #5302
lperron commentedon Aug 25, 2026
Thanks, I pushed the fix on the main branch.
fix #5302
fix #5302