1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.math4.legacy.optim.linear;
18
19 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.Collection;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Set;
25 import java.util.TreeSet;
26
27 import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
28 import org.apache.commons.math4.legacy.linear.Array2DRowRealMatrix;
29 import org.apache.commons.math4.legacy.linear.RealVector;
30 import org.apache.commons.math4.legacy.optim.PointValuePair;
31 import org.apache.commons.math4.legacy.optim.nonlinear.scalar.GoalType;
32 import org.apache.commons.numbers.core.Precision;
33
34
35
36
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
63
64
65
66
67 class SimplexTableau {
68
69
70 private static final String NEGATIVE_VAR_COLUMN_LABEL = "x-";
71
72 private static final long EXPN = 0x7ff0000000000000L;
73
74 private static final long FRAC = 0x800fffffffffffffL;
75
76 private static final int MAX_IEEE_EXP = 2047;
77
78 private static final int MIN_IEEE_EXP = 0;
79
80 private static final int OFFSET_IEEE_EXP = 1023;
81
82 private static final int IEEE_EXPONENT_SHIFT = 52;
83
84
85 private final LinearObjectiveFunction f;
86
87
88 private final List<LinearConstraint> constraints;
89
90
91 private final boolean restrictToNonNegative;
92
93
94 private final List<String> columnLabels = new ArrayList<>();
95
96
97 private Array2DRowRealMatrix tableau;
98
99
100 private final int numDecisionVariables;
101
102
103 private final int numSlackVariables;
104
105
106 private int numArtificialVariables;
107
108
109 private final double epsilon;
110
111
112 private final int maxUlps;
113
114
115 private int[] basicVariables;
116
117
118 private int[] basicRows;
119
120
121 private int[] variableExpChange;
122
123
124
125
126
127
128
129
130
131
132
133
134
135 SimplexTableau(final LinearObjectiveFunction f,
136 final Collection<LinearConstraint> constraints,
137 final GoalType goalType,
138 final boolean restrictToNonNegative,
139 final double epsilon) {
140 this(f, constraints, goalType, restrictToNonNegative, epsilon, SimplexSolver.DEFAULT_ULPS);
141 }
142
143
144
145
146
147
148
149
150
151
152
153
154 SimplexTableau(final LinearObjectiveFunction f,
155 final Collection<LinearConstraint> constraints,
156 final GoalType goalType,
157 final boolean restrictToNonNegative,
158 final double epsilon,
159 final int maxUlps) throws DimensionMismatchException {
160 checkDimensions(f, constraints);
161 this.f = f;
162 this.constraints = normalizeConstraints(constraints);
163 this.restrictToNonNegative = restrictToNonNegative;
164 this.epsilon = epsilon;
165 this.maxUlps = maxUlps;
166 this.numDecisionVariables = f.getCoefficients().getDimension() + (restrictToNonNegative ? 0 : 1);
167 this.numSlackVariables = getConstraintTypeCounts(Relationship.LEQ) +
168 getConstraintTypeCounts(Relationship.GEQ);
169 this.numArtificialVariables = getConstraintTypeCounts(Relationship.EQ) +
170 getConstraintTypeCounts(Relationship.GEQ);
171 this.tableau = createTableau(goalType == GoalType.MAXIMIZE);
172
173
174 initializeBasicVariables(getSlackVariableOffset());
175 initializeColumnLabels();
176 }
177
178
179
180
181
182
183
184
185 private void checkDimensions(final LinearObjectiveFunction objectiveFunction,
186 final Collection<LinearConstraint> c) {
187 final int dimension = objectiveFunction.getCoefficients().getDimension();
188 for (final LinearConstraint constraint : c) {
189 final int constraintDimension = constraint.getCoefficients().getDimension();
190 if (constraintDimension != dimension) {
191 throw new DimensionMismatchException(constraintDimension, dimension);
192 }
193 }
194 }
195
196
197
198 protected void initializeColumnLabels() {
199 if (getNumObjectiveFunctions() == 2) {
200 columnLabels.add("W");
201 }
202 columnLabels.add("Z");
203 for (int i = 0; i < getOriginalNumDecisionVariables(); i++) {
204 columnLabels.add("x" + i);
205 }
206 if (!restrictToNonNegative) {
207 columnLabels.add(NEGATIVE_VAR_COLUMN_LABEL);
208 }
209 for (int i = 0; i < getNumSlackVariables(); i++) {
210 columnLabels.add("s" + i);
211 }
212 for (int i = 0; i < getNumArtificialVariables(); i++) {
213 columnLabels.add("a" + i);
214 }
215 columnLabels.add("RHS");
216 }
217
218
219
220
221
222
223 protected Array2DRowRealMatrix createTableau(final boolean maximize) {
224
225
226 int width = numDecisionVariables + numSlackVariables +
227 numArtificialVariables + getNumObjectiveFunctions() + 1;
228 int height = constraints.size() + getNumObjectiveFunctions();
229 Array2DRowRealMatrix matrix = new Array2DRowRealMatrix(height, width);
230
231
232 if (getNumObjectiveFunctions() == 2) {
233 matrix.setEntry(0, 0, -1);
234 }
235
236 int zIndex = (getNumObjectiveFunctions() == 1) ? 0 : 1;
237 matrix.setEntry(zIndex, zIndex, maximize ? 1 : -1);
238
239 double[][] scaled = new double[constraints.size() + 1][];
240
241 RealVector objectiveCoefficients = maximize ? f.getCoefficients().mapMultiply(-1) : f.getCoefficients();
242 scaled[0] = objectiveCoefficients.toArray();
243 double[] scaledRhs = new double[constraints.size() + 1];
244 double value = maximize ? f.getConstantTerm() : -1 * f.getConstantTerm();
245 scaledRhs[0] = value;
246
247 for (int i = 0; i < constraints.size(); i++) {
248 LinearConstraint constraint = constraints.get(i);
249 scaled[i + 1] = constraint.getCoefficients().toArray();
250 scaledRhs[i + 1] = constraint.getValue();
251 }
252 variableExpChange = new int[scaled[0].length];
253
254 scale(scaled, scaledRhs);
255
256 copyArray(scaled[0], matrix.getDataRef()[zIndex]);
257 matrix.setEntry(zIndex, width - 1, scaledRhs[0]);
258
259 if (!restrictToNonNegative) {
260 matrix.setEntry(zIndex, getSlackVariableOffset() - 1,
261 getInvertedCoefficientSum(scaled[0]));
262 }
263
264
265 int slackVar = 0;
266 int artificialVar = 0;
267 for (int i = 0; i < constraints.size(); i++) {
268 final LinearConstraint constraint = constraints.get(i);
269 final int row = getNumObjectiveFunctions() + i;
270
271
272 copyArray(scaled[i + 1], matrix.getDataRef()[row]);
273
274
275 if (!restrictToNonNegative) {
276 matrix.setEntry(row, getSlackVariableOffset() - 1,
277 getInvertedCoefficientSum(scaled[i + 1]));
278 }
279
280
281 matrix.setEntry(row, width - 1, scaledRhs[i + 1]);
282
283
284 if (constraint.getRelationship() == Relationship.LEQ) {
285 matrix.setEntry(row, getSlackVariableOffset() + slackVar++, 1);
286 } else if (constraint.getRelationship() == Relationship.GEQ) {
287 matrix.setEntry(row, getSlackVariableOffset() + slackVar++, -1);
288 }
289
290
291 if (constraint.getRelationship() == Relationship.EQ ||
292 constraint.getRelationship() == Relationship.GEQ) {
293 matrix.setEntry(0, getArtificialVariableOffset() + artificialVar, 1);
294 matrix.setEntry(row, getArtificialVariableOffset() + artificialVar++, 1);
295 matrix.setRowVector(0, matrix.getRowVector(0).subtract(matrix.getRowVector(row)));
296 }
297 }
298
299 return matrix;
300 }
301
302
303
304
305
306
307
308
309
310 private void scale(double[][] scaled, double[] scaledRhs) {
311
312
313
314
315
316
317
318 for (int i = 0; i < scaled.length; i++) {
319 int minExp = MAX_IEEE_EXP + 1;
320 int maxExp = MIN_IEEE_EXP - 1;
321 for (double d: scaled[i]) {
322 if (d != 0) {
323 int e = exponent(d);
324 if (e < minExp) {
325 minExp = e;
326 }
327 if (e > maxExp) {
328 maxExp = e;
329 }
330 }
331 }
332 if (scaledRhs[i] != 0) {
333 final int e = exponent(scaledRhs[i]);
334 if (e < minExp) {
335 minExp = e;
336 }
337 if (e > maxExp) {
338 maxExp = e;
339 }
340 }
341 final int expChange = computeExpChange(minExp, maxExp);
342 if (expChange != 0) {
343 scaledRhs[i] = updateExponent(scaledRhs[i], expChange);
344 updateExponent(scaled[i], expChange);
345 }
346 }
347
348
349
350
351
352
353 for (int i = 0; i < variableExpChange.length; i++) {
354 int minExp = MAX_IEEE_EXP + 1;
355 int maxExp = MIN_IEEE_EXP - 1;
356
357 for (double[] coefficients : scaled) {
358 final double d = coefficients[i];
359 if (d != 0) {
360 int e = exponent(d);
361 if (e < minExp) {
362 minExp = e;
363 }
364 if (e > maxExp) {
365 maxExp = e;
366 }
367 }
368 }
369 final int expChange = computeExpChange(minExp, maxExp);
370 variableExpChange[i] = expChange;
371 if (expChange != 0) {
372 for (double[] coefficients : scaled) {
373 coefficients[i] = updateExponent(coefficients[i], expChange);
374 }
375 }
376 }
377 }
378
379
380
381
382
383
384
385
386
387 private int computeExpChange(int minExp, int maxExp) {
388 int expChange = 0;
389 if (minExp <= MAX_IEEE_EXP &&
390 minExp > OFFSET_IEEE_EXP) {
391 expChange = OFFSET_IEEE_EXP - minExp;
392 } else if (maxExp >= MIN_IEEE_EXP &&
393 maxExp < OFFSET_IEEE_EXP) {
394 expChange = OFFSET_IEEE_EXP - maxExp;
395 }
396 return expChange;
397 }
398
399
400
401
402
403
404
405 private static void updateExponent(double[] dar, int exp) {
406 for (int i = 0; i < dar.length; i++) {
407 dar[i] = updateExponent(dar[i], exp);
408 }
409 }
410
411
412
413
414
415
416
417 private static int exponent(double d) {
418 final long bits = Double.doubleToLongBits(d);
419 return (int) ((bits & EXPN) >>> IEEE_EXPONENT_SHIFT);
420 }
421
422
423
424
425
426
427
428
429 private static double updateExponent(double d, int exp) {
430 if (d == 0 ||
431 exp == 0) {
432 return d;
433 }
434 final long bits = Double.doubleToLongBits(d);
435 return Double.longBitsToDouble((bits & FRAC) | ((((bits & EXPN) >>> IEEE_EXPONENT_SHIFT) + exp) << IEEE_EXPONENT_SHIFT));
436 }
437
438
439
440
441
442
443 public List<LinearConstraint> normalizeConstraints(Collection<LinearConstraint> originalConstraints) {
444 final List<LinearConstraint> normalized = new ArrayList<>(originalConstraints.size());
445 for (LinearConstraint constraint : originalConstraints) {
446 normalized.add(normalize(constraint));
447 }
448 return normalized;
449 }
450
451
452
453
454
455
456 private LinearConstraint normalize(final LinearConstraint constraint) {
457 if (constraint.getValue() < 0) {
458 return new LinearConstraint(constraint.getCoefficients().mapMultiply(-1),
459 constraint.getRelationship().oppositeRelationship(),
460 -1 * constraint.getValue());
461 }
462 return new LinearConstraint(constraint.getCoefficients(),
463 constraint.getRelationship(), constraint.getValue());
464 }
465
466
467
468
469
470 protected final int getNumObjectiveFunctions() {
471 return this.numArtificialVariables > 0 ? 2 : 1;
472 }
473
474
475
476
477
478
479 private int getConstraintTypeCounts(final Relationship relationship) {
480 int count = 0;
481 for (final LinearConstraint constraint : constraints) {
482 if (constraint.getRelationship() == relationship) {
483 ++count;
484 }
485 }
486 return count;
487 }
488
489
490
491
492
493
494 private static double getInvertedCoefficientSum(final double[] coefficients) {
495 double sum = 0;
496 for (double coefficient : coefficients) {
497 sum -= coefficient;
498 }
499 return sum;
500 }
501
502
503
504
505
506
507 protected Integer getBasicRow(final int col) {
508 final int row = basicVariables[col];
509 return row == -1 ? null : row;
510 }
511
512
513
514
515
516
517 protected int getBasicVariable(final int row) {
518 return basicRows[row];
519 }
520
521
522
523
524
525 private void initializeBasicVariables(final int startColumn) {
526 basicVariables = new int[getWidth() - 1];
527 basicRows = new int[getHeight()];
528
529 Arrays.fill(basicVariables, -1);
530
531 for (int i = startColumn; i < getWidth() - 1; i++) {
532 Integer row = findBasicRow(i);
533 if (row != null) {
534 basicVariables[i] = row;
535 basicRows[row] = i;
536 }
537 }
538 }
539
540
541
542
543
544
545 private Integer findBasicRow(final int col) {
546 Integer row = null;
547 for (int i = 0; i < getHeight(); i++) {
548 final double entry = getEntry(i, col);
549 if (Precision.equals(entry, 1d, maxUlps) && row == null) {
550 row = i;
551 } else if (!Precision.equals(entry, 0d, maxUlps)) {
552 return null;
553 }
554 }
555 return row;
556 }
557
558
559
560
561
562 protected void dropPhase1Objective() {
563 if (getNumObjectiveFunctions() == 1) {
564 return;
565 }
566
567 final Set<Integer> columnsToDrop = new TreeSet<>();
568 columnsToDrop.add(0);
569
570
571 for (int i = getNumObjectiveFunctions(); i < getArtificialVariableOffset(); i++) {
572 final double entry = getEntry(0, i);
573 if (Precision.compareTo(entry, 0d, epsilon) > 0) {
574 columnsToDrop.add(i);
575 }
576 }
577
578
579 for (int i = 0; i < getNumArtificialVariables(); i++) {
580 int col = i + getArtificialVariableOffset();
581 if (getBasicRow(col) == null) {
582 columnsToDrop.add(col);
583 }
584 }
585
586 final double[][] matrix = new double[getHeight() - 1][getWidth() - columnsToDrop.size()];
587 for (int i = 1; i < getHeight(); i++) {
588 int col = 0;
589 for (int j = 0; j < getWidth(); j++) {
590 if (!columnsToDrop.contains(j)) {
591 matrix[i - 1][col++] = getEntry(i, j);
592 }
593 }
594 }
595
596
597 Integer[] drop = columnsToDrop.toArray(new Integer[0]);
598 for (int i = drop.length - 1; i >= 0; i--) {
599 columnLabels.remove((int) drop[i]);
600 }
601
602 this.tableau = new Array2DRowRealMatrix(matrix);
603 this.numArtificialVariables = 0;
604
605 initializeBasicVariables(getNumObjectiveFunctions());
606 }
607
608
609
610
611
612 private void copyArray(final double[] src, final double[] dest) {
613 System.arraycopy(src, 0, dest, getNumObjectiveFunctions(), src.length);
614 }
615
616
617
618
619
620 boolean isOptimal() {
621 final double[] objectiveFunctionRow = getRow(0);
622 final int end = getRhsOffset();
623 for (int i = getNumObjectiveFunctions(); i < end; i++) {
624 final double entry = objectiveFunctionRow[i];
625 if (Precision.compareTo(entry, 0d, epsilon) < 0) {
626 return false;
627 }
628 }
629 return true;
630 }
631
632
633
634
635
636 protected PointValuePair getSolution() {
637 int negativeVarColumn = columnLabels.indexOf(NEGATIVE_VAR_COLUMN_LABEL);
638 Integer negativeVarBasicRow = negativeVarColumn > 0 ? getBasicRow(negativeVarColumn) : null;
639 double mostNegative = negativeVarBasicRow == null ? 0 : getEntry(negativeVarBasicRow, getRhsOffset());
640
641 final Set<Integer> usedBasicRows = new HashSet<>();
642 final double[] coefficients = new double[getOriginalNumDecisionVariables()];
643 for (int i = 0; i < coefficients.length; i++) {
644 int colIndex = columnLabels.indexOf("x" + i);
645 if (colIndex < 0) {
646 coefficients[i] = 0;
647 continue;
648 }
649 Integer basicRow = getBasicRow(colIndex);
650 if (basicRow != null && basicRow == 0) {
651
652
653
654 coefficients[i] = 0;
655 } else if (usedBasicRows.contains(basicRow)) {
656
657
658 coefficients[i] = 0 - (restrictToNonNegative ? 0 : mostNegative);
659 } else {
660 usedBasicRows.add(basicRow);
661 coefficients[i] =
662 (basicRow == null ? 0 : getEntry(basicRow, getRhsOffset())) -
663 (restrictToNonNegative ? 0 : mostNegative);
664 }
665 coefficients[i] = updateExponent(coefficients[i], variableExpChange[i]);
666 }
667 return new PointValuePair(coefficients, f.value(coefficients));
668 }
669
670
671
672
673
674
675
676 protected void performRowOperations(int pivotCol, int pivotRow) {
677
678 final double pivotVal = getEntry(pivotRow, pivotCol);
679 divideRow(pivotRow, pivotVal);
680
681
682 for (int i = 0; i < getHeight(); i++) {
683 if (i != pivotRow) {
684 final double multiplier = getEntry(i, pivotCol);
685 if (multiplier != 0.0) {
686 subtractRow(i, pivotRow, multiplier);
687 }
688 }
689 }
690
691
692 final int previousBasicVariable = getBasicVariable(pivotRow);
693 basicVariables[previousBasicVariable] = -1;
694 basicVariables[pivotCol] = pivotRow;
695 basicRows[pivotRow] = pivotCol;
696 }
697
698
699
700
701
702
703
704
705
706
707 protected void divideRow(final int dividendRowIndex, final double divisor) {
708 final double[] dividendRow = getRow(dividendRowIndex);
709 for (int j = 0; j < getWidth(); j++) {
710 dividendRow[j] /= divisor;
711 }
712 }
713
714
715
716
717
718
719
720
721
722
723
724 protected void subtractRow(final int minuendRowIndex, final int subtrahendRowIndex, final double multiplier) {
725 final double[] minuendRow = getRow(minuendRowIndex);
726 final double[] subtrahendRow = getRow(subtrahendRowIndex);
727 for (int i = 0; i < getWidth(); i++) {
728 minuendRow[i] -= subtrahendRow[i] * multiplier;
729 }
730 }
731
732
733
734
735
736 protected final int getWidth() {
737 return tableau.getColumnDimension();
738 }
739
740
741
742
743
744 protected final int getHeight() {
745 return tableau.getRowDimension();
746 }
747
748
749
750
751
752
753
754 protected final double getEntry(final int row, final int column) {
755 return tableau.getEntry(row, column);
756 }
757
758
759
760
761
762
763
764 protected final void setEntry(final int row, final int column, final double value) {
765 tableau.setEntry(row, column, value);
766 }
767
768
769
770
771
772 protected final int getSlackVariableOffset() {
773 return getNumObjectiveFunctions() + numDecisionVariables;
774 }
775
776
777
778
779
780 protected final int getArtificialVariableOffset() {
781 return getNumObjectiveFunctions() + numDecisionVariables + numSlackVariables;
782 }
783
784
785
786
787
788 protected final int getRhsOffset() {
789 return getWidth() - 1;
790 }
791
792
793
794
795
796
797
798
799
800
801 protected final int getNumDecisionVariables() {
802 return numDecisionVariables;
803 }
804
805
806
807
808
809
810 protected final int getOriginalNumDecisionVariables() {
811 return f.getCoefficients().getDimension();
812 }
813
814
815
816
817
818 protected final int getNumSlackVariables() {
819 return numSlackVariables;
820 }
821
822
823
824
825
826 protected final int getNumArtificialVariables() {
827 return numArtificialVariables;
828 }
829
830
831
832
833
834
835 protected final double[] getRow(int row) {
836 return tableau.getDataRef()[row];
837 }
838
839
840
841
842
843 protected final double[][] getData() {
844 return tableau.getData();
845 }
846
847
848 @Override
849 public boolean equals(Object other) {
850
851 if (this == other) {
852 return true;
853 }
854
855 if (other instanceof SimplexTableau) {
856 SimplexTableau rhs = (SimplexTableau) other;
857 return restrictToNonNegative == rhs.restrictToNonNegative &&
858 numDecisionVariables == rhs.numDecisionVariables &&
859 numSlackVariables == rhs.numSlackVariables &&
860 numArtificialVariables == rhs.numArtificialVariables &&
861 epsilon == rhs.epsilon &&
862 maxUlps == rhs.maxUlps &&
863 f.equals(rhs.f) &&
864 constraints.equals(rhs.constraints) &&
865 tableau.equals(rhs.tableau);
866 }
867 return false;
868 }
869
870
871 @Override
872 public int hashCode() {
873 return Boolean.valueOf(restrictToNonNegative).hashCode() ^
874 numDecisionVariables ^
875 numSlackVariables ^
876 numArtificialVariables ^
877 Double.valueOf(epsilon).hashCode() ^
878 maxUlps ^
879 f.hashCode() ^
880 constraints.hashCode() ^
881 tableau.hashCode();
882 }
883 }