Detect unbounded LPs in simplex instead of looping forever - #6
Open
shehio wants to merge 1 commit into
Open
Conversation
pick_row() returned row 0 when the pivot column held no positive entry, which is exactly the unboundedness condition. simplex() then pivoted on a non-positive element and never reached an optimal tableau, so it spun forever. Maximizing x subject to -x + y <= 1 did not terminate. pick_row() now returns -1 in that case and simplex() throws. matrix() also wrote through its arguments: it negated cT in place and replaced each A[i] with the assembled tableau row, so callers could not reuse or inspect their own constraint matrix afterwards. It now builds the rows without touching the inputs. The bundled Richland example still solves to P = 330.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
simplex()does not terminate on an unbounded linear program, andmatrix()writes through its arguments. Both paths were untested.Unboundedness Is Never Detected
pick_row()performs the minimum-ratio test. When the pivot column holds no positive entry, no basic variable bounds the entering variable and the program is unbounded — but the function returned its initialiser,index = 0:simplex()then pivoted on row 0 on a non-positive element,check()never came back true, and thewhileloop spun forever.Reproduced on
develop— maximizexsubject to−x + y ≤ 1:pick_row()now returns-1when the column has no positive entry, andsimplex()throwsError('The linear program is unbounded.'). The same LP now returns in milliseconds.matrix()Mutated Its Caller's ArraysIt negated
cTin place and assigned the assembled tableau row back intoA[i], so a caller could not reuse or even inspect its own inputs afterwards. Measured ondevelop:The rows are now built without writing back into
A, and the objective is negated into a local viamap. After the fixA,bandcTall compare equal to their original values.Verification
The bundled Richland example is unchanged:
P = 330, both through the test and throughnode src/linear-programming/simplex.js.Suite: 34 tests / 10 suites before → 36 tests / 10 suites after, all passing. The two new cases are the unbounded LP and the input-mutation check.
Left Alone
n,m,last_rowandlast_colare module-level mutable state written bymatrix()and read by every other function, so a tableau can only be solved while it is the most recently built one. That is a real latent hazard but it is orthogonal to these two fixes, so I have not touched it.🤖 Generated with Claude Code
https://claude.ai/code/session_01THEv7BFJd31NjvBRehbZPL