Commit 552896af authored by Ian Hickson's avatar Ian Hickson

Refactor cassowary so it uses imports rather than parts.

Also misc cleanup:
 - reorder members to be more consistent and fit the style guide
 - remove use of _Pair
 - made Variable.applyUpdate and Variable.owner public
 - added docs to Priority, tweaked the code a bit
 - added some docs to Result
 - removed the internal-error Result (replaced with asserts)
 - removed unused Results
 - made Result const
 - merged some files together since they had used privates a lot

I'm sorry this is completely unreviewable. I did the move from `lib/*`
to `lib/src/*` first, then did the `part`-to-`import` change, and then
found out how many of the files involved privates, which I wasn't
expecting. I can redo this as multiple commits if that would make it
easier to review.
parent 366385b3
...@@ -27,8 +27,7 @@ class _MyAutoLayoutDelegate extends AutoLayoutDelegate { ...@@ -27,8 +27,7 @@ class _MyAutoLayoutDelegate extends AutoLayoutDelegate {
// The widths of the first and the third boxes should be equal // The widths of the first and the third boxes should be equal
p1.width.equals(p3.width), p1.width.equals(p3.width),
// The width of the second box should be twice as much as that of the first // The width of the first box should be twice as much as that of the second
// and third
p1.width.equals(p2.width * al.cm(2.0)), p1.width.equals(p2.width * al.cm(2.0)),
// The height of the three boxes should be equal to that of the container // The height of the three boxes should be equal to that of the container
......
...@@ -26,8 +26,7 @@ class _MyAutoLayoutDelegate extends AutoLayoutDelegate { ...@@ -26,8 +26,7 @@ class _MyAutoLayoutDelegate extends AutoLayoutDelegate {
// The widths of the first and the third boxes should be equal // The widths of the first and the third boxes should be equal
p1.width.equals(p3.width), p1.width.equals(p3.width),
// The width of the second box should be twice as much as that of the first // The width of the first box should be twice as much as that of the second
// and third
p1.width.equals(p2.width * al.cm(2.0)), p1.width.equals(p2.width * al.cm(2.0)),
// The height of the three boxes should be equal to that of the container // The height of the three boxes should be equal to that of the container
......
...@@ -3,22 +3,23 @@ ...@@ -3,22 +3,23 @@
// found in the LICENSE file. // found in the LICENSE file.
/// An implementation of the Cassowary constraint solving algorithm in Dart. /// An implementation of the Cassowary constraint solving algorithm in Dart.
///
/// This is used by the [RenderAutoLayout] render object in the rendering
/// library and by the [AutoLayout] widget in the widget library.
///
/// See also:
///
/// * <https://en.wikipedia.org/wiki/Cassowary_(software)>
/// * <https://constraints.cs.washington.edu/solvers/cassowary-tochi.pdf>
library cassowary; library cassowary;
import 'dart:collection'; export 'src/constraint.dart';
import 'dart:math'; export 'src/expression.dart';
export 'src/term.dart';
part 'constraint.dart'; export 'src/equation_member.dart';
part 'expression.dart'; export 'src/constant_member.dart';
part 'term.dart'; export 'src/solver.dart';
part 'variable.dart'; export 'src/result.dart';
part 'equation_member.dart'; export 'src/parser_exception.dart';
part 'constant_member.dart'; export 'src/param.dart';
part 'solver.dart'; export 'src/priority.dart';
part 'symbol.dart';
part 'row.dart';
part 'utils.dart';
part 'result.dart';
part 'parser_exception.dart';
part 'param.dart';
part 'priority.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;
abstract class _EquationMember {
Expression asExpression();
bool get isConstant;
double get value;
Constraint operator >=(_EquationMember m) => asExpression() >= m;
Constraint operator <=(_EquationMember m) => asExpression() <= m;
Constraint equals(_EquationMember m) => asExpression().equals(m);
Expression operator +(_EquationMember m) => asExpression() + m;
Expression operator -(_EquationMember m) => asExpression() - m;
Expression operator *(_EquationMember m) => asExpression() * m;
Expression operator /(_EquationMember m) => asExpression() / m;
}
// 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 Result {
final String message;
final bool error;
const Result(this.message, { bool isError: true }) : error = isError;
static final Result success = const Result("Success", isError: false);
static final Result unimplemented = const Result("Unimplemented");
static final Result duplicateConstraint =
const Result("Duplicate Constraint");
static final Result unsatisfiableConstraint =
const Result("Unsatisfiable Constraint");
static final Result unknownConstraint =
const Result("Unknown Constraint");
static final Result duplicateEditVariable =
const Result("Duplicate Edit Variable");
static final Result badRequiredStrength =
const Result("Bad Required Strength");
static final Result unknownEditVariable =
const Result("Unknown Edit Variable");
static final Result internalSolverError =
const Result("Internal Solver Error");
}
// 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 _Row {
final Map<_Symbol, double> cells;
double constant = 0.0;
_Row(this.constant) : this.cells = new Map<_Symbol, double>();
_Row.fromRow(_Row row)
: this.cells = new Map<_Symbol, double>.from(row.cells),
this.constant = row.constant;
double add(double value) => constant += value;
void insertSymbol(_Symbol symbol, [double coefficient = 1.0]) {
double val = cells[symbol] ?? 0.0;
if (_nearZero(val + coefficient)) {
cells.remove(symbol);
} else {
cells[symbol] = val + coefficient;
}
}
void insertRow(_Row other, [double coefficient = 1.0]) {
constant += other.constant * coefficient;
other.cells.forEach((_Symbol s, double v) => insertSymbol(s, v * coefficient));
}
void removeSymbol(_Symbol symbol) {
cells.remove(symbol);
}
void reverseSign() {
constant = -constant;
cells.forEach((_Symbol s, double v) => cells[s] = -v);
}
void solveForSymbol(_Symbol symbol) {
assert(cells.containsKey(symbol));
double coefficient = -1.0 / cells[symbol];
cells.remove(symbol);
constant *= coefficient;
cells.forEach((_Symbol s, double v) => cells[s] = v * coefficient);
}
void solveForSymbols(_Symbol lhs, _Symbol rhs) {
insertSymbol(lhs, -1.0);
solveForSymbol(rhs);
}
double coefficientForSymbol(_Symbol symbol) => cells[symbol] ?? 0.0;
void substitute(_Symbol symbol, _Row row) {
double coefficient = cells[symbol];
if (coefficient == null) {
return;
}
cells.remove(symbol);
insertRow(row, coefficient);
}
@override
String toString() {
StringBuffer buffer = new StringBuffer();
buffer.write(constant);
cells.forEach((_Symbol symbol, double value) {
buffer.write(" + " + value.toString() + " * " + symbol.toString());
});
return buffer.toString();
}
}
...@@ -2,21 +2,27 @@ ...@@ -2,21 +2,27 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
part of cassowary; import 'expression.dart';
import 'equation_member.dart';
class ConstantMember extends _EquationMember { class ConstantMember extends EquationMember {
/// Creates a [ConstantMember] object.
///
/// The [cm] convenience method may be a more convenient way to create a
/// [ConstantMember] object.
ConstantMember(this.value); ConstantMember(this.value);
@override @override
final double value; Expression asExpression() => new Expression([], this.value);
@override @override
bool get isConstant => true; final double value;
@override @override
Expression asExpression() => new Expression([], this.value); bool get isConstant => true;
} }
ConstantMember cm(double value) { /// Creates a [ConstantMember].
return new ConstantMember(value); ///
} /// This is a convenience method to make cassowary expressions less verbose.
ConstantMember cm(double value) => new ConstantMember(value);
...@@ -2,16 +2,23 @@ ...@@ -2,16 +2,23 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
part of cassowary; import 'priority.dart';
import 'expression.dart';
enum Relation { equalTo, lessThanOrEqualTo, greaterThanOrEqualTo, } enum Relation {
equalTo,
lessThanOrEqualTo,
greaterThanOrEqualTo,
}
class Constraint { class Constraint {
Constraint(this.expression, this.relation);
final Relation relation; final Relation relation;
final Expression expression; final Expression expression;
double priority = Priority.required;
Constraint(this.expression, this.relation); double priority = Priority.required;
Constraint operator |(double p) => this..priority = p; Constraint operator |(double p) => this..priority = p;
...@@ -34,9 +41,8 @@ class Constraint { ...@@ -34,9 +41,8 @@ class Constraint {
buffer.write(' | priority = $priority'); buffer.write(' | priority = $priority');
if (priority == Priority.required) { if (priority == Priority.required)
buffer.write(' (required)'); buffer.write(' (required)');
}
return buffer.toString(); return buffer.toString();
} }
......
// 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.
import 'expression.dart';
import 'constraint.dart';
/// Base class for the various parts of cassowary equations.
abstract class EquationMember {
Expression asExpression();
bool get isConstant;
double get value;
Constraint operator >=(EquationMember m) => asExpression() >= m;
Constraint operator <=(EquationMember m) => asExpression() <= m;
Constraint equals(EquationMember m) => asExpression().equals(m);
Expression operator +(EquationMember m) => asExpression() + m;
Expression operator -(EquationMember m) => asExpression() - m;
Expression operator *(EquationMember m) => asExpression() * m;
Expression operator /(EquationMember m) => asExpression() / m;
}
...@@ -2,10 +2,22 @@ ...@@ -2,10 +2,22 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
part of cassowary; import 'constant_member.dart';
import 'constraint.dart';
import 'equation_member.dart';
import 'param.dart';
import 'parser_exception.dart';
import 'term.dart';
class _Multiplication {
const _Multiplication(this.multiplier, this.multiplicand);
final Expression multiplier;
final double multiplicand;
}
class Expression extends _EquationMember { class Expression extends EquationMember {
Expression(this.terms, this.constant); Expression(this.terms, this.constant);
Expression.fromExpression(Expression expr) Expression.fromExpression(Expression expr)
: this.terms = new List<Term>.from(expr.terms), : this.terms = new List<Term>.from(expr.terms),
this.constant = expr.constant; this.constant = expr.constant;
...@@ -14,6 +26,9 @@ class Expression extends _EquationMember { ...@@ -14,6 +26,9 @@ class Expression extends _EquationMember {
final double constant; final double constant;
@override
Expression asExpression() => this;
@override @override
bool get isConstant => terms.length == 0; bool get isConstant => terms.length == 0;
...@@ -21,9 +36,21 @@ class Expression extends _EquationMember { ...@@ -21,9 +36,21 @@ class Expression extends _EquationMember {
double get value => terms.fold(constant, (double value, Term term) => value + term.value); double get value => terms.fold(constant, (double value, Term term) => value + term.value);
@override @override
Expression asExpression() => this; Constraint operator >=(EquationMember value) {
return _createConstraint(value, Relation.greaterThanOrEqualTo);
}
@override
Constraint operator <=(EquationMember value) {
return _createConstraint(value, Relation.lessThanOrEqualTo);
}
Constraint _createConstraint(_EquationMember /* rhs */ value, Relation relation) { @override
Constraint equals(EquationMember value) {
return _createConstraint(value, Relation.equalTo);
}
Constraint _createConstraint(EquationMember /* rhs */ value, Relation relation) {
if (value is ConstantMember) { if (value is ConstantMember) {
return new Constraint( return new Constraint(
new Expression(new List<Term>.from(terms), constant - value.value), new Expression(new List<Term>.from(terms), constant - value.value),
...@@ -60,22 +87,7 @@ class Expression extends _EquationMember { ...@@ -60,22 +87,7 @@ class Expression extends _EquationMember {
} }
@override @override
Constraint operator >=(_EquationMember value) { Expression operator +(EquationMember m) {
return _createConstraint(value, Relation.greaterThanOrEqualTo);
}
@override
Constraint operator <=(_EquationMember value) {
return _createConstraint(value, Relation.lessThanOrEqualTo);
}
@override
Constraint equals(_EquationMember value) {
return _createConstraint(value, Relation.equalTo);
}
@override
Expression operator +(_EquationMember m) {
if (m is ConstantMember) if (m is ConstantMember)
return new Expression(new List<Term>.from(terms), constant + m.value); return new Expression(new List<Term>.from(terms), constant + m.value);
...@@ -100,7 +112,7 @@ class Expression extends _EquationMember { ...@@ -100,7 +112,7 @@ class Expression extends _EquationMember {
} }
@override @override
Expression operator -(_EquationMember m) { Expression operator -(EquationMember m) {
if (m is ConstantMember) if (m is ConstantMember)
return new Expression(new List<Term>.from(terms), constant - m.value); return new Expression(new List<Term>.from(terms), constant - m.value);
...@@ -126,49 +138,22 @@ class Expression extends _EquationMember { ...@@ -126,49 +138,22 @@ class Expression extends _EquationMember {
return null; return null;
} }
_EquationMember _applyMultiplicand(double m) {
List<Term> newTerms = terms.fold(
new List<Term>(),
(List<Term> list, Term term) {
return list..add(new Term(term.variable, term.coefficient * m));
}
);
return new Expression(newTerms, constant * m);
}
_Pair<Expression, double> _findMulitplierAndMultiplicand(_EquationMember m) {
// At least on of the the two members must be constant for the resulting
// expression to be linear
if (!this.isConstant && !m.isConstant)
return null;
if (this.isConstant)
return new _Pair<Expression, double>(m.asExpression(), this.value);
if (m.isConstant)
return new _Pair<Expression, double>(this.asExpression(), m.value);
assert(false);
return null;
}
@override @override
_EquationMember operator *(_EquationMember m) { EquationMember operator *(EquationMember m) {
_Pair<Expression, double> args = _findMulitplierAndMultiplicand(m); _Multiplication args = _findMulitplierAndMultiplicand(m);
if (args == null) { if (args == null) {
throw new ParserException( throw new ParserException(
'Could not find constant multiplicand or multiplier', 'Could not find constant multiplicand or multiplier',
<_EquationMember>[this, m] <EquationMember>[this, m]
); );
} }
return args.first._applyMultiplicand(args.second); return args.multiplier._applyMultiplicand(args.multiplicand);
} }
@override @override
_EquationMember operator /(_EquationMember m) { EquationMember operator /(EquationMember m) {
if (!m.isConstant) { if (!m.isConstant) {
throw new ParserException( throw new ParserException(
'The divisor was not a constant expression', [this, m]); 'The divisor was not a constant expression', [this, m]);
...@@ -178,6 +163,32 @@ class Expression extends _EquationMember { ...@@ -178,6 +163,32 @@ class Expression extends _EquationMember {
return this._applyMultiplicand(1.0 / m.value); return this._applyMultiplicand(1.0 / m.value);
} }
_Multiplication _findMulitplierAndMultiplicand(EquationMember m) {
// At least one of the the two members must be constant for the resulting
// expression to be linear
if (!this.isConstant && !m.isConstant)
return null;
if (this.isConstant)
return new _Multiplication(m.asExpression(), this.value);
if (m.isConstant)
return new _Multiplication(this.asExpression(), m.value);
assert(false);
return null;
}
EquationMember _applyMultiplicand(double m) {
List<Term> newTerms = terms.fold(
new List<Term>(),
(List<Term> list, Term term) {
return list..add(new Term(term.variable, term.coefficient * m));
}
);
return new Expression(newTerms, constant * m);
}
@override @override
String toString() { String toString() {
StringBuffer buffer = new StringBuffer(); StringBuffer buffer = new StringBuffer();
......
...@@ -2,12 +2,41 @@ ...@@ -2,12 +2,41 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
part of cassowary; import 'equation_member.dart';
import 'expression.dart';
import 'term.dart';
class Param extends _EquationMember { class Variable {
static int _total = 0;
Variable(this.value) : _tick = _total++;
final int _tick;
double value;
String name;
Param get owner => _owner;
Param _owner;
bool applyUpdate(double updated) {
bool res = updated != value;
value = updated;
return res;
}
String get debugName => name ?? 'variable$_tick';
@override
String toString() => debugName;
}
class Param extends EquationMember {
Param([double value = 0.0]) : variable = new Variable(value) { Param([double value = 0.0]) : variable = new Variable(value) {
variable._owner = this; variable._owner = this;
} }
Param.withContext(dynamic context, [double value = 0.0]) Param.withContext(dynamic context, [double value = 0.0])
: variable = new Variable(value), : variable = new Variable(value),
context = context { context = context {
...@@ -18,6 +47,9 @@ class Param extends _EquationMember { ...@@ -18,6 +47,9 @@ class Param extends _EquationMember {
dynamic context; dynamic context;
@override
Expression asExpression() => new Expression(<Term>[new Term(variable, 1.0)], 0.0);
@override @override
bool get isConstant => false; bool get isConstant => false;
...@@ -26,7 +58,4 @@ class Param extends _EquationMember { ...@@ -26,7 +58,4 @@ class Param extends _EquationMember {
String get name => variable.name; String get name => variable.name;
void set name(String name) { variable.name = name; } void set name(String name) { variable.name = name; }
@override
Expression asExpression() => new Expression(<Term>[new Term(variable, 1.0)], 0.0);
} }
...@@ -2,16 +2,19 @@ ...@@ -2,16 +2,19 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
part of cassowary; import 'equation_member.dart';
class ParserException implements Exception { class ParserException implements Exception {
final String message;
List<_EquationMember> members;
ParserException(this.message, this.members); ParserException(this.message, this.members);
final String message;
List<EquationMember> members;
@override @override
String toString() { String toString() {
if (message == null) return "Error while parsing constraint or expression"; if (message == null)
return "Error: '$message' while trying to parse constraint or expression"; return 'Error while parsing constraint or expression';
return 'Error: "$message" while trying to parse constraint or expression';
} }
} }
...@@ -2,14 +2,35 @@ ...@@ -2,14 +2,35 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
part of cassowary; import 'dart:math';
/// Utility functions for managing cassowary priorities.
///
/// Priorities in cassowary expressions are internally expressed as a number
/// between 0 and 1,000,000,000. These numbers can be created by using the
/// [Priority.create] static method.
class Priority { class Priority {
/// The priority level that, by convention, is the highest allowed priority level (1,000,000,000).
static final double required = create(1e3, 1e3, 1e3); static final double required = create(1e3, 1e3, 1e3);
/// A priority level that is below the [required] level but still near it (1,000,000).
static final double strong = create(1.0, 0.0, 0.0); static final double strong = create(1.0, 0.0, 0.0);
/// A priority level logarithmically in the middle of [strong] and [weak] (1,000).
static final double medium = create(0.0, 1.0, 0.0); static final double medium = create(0.0, 1.0, 0.0);
/// A priority level that, by convention, is the lowest allowed priority level (1).
static final double weak = create(0.0, 0.0, 1.0); static final double weak = create(0.0, 0.0, 1.0);
/// Computes a priority level by combining three numbers in the range 0..1000.
///
/// The first number is a multiple of [strong].
///
/// The second number is a multiple of [medium].
///
/// The third number is a multiple of [weak].
///
/// By convention, at least one of these numbers should be equal to or greater than 1.
static double create(double a, double b, double c) { static double create(double a, double b, double c) {
double result = 0.0; double result = 0.0;
result += max(0.0, min(1e3, a)) * 1e6; result += max(0.0, min(1e3, a)) * 1e6;
...@@ -17,8 +38,4 @@ class Priority { ...@@ -17,8 +38,4 @@ class Priority {
result += max(0.0, min(1e3, c)); result += max(0.0, min(1e3, c));
return result; return result;
} }
static double clamp(double value) {
return max(0.0, min(required, value));
}
} }
// 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.
import 'solver.dart';
/// Return values used by methods on the cassowary [Solver].
class Result {
const Result._(this.message, { bool isError: true }) : error = isError;
/// The human-readable string associated with this result.
///
/// This message is typically brief and intended for developers to help debug
/// erroneous expressions.
final String message;
/// Whether this [Result] represents an error (true) or not (false).
final bool error;
static const Result success =
const Result._('Success', isError: false);
static const Result duplicateConstraint =
const Result._('Duplicate constraint');
static const Result unsatisfiableConstraint =
const Result._('Unsatisfiable constraint');
static const Result unknownConstraint =
const Result._('Unknown constraint');
static const Result duplicateEditVariable =
const Result._('Duplicate edit variable');
static const Result badRequiredStrength =
const Result._('Bad required strength');
static const Result unknownEditVariable =
const Result._('Unknown edit variable');
}
...@@ -2,7 +2,148 @@ ...@@ -2,7 +2,148 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
part of cassowary; import 'dart:collection';
import 'constraint.dart';
import 'expression.dart';
import 'priority.dart';
import 'result.dart';
import 'param.dart';
import 'term.dart';
enum _SymbolType { invalid, external, slack, error, dummy, }
class _Symbol {
const _Symbol(this.type, this.tick);
final _SymbolType type;
final int tick;
@override
String toString() {
String typeString = 'unknown';
switch (type) {
case _SymbolType.invalid:
typeString = 'i';
break;
case _SymbolType.external:
typeString = 'v';
break;
case _SymbolType.slack:
typeString = 's';
break;
case _SymbolType.error:
typeString = 'e';
break;
case _SymbolType.dummy:
typeString = 'd';
break;
}
return '$typeString$tick';
}
}
class _Tag {
_Tag(this.marker, this.other);
_Tag.fromTag(_Tag tag)
: this.marker = tag.marker,
this.other = tag.other;
_Symbol marker;
_Symbol 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);
bool _nearZero(double value) {
const double epsilon = 1.0e-8;
return value < 0.0 ? -value < epsilon : value < epsilon;
}
class _Row {
_Row(this.constant) : this.cells = new Map<_Symbol, double>();
_Row.fromRow(_Row row)
: this.cells = new Map<_Symbol, double>.from(row.cells),
this.constant = row.constant;
final Map<_Symbol, double> cells;
double constant = 0.0;
double add(double value) => constant += value;
void insertSymbol(_Symbol symbol, [double coefficient = 1.0]) {
double val = cells[symbol] ?? 0.0;
if (_nearZero(val + coefficient)) {
cells.remove(symbol);
} else {
cells[symbol] = val + coefficient;
}
}
void insertRow(_Row other, [double coefficient = 1.0]) {
constant += other.constant * coefficient;
other.cells.forEach((_Symbol s, double v) => insertSymbol(s, v * coefficient));
}
void removeSymbol(_Symbol symbol) {
cells.remove(symbol);
}
void reverseSign() {
constant = -constant;
cells.forEach((_Symbol s, double v) => cells[s] = -v);
}
void solveForSymbol(_Symbol symbol) {
assert(cells.containsKey(symbol));
double coefficient = -1.0 / cells[symbol];
cells.remove(symbol);
constant *= coefficient;
cells.forEach((_Symbol s, double v) => cells[s] = v * coefficient);
}
void solveForSymbols(_Symbol lhs, _Symbol rhs) {
insertSymbol(lhs, -1.0);
solveForSymbol(rhs);
}
double coefficientForSymbol(_Symbol symbol) => cells[symbol] ?? 0.0;
void substitute(_Symbol symbol, _Row row) {
double coefficient = cells[symbol];
if (coefficient == null) {
return;
}
cells.remove(symbol);
insertRow(row, coefficient);
}
@override
String toString() {
StringBuffer buffer = new StringBuffer();
buffer.write(constant);
cells.forEach((_Symbol symbol, double value) {
buffer.write(" + " + value.toString() + " * " + symbol.toString());
});
return buffer.toString();
}
}
class Solver { class Solver {
final Map<Constraint, _Tag> _constraints = new Map<Constraint, _Tag>(); final Map<Constraint, _Tag> _constraints = new Map<Constraint, _Tag>();
...@@ -25,9 +166,8 @@ class Solver { ...@@ -25,9 +166,8 @@ class Solver {
} }
Result addConstraint(Constraint constraint) { Result addConstraint(Constraint constraint) {
if (_constraints.containsKey(constraint)) { if (_constraints.containsKey(constraint))
return Result.duplicateConstraint; return Result.duplicateConstraint;
}
_Tag tag = new _Tag(new _Symbol(_SymbolType.invalid, 0), _Tag tag = new _Tag(new _Symbol(_SymbolType.invalid, 0),
new _Symbol(_SymbolType.invalid, 0)); new _Symbol(_SymbolType.invalid, 0));
...@@ -45,9 +185,8 @@ class Solver { ...@@ -45,9 +185,8 @@ class Solver {
} }
if (subject.type == _SymbolType.invalid) { if (subject.type == _SymbolType.invalid) {
if (!_addWithArtificialVariableOnRow(row)) { if (!_addWithArtificialVariableOnRow(row))
return Result.unsatisfiableConstraint; return Result.unsatisfiableConstraint;
}
} else { } else {
row.solveForSymbol(subject); row.solveForSymbol(subject);
_substitute(subject, row); _substitute(subject, row);
...@@ -68,9 +207,8 @@ class Solver { ...@@ -68,9 +207,8 @@ class Solver {
Result removeConstraint(Constraint constraint) { Result removeConstraint(Constraint constraint) {
_Tag tag = _constraints[constraint]; _Tag tag = _constraints[constraint];
if (tag == null) { if (tag == null)
return Result.unknownConstraint; return Result.unknownConstraint;
}
tag = new _Tag.fromTag(tag); tag = new _Tag.fromTag(tag);
_constraints.remove(constraint); _constraints.remove(constraint);
...@@ -81,16 +219,11 @@ class Solver { ...@@ -81,16 +219,11 @@ class Solver {
if (row != null) { if (row != null) {
_rows.remove(tag.marker); _rows.remove(tag.marker);
} else { } else {
_Pair<_Symbol, _Row> rowPair = _leavingRowPairForMarkerSymbol(tag.marker); _Symbol leaving = _leavingSymbolForMarkerSymbol(tag.marker);
assert(leaving != null);
if (rowPair == null) {
return Result.internalSolverError;
}
_Symbol leaving = rowPair.first; row = _rows.remove(leaving);
row = rowPair.second; assert(row != null);
_Row removed = _rows.remove(rowPair.first);
assert(removed != null);
row.solveForSymbols(leaving, tag.marker); row.solveForSymbols(leaving, tag.marker);
_substitute(tag.marker, row); _substitute(tag.marker, row);
} }
...@@ -110,21 +243,19 @@ class Solver { ...@@ -110,21 +243,19 @@ class Solver {
} }
Result addEditVariable(Variable variable, double priority) { Result addEditVariable(Variable variable, double priority) {
if (_edits.containsKey(variable)) { if (_edits.containsKey(variable))
return Result.duplicateEditVariable; return Result.duplicateEditVariable;
}
if (!_isValidNonRequiredPriority(priority)) { if (!_isValidNonRequiredPriority(priority))
return Result.badRequiredStrength; return Result.badRequiredStrength;
}
Constraint constraint = new Constraint( Constraint constraint = new Constraint(
new Expression([new Term(variable, 1.0)], 0.0), Relation.equalTo); new Expression([new Term(variable, 1.0)], 0.0),
Relation.equalTo
);
constraint.priority = priority; constraint.priority = priority;
if (addConstraint(constraint) != Result.success) { assert(addConstraint(constraint) == Result.success);
return Result.internalSolverError;
}
_EditInfo info = new _EditInfo(); _EditInfo info = new _EditInfo();
info.tag = _constraints[constraint]; info.tag = _constraints[constraint];
...@@ -149,8 +280,7 @@ class Solver { ...@@ -149,8 +280,7 @@ class Solver {
if (info == null) if (info == null)
return Result.unknownEditVariable; return Result.unknownEditVariable;
if (removeConstraint(info.constraint) != Result.success) assert(removeConstraint(info.constraint) == Result.success);
return Result.internalSolverError;
_edits.remove(variable); _edits.remove(variable);
return Result.success; return Result.success;
...@@ -161,9 +291,8 @@ class Solver { ...@@ -161,9 +291,8 @@ class Solver {
} }
Result suggestValueForVariable(Variable variable, double value) { Result suggestValueForVariable(Variable variable, double value) {
if (!_edits.containsKey(variable)) { if (!_edits.containsKey(variable))
return Result.unknownEditVariable; return Result.unknownEditVariable;
}
_suggestValueForEditInfoWithoutDualOptimization(_edits[variable], value); _suggestValueForEditInfoWithoutDualOptimization(_edits[variable], value);
...@@ -179,8 +308,8 @@ class Solver { ...@@ -179,8 +308,8 @@ class Solver {
double updatedValue = row == null ? 0.0 : row.constant; double updatedValue = row == null ? 0.0 : row.constant;
if (variable._applyUpdate(updatedValue) && variable._owner != null) { if (variable.applyUpdate(updatedValue) && variable.owner != null) {
dynamic context = variable._owner.context; dynamic context = variable.owner.context;
if (context != null) if (context != null)
updates.add(context); updates.add(context);
} }
...@@ -293,34 +422,30 @@ class Solver { ...@@ -293,34 +422,30 @@ class Solver {
_Symbol _chooseSubjectForRow(_Row row, _Tag tag) { _Symbol _chooseSubjectForRow(_Row row, _Tag tag) {
for (_Symbol symbol in row.cells.keys) { for (_Symbol symbol in row.cells.keys) {
if (symbol.type == _SymbolType.external) { if (symbol.type == _SymbolType.external)
return symbol; return symbol;
} }
}
if (tag.marker.type == _SymbolType.slack || if (tag.marker.type == _SymbolType.slack ||
tag.marker.type == _SymbolType.error) { tag.marker.type == _SymbolType.error) {
if (row.coefficientForSymbol(tag.marker) < 0.0) { if (row.coefficientForSymbol(tag.marker) < 0.0)
return tag.marker; return tag.marker;
} }
}
if (tag.other.type == _SymbolType.slack || if (tag.other.type == _SymbolType.slack ||
tag.other.type == _SymbolType.error) { tag.other.type == _SymbolType.error) {
if (row.coefficientForSymbol(tag.other) < 0.0) { if (row.coefficientForSymbol(tag.other) < 0.0)
return tag.other; return tag.other;
} }
}
return new _Symbol(_SymbolType.invalid, 0); return new _Symbol(_SymbolType.invalid, 0);
} }
bool _allDummiesInRow(_Row row) { bool _allDummiesInRow(_Row row) {
for (_Symbol symbol in row.cells.keys) { for (_Symbol symbol in row.cells.keys) {
if (symbol.type != _SymbolType.dummy) { if (symbol.type != _SymbolType.dummy)
return false; return false;
} }
}
return true; return true;
} }
...@@ -342,23 +467,20 @@ class Solver { ...@@ -342,23 +467,20 @@ class Solver {
_Row foundRow = _rows[artificial]; _Row foundRow = _rows[artificial];
if (foundRow != null) { if (foundRow != null) {
_rows.remove(artificial); _rows.remove(artificial);
if (foundRow.cells.isEmpty) { if (foundRow.cells.isEmpty)
return success; return success;
}
_Symbol entering = _anyPivotableSymbol(foundRow); _Symbol entering = _anyPivotableSymbol(foundRow);
if (entering.type == _SymbolType.invalid) { if (entering.type == _SymbolType.invalid)
return false; return false;
}
foundRow.solveForSymbols(artificial, entering); foundRow.solveForSymbols(artificial, entering);
_substitute(entering, foundRow); _substitute(entering, foundRow);
_rows[entering] = foundRow; _rows[entering] = foundRow;
} }
for (_Row row in _rows.values) { for (_Row row in _rows.values)
row.removeSymbol(artificial); row.removeSymbol(artificial);
}
_objective.removeSymbol(artificial); _objective.removeSymbol(artificial);
return success; return success;
} }
...@@ -366,19 +488,13 @@ class Solver { ...@@ -366,19 +488,13 @@ class Solver {
Result _optimizeObjectiveRow(_Row objective) { Result _optimizeObjectiveRow(_Row objective) {
while (true) { while (true) {
_Symbol entering = _enteringSymbolForObjectiveRow(objective); _Symbol entering = _enteringSymbolForObjectiveRow(objective);
if (entering.type == _SymbolType.invalid) { if (entering.type == _SymbolType.invalid)
return Result.success; return Result.success;
}
_Pair<_Symbol, _Row> leavingPair = _leavingRowForEnteringSymbol(entering); _Symbol leaving = _leavingSymbolForEnteringSymbol(entering);
assert(leaving != null);
if (leavingPair == null) {
return Result.internalSolverError;
}
_Symbol leaving = leavingPair.first; _Row row = _rows.remove(leaving);
_Row row = leavingPair.second;
_rows.remove(leavingPair.first);
row.solveForSymbols(leaving, entering); row.solveForSymbols(leaving, entering);
_substitute(entering, row); _substitute(entering, row);
_rows[entering] = row; _rows[entering] = row;
...@@ -389,38 +505,28 @@ class Solver { ...@@ -389,38 +505,28 @@ class Solver {
Map<_Symbol, double> cells = objective.cells; Map<_Symbol, double> cells = objective.cells;
for (_Symbol symbol in cells.keys) { for (_Symbol symbol in cells.keys) {
if (symbol.type != _SymbolType.dummy && cells[symbol] < 0.0) { if (symbol.type != _SymbolType.dummy && cells[symbol] < 0.0)
return symbol; return symbol;
} }
}
return new _Symbol(_SymbolType.invalid, 0); return new _Symbol(_SymbolType.invalid, 0);
} }
_Pair<_Symbol, _Row> _leavingRowForEnteringSymbol(_Symbol entering) { _Symbol _leavingSymbolForEnteringSymbol(_Symbol entering) {
double ratio = double.MAX_FINITE; double ratio = double.MAX_FINITE;
_Pair<_Symbol, _Row> result = new _Pair<_Symbol, _Row>(null, null); _Symbol result;
_rows.forEach((_Symbol symbol, _Row row) { _rows.forEach((_Symbol symbol, _Row row) {
if (symbol.type != _SymbolType.external) { if (symbol.type != _SymbolType.external) {
double temp = row.coefficientForSymbol(entering); double temp = row.coefficientForSymbol(entering);
if (temp < 0.0) { if (temp < 0.0) {
double tempRatio = -row.constant / temp; double tempRatio = -row.constant / temp;
if (tempRatio < ratio) { if (tempRatio < ratio) {
ratio = tempRatio; ratio = tempRatio;
result.first = symbol; result = symbol;
result.second = row;
} }
} }
} }
}); });
if (result.first == null || result.second == null) {
return null;
}
return result; return result;
} }
...@@ -431,12 +537,10 @@ class Solver { ...@@ -431,12 +537,10 @@ class Solver {
_infeasibleRows.add(first); _infeasibleRows.add(first);
} }
}); });
_objective.substitute(symbol, row); _objective.substitute(symbol, row);
if (_artificial != null) { if (_artificial != null)
_artificial.substitute(symbol, row); _artificial.substitute(symbol, row);
} }
}
_Symbol _anyPivotableSymbol(_Row row) { _Symbol _anyPivotableSymbol(_Row row) {
for (_Symbol symbol in row.cells.keys) { for (_Symbol symbol in row.cells.keys) {
...@@ -466,43 +570,34 @@ class Solver { ...@@ -466,43 +570,34 @@ class Solver {
} }
} }
_Pair<_Symbol, _Row> _leavingRowPairForMarkerSymbol(_Symbol marker) { _Symbol _leavingSymbolForMarkerSymbol(_Symbol marker) {
double r1 = double.MAX_FINITE; double r1 = double.MAX_FINITE;
double r2 = double.MAX_FINITE; double r2 = double.MAX_FINITE;
_Pair<_Symbol, _Row> first, second, third; _Symbol first, second, third;
_rows.forEach((_Symbol symbol, _Row row) { _rows.forEach((_Symbol symbol, _Row row) {
double c = row.coefficientForSymbol(marker); double c = row.coefficientForSymbol(marker);
if (c == 0.0)
if (c == 0.0) {
return; return;
}
if (symbol.type == _SymbolType.external) { if (symbol.type == _SymbolType.external) {
third = new _Pair<_Symbol, _Row>(symbol, row); third = symbol;
} else if (c < 0.0) { } else if (c < 0.0) {
double r = -row.constant / c; double r = -row.constant / c;
if (r < r1) { if (r < r1) {
r1 = r; r1 = r;
first = new _Pair<_Symbol, _Row>(symbol, row); first = symbol;
} }
} else { } else {
double r = row.constant / c; double r = row.constant / c;
if (r < r2) { if (r < r2) {
r2 = r; r2 = r;
second = new _Pair<_Symbol, _Row>(symbol, row); second = symbol;
} }
} }
}); });
if (first != null) { return first ?? second ?? third;
return first;
}
if (second != null) {
return second;
}
return third;
} }
void _suggestValueForEditInfoWithoutDualOptimization( void _suggestValueForEditInfoWithoutDualOptimization(
...@@ -551,9 +646,7 @@ class Solver { ...@@ -551,9 +646,7 @@ class Solver {
if (row != null && row.constant < 0.0) { if (row != null && row.constant < 0.0) {
_Symbol entering = _dualEnteringSymbolForRow(row); _Symbol entering = _dualEnteringSymbolForRow(row);
if (entering.type == _SymbolType.invalid) { assert(entering.type != _SymbolType.invalid);
return Result.internalSolverError;
}
_rows.remove(leaving); _rows.remove(leaving);
...@@ -630,25 +723,3 @@ class Solver { ...@@ -630,25 +723,3 @@ class Solver {
return buffer.toString(); 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);
...@@ -2,23 +2,26 @@ ...@@ -2,23 +2,26 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
part of cassowary; import 'equation_member.dart';
import 'expression.dart';
import 'param.dart';
class Term extends _EquationMember { class Term extends EquationMember {
Term(this.variable, this.coefficient); Term(this.variable, this.coefficient);
final Variable variable; final Variable variable;
final double coefficient; final double coefficient;
@override @override
bool get isConstant => false; Expression asExpression() =>
new Expression([new Term(this.variable, this.coefficient)], 0.0);
@override @override
double get value => coefficient * variable.value; bool get isConstant => false;
@override @override
Expression asExpression() => double get value => coefficient * variable.value;
new Expression([new Term(this.variable, this.coefficient)], 0.0);
@override @override
String toString() { String toString() {
......
// 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;
enum _SymbolType { invalid, external, slack, error, dummy, }
class _Symbol {
final _SymbolType type;
final int tick;
_Symbol(this.type, this.tick);
@override
String toString() {
String typeString = 'unknown';
switch (type) {
case _SymbolType.invalid:
typeString = 'i';
break;
case _SymbolType.external:
typeString = 'v';
break;
case _SymbolType.slack:
typeString = 's';
break;
case _SymbolType.error:
typeString = 'e';
break;
case _SymbolType.dummy:
typeString = 'd';
break;
}
return '$typeString$tick';
}
}
// 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;
bool _nearZero(double value) {
const double epsilon = 1.0e-8;
return value < 0.0 ? -value < epsilon : value < epsilon;
}
class _Pair<X, Y> {
X first;
Y second;
_Pair(this.first, this.second);
}
// 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 Variable {
double value;
String name;
Param _owner;
final int _tick;
static int _total = 0;
Variable(this.value) : _tick = _total++;
bool _applyUpdate(double updated) {
bool res = updated != value;
value = updated;
return res;
}
String get debugName => name ?? 'variable$_tick';
@override
String toString() => debugName;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment