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 {
// The widths of the first and the third boxes should be equal
p1.width.equals(p3.width),
// The width of the second box should be twice as much as that of the first
// and third
// The width of the first box should be twice as much as that of the second
p1.width.equals(p2.width * al.cm(2.0)),
// The height of the three boxes should be equal to that of the container
......
......@@ -26,8 +26,7 @@ class _MyAutoLayoutDelegate extends AutoLayoutDelegate {
// The widths of the first and the third boxes should be equal
p1.width.equals(p3.width),
// The width of the second box should be twice as much as that of the first
// and third
// The width of the first box should be twice as much as that of the second
p1.width.equals(p2.width * al.cm(2.0)),
// The height of the three boxes should be equal to that of the container
......
......@@ -3,22 +3,23 @@
// found in the LICENSE file.
/// 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;
import 'dart:collection';
import 'dart:math';
part 'constraint.dart';
part 'expression.dart';
part 'term.dart';
part 'variable.dart';
part 'equation_member.dart';
part 'constant_member.dart';
part 'solver.dart';
part 'symbol.dart';
part 'row.dart';
part 'utils.dart';
part 'result.dart';
part 'parser_exception.dart';
part 'param.dart';
part 'priority.dart';
export 'src/constraint.dart';
export 'src/expression.dart';
export 'src/term.dart';
export 'src/equation_member.dart';
export 'src/constant_member.dart';
export 'src/solver.dart';
export 'src/result.dart';
export 'src/parser_exception.dart';
export 'src/param.dart';
export 'src/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 @@
// Use of this source code is governed by a BSD-style license that can be
// 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);
@override
final double value;
Expression asExpression() => new Expression([], this.value);
@override
bool get isConstant => true;
final double value;
@override
Expression asExpression() => new Expression([], this.value);
bool get isConstant => true;
}
ConstantMember cm(double value) {
return new ConstantMember(value);
}
/// Creates a [ConstantMember].
///
/// This is a convenience method to make cassowary expressions less verbose.
ConstantMember cm(double value) => new ConstantMember(value);
......@@ -2,16 +2,23 @@
// Use of this source code is governed by a BSD-style license that can be
// 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 {
Constraint(this.expression, this.relation);
final Relation relation;
final Expression expression;
double priority = Priority.required;
Constraint(this.expression, this.relation);
double priority = Priority.required;
Constraint operator |(double p) => this..priority = p;
......@@ -34,9 +41,8 @@ class Constraint {
buffer.write(' | priority = $priority');
if (priority == Priority.required) {
if (priority == Priority.required)
buffer.write(' (required)');
}
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 @@
// Use of this source code is governed by a BSD-style license that can be
// 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.fromExpression(Expression expr)
: this.terms = new List<Term>.from(expr.terms),
this.constant = expr.constant;
......@@ -14,6 +26,9 @@ class Expression extends _EquationMember {
final double constant;
@override
Expression asExpression() => this;
@override
bool get isConstant => terms.length == 0;
......@@ -21,9 +36,21 @@ class Expression extends _EquationMember {
double get value => terms.fold(constant, (double value, Term term) => value + term.value);
@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) {
return new Constraint(
new Expression(new List<Term>.from(terms), constant - value.value),
......@@ -60,22 +87,7 @@ class Expression extends _EquationMember {
}
@override
Constraint operator >=(_EquationMember value) {
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) {
Expression operator +(EquationMember m) {
if (m is ConstantMember)
return new Expression(new List<Term>.from(terms), constant + m.value);
......@@ -100,7 +112,7 @@ class Expression extends _EquationMember {
}
@override
Expression operator -(_EquationMember m) {
Expression operator -(EquationMember m) {
if (m is ConstantMember)
return new Expression(new List<Term>.from(terms), constant - m.value);
......@@ -126,49 +138,22 @@ class Expression extends _EquationMember {
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
_EquationMember operator *(_EquationMember m) {
_Pair<Expression, double> args = _findMulitplierAndMultiplicand(m);
EquationMember operator *(EquationMember m) {
_Multiplication args = _findMulitplierAndMultiplicand(m);
if (args == null) {
throw new ParserException(
'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
_EquationMember operator /(_EquationMember m) {
EquationMember operator /(EquationMember m) {
if (!m.isConstant) {
throw new ParserException(
'The divisor was not a constant expression', [this, m]);
......@@ -178,6 +163,32 @@ class Expression extends _EquationMember {
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
String toString() {
StringBuffer buffer = new StringBuffer();
......
......@@ -2,12 +2,41 @@
// Use of this source code is governed by a BSD-style license that can be
// 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) {
variable._owner = this;
}
Param.withContext(dynamic context, [double value = 0.0])
: variable = new Variable(value),
context = context {
......@@ -18,6 +47,9 @@ class Param extends _EquationMember {
dynamic context;
@override
Expression asExpression() => new Expression(<Term>[new Term(variable, 1.0)], 0.0);
@override
bool get isConstant => false;
......@@ -26,7 +58,4 @@ class Param extends _EquationMember {
String get name => variable.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 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of cassowary;
import 'equation_member.dart';
class ParserException implements Exception {
final String message;
List<_EquationMember> members;
ParserException(this.message, this.members);
final String message;
List<EquationMember> members;
@override
String toString() {
if (message == null) return "Error while parsing constraint or expression";
return "Error: '$message' while trying to parse constraint or expression";
if (message == null)
return 'Error while parsing constraint or expression';
return 'Error: "$message" while trying to parse constraint or expression';
}
}
......@@ -2,14 +2,35 @@
// Use of this source code is governed by a BSD-style license that can be
// 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 {
/// The priority level that, by convention, is the highest allowed priority level (1,000,000,000).
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);
/// A priority level logarithmically in the middle of [strong] and [weak] (1,000).
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);
/// 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) {
double result = 0.0;
result += max(0.0, min(1e3, a)) * 1e6;
......@@ -17,8 +38,4 @@ class Priority {
result += max(0.0, min(1e3, c));
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 @@
// Use of this source code is governed by a BSD-style license that can be
// 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 {
final Map<Constraint, _Tag> _constraints = new Map<Constraint, _Tag>();
......@@ -25,9 +166,8 @@ class Solver {
}
Result addConstraint(Constraint constraint) {
if (_constraints.containsKey(constraint)) {
if (_constraints.containsKey(constraint))
return Result.duplicateConstraint;
}
_Tag tag = new _Tag(new _Symbol(_SymbolType.invalid, 0),
new _Symbol(_SymbolType.invalid, 0));
......@@ -45,9 +185,8 @@ class Solver {
}
if (subject.type == _SymbolType.invalid) {
if (!_addWithArtificialVariableOnRow(row)) {
if (!_addWithArtificialVariableOnRow(row))
return Result.unsatisfiableConstraint;
}
} else {
row.solveForSymbol(subject);
_substitute(subject, row);
......@@ -68,9 +207,8 @@ class Solver {
Result removeConstraint(Constraint constraint) {
_Tag tag = _constraints[constraint];
if (tag == null) {
if (tag == null)
return Result.unknownConstraint;
}
tag = new _Tag.fromTag(tag);
_constraints.remove(constraint);
......@@ -81,16 +219,11 @@ class Solver {
if (row != null) {
_rows.remove(tag.marker);
} else {
_Pair<_Symbol, _Row> rowPair = _leavingRowPairForMarkerSymbol(tag.marker);
if (rowPair == null) {
return Result.internalSolverError;
}
_Symbol leaving = _leavingSymbolForMarkerSymbol(tag.marker);
assert(leaving != null);
_Symbol leaving = rowPair.first;
row = rowPair.second;
_Row removed = _rows.remove(rowPair.first);
assert(removed != null);
row = _rows.remove(leaving);
assert(row != null);
row.solveForSymbols(leaving, tag.marker);
_substitute(tag.marker, row);
}
......@@ -110,21 +243,19 @@ class Solver {
}
Result addEditVariable(Variable variable, double priority) {
if (_edits.containsKey(variable)) {
if (_edits.containsKey(variable))
return Result.duplicateEditVariable;
}
if (!_isValidNonRequiredPriority(priority)) {
if (!_isValidNonRequiredPriority(priority))
return Result.badRequiredStrength;
}
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;
if (addConstraint(constraint) != Result.success) {
return Result.internalSolverError;
}
assert(addConstraint(constraint) == Result.success);
_EditInfo info = new _EditInfo();
info.tag = _constraints[constraint];
......@@ -149,8 +280,7 @@ class Solver {
if (info == null)
return Result.unknownEditVariable;
if (removeConstraint(info.constraint) != Result.success)
return Result.internalSolverError;
assert(removeConstraint(info.constraint) == Result.success);
_edits.remove(variable);
return Result.success;
......@@ -161,9 +291,8 @@ class Solver {
}
Result suggestValueForVariable(Variable variable, double value) {
if (!_edits.containsKey(variable)) {
if (!_edits.containsKey(variable))
return Result.unknownEditVariable;
}
_suggestValueForEditInfoWithoutDualOptimization(_edits[variable], value);
......@@ -179,8 +308,8 @@ class Solver {
double updatedValue = row == null ? 0.0 : row.constant;
if (variable._applyUpdate(updatedValue) && variable._owner != null) {
dynamic context = variable._owner.context;
if (variable.applyUpdate(updatedValue) && variable.owner != null) {
dynamic context = variable.owner.context;
if (context != null)
updates.add(context);
}
......@@ -293,34 +422,30 @@ class Solver {
_Symbol _chooseSubjectForRow(_Row row, _Tag tag) {
for (_Symbol symbol in row.cells.keys) {
if (symbol.type == _SymbolType.external) {
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) {
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) {
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) {
if (symbol.type != _SymbolType.dummy)
return false;
}
}
return true;
}
......@@ -342,23 +467,20 @@ class Solver {
_Row foundRow = _rows[artificial];
if (foundRow != null) {
_rows.remove(artificial);
if (foundRow.cells.isEmpty) {
if (foundRow.cells.isEmpty)
return success;
}
_Symbol entering = _anyPivotableSymbol(foundRow);
if (entering.type == _SymbolType.invalid) {
if (entering.type == _SymbolType.invalid)
return false;
}
foundRow.solveForSymbols(artificial, entering);
_substitute(entering, foundRow);
_rows[entering] = foundRow;
}
for (_Row row in _rows.values) {
for (_Row row in _rows.values)
row.removeSymbol(artificial);
}
_objective.removeSymbol(artificial);
return success;
}
......@@ -366,19 +488,13 @@ class Solver {
Result _optimizeObjectiveRow(_Row objective) {
while (true) {
_Symbol entering = _enteringSymbolForObjectiveRow(objective);
if (entering.type == _SymbolType.invalid) {
if (entering.type == _SymbolType.invalid)
return Result.success;
}
_Pair<_Symbol, _Row> leavingPair = _leavingRowForEnteringSymbol(entering);
if (leavingPair == null) {
return Result.internalSolverError;
}
_Symbol leaving = _leavingSymbolForEnteringSymbol(entering);
assert(leaving != null);
_Symbol leaving = leavingPair.first;
_Row row = leavingPair.second;
_rows.remove(leavingPair.first);
_Row row = _rows.remove(leaving);
row.solveForSymbols(leaving, entering);
_substitute(entering, row);
_rows[entering] = row;
......@@ -389,38 +505,28 @@ class Solver {
Map<_Symbol, double> cells = objective.cells;
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 new _Symbol(_SymbolType.invalid, 0);
}
_Pair<_Symbol, _Row> _leavingRowForEnteringSymbol(_Symbol entering) {
_Symbol _leavingSymbolForEnteringSymbol(_Symbol entering) {
double ratio = double.MAX_FINITE;
_Pair<_Symbol, _Row> result = new _Pair<_Symbol, _Row>(null, null);
_Symbol result;
_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;
result = symbol;
}
}
}
});
if (result.first == null || result.second == null) {
return null;
}
return result;
}
......@@ -431,12 +537,10 @@ class Solver {
_infeasibleRows.add(first);
}
});
_objective.substitute(symbol, row);
if (_artificial != null) {
if (_artificial != null)
_artificial.substitute(symbol, row);
}
}
_Symbol _anyPivotableSymbol(_Row row) {
for (_Symbol symbol in row.cells.keys) {
......@@ -466,43 +570,34 @@ class Solver {
}
}
_Pair<_Symbol, _Row> _leavingRowPairForMarkerSymbol(_Symbol marker) {
_Symbol _leavingSymbolForMarkerSymbol(_Symbol marker) {
double r1 = double.MAX_FINITE;
double r2 = double.MAX_FINITE;
_Pair<_Symbol, _Row> first, second, third;
_Symbol first, second, third;
_rows.forEach((_Symbol symbol, _Row row) {
double c = row.coefficientForSymbol(marker);
if (c == 0.0) {
if (c == 0.0)
return;
}
if (symbol.type == _SymbolType.external) {
third = new _Pair<_Symbol, _Row>(symbol, row);
third = symbol;
} else if (c < 0.0) {
double r = -row.constant / c;
if (r < r1) {
r1 = r;
first = new _Pair<_Symbol, _Row>(symbol, row);
first = symbol;
}
} else {
double r = row.constant / c;
if (r < r2) {
r2 = r;
second = new _Pair<_Symbol, _Row>(symbol, row);
second = symbol;
}
}
});
if (first != null) {
return first;
}
if (second != null) {
return second;
}
return third;
return first ?? second ?? third;
}
void _suggestValueForEditInfoWithoutDualOptimization(
......@@ -551,9 +646,7 @@ class Solver {
if (row != null && row.constant < 0.0) {
_Symbol entering = _dualEnteringSymbolForRow(row);
if (entering.type == _SymbolType.invalid) {
return Result.internalSolverError;
}
assert(entering.type != _SymbolType.invalid);
_rows.remove(leaving);
......@@ -630,25 +723,3 @@ class Solver {
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 @@
// Use of this source code is governed by a BSD-style license that can be
// 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);
final Variable variable;
final double coefficient;
@override
bool get isConstant => false;
Expression asExpression() =>
new Expression([new Term(this.variable, this.coefficient)], 0.0);
@override
double get value => coefficient * variable.value;
bool get isConstant => false;
@override
Expression asExpression() =>
new Expression([new Term(this.variable, this.coefficient)], 0.0);
double get value => coefficient * variable.value;
@override
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