Skip to content

Commit 77e2df6

Browse files
committed
Fix for unbounded LP
1 parent 8cb3ee5 commit 77e2df6

File tree

5 files changed

+57
-17
lines changed

5 files changed

+57
-17
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ Added / Changed / Deprecated / Fixed / Removed / Security
2323

2424
- Refactored `SampleSet` (internally) to be better aligned with recent changes to other parts of the library. Primarily the class now uses `int` indices for all internal calculations. Also added a new `java.util.stream.Collector` implementation to simplify `SampleSet` creation from streams.
2525

26+
### Fixed
27+
28+
#### org.ojalgo.optimisation
29+
30+
- Fixed a bug where the (new) LP solver would fail to recognise unbounded problems and instead return a solution with infinite values, stating it to be optimal.
31+
2632
## [55.0.0] – 2024-09-28
2733

2834
### Changed

src/main/java/org/ojalgo/optimisation/ExpressionsBasedModel.java

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,8 @@ public static interface EntityMap extends ProblemStructure {
239239

240240
public enum FileFormat {
241241

242-
EBM, MPS;
242+
EBM,
243+
MPS;
243244

244245
/**
245246
* Apart from the "native" EBM file format, currently only supports the MPS file format, but with some
@@ -692,12 +693,12 @@ public static ExpressionsBasedModel parse(final File file) {
692693

693694
public static ExpressionsBasedModel parse(final InputStream input, final FileFormat format) {
694695
switch (format) {
695-
case MPS:
696-
return FileFormatMPS.read(input);
697-
case EBM:
698-
return FileFormatEBM.read(input);
699-
default:
700-
throw new IllegalArgumentException();
696+
case MPS:
697+
return FileFormatMPS.read(input);
698+
case EBM:
699+
return FileFormatEBM.read(input);
700+
default:
701+
throw new IllegalArgumentException();
701702
}
702703
}
703704

@@ -1420,7 +1421,7 @@ public void setKnownSolution(final Optimisation.Result knownSolution) {
14201421

14211422
/**
14221423
* For test/validation during solver development.
1423-
*
1424+
*
14241425
* @param knownSolution The optimal solution
14251426
* @param handler What to do if validation fails
14261427
*/
@@ -1599,16 +1600,20 @@ private Optimisation.Result optimise() {
15991600

16001601
Optimisation.Result result = prepared.solve(null);
16011602

1602-
for (int i = 0, limit = myVariables.size(); i < limit; i++) {
1603-
Variable tmpVariable = myVariables.get(i);
1604-
if (!tmpVariable.isFixed()) {
1605-
tmpVariable.setValue(options.solution.toBigDecimal(result.doubleValue(i)));
1603+
Optimisation.State retState = result.getState();
1604+
1605+
if (retState.isApproximate()) {
1606+
for (int i = 0, limit = myVariables.size(); i < limit; i++) {
1607+
Variable tmpVariable = myVariables.get(i);
1608+
if (!tmpVariable.isFixed()) {
1609+
tmpVariable.setValue(options.solution.toBigDecimal(result.doubleValue(i)));
1610+
}
16061611
}
16071612
}
1608-
16091613
Result retSolution = this.getVariableValues();
1614+
16101615
double retValue = this.objective().evaluate(retSolution).doubleValue();
1611-
Optimisation.State retState = result.getState();
1616+
16121617
List<KeyedPrimitive<EntryPair<ModelEntity<?>, ConstraintType>>> matchedMultipliers = result.getMatchedMultipliers();
16131618

16141619
prepared.dispose();

src/main/java/org/ojalgo/optimisation/linear/SimplexSolver.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@
5555
abstract class SimplexSolver extends LinearSolver {
5656

5757
enum Direction {
58-
DECREASE, INCREASE, STAY;
58+
DECREASE,
59+
INCREASE,
60+
STAY;
5961
}
6062

6163
/**
@@ -942,7 +944,7 @@ private boolean testPrimalExitRatio(final IterDescr iteration) {
942944
}
943945
}
944946

945-
if (iteration.ratioPrimal >= range) {
947+
if (iteration.ratioPrimal >= range && Double.isFinite(iteration.ratioPrimal)) {
946948

947949
if (this.isLogDebug()) {
948950
this.log("Bound switch!");

src/test/java/org/ojalgo/optimisation/linear/LinearProblems.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,33 @@ public void testMath286() {
252252
TestUtils.assertEquals(0, claimedValue.compareTo(tmpFullValue));
253253
}
254254

255+
/**
256+
* https://github.com/vagmcs/Optimus/issues/43
257+
* <p>
258+
* The problem is unbounded (not infeasible). A feasible solution is x=120 and y=80.
259+
* <p>
260+
* In this case ojAlgo finds a feasible solution, but then somehow fails to identify that it's unbounded
261+
* and instead returns a solution with an infinite value.
262+
*/
263+
@Test
264+
public void testOptimus43() {
265+
266+
ExpressionsBasedModel model = new ExpressionsBasedModel();
267+
268+
if (DEBUG) {
269+
model.options.debug(LinearSolver.class);
270+
}
271+
272+
Variable x = model.newVariable("x").weight(-2);
273+
Variable y = model.newVariable("y").lower(80).upper(170).weight(5);
274+
275+
model.addExpression().set(x, 1).set(y, 1).lower(200);
276+
277+
Result result = model.minimise();
278+
279+
TestUtils.assertEquals(State.UNBOUNDED, result.getState());
280+
}
281+
255282
/**
256283
* Didn't recognise this as an infeasible problem.
257284
*/

src/test/java/org/ojalgo/optimisation/service/OptimisationServiceTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
@Tag("network")
1212
public class OptimisationServiceTest {
1313

14-
private static final String HOST = "http://13.60.238.155:8080";
14+
private static final String HOST = "http://16.16.99.66:8080";
1515
// private static final String HOST = "http://localhost:8080";
1616
// private static final String HOST = "http://test-service.optimatika.se:8080";
1717

0 commit comments

Comments
 (0)