Hixie 6795efacab Enable always_specify_types lint
And fix the zillion issues that uncovered.
2016-03-12 00:37:31 -08:00

654 lines
17 KiB
Dart

// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of cassowary;
class Solver {
final Map<Constraint, _Tag> _constraints = new Map<Constraint, _Tag>();
final Map<_Symbol, _Row> _rows = new Map<_Symbol, _Row>();
final Map<Variable, _Symbol> _vars = new Map<Variable, _Symbol>();
final Map<Variable, _EditInfo> _edits = new Map<Variable, _EditInfo>();
final List<_Symbol> _infeasibleRows = new List<_Symbol>();
final _Row _objective = new _Row(0.0);
_Row _artificial = new _Row(0.0);
int tick = 1;
/// Attempts to add the constraints in the list to the solver. If it cannot
/// add any for some reason, a cleanup is attempted so that either all
/// constraints will be added or none.
Result addConstraints(List<Constraint> constraints) {
_SolverBulkUpdate applier = (Constraint c) => addConstraint(c);
_SolverBulkUpdate undoer = (Constraint c) => removeConstraint(c);
return _bulkEdit(constraints, applier, undoer);
}
Result addConstraint(Constraint constraint) {
if (_constraints.containsKey(constraint)) {
return Result.duplicateConstraint;
}
_Tag tag = new _Tag(new _Symbol(_SymbolType.invalid, 0),
new _Symbol(_SymbolType.invalid, 0));
_Row row = _createRow(constraint, tag);
_Symbol subject = _chooseSubjectForRow(row, tag);
if (subject.type == _SymbolType.invalid && _allDummiesInRow(row)) {
if (!_nearZero(row.constant)) {
return Result.unsatisfiableConstraint;
} else {
subject = tag.marker;
}
}
if (subject.type == _SymbolType.invalid) {
if (!_addWithArtificialVariableOnRow(row)) {
return Result.unsatisfiableConstraint;
}
} else {
row.solveForSymbol(subject);
_substitute(subject, row);
_rows[subject] = row;
}
_constraints[constraint] = tag;
return _optimizeObjectiveRow(_objective);
}
Result removeConstraints(List<Constraint> constraints) {
_SolverBulkUpdate applier = (Constraint c) => removeConstraint(c);
_SolverBulkUpdate undoer = (Constraint c) => addConstraint(c);
return _bulkEdit(constraints, applier, undoer);
}
Result removeConstraint(Constraint constraint) {
_Tag tag = _constraints[constraint];
if (tag == null) {
return Result.unknownConstraint;
}
tag = new _Tag.fromTag(tag);
_constraints.remove(constraint);
_removeConstraintEffects(constraint, tag);
_Row row = _rows[tag.marker];
if (row != null) {
_rows.remove(tag.marker);
} else {
_Pair<_Symbol, _Row> rowPair = _leavingRowPairForMarkerSymbol(tag.marker);
if (rowPair == null) {
return Result.internalSolverError;
}
_Symbol leaving = rowPair.first;
row = rowPair.second;
_Row removed = _rows.remove(rowPair.first);
assert(removed != null);
row.solveForSymbols(leaving, tag.marker);
_substitute(tag.marker, row);
}
return _optimizeObjectiveRow(_objective);
}
bool hasConstraint(Constraint constraint) {
return _constraints.containsKey(constraint);
}
Result addEditVariables(List<Variable> variables, double priority) {
_SolverBulkUpdate applier = (Variable v) => addEditVariable(v, priority);
_SolverBulkUpdate undoer = (Variable v) => removeEditVariable(v);
return _bulkEdit(variables, applier, undoer);
}
Result addEditVariable(Variable variable, double priority) {
if (_edits.containsKey(variable)) {
return Result.duplicateEditVariable;
}
if (!_isValidNonRequiredPriority(priority)) {
return Result.badRequiredStrength;
}
Constraint constraint = new Constraint(
new Expression([new Term(variable, 1.0)], 0.0), Relation.equalTo);
constraint.priority = priority;
if (addConstraint(constraint) != Result.success) {
return Result.internalSolverError;
}
_EditInfo info = new _EditInfo();
info.tag = _constraints[constraint];
info.constraint = constraint;
info.constant = 0.0;
_edits[variable] = info;
return Result.success;
}
Result removeEditVariables(List<Variable> variables) {
_SolverBulkUpdate applier = (Variable v) => removeEditVariable(v);
_SolverBulkUpdate undoer = (Variable v) =>
addEditVariable(v, _edits[v].constraint.priority);
return _bulkEdit(variables, applier, undoer);
}
Result removeEditVariable(Variable variable) {
_EditInfo info = _edits[variable];
if (info == null)
return Result.unknownEditVariable;
if (removeConstraint(info.constraint) != Result.success)
return Result.internalSolverError;
_edits.remove(variable);
return Result.success;
}
bool hasEditVariable(Variable variable) {
return _edits.containsKey(variable);
}
Result suggestValueForVariable(Variable variable, double value) {
if (!_edits.containsKey(variable)) {
return Result.unknownEditVariable;
}
_suggestValueForEditInfoWithoutDualOptimization(_edits[variable], value);
return _dualOptimize();
}
Set<dynamic> flushUpdates() {
Set<dynamic> updates = new HashSet<dynamic>();
for (Variable variable in _vars.keys) {
_Symbol symbol = _vars[variable];
_Row row = _rows[symbol];
double updatedValue = row == null ? 0.0 : row.constant;
if (variable._applyUpdate(updatedValue) && variable._owner != null) {
dynamic context = variable._owner.context;
if (context != null)
updates.add(context);
}
}
return updates;
}
Result _bulkEdit(
Iterable<dynamic> items,
_SolverBulkUpdate applier,
_SolverBulkUpdate undoer
) {
List<dynamic> applied = <dynamic>[];
bool needsCleanup = false;
Result result = Result.success;
for (dynamic item in items) {
result = applier(item);
if (result == Result.success) {
applied.add(item);
} else {
needsCleanup = true;
break;
}
}
if (needsCleanup) {
for (dynamic item in applied.reversed)
undoer(item);
}
return result;
}
_Symbol _symbolForVariable(Variable variable) {
_Symbol symbol = _vars[variable];
if (symbol != null)
return symbol;
symbol = new _Symbol(_SymbolType.external, tick++);
_vars[variable] = symbol;
return symbol;
}
_Row _createRow(Constraint constraint, _Tag tag) {
Expression expr = new Expression.fromExpression(constraint.expression);
_Row row = new _Row(expr.constant);
expr.terms.forEach((Term term) {
if (!_nearZero(term.coefficient)) {
_Symbol symbol = _symbolForVariable(term.variable);
_Row foundRow = _rows[symbol];
if (foundRow != null) {
row.insertRow(foundRow, term.coefficient);
} else {
row.insertSymbol(symbol, term.coefficient);
}
}
});
switch (constraint.relation) {
case Relation.lessThanOrEqualTo:
case Relation.greaterThanOrEqualTo:
{
double coefficient =
constraint.relation == Relation.lessThanOrEqualTo ? 1.0 : -1.0;
_Symbol slack = new _Symbol(_SymbolType.slack, tick++);
tag.marker = slack;
row.insertSymbol(slack, coefficient);
if (constraint.priority < Priority.required) {
_Symbol error = new _Symbol(_SymbolType.error, tick++);
tag.other = error;
row.insertSymbol(error, -coefficient);
_objective.insertSymbol(error, constraint.priority);
}
}
break;
case Relation.equalTo:
if (constraint.priority < Priority.required) {
_Symbol errPlus = new _Symbol(_SymbolType.error, tick++);
_Symbol errMinus = new _Symbol(_SymbolType.error, tick++);
tag.marker = errPlus;
tag.other = errMinus;
row.insertSymbol(errPlus, -1.0);
row.insertSymbol(errMinus, 1.0);
_objective.insertSymbol(errPlus, constraint.priority);
_objective.insertSymbol(errMinus, constraint.priority);
} else {
_Symbol dummy = new _Symbol(_SymbolType.dummy, tick++);
tag.marker = dummy;
row.insertSymbol(dummy);
}
break;
}
if (row.constant < 0.0) {
row.reverseSign();
}
return row;
}
_Symbol _chooseSubjectForRow(_Row row, _Tag tag) {
for (_Symbol symbol in row.cells.keys) {
if (symbol.type == _SymbolType.external) {
return symbol;
}
}
if (tag.marker.type == _SymbolType.slack ||
tag.marker.type == _SymbolType.error) {
if (row.coefficientForSymbol(tag.marker) < 0.0) {
return tag.marker;
}
}
if (tag.other.type == _SymbolType.slack ||
tag.other.type == _SymbolType.error) {
if (row.coefficientForSymbol(tag.other) < 0.0) {
return tag.other;
}
}
return new _Symbol(_SymbolType.invalid, 0);
}
bool _allDummiesInRow(_Row row) {
for (_Symbol symbol in row.cells.keys) {
if (symbol.type != _SymbolType.dummy) {
return false;
}
}
return true;
}
bool _addWithArtificialVariableOnRow(_Row row) {
_Symbol artificial = new _Symbol(_SymbolType.slack, tick++);
_rows[artificial] = new _Row.fromRow(row);
_artificial = new _Row.fromRow(row);
Result result = _optimizeObjectiveRow(_artificial);
if (result.error) {
// FIXME(csg): Propagate this up!
return false;
}
bool success = _nearZero(_artificial.constant);
_artificial = new _Row(0.0);
_Row foundRow = _rows[artificial];
if (foundRow != null) {
_rows.remove(artificial);
if (foundRow.cells.isEmpty) {
return success;
}
_Symbol entering = _anyPivotableSymbol(foundRow);
if (entering.type == _SymbolType.invalid) {
return false;
}
foundRow.solveForSymbols(artificial, entering);
_substitute(entering, foundRow);
_rows[entering] = foundRow;
}
for (_Row row in _rows.values) {
row.removeSymbol(artificial);
}
_objective.removeSymbol(artificial);
return success;
}
Result _optimizeObjectiveRow(_Row objective) {
while (true) {
_Symbol entering = _enteringSymbolForObjectiveRow(objective);
if (entering.type == _SymbolType.invalid) {
return Result.success;
}
_Pair<_Symbol, _Row> leavingPair = _leavingRowForEnteringSymbol(entering);
if (leavingPair == null) {
return Result.internalSolverError;
}
_Symbol leaving = leavingPair.first;
_Row row = leavingPair.second;
_rows.remove(leavingPair.first);
row.solveForSymbols(leaving, entering);
_substitute(entering, row);
_rows[entering] = row;
}
}
_Symbol _enteringSymbolForObjectiveRow(_Row objective) {
Map<_Symbol, double> cells = objective.cells;
for (_Symbol symbol in cells.keys) {
if (symbol.type != _SymbolType.dummy && cells[symbol] < 0.0) {
return symbol;
}
}
return new _Symbol(_SymbolType.invalid, 0);
}
_Pair<_Symbol, _Row> _leavingRowForEnteringSymbol(_Symbol entering) {
double ratio = double.MAX_FINITE;
_Pair<_Symbol, _Row> result = new _Pair<_Symbol, _Row>(null, null);
_rows.forEach((_Symbol symbol, _Row row) {
if (symbol.type != _SymbolType.external) {
double temp = row.coefficientForSymbol(entering);
if (temp < 0.0) {
double tempRatio = -row.constant / temp;
if (tempRatio < ratio) {
ratio = tempRatio;
result.first = symbol;
result.second = row;
}
}
}
});
if (result.first == null || result.second == null) {
return null;
}
return result;
}
void _substitute(_Symbol symbol, _Row row) {
_rows.forEach((_Symbol first, _Row second) {
second.substitute(symbol, row);
if (first.type != _SymbolType.external && second.constant < 0.0) {
_infeasibleRows.add(first);
}
});
_objective.substitute(symbol, row);
if (_artificial != null) {
_artificial.substitute(symbol, row);
}
}
_Symbol _anyPivotableSymbol(_Row row) {
for (_Symbol symbol in row.cells.keys) {
if (symbol.type == _SymbolType.slack ||
symbol.type == _SymbolType.error) {
return symbol;
}
}
return new _Symbol(_SymbolType.invalid, 0);
}
void _removeConstraintEffects(Constraint cn, _Tag tag) {
if (tag.marker.type == _SymbolType.error) {
_removeMarkerEffects(tag.marker, cn.priority);
}
if (tag.other.type == _SymbolType.error) {
_removeMarkerEffects(tag.other, cn.priority);
}
}
void _removeMarkerEffects(_Symbol marker, double strength) {
_Row row = _rows[marker];
if (row != null) {
_objective.insertRow(row, -strength);
} else {
_objective.insertSymbol(marker, -strength);
}
}
_Pair<_Symbol, _Row> _leavingRowPairForMarkerSymbol(_Symbol marker) {
double r1 = double.MAX_FINITE;
double r2 = double.MAX_FINITE;
_Pair<_Symbol, _Row> first, second, third;
_rows.forEach((_Symbol symbol, _Row row) {
double c = row.coefficientForSymbol(marker);
if (c == 0.0) {
return;
}
if (symbol.type == _SymbolType.external) {
third = new _Pair<_Symbol, _Row>(symbol, row);
} else if (c < 0.0) {
double r = -row.constant / c;
if (r < r1) {
r1 = r;
first = new _Pair<_Symbol, _Row>(symbol, row);
}
} else {
double r = row.constant / c;
if (r < r2) {
r2 = r;
second = new _Pair<_Symbol, _Row>(symbol, row);
}
}
});
if (first != null) {
return first;
}
if (second != null) {
return second;
}
return third;
}
void _suggestValueForEditInfoWithoutDualOptimization(
_EditInfo info, double value) {
double delta = value - info.constant;
info.constant = value;
{
_Symbol symbol = info.tag.marker;
_Row row = _rows[info.tag.marker];
if (row != null) {
if (row.add(-delta) < 0.0) {
_infeasibleRows.add(symbol);
}
return;
}
symbol = info.tag.other;
row = _rows[info.tag.other];
if (row != null) {
if (row.add(delta) < 0.0) {
_infeasibleRows.add(symbol);
}
return;
}
}
for (_Symbol symbol in _rows.keys) {
_Row row = _rows[symbol];
double coeff = row.coefficientForSymbol(info.tag.marker);
if (coeff != 0.0 &&
row.add(delta * coeff) < 0.0 &&
symbol.type != _SymbolType.external) {
_infeasibleRows.add(symbol);
}
}
}
Result _dualOptimize() {
while (_infeasibleRows.length != 0) {
_Symbol leaving = _infeasibleRows.removeLast();
_Row row = _rows[leaving];
if (row != null && row.constant < 0.0) {
_Symbol entering = _dualEnteringSymbolForRow(row);
if (entering.type == _SymbolType.invalid) {
return Result.internalSolverError;
}
_rows.remove(leaving);
row.solveForSymbols(leaving, entering);
_substitute(entering, row);
_rows[entering] = row;
}
}
return Result.success;
}
_Symbol _dualEnteringSymbolForRow(_Row row) {
_Symbol entering;
double ratio = double.MAX_FINITE;
Map<_Symbol, double> rowCells = row.cells;
for (_Symbol symbol in rowCells.keys) {
double value = rowCells[symbol];
if (value > 0.0 && symbol.type != _SymbolType.dummy) {
double coeff = _objective.coefficientForSymbol(symbol);
double r = coeff / value;
if (r < ratio) {
ratio = r;
entering = symbol;
}
}
}
return entering ?? new _Symbol(_SymbolType.invalid, 0);
}
String toString() {
StringBuffer buffer = new StringBuffer();
String separator = "\n~~~~~~~~~";
// Objective
buffer.writeln(separator + " Objective");
buffer.writeln(_objective.toString());
// Tableau
buffer.writeln(separator + " Tableau");
_rows.forEach((_Symbol symbol, _Row row) {
buffer.writeln('$symbol | $row');
});
// Infeasible
buffer.writeln(separator + " Infeasible");
_infeasibleRows.forEach((_Symbol symbol) {
buffer.writeln(symbol);
});
// Variables
buffer.writeln(separator + " Variables");
_vars.forEach((Variable variable, _Symbol symbol) {
buffer.writeln('$variable = $symbol');
});
// Edit Variables
buffer.writeln(separator + " Edit Variables");
_edits.forEach((Variable variable, _EditInfo editinfo) {
buffer.writeln(variable);
});
// Constraints
buffer.writeln(separator + " Constraints");
_constraints.forEach((Constraint constraint, _Tag tag) {
buffer.writeln(constraint);
});
return buffer.toString();
}
}
class _Tag {
_Symbol marker;
_Symbol other;
_Tag(this.marker, this.other);
_Tag.fromTag(_Tag tag)
: this.marker = tag.marker,
this.other = tag.other;
}
class _EditInfo {
_Tag tag;
Constraint constraint;
double constant;
}
bool _isValidNonRequiredPriority(double priority) {
return (priority >= 0.0 && priority < Priority.required);
}
typedef Result _SolverBulkUpdate(dynamic item);