1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.math3.optim.linear;
18
19 import java.io.IOException;
20 import java.io.ObjectInputStream;
21 import java.io.ObjectOutputStream;
22 import java.io.Serializable;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Set;
29 import java.util.TreeSet;
30
31 import org.apache.commons.math3.linear.Array2DRowRealMatrix;
32 import org.apache.commons.math3.linear.MatrixUtils;
33 import org.apache.commons.math3.linear.RealVector;
34 import org.apache.commons.math3.optim.nonlinear.scalar.GoalType;
35 import org.apache.commons.math3.optim.PointValuePair;
36 import org.apache.commons.math3.util.Precision;
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62 class SimplexTableau implements Serializable {
63
64
65 private static final String NEGATIVE_VAR_COLUMN_LABEL = "x-";
66
67
68 private static final long serialVersionUID = -1369660067587938365L;
69
70
71 private final LinearObjectiveFunction f;
72
73
74 private final List<LinearConstraint> constraints;
75
76
77 private final boolean restrictToNonNegative;
78
79
80 private final List<String> columnLabels = new ArrayList<String>();
81
82
83 private transient Array2DRowRealMatrix tableau;
84
85
86 private final int numDecisionVariables;
87
88
89 private final int numSlackVariables;
90
91
92 private int numArtificialVariables;
93
94
95 private final double epsilon;
96
97
98 private final int maxUlps;
99
100
101 private int[] basicVariables;
102
103
104 private int[] basicRows;
105
106
107
108
109
110
111
112
113
114
115
116 SimplexTableau(final LinearObjectiveFunction f,
117 final Collection<LinearConstraint> constraints,
118 final GoalType goalType,
119 final boolean restrictToNonNegative,
120 final double epsilon) {
121 this(f, constraints, goalType, restrictToNonNegative, epsilon, SimplexSolver.DEFAULT_ULPS);
122 }
123
124
125
126
127
128
129
130
131
132
133 SimplexTableau(final LinearObjectiveFunction f,
134 final Collection<LinearConstraint> constraints,
135 final GoalType goalType,
136 final boolean restrictToNonNegative,
137 final double epsilon,
138 final int maxUlps) {
139 this.f = f;
140 this.constraints = normalizeConstraints(constraints);
141 this.restrictToNonNegative = restrictToNonNegative;
142 this.epsilon = epsilon;
143 this.maxUlps = maxUlps;
144 this.numDecisionVariables = f.getCoefficients().getDimension() + (restrictToNonNegative ? 0 : 1);
145 this.numSlackVariables = getConstraintTypeCounts(Relationship.LEQ) +
146 getConstraintTypeCounts(Relationship.GEQ);
147 this.numArtificialVariables = getConstraintTypeCounts(Relationship.EQ) +
148 getConstraintTypeCounts(Relationship.GEQ);
149 this.tableau = createTableau(goalType == GoalType.MAXIMIZE);
150
151
152 initializeBasicVariables(getSlackVariableOffset());
153 initializeColumnLabels();
154 }
155
156
157
158
159 protected void initializeColumnLabels() {
160 if (getNumObjectiveFunctions() == 2) {
161 columnLabels.add("W");
162 }
163 columnLabels.add("Z");
164 for (int i = 0; i < getOriginalNumDecisionVariables(); i++) {
165 columnLabels.add("x" + i);
166 }
167 if (!restrictToNonNegative) {
168 columnLabels.add(NEGATIVE_VAR_COLUMN_LABEL);
169 }
170 for (int i = 0; i < getNumSlackVariables(); i++) {
171 columnLabels.add("s" + i);
172 }
173 for (int i = 0; i < getNumArtificialVariables(); i++) {
174 columnLabels.add("a" + i);
175 }
176 columnLabels.add("RHS");
177 }
178
179
180
181
182
183
184 protected Array2DRowRealMatrix createTableau(final boolean maximize) {
185
186
187 int width = numDecisionVariables + numSlackVariables +
188 numArtificialVariables + getNumObjectiveFunctions() + 1;
189 int height = constraints.size() + getNumObjectiveFunctions();
190 Array2DRowRealMatrix matrix = new Array2DRowRealMatrix(height, width);
191
192
193 if (getNumObjectiveFunctions() == 2) {
194 matrix.setEntry(0, 0, -1);
195 }
196
197 int zIndex = (getNumObjectiveFunctions() == 1) ? 0 : 1;
198 matrix.setEntry(zIndex, zIndex, maximize ? 1 : -1);
199 RealVector objectiveCoefficients = maximize ? f.getCoefficients().mapMultiply(-1) : f.getCoefficients();
200 copyArray(objectiveCoefficients.toArray(), matrix.getDataRef()[zIndex]);
201 matrix.setEntry(zIndex, width - 1, maximize ? f.getConstantTerm() : -1 * f.getConstantTerm());
202
203 if (!restrictToNonNegative) {
204 matrix.setEntry(zIndex, getSlackVariableOffset() - 1,
205 getInvertedCoefficientSum(objectiveCoefficients));
206 }
207
208
209 int slackVar = 0;
210 int artificialVar = 0;
211 for (int i = 0; i < constraints.size(); i++) {
212 LinearConstraint constraint = constraints.get(i);
213 int row = getNumObjectiveFunctions() + i;
214
215
216 copyArray(constraint.getCoefficients().toArray(), matrix.getDataRef()[row]);
217
218
219 if (!restrictToNonNegative) {
220 matrix.setEntry(row, getSlackVariableOffset() - 1,
221 getInvertedCoefficientSum(constraint.getCoefficients()));
222 }
223
224
225 matrix.setEntry(row, width - 1, constraint.getValue());
226
227
228 if (constraint.getRelationship() == Relationship.LEQ) {
229 matrix.setEntry(row, getSlackVariableOffset() + slackVar++, 1);
230 } else if (constraint.getRelationship() == Relationship.GEQ) {
231 matrix.setEntry(row, getSlackVariableOffset() + slackVar++, -1);
232 }
233
234
235 if ((constraint.getRelationship() == Relationship.EQ) ||
236 (constraint.getRelationship() == Relationship.GEQ)) {
237 matrix.setEntry(0, getArtificialVariableOffset() + artificialVar, 1);
238 matrix.setEntry(row, getArtificialVariableOffset() + artificialVar++, 1);
239 matrix.setRowVector(0, matrix.getRowVector(0).subtract(matrix.getRowVector(row)));
240 }
241 }
242
243 return matrix;
244 }
245
246
247
248
249
250
251 public List<LinearConstraint> normalizeConstraints(Collection<LinearConstraint> originalConstraints) {
252 List<LinearConstraint> normalized = new ArrayList<LinearConstraint>(originalConstraints.size());
253 for (LinearConstraint constraint : originalConstraints) {
254 normalized.add(normalize(constraint));
255 }
256 return normalized;
257 }
258
259
260
261
262
263
264 private LinearConstraint normalize(final LinearConstraint constraint) {
265 if (constraint.getValue() < 0) {
266 return new LinearConstraint(constraint.getCoefficients().mapMultiply(-1),
267 constraint.getRelationship().oppositeRelationship(),
268 -1 * constraint.getValue());
269 }
270 return new LinearConstraint(constraint.getCoefficients(),
271 constraint.getRelationship(), constraint.getValue());
272 }
273
274
275
276
277
278 protected final int getNumObjectiveFunctions() {
279 return this.numArtificialVariables > 0 ? 2 : 1;
280 }
281
282
283
284
285
286
287 private int getConstraintTypeCounts(final Relationship relationship) {
288 int count = 0;
289 for (final LinearConstraint constraint : constraints) {
290 if (constraint.getRelationship() == relationship) {
291 ++count;
292 }
293 }
294 return count;
295 }
296
297
298
299
300
301
302 protected static double getInvertedCoefficientSum(final RealVector coefficients) {
303 double sum = 0;
304 for (double coefficient : coefficients.toArray()) {
305 sum -= coefficient;
306 }
307 return sum;
308 }
309
310
311
312
313
314
315 protected Integer getBasicRow(final int col) {
316 final int row = basicVariables[col];
317 return row == -1 ? null : row;
318 }
319
320
321
322
323
324
325 protected int getBasicVariable(final int row) {
326 return basicRows[row];
327 }
328
329
330
331
332
333 private void initializeBasicVariables(final int startColumn) {
334 basicVariables = new int[getWidth() - 1];
335 basicRows = new int[getHeight()];
336
337 Arrays.fill(basicVariables, -1);
338
339 for (int i = startColumn; i < getWidth() - 1; i++) {
340 Integer row = findBasicRow(i);
341 if (row != null) {
342 basicVariables[i] = row;
343 basicRows[row] = i;
344 }
345 }
346 }
347
348
349
350
351
352
353 private Integer findBasicRow(final int col) {
354 Integer row = null;
355 for (int i = 0; i < getHeight(); i++) {
356 final double entry = getEntry(i, col);
357 if (Precision.equals(entry, 1d, maxUlps) && (row == null)) {
358 row = i;
359 } else if (!Precision.equals(entry, 0d, maxUlps)) {
360 return null;
361 }
362 }
363 return row;
364 }
365
366
367
368
369
370 protected void dropPhase1Objective() {
371 if (getNumObjectiveFunctions() == 1) {
372 return;
373 }
374
375 final Set<Integer> columnsToDrop = new TreeSet<Integer>();
376 columnsToDrop.add(0);
377
378
379 for (int i = getNumObjectiveFunctions(); i < getArtificialVariableOffset(); i++) {
380 final double entry = getEntry(0, i);
381 if (Precision.compareTo(entry, 0d, epsilon) > 0) {
382 columnsToDrop.add(i);
383 }
384 }
385
386
387 for (int i = 0; i < getNumArtificialVariables(); i++) {
388 int col = i + getArtificialVariableOffset();
389 if (getBasicRow(col) == null) {
390 columnsToDrop.add(col);
391 }
392 }
393
394 final double[][] matrix = new double[getHeight() - 1][getWidth() - columnsToDrop.size()];
395 for (int i = 1; i < getHeight(); i++) {
396 int col = 0;
397 for (int j = 0; j < getWidth(); j++) {
398 if (!columnsToDrop.contains(j)) {
399 matrix[i - 1][col++] = getEntry(i, j);
400 }
401 }
402 }
403
404
405 Integer[] drop = columnsToDrop.toArray(new Integer[columnsToDrop.size()]);
406 for (int i = drop.length - 1; i >= 0; i--) {
407 columnLabels.remove((int) drop[i]);
408 }
409
410 this.tableau = new Array2DRowRealMatrix(matrix);
411 this.numArtificialVariables = 0;
412
413 initializeBasicVariables(getNumObjectiveFunctions());
414 }
415
416
417
418
419
420 private void copyArray(final double[] src, final double[] dest) {
421 System.arraycopy(src, 0, dest, getNumObjectiveFunctions(), src.length);
422 }
423
424
425
426
427
428 boolean isOptimal() {
429 final double[] objectiveFunctionRow = getRow(0);
430 final int end = getRhsOffset();
431 for (int i = getNumObjectiveFunctions(); i < end; i++) {
432 final double entry = objectiveFunctionRow[i];
433 if (Precision.compareTo(entry, 0d, epsilon) < 0) {
434 return false;
435 }
436 }
437 return true;
438 }
439
440
441
442
443
444 protected PointValuePair getSolution() {
445 int negativeVarColumn = columnLabels.indexOf(NEGATIVE_VAR_COLUMN_LABEL);
446 Integer negativeVarBasicRow = negativeVarColumn > 0 ? getBasicRow(negativeVarColumn) : null;
447 double mostNegative = negativeVarBasicRow == null ? 0 : getEntry(negativeVarBasicRow, getRhsOffset());
448
449 final Set<Integer> usedBasicRows = new HashSet<Integer>();
450 final double[] coefficients = new double[getOriginalNumDecisionVariables()];
451 for (int i = 0; i < coefficients.length; i++) {
452 int colIndex = columnLabels.indexOf("x" + i);
453 if (colIndex < 0) {
454 coefficients[i] = 0;
455 continue;
456 }
457 Integer basicRow = getBasicRow(colIndex);
458 if (basicRow != null && basicRow == 0) {
459
460
461
462 coefficients[i] = 0;
463 } else if (usedBasicRows.contains(basicRow)) {
464
465
466 coefficients[i] = 0 - (restrictToNonNegative ? 0 : mostNegative);
467 } else {
468 usedBasicRows.add(basicRow);
469 coefficients[i] =
470 (basicRow == null ? 0 : getEntry(basicRow, getRhsOffset())) -
471 (restrictToNonNegative ? 0 : mostNegative);
472 }
473 }
474 return new PointValuePair(coefficients, f.value(coefficients));
475 }
476
477
478
479
480
481
482
483 protected void performRowOperations(int pivotCol, int pivotRow) {
484
485 final double pivotVal = getEntry(pivotRow, pivotCol);
486 divideRow(pivotRow, pivotVal);
487
488
489 for (int i = 0; i < getHeight(); i++) {
490 if (i != pivotRow) {
491 final double multiplier = getEntry(i, pivotCol);
492 if (multiplier != 0.0) {
493 subtractRow(i, pivotRow, multiplier);
494 }
495 }
496 }
497
498
499 final int previousBasicVariable = getBasicVariable(pivotRow);
500 basicVariables[previousBasicVariable] = -1;
501 basicVariables[pivotCol] = pivotRow;
502 basicRows[pivotRow] = pivotCol;
503 }
504
505
506
507
508
509
510
511
512
513
514 protected void divideRow(final int dividendRowIndex, final double divisor) {
515 final double[] dividendRow = getRow(dividendRowIndex);
516 for (int j = 0; j < getWidth(); j++) {
517 dividendRow[j] /= divisor;
518 }
519 }
520
521
522
523
524
525
526
527
528
529
530
531 protected void subtractRow(final int minuendRowIndex, final int subtrahendRowIndex, final double multiplier) {
532 final double[] minuendRow = getRow(minuendRowIndex);
533 final double[] subtrahendRow = getRow(subtrahendRowIndex);
534 for (int i = 0; i < getWidth(); i++) {
535 minuendRow[i] -= subtrahendRow[i] * multiplier;
536 }
537 }
538
539
540
541
542
543 protected final int getWidth() {
544 return tableau.getColumnDimension();
545 }
546
547
548
549
550
551 protected final int getHeight() {
552 return tableau.getRowDimension();
553 }
554
555
556
557
558
559
560
561 protected final double getEntry(final int row, final int column) {
562 return tableau.getEntry(row, column);
563 }
564
565
566
567
568
569
570
571 protected final void setEntry(final int row, final int column, final double value) {
572 tableau.setEntry(row, column, value);
573 }
574
575
576
577
578
579 protected final int getSlackVariableOffset() {
580 return getNumObjectiveFunctions() + numDecisionVariables;
581 }
582
583
584
585
586
587 protected final int getArtificialVariableOffset() {
588 return getNumObjectiveFunctions() + numDecisionVariables + numSlackVariables;
589 }
590
591
592
593
594
595 protected final int getRhsOffset() {
596 return getWidth() - 1;
597 }
598
599
600
601
602
603
604
605
606
607
608 protected final int getNumDecisionVariables() {
609 return numDecisionVariables;
610 }
611
612
613
614
615
616
617 protected final int getOriginalNumDecisionVariables() {
618 return f.getCoefficients().getDimension();
619 }
620
621
622
623
624
625 protected final int getNumSlackVariables() {
626 return numSlackVariables;
627 }
628
629
630
631
632
633 protected final int getNumArtificialVariables() {
634 return numArtificialVariables;
635 }
636
637
638
639
640
641
642 protected final double[] getRow(int row) {
643 return tableau.getDataRef()[row];
644 }
645
646
647
648
649
650 protected final double[][] getData() {
651 return tableau.getData();
652 }
653
654 @Override
655 public boolean equals(Object other) {
656
657 if (this == other) {
658 return true;
659 }
660
661 if (other instanceof SimplexTableau) {
662 SimplexTableau rhs = (SimplexTableau) other;
663 return (restrictToNonNegative == rhs.restrictToNonNegative) &&
664 (numDecisionVariables == rhs.numDecisionVariables) &&
665 (numSlackVariables == rhs.numSlackVariables) &&
666 (numArtificialVariables == rhs.numArtificialVariables) &&
667 (epsilon == rhs.epsilon) &&
668 (maxUlps == rhs.maxUlps) &&
669 f.equals(rhs.f) &&
670 constraints.equals(rhs.constraints) &&
671 tableau.equals(rhs.tableau);
672 }
673 return false;
674 }
675
676 @Override
677 public int hashCode() {
678 return Boolean.valueOf(restrictToNonNegative).hashCode() ^
679 numDecisionVariables ^
680 numSlackVariables ^
681 numArtificialVariables ^
682 Double.valueOf(epsilon).hashCode() ^
683 maxUlps ^
684 f.hashCode() ^
685 constraints.hashCode() ^
686 tableau.hashCode();
687 }
688
689
690
691
692
693
694 private void writeObject(ObjectOutputStream oos)
695 throws IOException {
696 oos.defaultWriteObject();
697 MatrixUtils.serializeRealMatrix(tableau, oos);
698 }
699
700
701
702
703
704
705
706 private void readObject(ObjectInputStream ois)
707 throws ClassNotFoundException, IOException {
708 ois.defaultReadObject();
709 MatrixUtils.deserializeRealMatrix(this, "tableau", ois);
710 }
711 }