blob: 45eb62b904a931bbcac554f81191ebc05bc78b45 [file]
// 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 = 0;
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 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 =
_getLeavingRowPairForMarkerSymbol(tag.marker);
if (rowPair == null) {
return Result.internalSolverError;
}
Symbol leaving = rowPair.first;
row = rowPair.second;
var 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 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);
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 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();
}
void updateVariable() {}
Solver operator <<(Constraint c) => this..addConstraint(c);
Symbol _getSymbolForVariable(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) {
if (!_nearZero(term.coefficient)) {
Symbol symbol = _getSymbolForVariable(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.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.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 = _getEnteringSymbolForObjectiveRow(objective);
if (entering.type == SymbolType.invalid) {
return Result.success;
}
_Pair<Symbol, Row> leavingPair =
_getLeavingRowForEnteringSymbol(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 _getEnteringSymbolForObjectiveRow(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> _getLeavingRowForEnteringSymbol(Symbol entering) {
double ratio = double.MAX_FINITE;
_Pair<Symbol, Row> result = new _Pair(null, null);
_rows.forEach((symbol, row) {
if (symbol.type != SymbolType.external) {
double temp = row.coefficientForSymbol(entering);
if (temp < 0.0) {
double temp_ratio = -row.constant / temp;
if (temp_ratio < ratio) {
ratio = temp_ratio;
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((first, 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> _getLeavingRowPairForMarkerSymbol(Symbol marker) {
double r1 = double.MAX_FINITE;
double r2 = double.MAX_FINITE;
_Pair<Symbol, Row> first, second, third;
_rows.forEach((symbol, row) {
double c = row.coefficientForSymbol(marker);
if (c == 0.0) {
return;
}
if (symbol.type == SymbolType.external) {
third = new _Pair(symbol, row);
} else if (c < 0.0) {
double r = -row.constant / c;
if (r < r1) {
r1 = r;
first = new _Pair(symbol, row);
}
} else {
double r = row.constant / c;
if (r < r2) {
r2 = r;
second = new _Pair(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 = _getDualEnteringSymbolForRow(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 _getDualEnteringSymbolForRow(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 _elvis(entering, new Symbol(SymbolType.invalid, 0));
}
}
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 < Constraint.requiredPriority);
}