1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.constraintlayout.core;
18 
19 import static androidx.constraintlayout.core.SolverVariable.STRENGTH_EQUALITY;
20 import static androidx.constraintlayout.core.SolverVariable.STRENGTH_HIGH;
21 import static androidx.constraintlayout.core.SolverVariable.STRENGTH_HIGHEST;
22 import static androidx.constraintlayout.core.SolverVariable.STRENGTH_LOW;
23 import static androidx.constraintlayout.core.SolverVariable.STRENGTH_MEDIUM;
24 
25 import java.util.ArrayList;
26 
27 public class ArrayRow implements LinearSystem.Row {
28     private static final boolean DEBUG = false;
29 
30     SolverVariable mVariable = null;
31     float mConstantValue = 0;
32     boolean mUsed = false;
33     private static final boolean FULL_NEW_CHECK = false; // full validation (debug purposes)
34 
35     ArrayList<SolverVariable> mVariablesToUpdate = new ArrayList<>();
36 
37     public ArrayRowVariables variables;
38 
39     public interface ArrayRowVariables {
40 
41         // @TODO: add description
getCurrentSize()42         int getCurrentSize();
43 
44         // @TODO: add description
getVariable(int index)45         SolverVariable getVariable(int index);
46 
47         // @TODO: add description
getVariableValue(int index)48         float getVariableValue(int index);
49 
50         // @TODO: add description
get(SolverVariable variable)51         float get(SolverVariable variable);
52 
53         // @TODO: add description
indexOf(SolverVariable variable)54         int indexOf(SolverVariable variable);
55 
56         // @TODO: add description
display()57         void display();
58 
59         // @TODO: add description
clear()60         void clear();
61 
62         // @TODO: add description
contains(SolverVariable variable)63         boolean contains(SolverVariable variable);
64 
65         // @TODO: add description
put(SolverVariable variable, float value)66         void put(SolverVariable variable, float value);
67 
68         // @TODO: add description
sizeInBytes()69         int sizeInBytes();
70 
71         // @TODO: add description
invert()72         void invert();
73 
74         // @TODO: add description
remove(SolverVariable v, boolean removeFromDefinition)75         float remove(SolverVariable v, boolean removeFromDefinition);
76 
77         // @TODO: add description
divideByAmount(float amount)78         void divideByAmount(float amount);
79 
80         // @TODO: add description
add(SolverVariable v, float value, boolean removeFromDefinition)81         void add(SolverVariable v, float value, boolean removeFromDefinition);
82 
83         // @TODO: add description
use(ArrayRow definition, boolean removeFromDefinition)84         float use(ArrayRow definition, boolean removeFromDefinition);
85     }
86 
87     boolean mIsSimpleDefinition = false;
88 
ArrayRow()89     public ArrayRow() {
90     }
91 
ArrayRow(Cache cache)92     public ArrayRow(Cache cache) {
93         variables = new ArrayLinkedVariables(this, cache);
94         //variables = new OptimizedSolverVariableValues(this, cache);
95     }
96 
hasKeyVariable()97     boolean hasKeyVariable() {
98         return !(
99                 (mVariable == null)
100                         || (mVariable.mType != SolverVariable.Type.UNRESTRICTED
101                         && mConstantValue < 0)
102         );
103     }
104 
105     // @TODO: add description
106     @Override
toString()107     public String toString() {
108         return toReadableString();
109     }
110 
toReadableString()111     String toReadableString() {
112         String s = "";
113         if (mVariable == null) {
114             s += "0";
115         } else {
116             s += mVariable;
117         }
118         s += " = ";
119         boolean addedVariable = false;
120         if (mConstantValue != 0) {
121             s += mConstantValue;
122             addedVariable = true;
123         }
124         int count = variables.getCurrentSize();
125         for (int i = 0; i < count; i++) {
126             SolverVariable v = variables.getVariable(i);
127             if (v == null) {
128                 continue;
129             }
130             float amount = variables.getVariableValue(i);
131             if (amount == 0) {
132                 continue;
133             }
134             String name = v.toString();
135             if (!addedVariable) {
136                 if (amount < 0) {
137                     s += "- ";
138                     amount *= -1;
139                 }
140             } else {
141                 if (amount > 0) {
142                     s += " + ";
143                 } else {
144                     s += " - ";
145                     amount *= -1;
146                 }
147             }
148             if (amount == 1) {
149                 s += name;
150             } else {
151                 s += amount + " " + name;
152             }
153             addedVariable = true;
154         }
155         if (!addedVariable) {
156             s += "0.0";
157         }
158         if (DEBUG) {
159             variables.display();
160         }
161         return s;
162     }
163 
164     // @TODO: add description
reset()165     public void reset() {
166         mVariable = null;
167         variables.clear();
168         mConstantValue = 0;
169         mIsSimpleDefinition = false;
170     }
171 
hasVariable(SolverVariable v)172     boolean hasVariable(SolverVariable v) {
173         return variables.contains(v);
174     }
175 
createRowDefinition(SolverVariable variable, int value)176     ArrayRow createRowDefinition(SolverVariable variable, int value) {
177         this.mVariable = variable;
178         variable.computedValue = value;
179         mConstantValue = value;
180         mIsSimpleDefinition = true;
181         return this;
182     }
183 
184     // @TODO: add description
createRowEquals(SolverVariable variable, int value)185     public ArrayRow createRowEquals(SolverVariable variable, int value) {
186         if (value < 0) {
187             mConstantValue = -1 * value;
188             variables.put(variable, 1);
189         } else {
190             mConstantValue = value;
191             variables.put(variable, -1);
192         }
193         return this;
194     }
195 
196     // @TODO: add description
createRowEquals(SolverVariable variableA, SolverVariable variableB, int margin)197     public ArrayRow createRowEquals(SolverVariable variableA,
198             SolverVariable variableB,
199             int margin) {
200         boolean inverse = false;
201         if (margin != 0) {
202             int m = margin;
203             if (m < 0) {
204                 m = -1 * m;
205                 inverse = true;
206             }
207             mConstantValue = m;
208         }
209         if (!inverse) {
210             variables.put(variableA, -1);
211             variables.put(variableB, 1);
212         } else {
213             variables.put(variableA, 1);
214             variables.put(variableB, -1);
215         }
216         return this;
217     }
218 
219     // @TODO: add description
addSingleError(SolverVariable error, int sign)220     ArrayRow addSingleError(SolverVariable error, int sign) {
221         variables.put(error, (float) sign);
222         return this;
223     }
224 
225     // @TODO: add description
createRowGreaterThan(SolverVariable variableA, SolverVariable variableB, SolverVariable slack, int margin)226     public ArrayRow createRowGreaterThan(SolverVariable variableA,
227             SolverVariable variableB, SolverVariable slack,
228             int margin) {
229         boolean inverse = false;
230         if (margin != 0) {
231             int m = margin;
232             if (m < 0) {
233                 m = -1 * m;
234                 inverse = true;
235             }
236             mConstantValue = m;
237         }
238         if (!inverse) {
239             variables.put(variableA, -1);
240             variables.put(variableB, 1);
241             variables.put(slack, 1);
242         } else {
243             variables.put(variableA, 1);
244             variables.put(variableB, -1);
245             variables.put(slack, -1);
246         }
247         return this;
248     }
249 
250     // @TODO: add description
createRowGreaterThan(SolverVariable a, int b, SolverVariable slack)251     public ArrayRow createRowGreaterThan(SolverVariable a, int b, SolverVariable slack) {
252         mConstantValue = b;
253         variables.put(a, -1);
254         return this;
255     }
256 
257     // @TODO: add description
createRowLowerThan(SolverVariable variableA, SolverVariable variableB, SolverVariable slack, int margin)258     public ArrayRow createRowLowerThan(SolverVariable variableA, SolverVariable variableB,
259             SolverVariable slack, int margin) {
260         boolean inverse = false;
261         if (margin != 0) {
262             int m = margin;
263             if (m < 0) {
264                 m = -1 * m;
265                 inverse = true;
266             }
267             mConstantValue = m;
268         }
269         if (!inverse) {
270             variables.put(variableA, -1);
271             variables.put(variableB, 1);
272             variables.put(slack, -1);
273         } else {
274             variables.put(variableA, 1);
275             variables.put(variableB, -1);
276             variables.put(slack, 1);
277         }
278         return this;
279     }
280 
281     // @TODO: add description
createRowEqualMatchDimensions(float currentWeight, float totalWeights, float nextWeight, SolverVariable variableStartA, SolverVariable variableEndA, SolverVariable variableStartB, SolverVariable variableEndB)282     public ArrayRow createRowEqualMatchDimensions(float currentWeight,
283             float totalWeights, float nextWeight,
284             SolverVariable variableStartA,
285             SolverVariable variableEndA,
286             SolverVariable variableStartB,
287             SolverVariable variableEndB) {
288         mConstantValue = 0;
289         if (totalWeights == 0 || (currentWeight == nextWeight)) {
290             // endA - startA == endB - startB
291             // 0 = startA - endA + endB - startB
292             variables.put(variableStartA, 1);
293             variables.put(variableEndA, -1);
294             variables.put(variableEndB, 1);
295             variables.put(variableStartB, -1);
296         } else {
297             if (currentWeight == 0) {
298                 variables.put(variableStartA, 1);
299                 variables.put(variableEndA, -1);
300             } else if (nextWeight == 0) {
301                 variables.put(variableStartB, 1);
302                 variables.put(variableEndB, -1);
303             } else {
304                 float cw = currentWeight / totalWeights;
305                 float nw = nextWeight / totalWeights;
306                 float w = cw / nw;
307 
308                 // endA - startA == w * (endB - startB)
309                 // 0 = startA - endA + w * (endB - startB)
310                 variables.put(variableStartA, 1);
311                 variables.put(variableEndA, -1);
312                 variables.put(variableEndB, w);
313                 variables.put(variableStartB, -w);
314             }
315         }
316         return this;
317     }
318 
319     // @TODO: add description
createRowEqualDimension(float currentWeight, float totalWeights, float nextWeight, SolverVariable variableStartA, int marginStartA, SolverVariable variableEndA, int marginEndA, SolverVariable variableStartB, int marginStartB, SolverVariable variableEndB, int marginEndB)320     public ArrayRow createRowEqualDimension(float currentWeight,
321             float totalWeights, float nextWeight,
322             SolverVariable variableStartA, int marginStartA,
323             SolverVariable variableEndA, int marginEndA,
324             SolverVariable variableStartB, int marginStartB,
325             SolverVariable variableEndB, int marginEndB) {
326         if (totalWeights == 0 || (currentWeight == nextWeight)) {
327             // endA - startA + marginStartA + marginEndA
328             //      == endB - startB + marginStartB + marginEndB
329             // 0 = startA - endA - marginStartA - marginEndA
330             //      + endB - startB + marginStartB + marginEndB
331             // 0 = (- marginStartA - marginEndA + marginStartB + marginEndB)
332             //      + startA - endA + endB - startB
333             mConstantValue = -marginStartA - marginEndA + marginStartB + marginEndB;
334             variables.put(variableStartA, 1);
335             variables.put(variableEndA, -1);
336             variables.put(variableEndB, 1);
337             variables.put(variableStartB, -1);
338         } else {
339             float cw = currentWeight / totalWeights;
340             float nw = nextWeight / totalWeights;
341             float w = cw / nw;
342             // (endA - startA + marginStartA + marginEndA) =
343             //      w * (endB - startB) + marginStartB + marginEndB;
344             // 0 = (startA - endA - marginStartA - marginEndA)
345             //      + w * (endB - startB) + marginStartB + marginEndB
346             // 0 = (- marginStartA - marginEndA + marginStartB + marginEndB)
347             //      + startA - endA + w * endB - w * startB
348             mConstantValue = -marginStartA - marginEndA + w * marginStartB + w * marginEndB;
349             variables.put(variableStartA, 1);
350             variables.put(variableEndA, -1);
351             variables.put(variableEndB, w);
352             variables.put(variableStartB, -w);
353         }
354         return this;
355     }
356 
createRowCentering(SolverVariable variableA, SolverVariable variableB, int marginA, float bias, SolverVariable variableC, SolverVariable variableD, int marginB)357     ArrayRow createRowCentering(SolverVariable variableA, SolverVariable variableB,
358             int marginA, float bias, SolverVariable variableC,
359             SolverVariable variableD, int marginB) {
360         if (variableB == variableC) {
361             // centering on the same position
362             // B - A == D - B
363             // 0 = A + D - 2 * B
364             variables.put(variableA, 1);
365             variables.put(variableD, 1);
366             variables.put(variableB, -2);
367             return this;
368         }
369         if (bias == 0.5f) {
370             // don't bother applying the bias, we are centered
371             // A - B = C - D
372             // 0 = A - B - C + D
373             // with margin:
374             // A - B - Ma = C - D - Mb
375             // 0 = A - B - C + D - Ma + Mb
376             variables.put(variableA, 1f);
377             variables.put(variableB, -1f);
378             variables.put(variableC, -1f);
379             variables.put(variableD, 1f);
380             if (marginA > 0 || marginB > 0) {
381                 mConstantValue = -marginA + marginB;
382             }
383         } else if (bias <= 0) {
384             // A = B + m
385             variables.put(variableA, -1);
386             variables.put(variableB, 1);
387             mConstantValue = marginA;
388         } else if (bias >= 1) {
389             // D = C - m
390             variables.put(variableD, -1);
391             variables.put(variableC, 1);
392             mConstantValue = -marginB;
393         } else {
394             variables.put(variableA, 1 * (1 - bias));
395             variables.put(variableB, -1 * (1 - bias));
396             variables.put(variableC, -1 * bias);
397             variables.put(variableD, 1 * bias);
398             if (marginA > 0 || marginB > 0) {
399                 mConstantValue = -marginA * (1 - bias) + marginB * bias;
400             }
401         }
402         return this;
403     }
404 
405     // @TODO: add description
addError(LinearSystem system, int strength)406     public ArrayRow addError(LinearSystem system, int strength) {
407         variables.put(system.createErrorVariable(strength, "ep"), 1);
408         variables.put(system.createErrorVariable(strength, "em"), -1);
409         return this;
410     }
411 
createRowDimensionPercent(SolverVariable variableA, SolverVariable variableC, float percent)412     ArrayRow createRowDimensionPercent(SolverVariable variableA,
413             SolverVariable variableC, float percent) {
414         variables.put(variableA, -1);
415         variables.put(variableC, percent);
416         return this;
417     }
418 
419     /**
420      * Create a constraint to express {@code A = B + (C - D)} * ratio
421      * We use this for ratio, where for example {@code Right = Left + (Bottom - Top) * percent}
422      *
423      * @param variableA variable A
424      * @param variableB variable B
425      * @param variableC variable C
426      * @param variableD variable D
427      * @param ratio     ratio between AB and CD
428      * @return the row
429      */
createRowDimensionRatio(SolverVariable variableA, SolverVariable variableB, SolverVariable variableC, SolverVariable variableD, float ratio)430     public ArrayRow createRowDimensionRatio(SolverVariable variableA, SolverVariable variableB,
431             SolverVariable variableC, SolverVariable variableD,
432             float ratio) {
433         // A = B + (C - D) * ratio
434         variables.put(variableA, -1);
435         variables.put(variableB, 1);
436         variables.put(variableC, ratio);
437         variables.put(variableD, -ratio);
438         return this;
439     }
440 
441     /**
442      * Create a constraint to express At + (Ab-At)/2 = Bt + (Bb-Bt)/2 - angle
443      */
createRowWithAngle(SolverVariable at, SolverVariable ab, SolverVariable bt, SolverVariable bb, float angleComponent)444     public ArrayRow createRowWithAngle(SolverVariable at,
445             SolverVariable ab,
446             SolverVariable bt,
447             SolverVariable bb,
448             float angleComponent) {
449         variables.put(bt, 0.5f);
450         variables.put(bb, 0.5f);
451         variables.put(at, -0.5f);
452         variables.put(ab, -0.5f);
453         mConstantValue = -angleComponent;
454         return this;
455     }
456 
sizeInBytes()457     int sizeInBytes() {
458         int size = 0;
459         if (mVariable != null) {
460             size += 4; // object
461         }
462         size += 4; // constantValue
463         size += 4; // used
464 
465         size += variables.sizeInBytes();
466         return size;
467     }
468 
ensurePositiveConstant()469     void ensurePositiveConstant() {
470         // Ensure that if we have a constant it's positive
471         if (mConstantValue < 0) {
472             // If not, simply multiply the equation by -1
473             mConstantValue *= -1;
474             variables.invert();
475         }
476     }
477 
478     /**
479      * Pick a subject variable out of the existing ones.
480      * - if a variable is unrestricted
481      * - or if it's a negative new variable (not found elsewhere)
482      * - otherwise we have to add a new additional variable
483      *
484      * @return true if we added an extra variable to the system
485      */
chooseSubject(LinearSystem system)486     boolean chooseSubject(LinearSystem system) {
487         boolean addedExtra = false;
488         SolverVariable pivotCandidate = chooseSubjectInVariables(system);
489         if (pivotCandidate == null) {
490             // need to add extra variable
491             addedExtra = true;
492         } else {
493             pivot(pivotCandidate);
494         }
495         if (variables.getCurrentSize() == 0) {
496             mIsSimpleDefinition = true;
497         }
498         return addedExtra;
499     }
500 
501     /**
502      * Pick a subject variable out of the existing ones.
503      * - if a variable is unrestricted
504      * - or if it's a negative new variable (not found elsewhere)
505      * - otherwise we return null
506      *
507      * @return a candidate variable we can pivot on or null if not found
508      */
chooseSubjectInVariables(LinearSystem system)509     SolverVariable chooseSubjectInVariables(LinearSystem system) {
510         // if unrestricted, pick it
511         // if restricted, needs to be < 0 and new
512         //
513         SolverVariable restrictedCandidate = null;
514         SolverVariable unrestrictedCandidate = null;
515         float unrestrictedCandidateAmount = 0;
516         float restrictedCandidateAmount = 0;
517         boolean unrestrictedCandidateIsNew = false;
518         boolean restrictedCandidateIsNew = false;
519 
520         final int currentSize = variables.getCurrentSize();
521         for (int i = 0; i < currentSize; i++) {
522             float amount = variables.getVariableValue(i);
523             SolverVariable variable = variables.getVariable(i);
524             if (variable.mType == SolverVariable.Type.UNRESTRICTED) {
525                 if (unrestrictedCandidate == null) {
526                     unrestrictedCandidate = variable;
527                     unrestrictedCandidateAmount = amount;
528                     unrestrictedCandidateIsNew = isNew(variable, system);
529                 } else if (unrestrictedCandidateAmount > amount) {
530                     unrestrictedCandidate = variable;
531                     unrestrictedCandidateAmount = amount;
532                     unrestrictedCandidateIsNew = isNew(variable, system);
533                 } else if (!unrestrictedCandidateIsNew && isNew(variable, system)) {
534                     unrestrictedCandidate = variable;
535                     unrestrictedCandidateAmount = amount;
536                     unrestrictedCandidateIsNew = true;
537                 }
538             } else if (unrestrictedCandidate == null) {
539                 if (amount < 0) {
540                     if (restrictedCandidate == null) {
541                         restrictedCandidate = variable;
542                         restrictedCandidateAmount = amount;
543                         restrictedCandidateIsNew = isNew(variable, system);
544                     } else if (restrictedCandidateAmount > amount) {
545                         restrictedCandidate = variable;
546                         restrictedCandidateAmount = amount;
547                         restrictedCandidateIsNew = isNew(variable, system);
548                     } else if (!restrictedCandidateIsNew && isNew(variable, system)) {
549                         restrictedCandidate = variable;
550                         restrictedCandidateAmount = amount;
551                         restrictedCandidateIsNew = true;
552                     }
553                 }
554             }
555         }
556 
557         if (unrestrictedCandidate != null) {
558             return unrestrictedCandidate;
559         }
560         return restrictedCandidate;
561     }
562 
563     /**
564      * Returns true if the variable is new to the system, i.e. is already present
565      * in one of the rows. This function is called while choosing the subject of a new row.
566      *
567      * @param variable the variable to check for
568      * @param system   the linear system we check
569      */
isNew(SolverVariable variable, LinearSystem system)570     private boolean isNew(SolverVariable variable, LinearSystem system) {
571         if (FULL_NEW_CHECK) {
572             boolean isNew = true;
573             for (int i = 0; i < system.mNumRows; i++) {
574                 ArrayRow row = system.mRows[i];
575                 if (row.hasVariable(variable)) {
576                     isNew = false;
577                 }
578             }
579             if (variable.usageInRowCount <= 1 != isNew) {
580                 System.out.println("Problem with usage tracking");
581             }
582             return isNew;
583         }
584         // We maintain a usage count -- variables are ref counted if they are present
585         // in the right side of a row or not. If the count is zero or one, the variable
586         // is new (if one, it means it exist in a row, but this is the row we insert)
587         return variable.usageInRowCount <= 1;
588     }
589 
pivot(SolverVariable v)590     void pivot(SolverVariable v) {
591         if (mVariable != null) {
592             // first, move back the variable to its column
593             variables.put(mVariable, -1f);
594             mVariable.mDefinitionId = -1;
595             mVariable = null;
596         }
597 
598         float amount = variables.remove(v, true) * -1;
599         mVariable = v;
600         if (amount == 1) {
601             return;
602         }
603         mConstantValue = mConstantValue / amount;
604         variables.divideByAmount(amount);
605     }
606 
607     // Row compatibility
608 
609     @Override
isEmpty()610     public boolean isEmpty() {
611         return (mVariable == null && mConstantValue == 0 && variables.getCurrentSize() == 0);
612     }
613 
614     @Override
updateFromRow(LinearSystem system, ArrayRow definition, boolean removeFromDefinition)615     public void updateFromRow(LinearSystem system,
616             ArrayRow definition,
617             boolean removeFromDefinition) {
618         float value = variables.use(definition, removeFromDefinition);
619 
620         mConstantValue += definition.mConstantValue * value;
621         if (removeFromDefinition) {
622             definition.mVariable.removeFromRow(this);
623         }
624         if (LinearSystem.SIMPLIFY_SYNONYMS
625                 && mVariable != null && variables.getCurrentSize() == 0) {
626             mIsSimpleDefinition = true;
627             system.hasSimpleDefinition = true;
628         }
629     }
630 
631     // @TODO: add description
632     @Override
updateFromFinalVariable(LinearSystem system, SolverVariable variable, boolean removeFromDefinition)633     public void updateFromFinalVariable(LinearSystem system,
634             SolverVariable variable,
635             boolean removeFromDefinition) {
636         if (variable == null || !variable.isFinalValue) {
637             return;
638         }
639         float value = variables.get(variable);
640         mConstantValue += variable.computedValue * value;
641         variables.remove(variable, removeFromDefinition);
642         if (removeFromDefinition) {
643             variable.removeFromRow(this);
644         }
645         if (LinearSystem.SIMPLIFY_SYNONYMS
646                 && variables.getCurrentSize() == 0) {
647             mIsSimpleDefinition = true;
648             system.hasSimpleDefinition = true;
649         }
650     }
651 
652     // @TODO: add description
updateFromSynonymVariable(LinearSystem system, SolverVariable variable, boolean removeFromDefinition)653     public void updateFromSynonymVariable(LinearSystem system,
654             SolverVariable variable,
655             boolean removeFromDefinition) {
656         if (variable == null || !variable.mIsSynonym) {
657             return;
658         }
659         float value = variables.get(variable);
660         mConstantValue += variable.mSynonymDelta * value;
661         variables.remove(variable, removeFromDefinition);
662         if (removeFromDefinition) {
663             variable.removeFromRow(this);
664         }
665         variables.add(system.mCache.mIndexedVariables[variable.mSynonym],
666                 value, removeFromDefinition);
667         if (LinearSystem.SIMPLIFY_SYNONYMS
668                 && variables.getCurrentSize() == 0) {
669             mIsSimpleDefinition = true;
670             system.hasSimpleDefinition = true;
671         }
672     }
673 
pickPivotInVariables(boolean[] avoid, SolverVariable exclude)674     private SolverVariable pickPivotInVariables(boolean[] avoid, SolverVariable exclude) {
675         boolean all = true;
676         float value = 0;
677         SolverVariable pivot = null;
678         SolverVariable pivotSlack = null;
679         float valueSlack = 0;
680 
681         final int currentSize = variables.getCurrentSize();
682         for (int i = 0; i < currentSize; i++) {
683             float currentValue = variables.getVariableValue(i);
684             if (currentValue < 0) {
685                 // We can return the first negative candidate as in ArrayLinkedVariables
686                 // they are already sorted by id
687                 SolverVariable v = variables.getVariable(i);
688                 if (!((avoid != null && avoid[v.id]) || (v == exclude))) {
689                     if (all) {
690                         if (v.mType == SolverVariable.Type.SLACK
691                                 || v.mType == SolverVariable.Type.ERROR) {
692                             if (currentValue < value) {
693                                 value = currentValue;
694                                 pivot = v;
695                             }
696                         }
697                     } else {
698                         if (v.mType == SolverVariable.Type.SLACK) {
699                             if (currentValue < valueSlack) {
700                                 valueSlack = currentValue;
701                                 pivotSlack = v;
702                             }
703                         } else if (v.mType == SolverVariable.Type.ERROR) {
704                             if (currentValue < value) {
705                                 value = currentValue;
706                                 pivot = v;
707                             }
708                         }
709                     }
710                 }
711             }
712         }
713         if (all) {
714             return pivot;
715         }
716         return pivot != null ? pivot : pivotSlack;
717     }
718 
719     // @TODO: add description
pickPivot(SolverVariable exclude)720     public SolverVariable pickPivot(SolverVariable exclude) {
721         return pickPivotInVariables(null, exclude);
722     }
723 
724     @Override
getPivotCandidate(LinearSystem system, boolean[] avoid)725     public SolverVariable getPivotCandidate(LinearSystem system, boolean[] avoid) {
726         return pickPivotInVariables(avoid, null);
727     }
728 
729     @Override
clear()730     public void clear() {
731         variables.clear();
732         mVariable = null;
733         mConstantValue = 0;
734     }
735 
736     /**
737      * Used to initiate a goal from a given row (to see if we can remove an extra var)
738      */
739     @Override
initFromRow(@uppressWarnings"HiddenTypeParameter") LinearSystem.Row row)740     public void initFromRow(@SuppressWarnings("HiddenTypeParameter") LinearSystem.Row row) {
741         if (row instanceof ArrayRow) {
742             ArrayRow copiedRow = (ArrayRow) row;
743             mVariable = null;
744             variables.clear();
745             for (int i = 0; i < copiedRow.variables.getCurrentSize(); i++) {
746                 SolverVariable var = copiedRow.variables.getVariable(i);
747                 float val = copiedRow.variables.getVariableValue(i);
748                 variables.add(var, val, true);
749             }
750         }
751     }
752 
753     @Override
addError(SolverVariable error)754     public void addError(SolverVariable error) {
755         float weight = 1;
756         if (error.strength == STRENGTH_LOW) {
757             weight = 1F;
758         } else if (error.strength == STRENGTH_MEDIUM) {
759             weight = 1E3F;
760         } else if (error.strength == STRENGTH_HIGH) {
761             weight = 1E6F;
762         } else if (error.strength == STRENGTH_HIGHEST) {
763             weight = 1E9F;
764         } else if (error.strength == STRENGTH_EQUALITY) {
765             weight = 1E12F;
766         }
767         variables.put(error, weight);
768     }
769 
770     @Override
getKey()771     public SolverVariable getKey() {
772         return mVariable;
773     }
774 
775     @Override
updateFromSystem(LinearSystem system)776     public void updateFromSystem(LinearSystem system) {
777         if (system.mRows.length == 0) {
778             return;
779         }
780 
781         boolean done = false;
782         while (!done) {
783             int currentSize = variables.getCurrentSize();
784             for (int i = 0; i < currentSize; i++) {
785                 SolverVariable variable = variables.getVariable(i);
786                 if (variable.mDefinitionId != -1 || variable.isFinalValue || variable.mIsSynonym) {
787                     mVariablesToUpdate.add(variable);
788                 }
789             }
790             final int size = mVariablesToUpdate.size();
791             if (size > 0) {
792                 for (int i = 0; i < size; i++) {
793                     SolverVariable variable = mVariablesToUpdate.get(i);
794                     if (variable.isFinalValue) {
795                         updateFromFinalVariable(system, variable, true);
796                     } else if (variable.mIsSynonym) {
797                         updateFromSynonymVariable(system, variable, true);
798                     } else {
799                         updateFromRow(system, system.mRows[variable.mDefinitionId], true);
800                     }
801                 }
802                 mVariablesToUpdate.clear();
803             } else {
804                 done = true;
805             }
806         }
807         if (LinearSystem.SIMPLIFY_SYNONYMS
808                 && mVariable != null && variables.getCurrentSize() == 0) {
809             mIsSimpleDefinition = true;
810             system.hasSimpleDefinition = true;
811         }
812     }
813 }
814