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