diff --git a/src/main/java/no/birkett/kiwi/Constraint.java b/src/main/java/no/birkett/kiwi/Constraint.java index 3619da0..3e2d4a4 100644 --- a/src/main/java/no/birkett/kiwi/Constraint.java +++ b/src/main/java/no/birkett/kiwi/Constraint.java @@ -1,5 +1,7 @@ package no.birkett.kiwi; +import java.util.*; + /** * Created by alex on 30/01/15. */ @@ -17,7 +19,7 @@ public class Constraint { } public Constraint(Expression expr, RelationalOperator op, double strength) { - this.expression = expr; + this.expression = reduce(expr); this.op = op; this.strength = Strength.clip(strength); } @@ -26,6 +28,26 @@ public class Constraint { this(other.expression, other.op, strength); } + private static Expression reduce(Expression expr){ + + Map vars = new LinkedHashMap<>(); + for(Term term: expr.getTerms()){ + Double value = vars.get(term.getVariable()); + if(value == null){ + value = 0.0; + } + value += term.coefficient; + vars.put(term.getVariable(), value); + } + + List reducedTerms = new ArrayList<>(); + for(Variable variable: vars.keySet()){ + reducedTerms.add(new Term(variable, vars.get(variable))); + } + + return new Expression(reducedTerms, expr.getConstant()); + } + public Expression getExpression() { return expression; } diff --git a/src/main/java/no/birkett/kiwi/DuplicateEditVariableException.java b/src/main/java/no/birkett/kiwi/DuplicateEditVariableException.java new file mode 100644 index 0000000..b951b4e --- /dev/null +++ b/src/main/java/no/birkett/kiwi/DuplicateEditVariableException.java @@ -0,0 +1,8 @@ +package no.birkett.kiwi; + +/** + * Created by yongsun on 1/13/16. + */ +public class DuplicateEditVariableException extends Exception { + +} diff --git a/src/main/java/no/birkett/kiwi/Row.java b/src/main/java/no/birkett/kiwi/Row.java index 588bcd3..9f2973d 100644 --- a/src/main/java/no/birkett/kiwi/Row.java +++ b/src/main/java/no/birkett/kiwi/Row.java @@ -1,6 +1,7 @@ package no.birkett.kiwi; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -11,7 +12,7 @@ public class Row { private double constant; - private Map cells = new HashMap(); + private Map cells = new LinkedHashMap<>(); public Row() { this(0); @@ -22,10 +23,20 @@ public class Row { } public Row(Row other) { - this.cells = other.cells; + this.cells = new LinkedHashMap<>(other.cells); this.constant = other.constant; } + public Row deepCopy(){ + Row result = new Row(); + result.constant = this.constant; + result.cells = new LinkedHashMap<>(); + for(Symbol s: this.cells.keySet()){ + result.cells.put(s, this.cells.get(s)); + } + return result; + } + public double getConstant() { return constant; } @@ -60,16 +71,28 @@ public class Row { */ void insert(Symbol symbol, double coefficient) { - Double existingCoefficient = cells.get(symbol); + //this looks different than c++ code +// Double existingCoefficient = cells.get(symbol); +// +// if (existingCoefficient != null) { +// coefficient = existingCoefficient; +// } +// +// if (Util.nearZero(coefficient)) { +// cells.remove(symbol); +// } else { +// cells.put(symbol, coefficient); +// } - if (existingCoefficient != null) { - coefficient = existingCoefficient; + //changes start here + Double value = this.cells.get(symbol); + if(value == null){ + this.cells.put(symbol, 0.0); } - - if (Util.nearZero(coefficient)) { - cells.remove(symbol); - } else { - cells.put(symbol, Double.valueOf(coefficient)); + double temp = this.cells.get(symbol) + coefficient; + this.cells.put(symbol, temp); + if(Util.nearZero(temp)){ + this.cells.remove(symbol); } } @@ -96,11 +119,21 @@ public class Row { void insert(Row other, double coefficient) { this.constant += other.constant * coefficient; - Set> map = other.getCells().entrySet(); + for(Symbol s: other.cells.keySet()){ + double coeff = other.cells.get(s) * coefficient; - for (Map.Entry entry : map) { - double coeff = entry.getValue() * coefficient; - insert(entry.getKey(), coeff); + //insert(s, coeff); this line looks different than the c++ + + //changes start here + Double value = this.cells.get(s); + if(value == null){ + this.cells.put(s, 0.0); + } + double temp = this.cells.get(s) + coeff; + this.cells.put(s, temp); + if(Util.nearZero(temp)){ + this.cells.remove(s); + } } } @@ -113,7 +146,7 @@ public class Row { * @param other */ void insert(Row other) { - insert(other, 0); + insert(other, 1.0); } /** @@ -134,11 +167,12 @@ public class Row { void reverseSign() { this.constant = -this.constant; - Set> map = getCells().entrySet(); - - for (Map.Entry entry : map) { - entry.setValue(-entry.getValue()); + Map newCells = new LinkedHashMap<>(); + for(Symbol s: cells.keySet()){ + double value = - cells.get(s); + newCells.put(s, value); } + this.cells = newCells; } /** @@ -158,11 +192,12 @@ public class Row { cells.remove(symbol); this.constant *= coeff; - Set> map = getCells().entrySet(); - - for (Map.Entry entry : map) { - entry.setValue(entry.getValue() * coeff); + HashMap newCells = new LinkedHashMap<>(); + for(Symbol s: cells.keySet()){ + double value = cells.get(s) * coeff; + newCells.put(s, value); } + this.cells = newCells; } /** diff --git a/src/main/java/no/birkett/kiwi/Solver.java b/src/main/java/no/birkett/kiwi/Solver.java index ccb6522..4f4db1a 100644 --- a/src/main/java/no/birkett/kiwi/Solver.java +++ b/src/main/java/no/birkett/kiwi/Solver.java @@ -1,6 +1,8 @@ package no.birkett.kiwi; +import com.oracle.tools.packager.Log; + import java.util.*; /** @@ -11,23 +13,29 @@ public class Solver { private static class Tag { Symbol marker; Symbol other; + + public Tag(){ + marker = new Symbol(); + other = new Symbol(); + } } private static class EditInfo { Tag tag; Constraint constraint; double constant; + + public EditInfo(Constraint constraint, Tag tag, double constant){ + this.constraint = constraint; + this.tag = tag; + this.constant = constant; + } } - private static class RowAndTag { - Tag tag; - Row row; - } - - private Map cns = new HashMap(); - private Map rows = new HashMap(); - private Map vars = new HashMap(); - private Map edits = new HashMap(); + private Map cns = new LinkedHashMap(); + private Map rows = new LinkedHashMap(); + private Map vars = new LinkedHashMap(); + private Map edits = new LinkedHashMap(); private List infeasibleRows = new ArrayList(); private Row objective = new Row(); private Row artificial; @@ -47,50 +55,219 @@ public class Solver { throw new DuplicateConstraintException(constraint); } - // Creating a row causes symbols to reserved for the variables - // in the constraint. If this method exits with an exception, - // then its possible those variables will linger in the var map. - // Since its likely that those variables will be used in other - // constraints and since exceptional conditions are uncommon, - // i'm not too worried about aggressive cleanup of the var map. + Tag tag = new Tag(); + Row row = createRow(constraint, tag); + Symbol subject = chooseSubject(row, tag); - RowAndTag rowAndTag = createRow(constraint); - - Symbol subject = chooseSubject(rowAndTag.row, rowAndTag.tag); - - // If chooseSubject could find a valid entering symbol, one - // last option is available if the entire row is composed of - // dummy variables. If the constant of the row is zero, then - // this represents redundant constraints and the new dummy - // marker can enter the basis. If the constant is non-zero, - // then it represents an unsatisfiable constraint. - if (subject.getType() == Symbol.Type.INVALID && allDummies(rowAndTag.row)) { - if (!Util.nearZero(rowAndTag.row.getConstant())) { + if(subject.getType() == Symbol.Type.INVALID && allDummies(row)){ + if (!Util.nearZero(row.getConstant())) { throw new UnsatisfiableConstraintException(constraint); } else { - subject = rowAndTag.tag.marker; + subject = tag.marker; } } - // If an entering symbol still isn't found, then the row must - // be added using an artificial variable. If that fails, then - // the row represents an unsatisfiable constraint. if (subject.getType() == Symbol.Type.INVALID) { - if (!addWithArtificialVariable(rowAndTag.row)) { + if (!addWithArtificialVariable(row)) { throw new UnsatisfiableConstraintException(constraint); } } else { - rowAndTag.row.solveFor(subject); - substitute(subject, rowAndTag.row); - this.rows.put(subject, rowAndTag.row); + row.solveFor(subject); + substitute(subject, row); + this.rows.put(subject, row); } - this.cns.put(constraint, rowAndTag.tag); + this.cns.put(constraint, tag); - // Optimizing after each constraint is added performs less - // aggregate work due to a smaller average system size. It - // also ensures the solver remains in a consistent state. - optimize(this.objective); + optimize(objective); + } + + void removeConstraint(Constraint constraint) throws UnknownConstraintException, InternalSolverError{ + Tag tag = cns.get(constraint); + if(tag == null){ + throw new UnknownConstraintException(constraint); + } + + cns.remove(constraint); + removeConstraintEffects(constraint, tag); + + Row row = rows.get(tag.marker); + if(row != null){ + rows.remove(tag.marker); + } + else{ + row = getMarkerLeavingRow(tag.marker); + if(row == null){ + throw new InternalSolverError("internal solver error"); + } + + //This looks wrong! changes made below + //Symbol leaving = tag.marker; + //rows.remove(tag.marker); + + Symbol leaving = null; + for(Symbol s: rows.keySet()){ + if(rows.get(s) == row){ + leaving = s; + } + } + if(leaving == null){ + throw new InternalSolverError("internal solver error"); + } + + rows.remove(leaving); + row.solveFor(leaving, tag.marker); + substitute(tag.marker, row); + } + optimize(objective); + } + + void removeConstraintEffects(Constraint constraint, Tag tag){ + if(tag.marker.getType() == Symbol.Type.ERROR){ + removeMarkerEffects(tag.marker, constraint.getStrength()); + } + else if(tag.other.getType() == Symbol.Type.ERROR){ + removeMarkerEffects(tag.other, constraint.getStrength()); + } + } + + void removeMarkerEffects(Symbol marker, double strength){ + Row row = rows.get(marker); + if(row != null){ + objective.insert(row, -strength); + }else { + objective.insert(marker, -strength); + } + } + + Row getMarkerLeavingRow(Symbol marker){ + double dmax = Double.MAX_VALUE; + double r1 = dmax; + double r2 = dmax; + + Row first = null; + Row second = null; + Row third = null; + + for(Symbol s: rows.keySet()){ + Row candidateRow = rows.get(s); + double c = candidateRow.coefficientFor(marker); + if(c == 0.0){ + continue; + } + if(s.getType() == Symbol.Type.EXTERNAL){ + third = candidateRow; + } + else if(c < 0.0){ + double r = - candidateRow.getConstant() / c; + if(r < r1){ + r1 = r; + first = candidateRow; + } + } + else{ + double r = candidateRow.getConstant() / c; + if(r < r2){ + r2 = r; + second = candidateRow; + } + } + } + + if(first != null){ + return first; + } + if(second != null){ + return second; + } + return third; + } + + public boolean hasConstraint(Constraint constraint){ + return cns.containsKey(constraint); + } + + public void addEditVariable(Variable variable, double strength) throws DuplicateEditVariableException, RequiredFailureException{ + if(edits.containsKey(variable)){ + throw new DuplicateEditVariableException(); + } + + strength = Strength.clip(strength); + + if(strength == Strength.REQUIRED){ + throw new RequiredFailureException(); + } + + List terms = new ArrayList<>(); + terms.add(new Term(variable)); + Constraint constraint = new Constraint(new Expression(terms), RelationalOperator.OP_EQ, strength); + + try { + addConstraint(constraint); + } catch (DuplicateConstraintException e) { + e.printStackTrace(); + } catch (UnsatisfiableConstraintException e) { + e.printStackTrace(); + } + + + EditInfo info = new EditInfo(constraint, cns.get(constraint), 0.0); + edits.put(variable, info); + } + + public void removeEditVariable(Variable variable) throws UnknownEditVariableException{ + EditInfo edit = edits.get(variable); + if(edit == null){ + throw new UnknownEditVariableException(); + } + + try { + removeConstraint(edit.constraint); + } catch (UnknownConstraintException e) { + e.printStackTrace(); + } + + edits.remove(variable); + } + + public boolean hasEditVariable(Variable variable){ + return edits.containsKey(variable); + } + + public void suggestValue(Variable variable, double value) throws UnknownEditVariableException{ + EditInfo info = edits.get(variable); + if(info == null){ + throw new UnknownEditVariableException(); + } + + double delta = value - info.constant; + info.constant = value; + + Row row = rows.get(info.tag.marker); + if(row != null){ + if(row.add(-delta) < 0.0){ + infeasibleRows.add(info.tag.marker); + } + return; + } + + row = rows.get(info.tag.other); + if(row != null){ + if(row.add(delta) < 0.0){ + infeasibleRows.add(info.tag.other); + } + return; + } + + for(Symbol s: rows.keySet()){ + Row currentRow = rows.get(s); + double coefficient = currentRow.coefficientFor(info.tag.marker); + if(coefficient != 0.0 && currentRow.add(delta * coefficient) < 0.0 && s.getType() != Symbol.Type.EXTERNAL){ + infeasibleRows.add(s); + } + } + + dualOptimize(); } /** @@ -128,14 +305,11 @@ public class Solver { * The tag will be updated with the marker and error symbols to use * for tracking the movement of the constraint in the tableau. */ - RowAndTag createRow(Constraint constraint) { + Row createRow(Constraint constraint, Tag tag) { + Expression expression = constraint.getExpression(); + Row row = new Row(expression.getConstant()); - Expression expr = constraint.getExpression(); - Row row = new Row(expr.getConstant()); - Tag tag = new Tag(); - - // Substitute the current basic variables into the row. - for (Term term : expr.getTerms()) { + for (Term term : expression.getTerms()) { if (!Util.nearZero(term.getCoefficient())) { Symbol symbol = getVarSymbol(term.getVariable()); @@ -149,7 +323,6 @@ public class Solver { } } - // Add the necessary slack, error, and dummy variables. switch (constraint.getOp()) { case OP_LE: case OP_GE: { @@ -189,12 +362,7 @@ public class Solver { row.reverseSign(); } - RowAndTag rowAndTag = new RowAndTag(); - rowAndTag.row = row; - rowAndTag.tag = tag; - - - return rowAndTag; + return row; } /** @@ -237,9 +405,9 @@ public class Solver { // Create and add the artificial variable to the tableau Symbol art = new Symbol(Symbol.Type.SLACK, idTick++); - rows.put(art, new Row(row)); + rows.put(art, row.deepCopy()); - this.artificial = new Row(row); + this.artificial = row.deepCopy(); // Optimize the artificial objective. This is successful // only if the artificial objective is optimized to zero. @@ -253,10 +421,25 @@ public class Solver { Row rowptr = this.rows.get(art); if (rowptr != null) { - rows.remove(rowptr); + + /**this looks wrong!!!*/ + //rows.remove(rowptr); + + LinkedList deleteQueue = new LinkedList<>(); + for(Symbol s: rows.keySet()){ + if(rows.get(s) == rowptr){ + deleteQueue.add(s); + } + } + while(!deleteQueue.isEmpty()){ + rows.remove(deleteQueue.pop()); + } + deleteQueue.clear(); + if (rowptr.getCells().isEmpty()) { return success; } + Symbol entering = anyPivotableSymbol(rowptr); if (entering.getType() == Symbol.Type.INVALID) { return false; // unsatisfiable (will this ever happen?) @@ -312,23 +495,46 @@ public class Solver { return; } - Map.Entry entry = getLeavingRow(entering); + Row entry = getLeavingRow(entering); + if(entry == null){ + throw new InternalSolverError("The objective is unbounded."); + } + Symbol leaving = null; - if (entry == null) { - throw new InternalSolverError("The objective is unbounded."); + for(Symbol key: rows.keySet()){ + if(rows.get(key) == entry){ + leaving = key; + } } - // pivot the entering symbol into the basis - Symbol leaving = entry.getKey(); - Row row = entry.getValue(); + Symbol entryKey = null; + for(Symbol key: rows.keySet()){ + if(rows.get(key) == entry){ + entryKey = key; + } + } - this.rows.remove(entry.getKey()); + rows.remove(entryKey); + entry.solveFor(leaving, entering); + substitute(entering, entry); + rows.put(entering, entry); + } + } - row.solveFor(leaving, entering); - - substitute(entering, row); - - this.rows.put(entering, row); + void dualOptimize() throws InternalSolverError{ + while(!infeasibleRows.isEmpty()){ + Symbol leaving = infeasibleRows.remove(infeasibleRows.size() - 1); + Row row = rows.get(leaving); + if(row != null && row.getConstant() < 0.0){ + Symbol entering = getDualEnteringSymbol(row); + if(entering.getType() == Symbol.Type.INVALID){ + throw new InternalSolverError("internal solver error"); + } + rows.remove(entering); + row.solveFor(leaving, entering); + substitute(entering, row); + rows.put(entering, row); + } } } @@ -353,6 +559,26 @@ public class Solver { } + private Symbol getDualEnteringSymbol(Row row){ + Symbol entering = new Symbol(); + double ratio = Double.MAX_VALUE; + for(Symbol s: row.getCells().keySet()){ + if(s.getType() != Symbol.Type.DUMMY){ + double currentCell = row.getCells().get(s); + if(currentCell > 0.0){ + double coefficient = objective.coefficientFor(s); + double r = coefficient / currentCell; + if(r < ratio){ + ratio = r; + entering = s; + } + } + } + } + return entering; + } + + /** * Get the first Slack or Error symbol in the row. *

@@ -360,7 +586,7 @@ public class Solver { */ private Symbol anyPivotableSymbol(Row row) { Symbol symbol = null; - for (Map.Entry entry : objective.getCells().entrySet()) { + for (Map.Entry entry : row.getCells().entrySet()) { if (entry.getKey().getType() == Symbol.Type.SLACK || entry.getKey().getType() == Symbol.Type.ERROR) { symbol = entry.getKey(); } @@ -382,23 +608,24 @@ public class Solver { * found, the end() iterator will be returned. This indicates that * the objective function is unbounded. */ - private Map.Entry getLeavingRow(Symbol entering) { - // TODO check + private Row getLeavingRow(Symbol entering) { double ratio = Double.MAX_VALUE; - Map.Entry found = null; - for (Map.Entry row : rows.entrySet()) { - if (row.getKey().getType() != Symbol.Type.EXTERNAL) { - double temp = row.getValue().coefficientFor(entering); - if (temp < 0.0) { - double temp_ratio = -row.getValue().getConstant() / temp; - if (temp_ratio < ratio) { + Row row = null; + + for(Symbol key: rows.keySet()){ + if(key.getType() != Symbol.Type.EXTERNAL){ + Row candidateRow = rows.get(key); + double temp = candidateRow.coefficientFor(entering); + if(temp < 0){ + double temp_ratio = (-candidateRow.getConstant() / temp); + if(temp_ratio < ratio){ ratio = temp_ratio; - found = row; + row = candidateRow; } } } } - return found; + return row; } /** @@ -412,6 +639,7 @@ public class Solver { symbol = vars.get(variable); } else { symbol = new Symbol(Symbol.Type.EXTERNAL, idTick++); + symbol.setVariableName(variable.getName()); vars.put(variable, symbol); } return symbol; diff --git a/src/main/java/no/birkett/kiwi/Symbol.java b/src/main/java/no/birkett/kiwi/Symbol.java index 6347a9b..a851d8b 100644 --- a/src/main/java/no/birkett/kiwi/Symbol.java +++ b/src/main/java/no/birkett/kiwi/Symbol.java @@ -15,6 +15,7 @@ public class Symbol { private Type type; private long id; + private String variableName; public Symbol() { this(Type.INVALID, 0); @@ -41,6 +42,14 @@ public class Symbol { this.id = id; } + public String getVariableName() { + return variableName; + } + + public void setVariableName(String variableName) { + this.variableName = variableName; + } + boolean lessThan(Symbol other) { return this.id < other.getId(); } diff --git a/src/main/java/no/birkett/kiwi/Symbolics.java b/src/main/java/no/birkett/kiwi/Symbolics.java index ad61eb6..9674dbb 100644 --- a/src/main/java/no/birkett/kiwi/Symbolics.java +++ b/src/main/java/no/birkett/kiwi/Symbolics.java @@ -41,7 +41,7 @@ public class Symbolics { public static Expression multiply(Expression expression, double coefficient) { List terms = new ArrayList(); - + for (Term term : expression.getTerms()) { terms.add(multiply(term, coefficient)); } diff --git a/src/main/java/no/birkett/kiwi/UnknownConstraintException.java b/src/main/java/no/birkett/kiwi/UnknownConstraintException.java new file mode 100644 index 0000000..6f1384a --- /dev/null +++ b/src/main/java/no/birkett/kiwi/UnknownConstraintException.java @@ -0,0 +1,11 @@ +package no.birkett.kiwi; + +/** + * Created by yongsun on 1/13/16. + */ +public class UnknownConstraintException extends Exception { + + public UnknownConstraintException(Constraint constraint){ + + } +} diff --git a/src/main/java/no/birkett/kiwi/UnknownEditVariableException.java b/src/main/java/no/birkett/kiwi/UnknownEditVariableException.java new file mode 100644 index 0000000..e0abeee --- /dev/null +++ b/src/main/java/no/birkett/kiwi/UnknownEditVariableException.java @@ -0,0 +1,7 @@ +package no.birkett.kiwi; + +/** + * Created by yongsun on 1/13/16. + */ +public class UnknownEditVariableException extends Exception { +} diff --git a/src/main/java/no/birkett/kiwi/Variable.java b/src/main/java/no/birkett/kiwi/Variable.java index 9959000..71b4041 100644 --- a/src/main/java/no/birkett/kiwi/Variable.java +++ b/src/main/java/no/birkett/kiwi/Variable.java @@ -24,4 +24,7 @@ public class Variable { this.value = value; } + public String getName() { + return name; + } } diff --git a/src/test/java/no/birkett/kiwi/ConstraintParser.java b/src/test/java/no/birkett/kiwi/ConstraintParser.java index 349a224..b941d98 100644 --- a/src/test/java/no/birkett/kiwi/ConstraintParser.java +++ b/src/test/java/no/birkett/kiwi/ConstraintParser.java @@ -1,8 +1,6 @@ package no.birkett.kiwi; -import java.util.ArrayList; -import java.util.List; -import java.util.Stack; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -31,8 +29,7 @@ public class ConstraintParser { Expression expression = resolveExpression(matcher.group(3), variableResolver); double strength = parseStrength(matcher.group(4)); - return new Constraint(Symbolics.subtract(variable, expression), operator); - + return new Constraint(Symbolics.subtract(variable, expression), operator, strength); } else { throw new RuntimeException("could not parse " + constraintString); } diff --git a/src/test/java/no/birkett/kiwi/RealWorldTests.java b/src/test/java/no/birkett/kiwi/RealWorldTests.java index 82bb004..58d8d99 100644 --- a/src/test/java/no/birkett/kiwi/RealWorldTests.java +++ b/src/test/java/no/birkett/kiwi/RealWorldTests.java @@ -2,9 +2,7 @@ package no.birkett.kiwi; import org.junit.Test; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; +import java.util.*; import static org.junit.Assert.assertEquals; @@ -35,100 +33,121 @@ public class RealWorldTests { private static final String[] CONSTRAINTS = { - "container.columnWidth == container.width * 0.4", - "container.thumbHeight == container.columnWidth / 2", - "container.padding == container.width * (0.2 / 3)", +// "container.width == 500", +// "container.height == 500", +// "container.left == 0", +// "container.top == 0", +// +// "view.left == container.left + 100", +// "view.top == container.top + 100", +// "view.bottom == container.bottom - 100", +// "view.right == container.right - 100", +// +// "view2.left == view.left", +// "view2.right <= view.right", +// "view2.width == 250 !weak" + + "container.columnWidth == container.width * 0.4 + 30", + "container.thumbHeight == container.columnWidth / 2 - 25", + "container.padding == container.width * 0.1", + "container.leftPadding == container.padding", - "container.rightPadding == container.width - container.padding", + + "container.rightPadding == container.width * 1.5 - 30", + "container.paddingUnderThumb == 5", "container.rowPadding == 15", "container.buttonPadding == 20", + "container.width == 200 !medium" - "thumb0.left == container.leftPadding", - "thumb0.top == container.padding", - "thumb0.height == container.thumbHeight", - "thumb0.width == container.columnWidth", - "title0.left == container.leftPadding", - "title0.top == thumb0.bottom + container.paddingUnderThumb", - "title0.height == title0.intrinsicHeight", - "title0.width == container.columnWidth", - "thumb1.right == container.rightPadding", - "thumb1.top == container.padding", - "thumb1.height == container.thumbHeight", - "thumb1.width == container.columnWidth", - - "title1.right == container.rightPadding", - "title1.top == thumb0.bottom + container.paddingUnderThumb", - "title1.height == title1.intrinsicHeight", - "title1.width == container.columnWidth", - - "thumb2.left == container.leftPadding", - "thumb2.top >= title0.bottom + container.rowPadding", - "thumb2.top == title0.bottom + container.rowPadding !weak", - "thumb2.top >= title1.bottom + container.rowPadding", - "thumb2.top == title1.bottom + container.rowPadding !weak", - "thumb2.height == container.thumbHeight", - "thumb2.width == container.columnWidth", - - "title2.left == container.leftPadding", - "title2.top == thumb2.bottom + container.paddingUnderThumb", - "title2.height == title2.intrinsicHeight", - "title2.width == container.columnWidth", - - "thumb3.right == container.rightPadding", - "thumb3.top == thumb2.top", - - "thumb3.height == container.thumbHeight", - "thumb3.width == container.columnWidth", - - "title3.right == container.rightPadding", - "title3.top == thumb3.bottom + container.paddingUnderThumb", - "title3.height == title3.intrinsicHeight", - "title3.width == container.columnWidth", - - "thumb4.left == container.leftPadding", - "thumb4.top >= title2.bottom + container.rowPadding", - "thumb4.top >= title3.bottom + container.rowPadding", - "thumb4.top == title2.bottom + container.rowPadding !weak", - "thumb4.top == title3.bottom + container.rowPadding !weak", - "thumb4.height == container.thumbHeight", - "thumb4.width == container.columnWidth", - - "title4.left == container.leftPadding", - "title4.top == thumb4.bottom + container.paddingUnderThumb", - "title4.height == title4.intrinsicHeight", - "title4.width == container.columnWidth", - - "thumb5.right == container.rightPadding", - "thumb5.top == thumb4.top", - "thumb5.height == container.thumbHeight", - "thumb5.width == container.columnWidth", - - "title5.right == container.rightPadding", - "title5.top == thumb5.bottom + container.paddingUnderThumb", - "title5.height == title5.intrinsicHeight", - "title5.width == container.columnWidth", - - "line.height == 1", - "line.width == container.width", - "line.top >= title4.bottom + container.rowPadding", - "line.top >= title5.bottom + container.rowPadding", - - "more.top == line.bottom + container.buttonPadding", - "more.height == more.intrinsicHeight", - "more.left == container.leftPadding", - "more.right == container.rightPadding", - - "container.height == more.bottom + container.buttonPadding"}; +// "thumb0.left == container.leftPadding", +// "thumb0.top == container.padding", +// "thumb0.height == container.thumbHeight", +// "thumb0.width == container.columnWidth", +// +// "title0.left == container.leftPadding", +// "title0.top == thumb0.bottom + container.paddingUnderThumb", +// "title0.height == title0.intrinsicHeight", +// "title0.width == container.columnWidth", +// +// "thumb1.right == container.rightPadding", +// "thumb1.top == container.padding", +// "thumb1.height == container.thumbHeight", +// "thumb1.width == container.columnWidth", +// +// "title1.right == container.rightPadding", +// "title1.top == thumb0.bottom + container.paddingUnderThumb", +// "title1.height == title1.intrinsicHeight", +// "title1.width == container.columnWidth", +// +// "thumb2.left == container.leftPadding", +// "thumb2.top >= title0.bottom + container.rowPadding", +// "thumb2.top == title0.bottom + container.rowPadding !weak", +// "thumb2.top >= title1.bottom + container.rowPadding", +// "thumb2.top == title1.bottom + container.rowPadding !weak", +// "thumb2.height == container.thumbHeight", +// "thumb2.width == container.columnWidth", +// +// "title2.left == container.leftPadding", +// "title2.top == thumb2.bottom + container.paddingUnderThumb", +// "title2.height == title2.intrinsicHeight", +// "title2.width == container.columnWidth", +// +// "thumb3.right == container.rightPadding", +// "thumb3.top == thumb2.top", +// +// "thumb3.height == container.thumbHeight", +// "thumb3.width == container.columnWidth", +// +// "title3.right == container.rightPadding", +// "title3.top == thumb3.bottom + container.paddingUnderThumb", +// "title3.height == title3.intrinsicHeight", +// "title3.width == container.columnWidth", +// +// "thumb4.left == container.leftPadding", +// "thumb4.top >= title2.bottom + container.rowPadding", +// "thumb4.top >= title3.bottom + container.rowPadding", +// "thumb4.top == title2.bottom + container.rowPadding !weak", +// "thumb4.top == title3.bottom + container.rowPadding !weak", +// "thumb4.height == container.thumbHeight", +// "thumb4.width == container.columnWidth", +// +// "title4.left == container.leftPadding", +// "title4.top == thumb4.bottom + container.paddingUnderThumb", +// "title4.height == title4.intrinsicHeight", +// "title4.width == container.columnWidth", +// +// "thumb5.right == container.rightPadding", +// "thumb5.top == thumb4.top", +// "thumb5.height == container.thumbHeight", +// "thumb5.width == container.columnWidth", +// +// "title5.right == container.rightPadding", +// "title5.top == thumb5.bottom + container.paddingUnderThumb", +// "title5.height == title5.intrinsicHeight", +// "title5.width == container.columnWidth", +// +// "line.height == 1", +// "line.width == container.width", +// "line.top >= title4.bottom + container.rowPadding", +// "line.top >= title5.bottom + container.rowPadding", +// +// "more.top == line.bottom + container.buttonPadding", +// "more.height == more.intrinsicHeight", +// "more.left == container.leftPadding", +// "more.right == container.rightPadding", +// +// "container.height == more.bottom + container.buttonPadding" + }; public ConstraintParser.CassowaryVariableResolver createVariableResolver(final Solver solver, final HashMap> nodeHashMap) { ConstraintParser.CassowaryVariableResolver variableResolver = new ConstraintParser.CassowaryVariableResolver() { private Variable getVariableFromNode(HashMap node, String variableName) { - + try { if (node.containsKey(variableName)) { return node.get(variableName); @@ -147,9 +166,9 @@ public class RealWorldTests { return variable; } } catch(DuplicateConstraintException e) { - + e.printStackTrace(); } catch (UnsatisfiableConstraintException e) { - + e.printStackTrace(); } return null; @@ -196,48 +215,104 @@ public class RealWorldTests { return variableResolver; } + @Test + public void testSimple() { + int a = 20; + assertEquals(20, a); + } @Test public void testGridLayout() throws DuplicateConstraintException, UnsatisfiableConstraintException { final Solver solver = new Solver(); - - final HashMap> nodeHashMap = new HashMap>(); + final HashMap> nodeHashMap = new HashMap<>(); ConstraintParser.CassowaryVariableResolver variableResolver = createVariableResolver(solver, nodeHashMap); - for (String constraint : CONSTRAINTS) { - solver.addConstraint(ConstraintParser.parseConstraint(constraint, variableResolver)); - } - solver.addConstraint(ConstraintParser.parseConstraint("container.width == 300", variableResolver)); - solver.addConstraint(ConstraintParser.parseConstraint("title0.intrinsicHeight == 100", variableResolver)); - solver.addConstraint(ConstraintParser.parseConstraint("title1.intrinsicHeight == 110", variableResolver)); - solver.addConstraint(ConstraintParser.parseConstraint("title2.intrinsicHeight == 120", variableResolver)); - solver.addConstraint(ConstraintParser.parseConstraint("title3.intrinsicHeight == 130", variableResolver)); - solver.addConstraint(ConstraintParser.parseConstraint("title4.intrinsicHeight == 140", variableResolver)); - solver.addConstraint(ConstraintParser.parseConstraint("title5.intrinsicHeight == 150", variableResolver)); - solver.addConstraint(ConstraintParser.parseConstraint("more.intrinsicHeight == 160", variableResolver)); + List cache = new ArrayList<>(); + + for (String constraint : CONSTRAINTS) { + Constraint con = ConstraintParser.parseConstraint(constraint, variableResolver); + solver.addConstraint(con); + cache.add(con); + } solver.updateVariables(); - assertEquals(20, nodeHashMap.get("thumb0").get("top").getValue(), EPSILON); - assertEquals(20, nodeHashMap.get("thumb1").get("top").getValue(), EPSILON); +// assertEquals(300, nodeHashMap.get("container").get("width").getValue(), EPSILON); +// assertEquals(150, nodeHashMap.get("container").get("columnWidth").getValue(), EPSILON); +// assertEquals(50, nodeHashMap.get("container").get("thumbHeight").getValue(), EPSILON); +// assertEquals(30, nodeHashMap.get("container").get("padding").getValue(), EPSILON); +// assertEquals(30, nodeHashMap.get("container").get("leftPadding").getValue(), EPSILON); - assertEquals(85, nodeHashMap.get("title0").get("top").getValue(), EPSILON); - assertEquals(85, nodeHashMap.get("title1").get("top").getValue(), EPSILON); - assertEquals(210, nodeHashMap.get("thumb2").get("top").getValue(), EPSILON); - assertEquals(210, nodeHashMap.get("thumb3").get("top").getValue(), EPSILON); +// try { +// solver.removeConstraint(cache.get(cache.size() -1)); +// } catch (UnknownConstraintException e) { +// e.printStackTrace(); +// } +// solver.addConstraint(ConstraintParser.parseConstraint("container.width == 300", variableResolver, 8)); - assertEquals(275, nodeHashMap.get("title2").get("top").getValue(), EPSILON); - assertEquals(275, nodeHashMap.get("title3").get("top").getValue(), EPSILON); + Variable width = variableResolver.resolveVariable("container.width"); + try { + solver.addEditVariable(width, Strength.STRONG); + } catch (DuplicateEditVariableException e) { + e.printStackTrace(); + } catch (RequiredFailureException e) { + e.printStackTrace(); + } - assertEquals(420, nodeHashMap.get("thumb4").get("top").getValue(), EPSILON); - assertEquals(420, nodeHashMap.get("thumb5").get("top").getValue(), EPSILON); + try { + solver.suggestValue(width, 300); + } catch (UnknownEditVariableException e) { + e.printStackTrace(); + } - assertEquals(485, nodeHashMap.get("title4").get("top").getValue(), EPSILON); - assertEquals(485, nodeHashMap.get("title5").get("top").getValue(), EPSILON); + solver.updateVariables(); + + assertEquals(300, nodeHashMap.get("container").get("width").getValue(), EPSILON); + + try { + solver.suggestValue(width, 200); + } catch (UnknownEditVariableException e) { + e.printStackTrace(); + } + solver.updateVariables(); + assertEquals(200, nodeHashMap.get("container").get("width").getValue(), EPSILON); + +// assertEquals(150, nodeHashMap.get("container").get("columnWidth").getValue(), EPSILON); +// assertEquals(50, nodeHashMap.get("container").get("thumbHeight").getValue(), EPSILON); +// assertEquals(30, nodeHashMap.get("container").get("padding").getValue(), EPSILON); +// assertEquals(30, nodeHashMap.get("container").get("leftPadding").getValue(), EPSILON); + +// assertEquals(280, nodeHashMap.get("container").get("test").getValue(), EPSILON); + +// assertEquals(270, nodeHashMap.get("container").get("width").getValue() - nodeHashMap.get("container").get("padding").getValue(), EPSILON); +// assertEquals(270, nodeHashMap.get("container").get("rightPadding").getValue(), EPSILON); + + +// assertEquals(100, nodeHashMap.get("view").get("top").getValue(), EPSILON); +// assertEquals(400, nodeHashMap.get("view").get("bottom").getValue(), EPSILON); +// assertEquals(350, nodeHashMap.get("view2").get("right").getValue(), EPSILON); + +// assertEquals(20, nodeHashMap.get("thumb0").get("top").getValue(), EPSILON); +// assertEquals(20, nodeHashMap.get("thumb1").get("top").getValue(), EPSILON); +// +// assertEquals(85, nodeHashMap.get("title0").get("top").getValue(), EPSILON); +// assertEquals(85, nodeHashMap.get("title1").get("top").getValue(), EPSILON); +// +// assertEquals(210, nodeHashMap.get("thumb2").get("top").getValue(), EPSILON); +// assertEquals(210, nodeHashMap.get("thumb3").get("top").getValue(), EPSILON); +// +// assertEquals(275, nodeHashMap.get("title2").get("top").getValue(), EPSILON); +// assertEquals(275, nodeHashMap.get("title3").get("top").getValue(), EPSILON); +// +// assertEquals(420, nodeHashMap.get("thumb4").get("top").getValue(), EPSILON); +// assertEquals(420, nodeHashMap.get("thumb5").get("top").getValue(), EPSILON); +// +// assertEquals(485, nodeHashMap.get("title4").get("top").getValue(), EPSILON); +// assertEquals(485, nodeHashMap.get("title5").get("top").getValue(), EPSILON); }