Skip to content

Reuse the objective constraint and order media bound assignment - #166

Merged
Fxe merged 1 commit into
ModelSEED:devfrom
cshenry:fix-objective-constraint-reuse-and-media-bounds
Nov 11, 2026
Merged

Fxe merged 1 commit into
ModelSEED:devfrom
cshenry:fix-objective-constraint-reuse-and-media-bounds

Conversation

@cshenry

@cshenry cshenry commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

What this accomplishes

Two independent fixes in constraint and bound construction. Each ships a test that fails without it.

1. ObjConstPkg accumulated duplicate objective constraints

build_package added a new constraint on every call. Calling it twice — which gapfilling does when it tightens an objective threshold — left two objc constraints on the solver, with the stricter one silently binding. Nothing errored; the model was just more constrained than the caller asked for.

build_constraint now compares the linear coefficients of each existing objc constraint against the current objective and, on a match, updates that constraint's bounds in place and returns it. A genuinely different objective still gets its own constraint.

build_package also now returns the constraint. Previously it discarded the return value, so a caller had no handle to adjust the bounds later.

2. KBaseMediaPkg could pass through an inverted bound state

Exchange bounds were assigned lower-then-upper unconditionally:

reaction.lower_bound = -1 * self.parameters["default_uptake"]
reaction.upper_bound = self.parameters["default_excretion"]

When the media's uptake implies a lower bound above the reaction's current upper bound — a reaction already restricted to a negative upper bound — that order passes through a state where lb > ub. The upper bound is now raised first in that case.

KBaseMediaPkg also records the applied media on self.current_media, so packages built later can see what media is in force rather than re-deriving it.

Tests

tests/fbapkg/test_objconst_and_media_bounds.py — 5 cases on an inline two-reaction model: constraint reuse across repeated builds, the returned handle, a genuinely different objective still getting its own constraint, the inverted-bound ordering case, and current_media initialisation.

Verified on both sides: 3 of the 5 fail against the unpatched tree. The bound-ordering case was corrected after an initial version passed either way and therefore guarded nothing — it now sets upper_bound = -200 against a media uptake of 100, which is the condition the reorder exists for.

No fixture files, so these tests need no biochemistry database or commercial solver licence.

tests/fbapkg/test_objconst_and_media_bounds.py   5 passed
tests/                                          63 passed

Two independent fixes in constraint and bound construction, each with a
test that fails without it.

1. ObjConstPkg added a NEW constraint on every build_package call. Calling
   it twice -- which gapfilling does when it tightens an objective
   threshold -- left two objc constraints on the solver, the stricter one
   silently binding. build_constraint now compares the linear coefficients
   of each existing objc constraint against the current objective and, on a
   match, updates that constraint's bounds in place and returns it.
   build_package also now RETURNS the constraint; previously it discarded
   it, so a caller had no handle to adjust later.

2. KBaseMediaPkg assigned exchange bounds lower-then-upper unconditionally.
   When the media's uptake implies a lower bound ABOVE the reaction's
   current upper bound -- a reaction already restricted to a negative upper
   bound -- that assignment order passes through a state with lb > ub. The
   upper bound is now raised first in that case. KBaseMediaPkg also records
   the applied media on self.current_media, so later packages can see what
   media is in force instead of re-deriving it.

Tests: tests/fbapkg/test_objconst_and_media_bounds.py, 5 cases on an
inline two-reaction model -- constraint reuse across repeated builds, the
returned handle, a genuinely different objective still getting its own
constraint, the inverted-bound ordering case, and current_media
initialisation. Verified on both sides: 3 of the 5 fail against the
unpatched tree, and the bound-ordering case was corrected after an initial
version passed either way and therefore guarded nothing.

Full suite: 63 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Fxe Fxe closed this Nov 11, 2026
@Fxe Fxe reopened this Nov 11, 2026
@Fxe
Fxe merged commit d12dd6d into ModelSEED:dev Nov 11, 2026
9 of 24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants