Commit 27970bd8 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Remove package:flutter/cassowary.dart (#7350)

We didn't end up using this mechanism.
parent cd09370c
// Copyright 2016 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.
// This example shows how to use the Cassowary autolayout system directly in the
// underlying render tree.
import 'package:flutter/cassowary.dart' as al;
import 'package:flutter/rendering.dart';
class _MyAutoLayoutDelegate extends AutoLayoutDelegate {
AutoLayoutRect p1 = new AutoLayoutRect();
AutoLayoutRect p2 = new AutoLayoutRect();
AutoLayoutRect p3 = new AutoLayoutRect();
AutoLayoutRect p4 = new AutoLayoutRect();
@override
List<al.Constraint> getConstraints(AutoLayoutRect parent) {
return <al.Constraint>[
// Sum of widths of each box must be equal to that of the container
parent.width.equals(p1.width + p2.width + p3.width),
// The boxes must be stacked left to right
p1.right <= p2.left,
p2.right <= p3.left,
// The widths of the first and the third boxes should be equal
p1.width.equals(p3.width),
// 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
p1.height.equals(p2.height),
p2.height.equals(p3.height),
p3.height.equals(parent.height),
// The fourth box should be half as wide as the second and must be attached
// to the right edge of the same (by its center)
p4.width.equals(p2.width / al.cm(2.0)),
p4.height.equals(al.cm(50.0)),
p4.horizontalCenter.equals(p2.right),
p4.verticalCenter.equals(p2.height / al.cm(2.0)),
];
}
@override
bool shouldUpdateConstraints(AutoLayoutDelegate oldDelegate) => true;
}
void main() {
RenderDecoratedBox c1 = new RenderDecoratedBox(
decoration: new BoxDecoration(backgroundColor: const Color(0xFFFF0000))
);
RenderDecoratedBox c2 = new RenderDecoratedBox(
decoration: new BoxDecoration(backgroundColor: const Color(0xFF00FF00))
);
RenderDecoratedBox c3 = new RenderDecoratedBox(
decoration: new BoxDecoration(backgroundColor: const Color(0xFF0000FF))
);
RenderDecoratedBox c4 = new RenderDecoratedBox(
decoration: new BoxDecoration(backgroundColor: const Color(0xFFFFFFFF))
);
_MyAutoLayoutDelegate delegate = new _MyAutoLayoutDelegate();
RenderAutoLayout root = new RenderAutoLayout(
delegate: delegate,
children: <RenderBox>[c1, c2, c3, c4]
);
AutoLayoutParentData parentData1 = c1.parentData;
AutoLayoutParentData parentData2 = c2.parentData;
AutoLayoutParentData parentData3 = c3.parentData;
AutoLayoutParentData parentData4 = c4.parentData;
parentData1.rect = delegate.p1;
parentData2.rect = delegate.p2;
parentData3.rect = delegate.p3;
parentData4.rect = delegate.p4;
new RenderingFlutterBinding(root: root);
}
// Copyright 2016 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.
// This example shows how to use the Cassowary autolayout system with widgets.
import 'package:flutter/cassowary.dart' as al;
import 'package:flutter/widgets.dart';
class _MyAutoLayoutDelegate extends AutoLayoutDelegate {
AutoLayoutRect p1 = new AutoLayoutRect();
AutoLayoutRect p2 = new AutoLayoutRect();
AutoLayoutRect p3 = new AutoLayoutRect();
AutoLayoutRect p4 = new AutoLayoutRect();
@override
List<al.Constraint> getConstraints(AutoLayoutRect parent) {
return <al.Constraint>[
// Sum of widths of each box must be equal to that of the container
parent.width.equals(p1.width + p2.width + p3.width),
// The boxes must be stacked left to right
p1.right <= p2.left,
p2.right <= p3.left,
// The widths of the first and the third boxes should be equal
p1.width.equals(p3.width),
// 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
p1.height.equals(p2.height),
p2.height.equals(p3.height),
p3.height.equals(parent.height),
// The fourth box should be half as wide as the second and must be attached
// to the right edge of the same (by its center)
p4.width.equals(p2.width / al.cm(2.0)),
p4.height.equals(al.cm(50.0)),
p4.horizontalCenter.equals(p2.right),
p4.verticalCenter.equals(p2.height / al.cm(2.0)),
];
}
@override
bool shouldUpdateConstraints(_MyAutoLayoutDelegate oldDelegate) => true;
}
class ColoredBoxes extends StatefulWidget {
@override
_ColoredBoxesState createState() => new _ColoredBoxesState();
}
class _ColoredBoxesState extends State<ColoredBoxes> {
final _MyAutoLayoutDelegate delegate = new _MyAutoLayoutDelegate();
@override
Widget build(BuildContext context) {
return new AutoLayout(
delegate: delegate,
children: <Widget>[
new AutoLayoutChild(
rect: delegate.p1,
child: new DecoratedBox(
decoration: new BoxDecoration(backgroundColor: const Color(0xFFFF0000))
)
),
new AutoLayoutChild(
rect: delegate.p2,
child: new DecoratedBox(
decoration: new BoxDecoration(backgroundColor: const Color(0xFF00FF00))
)
),
new AutoLayoutChild(
rect: delegate.p3,
child: new DecoratedBox(
decoration: new BoxDecoration(backgroundColor: const Color(0xFF0000FF))
)
),
new AutoLayoutChild(
rect: delegate.p4,
child: new DecoratedBox(
decoration: new BoxDecoration(backgroundColor: const Color(0xFFFFFFFF))
)
),
]
);
}
}
void main() {
runApp(new ColoredBoxes());
}
......@@ -8,7 +8,6 @@
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/packages" />
<excludeFolder url="file://$MODULE_DIR$/test/animation/packages" />
<excludeFolder url="file://$MODULE_DIR$/test/cassowary/packages" />
<excludeFolder url="file://$MODULE_DIR$/test/cupertino/packages" />
<excludeFolder url="file://$MODULE_DIR$/test/engine/packages" />
<excludeFolder url="file://$MODULE_DIR$/test/examples/packages" />
......@@ -30,4 +29,4 @@
<orderEntry type="library" name="Dart SDK" level="application" />
<orderEntry type="library" name="Dart Packages" level="project" />
</component>
</module>
\ No newline at end of file
</module>
// Copyright 2016 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.
/// An implementation of the Cassowary constraint solving algorithm in Dart.
///
/// To use, import `package:flutter/cassowary.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;
export 'src/cassowary/constraint.dart';
export 'src/cassowary/expression.dart';
export 'src/cassowary/term.dart';
export 'src/cassowary/equation_member.dart';
export 'src/cassowary/constant_member.dart';
export 'src/cassowary/solver.dart';
export 'src/cassowary/result.dart';
export 'src/cassowary/parser_exception.dart';
export 'src/cassowary/param.dart';
export 'src/cassowary/priority.dart';
......@@ -23,7 +23,6 @@
library rendering;
export 'src/rendering/animated_size.dart';
export 'src/rendering/auto_layout.dart';
export 'src/rendering/binding.dart';
export 'src/rendering/block.dart';
export 'src/rendering/box.dart';
......
Cassowary in Dart
=================
This is an implementation of the Cassowary constraint solving algorithm in Dart. The initial implementation was based on the [Kiwi toolkit](https://github.com/nucleic/kiwi) written in C++. Implements a subset of the functionality described in the [Cassowary paper](https://constraints.cs.washington.edu/solvers/cassowary-tochi.pdf) and makes specific affordances for the needs of [Flutter](http://flutter.io/)
# The Solver
A solver is the object that accepts constraints and updates member variables in an attempt to satisfy the same. It is rarely the case that the user will have to create a solver. Instead, one will be vended by Flutter.
# Parameters
In order to create constraints, the user needs to take specific parameter objects vended by elements in the view hierarchy and create expression from these. Constraints can then be obtained from these expressions. If the solver needs to update these parameters to satisfy constraints, it will call callbacks on these parameters.
# Constructing Constraints
A constraint is a linear equation of the form `ax + by + cz + ... + k == 0`. Constraints need not be equality relationships. Less/greater-than-or-equal-to (`<=` or `>=`) relationships can also be specified. In addition, each constraint is specified at a given priority to help resolve constraint ambiguities. A system can also be overconstrained.
The constraint as a whole is represented by an instance of the `Constraint` object. This in turn references an instance of an `Expression` (`ax + by + cz + ... k`), a realtionship and the finally the priority.
Each expression in turn is made up of a list of `Term`s and a constant. The term `ax` has the coefficient `a` and `Variable` `x`. The `Param` that is vended to the user is nothing but a wrapper for this variable and deals with detecting changes to it and updating the underlying view in the hierarchy.
Once the user obtains specific parameter objects, it is straightforward to create constraints. The following example sets up constraints that specify that the width of the element must be at least 100 units. It is assumed that the `left` and `right` `Param` objects have been obtained from the view in question.
```
Constraint widthAtLeast100 = right - left >= CM(100.0)
```
Lets go over this one step at a time: The expression `right - left` creates an instance of an `Expression` object. The expression consists of two terms. The `right` and `left` params wrap variables. The coefficients are 1.0 and -1.0 respectively and the constant -100.0. Constants need to be decorated with `CM` to aid with the operator overloading mechanism in Dart.
All variables are unrestricted. So there is nothing preventing the solver from making the left and right edges negative. We can specify our preference against this by specifying another constraint like so:
```
Constraint edgesPositive = (left >= CM(0.0))
```
When we construct these constraints for the solver, they are created at the default `Priority.required`. This means that the solver will resist adding constraints where there are ambiguities between two required constraints. To specify a weaker priority, you can use the `priority` setter or use the `|` symbol with the priority while constructing the constraint. Like so:
```
Constraint edgesPositive = (left >= CM(0.0) | Priority.weak)
```
Once the set of constraints are constructed, they are added to the solver and the results of the solution flushed out.
```
solver.addConstraints([widthAtLeast100, edgesPositive])
..flushVariableUpdates();
```
# Edit Constraints
When updates need to be applied to parameters that are a part of the solver, edit variables may be used. To illustrate this, we try to express the following case in terms of constraints and their update: On mouse down, we want to update the midpoint of our view and have the `left` and `right` parameters automatically updated (subject to the constraints already setup).
We create a parameter that we will use to represent the mouse coordinate.
```
Param mid = new Param(coordinate);
```
Then, we add a constraint that expresses the midpoint in terms of the parameters we already have.
```
solver.addConstraint(left + right == mid * CM(2.0));
```
Then, we specify that we intend to edit the midpoint. As we get updates, we tell the solver to satisfy all other constraints (admittedly our example is trivial).
```
solver.addEditVariable(mid, Priority.strong);
```
and finally
```
solver.flushVariableUpdates();
```
// Copyright 2016 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 'equation_member.dart';
import 'term.dart';
/// A member of a [Constraint] [Expression] that represent a constant at the
/// time the [Constraint] is added to the solver.
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
Expression asExpression() => new Expression(<Term>[], this.value);
@override
final double value;
@override
bool get isConstant => true;
}
/// Creates a [ConstantMember].
///
/// This is a convenience method to make cassowary expressions less verbose.
ConstantMember cm(double value) => new ConstantMember(value);
// Copyright 2016 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 'priority.dart';
import 'expression.dart';
/// Relationships between [Constraint] expressions.
///
/// A [Constraint] is created by specifying a relationship between two
/// expressions. The [Solver] tries to satisfy this relationship after the
/// [Constraint] has been added to it at a set priority.
enum Relation {
/// The relationship between the left and right hand sides of the expression
/// is `==`, (lhs == rhs).
equalTo,
/// The relationship between the left and right hand sides of the expression
/// is `<=`, (lhs <= rhs).
lessThanOrEqualTo,
/// The relationship between the left and right hand sides of the expression
/// is `>=`, (lhs => rhs).
greaterThanOrEqualTo,
}
/// A relationship between two expressions (represented by [Expression]) that
/// the [Solver] tries to hold true. In case of ambiguities, the [Solver] will
/// use priorities to determine [Constraint] precedence. Once a [Constraint] is
/// added to the [Solver], this [Priority] cannot be changed.
class Constraint {
/// Creates a new [Constraint] by specifying a single [Expression]. This
/// assumes that the right hand side [Expression] is the constant zero.
/// (`<expression> <relation> <0>`)
Constraint(this.expression, this.relation);
/// The [Relation] between a [Constraint] [Expression] and zero.
final Relation relation;
/// The [Constraint] [Expression]. The [Expression] on the right hand side of
/// constraint must be zero. If the [Expression] on the right is not zero,
/// it must be negated from the left hand [Expression] before a [Constraint]
/// can be created.
final Expression expression;
/// The [Constraint] [Priority]. The [Priority] can only be modified when the
/// [Constraint] is being created. Once it is added to the solver,
/// modifications to the [Constraint] [Priority] will have no effect on the
/// how the solver evaluates the constraint.
double priority = Priority.required;
/// The operator `|` is overloaded as a convenience so that constraint
/// priorities can be specifed along with the [Constraint] expression.
///
/// For example: `ax + by + cx <= 0 | Priority.weak`. See [Priority].
Constraint operator |(double p) => this..priority = p;
@override
String toString() {
StringBuffer buffer = new StringBuffer();
buffer.write(expression.toString());
switch (relation) {
case Relation.equalTo:
buffer.write(' == 0 ');
break;
case Relation.greaterThanOrEqualTo:
buffer.write(' >= 0 ');
break;
case Relation.lessThanOrEqualTo:
buffer.write(' <= 0 ');
break;
}
buffer.write(' | priority = $priority');
if (priority == Priority.required)
buffer.write(' (required)');
return buffer.toString();
}
}
// Copyright 2016 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';
/// A member that can be used to construct an [Expression] that may be
/// used to create a constraint. This is to facilitate the easy creation of
/// constraints. The use of the operator overloads is completely optional and
/// is only meant as a convenience. The [Constraint] expressions can be created
/// by manually creating instance of [Constraint] variables, then terms and
/// combining those to create expression.
abstract class EquationMember {
/// The representation of this member after it is hoisted to be an
/// expression.
Expression asExpression();
/// Returns if this member is a constant. Constant members can be combined
/// more easily without making the expression non-linear. This makes them
/// easier to use with multiplication and division operators. Constant
/// expression that have zero value may also eliminate other expressions from
/// the solver when used with the multiplication operator.
bool get isConstant;
/// The current constant value of this member. After a [Solver] flush, this is
/// value read by entities outside the [Solver].
double get value;
/// Creates a [Constraint] by using this member as the left hand side
/// expression and the argument as the right hand side [Expression] of a
/// [Constraint] with a [Relation.greaterThanOrEqualTo] relationship between
/// the two.
///
/// For example: `right - left >= cm(200.0)` would read, "the width of the
/// object is at least 200."
Constraint operator >=(EquationMember m) => asExpression() >= m;
/// Creates a [Constraint] by using this member as the left hand side
/// expression and the argument as the right hand side [Expression] of a
/// [Constraint] with a [Relation.lessThanOrEqualTo] relationship between the
/// two.
///
/// For example: `rightEdgeOfA <= leftEdgeOfB` would read, "the entities A and
/// B are stacked left to right."
Constraint operator <=(EquationMember m) => asExpression() <= m;
/// Creates a [Constraint] by using this member as the left hand side
/// expression and the argument as the right hand side [Expression] of a
/// [Constraint] with a [Relation.equalTo] relationship between the two.
///
/// For example: `topEdgeOfBoxA + cm(10.0) == topEdgeOfBoxB` woud read,
/// "the entities A and B have a padding on top of 10."
Constraint equals(EquationMember m) => asExpression().equals(m);
/// Creates a [Expression] by adding this member with the argument. Both
/// members may need to be hoisted to expressions themselves before this can
/// occur.
///
/// For example: `(left + right) / cm(2.0)` can be used as an [Expression]
/// equivalent of the `midPointX` property.
Expression operator +(EquationMember m) => asExpression() + m;
/// Creates a [Expression] by subtracting the argument from this member. Both
/// members may need to be hoisted to expressions themselves before this can
/// occur.
///
/// For example: `right - left` can be used as an [Expression]
/// equivalent of the `width` property.
Expression operator -(EquationMember m) => asExpression() - m;
/// Creates a [Expression] by multiplying this member with the argument. Both
/// members may need to be hoisted to expressions themselves before this can
/// occur.
///
/// Warning: This operation may throw a [ParserException] if the resulting
/// expression is no longer linear. This is because a non-linear [Expression]
/// may not be used to create a constraint. At least one of the [Expression]
/// members must evaluate to a constant.
///
/// For example: `((left + right) >= (cm(2.0) * mid)` declares a `midpoint`
/// constraint. Notice that at least one the members of the right hand
/// `Expression` is a constant.
Expression operator *(EquationMember m) => asExpression() * m;
/// Creates a [Expression] by dividing this member by the argument. Both
/// members may need to be hoisted to expressions themselves before this can
/// occur.
///
/// Warning: This operation may throw a [ParserException] if the resulting
/// expression is no longer linear. This is because a non-linear [Expression]
/// may not be used to create a constraint. The divisor (i.e. the argument)
/// must evaluate to a constant.
///
/// For example: `((left + right) / cm(2.0) >= mid` declares a `midpoint`
/// constraint. Notice that the divisor of the left hand [Expression] is a
/// constant.
Expression operator /(EquationMember m) => asExpression() / m;
}
// Copyright 2016 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 '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;
}
/// The representation of a linear [Expression] that can be used to create a
/// constraint.
class Expression extends EquationMember {
/// Creates a new linear [Expression] using the given terms and constant.
Expression(this.terms, this.constant);
/// Creates a new linear [Expression] by copying the terms and constant of
/// another expression.
Expression.fromExpression(Expression expr)
: this.terms = new List<Term>.from(expr.terms),
this.constant = expr.constant;
/// The list of terms in this linear expression. Terms in a an [Expression]
/// must have only one [Variable] (indeterminate) and a degree of 1.
final List<Term> terms;
/// The constant portion of this linear expression. This is just another
/// [Term] with no [Variable].
final double constant;
@override
Expression asExpression() => this;
@override
bool get isConstant => terms.isEmpty;
@override
double get value => terms.fold(constant, (double value, Term term) => value + term.value);
@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);
}
Constraint _createConstraint(EquationMember /* rhs */ value, Relation relation) {
if (value is ConstantMember) {
return new Constraint(
new Expression(new List<Term>.from(terms), constant - value.value),
relation
);
}
if (value is Param) {
List<Term> newTerms = new List<Term>.from(terms)
..add(new Term(value.variable, -1.0));
return new Constraint(new Expression(newTerms, constant), relation);
}
if (value is Term) {
List<Term> newTerms = new List<Term>.from(terms)
..add(new Term(value.variable, -value.coefficient));
return new Constraint(new Expression(newTerms, constant), relation);
}
if (value is Expression) {
List<Term> newTerms = value.terms.fold(
new List<Term>.from(terms),
(List<Term> list, Term t) {
return list..add(new Term(t.variable, -t.coefficient));
}
);
return new Constraint(
new Expression(newTerms, constant - value.constant),
relation
);
}
assert(false);
return null;
}
@override
Expression operator +(EquationMember m) {
if (m is ConstantMember)
return new Expression(new List<Term>.from(terms), constant + m.value);
if (m is Param) {
return new Expression(
new List<Term>.from(terms)..add(new Term(m.variable, 1.0)),
constant
);
}
if (m is Term)
return new Expression(new List<Term>.from(terms)..add(m), constant);
if (m is Expression) {
return new Expression(
new List<Term>.from(terms)..addAll(m.terms),
constant + m.constant
);
}
assert(false);
return null;
}
@override
Expression operator -(EquationMember m) {
if (m is ConstantMember)
return new Expression(new List<Term>.from(terms), constant - m.value);
if (m is Param) {
return new Expression(
new List<Term>.from(terms)..add(new Term(m.variable, -1.0)),
constant
);
}
if (m is Term) {
return new Expression(new List<Term>.from(terms)
..add(new Term(m.variable, -m.coefficient)), constant);
}
if (m is Expression) {
List<Term> copiedTerms = new List<Term>.from(terms);
for (Term t in m.terms)
copiedTerms.add(new Term(t.variable, -t.coefficient));
return new Expression(copiedTerms, constant - m.constant);
}
assert(false);
return null;
}
@override
Expression operator *(EquationMember m) {
_Multiplication args = _findMulitplierAndMultiplicand(m);
if (args == null) {
throw new ParserException(
'Could not find constant multiplicand or multiplier',
<EquationMember>[this, m]
);
}
return args.multiplier._applyMultiplicand(args.multiplicand);
}
@override
Expression operator /(EquationMember m) {
if (!m.isConstant) {
throw new ParserException(
'The divisor was not a constant expression', <EquationMember>[this, m]);
}
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;
}
Expression _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();
terms.forEach((Term t) => buffer.write('$t'));
if (constant != 0.0) {
buffer.write(constant.sign > 0.0 ? '+' : '-');
buffer.write(constant.abs());
}
return buffer.toString();
}
}
// Copyright 2016 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 'equation_member.dart';
import 'expression.dart';
import 'term.dart';
/// A [Variable] inside the layout [Solver]. It represents an indeterminate
/// in the [Expression] that is used to create the [Constraint]. If any entity
/// is interested in watching updates to the value of this indeterminate,
/// it can assign a watcher as the `owner`.
class Variable {
/// Creates a new [Variable] with the given constant value.
Variable(this.value);
/// The current value of the variable.
double value;
/// An optional name given to the variable. This is useful in debugging
/// [Solver] state.
String name;
/// Variables represent state inside the solver. This state is usually of
/// interest to some entity outside the solver. Such entities can (optionally)
/// associate themselves with these variables. This means that when solver
/// is flushed, it is easy to obtain a reference to the entity the variable
/// is associated with.
Param get owner => _owner;
Param _owner;
/// Used by the [Solver] to apply updates to this variable. Only updated
/// variables show up in [Solver] flush results.
bool applyUpdate(double updated) {
bool res = updated != value;
value = updated;
return res;
}
}
/// A [Param] wraps a [Variable] and makes it suitable to be used in an
/// expression.
class Param extends EquationMember {
/// Creates a new [Param] with the specified constant value.
Param([double value = 0.0]) : variable = new Variable(value) {
variable._owner = this;
}
/// Creates a new [Param] with the specified constant value that is tied
/// to some object outside the solver.
Param.withContext(dynamic context, [double value = 0.0])
: variable = new Variable(value),
context = context {
variable._owner = this;
}
/// The [Variable] associated with this [Param].
final Variable variable;
/// Some object outside the [Solver] that is associated with this Param.
dynamic context;
@override
Expression asExpression() => new Expression(<Term>[new Term(variable, 1.0)], 0.0);
@override
bool get isConstant => false;
@override
double get value => variable.value;
/// The name of the [Variable] associated with this [Param].
String get name => variable.name;
/// Set the name of the [Variable] associated with this [Param].
set name(String name) { variable.name = name; }
}
// Copyright 2016 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 'equation_member.dart';
/// Exception thrown when attempting to create a non-linear expression.
///
/// During the creation of constraints or expressions using the overloaded
/// operators, it may be possible to end up with non-linear expressions. Such
/// expressions are not suitable for [Constraint] creation because the [Solver]
/// will reject the same. A [ParserException] is thrown when a developer tries
/// to create such an expression.
///
/// The only cases where this is possible is when trying to multiply two
/// expressions where at least one of them is not a constant expression, or,
/// when trying to divide two expressions where the divisor is not constant.
class ParserException implements Exception {
/// Creates a new [ParserException] with a given message and a list of the
/// offending member for debugging purposes.
ParserException(this.message, this.members);
/// A detailed message describing the exception.
final String message;
/// The members that caused the exception.
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';
}
}
// Copyright 2016 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 '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;
result += max(0.0, min(1e3, b)) * 1e3;
result += max(0.0, min(1e3, c));
return result;
}
}
// Copyright 2016 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;
/// The result when the operation was successful.
static const Result success =
const Result._('Success', isError: false);
/// The result when the [Constraint] could not be added to the [Solver]
/// because it was already present in the solver.
static const Result duplicateConstraint =
const Result._('Duplicate constraint');
/// The result when the [Constraint] could not be added to the [Solver]
/// because it was unsatisfiable. Try lowering the [Priority] of the
/// [Constraint] and try again.
static const Result unsatisfiableConstraint =
const Result._('Unsatisfiable constraint');
/// The result when the [Constraint] could not be removed from the solver
/// because it was not present in the [Solver] to begin with.
static const Result unknownConstraint =
const Result._('Unknown constraint');
/// The result when could not add the edit [Variable] to the [Solver] because
/// it was already added to the [Solver] previously.
static const Result duplicateEditVariable =
const Result._('Duplicate edit variable');
/// The result when the [Constraint] constraint was added at an invalid
/// priority or an edit [Variable] was added at an invalid or required
/// priority.
static const Result badRequiredStrength =
const Result._('Bad required strength');
/// The result when the edit [Variable] could not be removed from the solver
/// because it was not present in the [Solver] to begin with.
static const Result unknownEditVariable =
const Result._('Unknown edit variable');
}
// Copyright 2016 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 '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 {
_Symbol(this.type);
final _SymbolType type;
}
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();
}
}
/// Solves cassowary constraints.
///
/// Typically clients will create a solver, [addConstraints], and then call
/// [flushUpdates] to actually solve the constraints.
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);
/// 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.
///
/// Check the [Result] returned to make sure the operation succeeded. Any
/// errors will be reported via the `message` property on the [Result].
///
/// Possible [Result]s:
///
/// * [Result.success]: All constraints successfully added.
/// * [Result.duplicateConstraint]: One of the constraints in the list was
/// already in the solver or the same constraint was specified multiple
/// times in the argument list. Remove the duplicates and try again.
/// * [Result.unsatisfiableConstraint]: One or more constraints were at
/// [Priority.required] but could not added because of conflicts with other
/// constraints at the same priority. Lower the priority of these
/// constraints and try again.
Result addConstraints(List<Constraint> constraints) {
_SolverBulkUpdate applier = (Constraint c) => addConstraint(c);
_SolverBulkUpdate undoer = (Constraint c) => removeConstraint(c);
return _bulkEdit(constraints, applier, undoer);
}
/// Attempts to add an individual [Constraint] to the solver.
///
/// Check the [Result] returned to make sure the operation succeeded. Any
/// errors will be reported via the `message` property on the [Result].
///
/// Possible [Result]s:
///
/// * [Result.success]: The constraint was successfully added.
/// * [Result.duplicateConstraint]: The constraint was already present in the
/// solver.
/// * [Result.unsatisfiableConstraint]: The constraint was at
/// [Priority.required] but could not be added because of a conflict with
/// another constraint at that priority already in the solver. Try lowering
/// the priority of the constraint and try again.
Result addConstraint(Constraint constraint) {
if (_constraints.containsKey(constraint))
return Result.duplicateConstraint;
_Tag tag = new _Tag(new _Symbol(_SymbolType.invalid),
new _Symbol(_SymbolType.invalid));
_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);
}
/// Attempts to remove a list of constraints from the solver. Either all
/// constraints are removed or none. If more fine-grained control over the
/// removal is required (for example, not failing on removal of constraints
/// not already present in the solver), try removing the each [Constraint]
/// individually and check the result on each attempt.
///
/// Check the [Result] returned to make sure the operation succeeded. Any
/// errors will be reported via the `message` property on the [Result].
///
/// Possible [Result]s:
///
/// * [Result.success]: The constraints were successfully removed from the
/// solver.
/// * [Result.unknownConstraint]: One or more constraints in the list were
/// not in the solver. So there was nothing to remove.
Result removeConstraints(List<Constraint> constraints) {
_SolverBulkUpdate applier = (Constraint c) => removeConstraint(c);
_SolverBulkUpdate undoer = (Constraint c) => addConstraint(c);
return _bulkEdit(constraints, applier, undoer);
}
/// Attempt to remove an individual [Constraint] from the solver.
///
/// Check the [Result] returned to make sure the operation succeeded. Any
/// errors will be reported via the `message` property on the [Result].
///
/// Possible [Result]s:
///
/// * [Result.success]: The [Constraint] was successfully removed from the
/// solver.
/// * [Result.unknownConstraint]: The [Constraint] was not in the solver so
/// there was nothing to remove.
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 {
_Symbol leaving = _leavingSymbolForMarkerSymbol(tag.marker);
assert(leaving != null);
row = _rows.remove(leaving);
assert(row != null);
row.solveForSymbols(leaving, tag.marker);
_substitute(tag.marker, row);
}
return _optimizeObjectiveRow(_objective);
}
/// Returns whether the given [Constraint] is present in the solver.
bool hasConstraint(Constraint constraint) {
return _constraints.containsKey(constraint);
}
/// Adds a list of edit [Variable]s to the [Solver] at a given priority.
/// Either all edit [Variable] are added or none. No edit variables may be
/// added at `Priority.required`.
///
/// Check the [Result] returned to make sure the operation succeeded. Any
/// errors will be reported via the `message` property on the [Result].
///
/// Possible [Result]s:
///
/// * [Result.success]: The edit variables were successfully added to [Solver]
/// at the specified priority.
/// * [Result.duplicateEditVariable]: One of more edit variables were already
/// present in the [Solver] or the same edit variables were specified
/// multiple times in the list. Remove the duplicates and try again.
/// * [Result.badRequiredStrength]: The edit variables were added at
/// [Priority.required]. Edit variables are used to
/// suggest values to the solver. Since suggestions can't be mandatory,
/// priorities cannot be [Priority.required]. If variable values need to be
/// fixed at [Priority.required], add that preference as a constraint. This
/// allows the solver to check for satisfiability of the constraint (w.r.t
/// other constraints at [Priority.required]) and check for duplicates.
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);
}
/// Attempt to add a single edit [Variable] to the [Solver] at the given
/// priority. No edit variables may be added to the [Solver] at
/// `Priority.required`.
///
/// Check the [Result] returned to make sure the operation succeeded. Any
/// errors will be reported via the `message` property on the [Result].
///
/// Possible [Result]s:
///
/// * [Result.success]: The edit variable was successfully added to [Solver]
/// at the specified priority.
/// * [Result.duplicateEditVariable]: The edit variable was already present
/// in the [Solver].
/// * [Result.badRequiredStrength]: The edit variable was added at
/// [Priority.required]. Edit variables are used to
/// suggest values to the solver. Since suggestions can't be mandatory,
/// priorities cannot be [Priority.required]. If variable values need to be
/// fixed at [Priority.required], add that preference as a constraint. This
/// allows the solver to check for satisfiability of the constraint (w.r.t
/// other constraints at [Priority.required]) and check for duplicates.
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(<Term>[new Term(variable, 1.0)], 0.0),
Relation.equalTo
);
constraint.priority = priority;
assert(addConstraint(constraint) == Result.success);
_EditInfo info = new _EditInfo();
info.tag = _constraints[constraint];
info.constraint = constraint;
info.constant = 0.0;
_edits[variable] = info;
return Result.success;
}
/// Attempt the remove the list of edit [Variable] from the solver. Either
/// all the specified edit variables are removed or none.
///
/// Check the [Result] returned to make sure the operation succeeded. Any
/// errors will be reported via the `message` property on the [Result].
///
/// Possible [Result]s:
///
/// * [Result.success]: The edit variables were successfully removed from the
/// [Solver].
/// * [Result.unknownEditVariable]: One of more edit variables were not
/// already present in the solver.
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);
}
/// Attempt to remove the specified edit [Variable] from the solver.
///
/// Check the [Result] returned to make sure the operation succeeded. Any
/// errors will be reported via the `message` property on the [Result].
///
/// Possible [Result]s:
///
/// * [Result.success]: The edit variable was successfully removed from the
/// solver.
/// * [Result.unknownEditVariable]: The edit variable was not present in the
/// solver. There was nothing to remove.
Result removeEditVariable(Variable variable) {
_EditInfo info = _edits[variable];
if (info == null)
return Result.unknownEditVariable;
assert(removeConstraint(info.constraint) == Result.success);
_edits.remove(variable);
return Result.success;
}
/// Returns whether the given edit [Variable] is present in the solver.
bool hasEditVariable(Variable variable) {
return _edits.containsKey(variable);
}
/// Suggest an updated value for the edit variable. The edit variable
/// must already be added to the solver.
///
/// Suggestions update values of variables within the [Solver] but take into
/// account all the constraints already present in the [Solver]. Depending
/// on the constraints, the value of the [Variable] may not actually be the
/// value specified. The actual value can be read after the next
/// `flushUpdates` call. Since these updates are merely "suggestions", they
/// cannot be at `Priority.required`.
///
///
/// Check the [Result] returned to make sure the operation succeeded. Any
/// errors will be reported via the `message` property on the [Result].
///
/// Possible [Result]s:
///
/// * [Result.success]: The suggestion was successfully applied to the
/// variable within the solver.
/// * [Result.unknownEditVariable]: The edit variable was not already present
/// in the [Solver]. So the suggestion could not be applied. Add this edit
/// variable to the solver and then apply the value again. If you have
/// already added the variable to the [Solver], make sure the [Result]
/// was `Result.success`.
Result suggestValueForVariable(Variable variable, double value) {
if (!_edits.containsKey(variable))
return Result.unknownEditVariable;
_suggestValueForEditInfoWithoutDualOptimization(_edits[variable], value);
return _dualOptimize();
}
/// Flush the results of solver. The set of all `context` objects associated
/// with variables in the [Solver] is returned. If a [Variable] does not
/// contain an associated context, its updates are ignored.
///
/// The addition and removal of constraints and edit variables to and from the
/// [Solver] as well as the application of suggestions to the added edit
/// variables leads to the modification of values on a lot of other variables.
/// External entities that rely on the values of the variables within the
/// [Solver] can read these updates in one shot by "flushing" out these
/// updates.
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);
_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);
tag.marker = slack;
row.insertSymbol(slack, coefficient);
if (constraint.priority < Priority.required) {
_Symbol error = new _Symbol(_SymbolType.error);
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);
_Symbol errMinus = new _Symbol(_SymbolType.error);
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);
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);
}
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);
_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;
_Symbol leaving = _leavingSymbolForEnteringSymbol(entering);
assert(leaving != null);
_Row row = _rows.remove(leaving);
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);
}
_Symbol _leavingSymbolForEnteringSymbol(_Symbol entering) {
double ratio = double.MAX_FINITE;
_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 = symbol;
}
}
}
});
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);
}
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);
}
}
_Symbol _leavingSymbolForMarkerSymbol(_Symbol marker) {
double r1 = double.MAX_FINITE;
double r2 = double.MAX_FINITE;
_Symbol 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 = symbol;
} else if (c < 0.0) {
double r = -row.constant / c;
if (r < r1) {
r1 = r;
first = symbol;
}
} else {
double r = row.constant / c;
if (r < r2) {
r2 = r;
second = symbol;
}
}
});
return first ?? second ?? 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.isNotEmpty) {
_Symbol leaving = _infeasibleRows.removeLast();
_Row row = _rows[leaving];
if (row != null && row.constant < 0.0) {
_Symbol entering = _dualEnteringSymbolForRow(row);
assert(entering.type != _SymbolType.invalid);
_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);
}
@override
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();
}
}
// Copyright 2016 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 'equation_member.dart';
import 'expression.dart';
import 'param.dart';
/// Represents a single term in an expression. This term contains a single
/// indeterminate and has degree 1.
class Term extends EquationMember {
/// Creates term with the given [Variable] and coefficient.
Term(this.variable, this.coefficient);
/// The [Variable] (or indeterminate) portion of this term. Variables are
/// usually tied to an opaque object (via its `context` property). On a
/// [Solver] flush, these context objects of updated variables are returned by
/// the solver. An external entity can then choose to interpret these values
/// in what manner it sees fit.
final Variable variable;
/// The coefficient of this term. Before addition of the [Constraint] to the
/// solver, terms with a zero coefficient are dropped.
final double coefficient;
@override
Expression asExpression() =>
new Expression(<Term>[new Term(this.variable, this.coefficient)], 0.0);
@override
bool get isConstant => false;
@override
double get value => coefficient * variable.value;
@override
String toString() {
StringBuffer buffer = new StringBuffer();
buffer.write(coefficient.sign > 0.0 ? "+" : "-");
if (coefficient.abs() != 1.0) {
buffer.write(coefficient.abs());
buffer.write("*");
}
buffer.write(variable);
return buffer.toString();
}
}
// Copyright 2016 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 'package:flutter/cassowary.dart' as al; // "auto layout"
import 'package:flutter/foundation.dart';
import 'box.dart';
import 'object.dart';
/// Hosts the edge parameters and vends useful methods to construct expressions
/// for constraints. Also sets up and manages implicit constraints and edit
/// variables.
class AutoLayoutRect {
/// Creates parameters for a rectangle for use with auto layout.
AutoLayoutRect()
: left = new al.Param(),
right = new al.Param(),
top = new al.Param(),
bottom = new al.Param();
/// A parameter that represents the left edge of the rectangle.
final al.Param left;
/// A parameter that represents the right edge of the rectangle.
final al.Param right;
/// A parameter that represents the top edge of the rectangle.
final al.Param top;
/// A parameter that represents the bottom edge of the rectangle.
final al.Param bottom;
/// An expression that represents the horizontal extent of the rectangle.
al.Expression get width => right - left;
/// An expression that represents the vertical extent of the rectangle.
al.Expression get height => bottom - top;
/// An expression that represents halfway between the left and right edges of the rectangle.
al.Expression get horizontalCenter => (left + right) / al.cm(2.0);
/// An expression that represents halfway between the top and bottom edges of the rectangle.
al.Expression get verticalCenter => (top + bottom) / al.cm(2.0);
/// Constraints that require that this rect contains the given rect.
List<al.Constraint> contains(AutoLayoutRect other) {
return <al.Constraint>[
other.left >= left,
other.right <= right,
other.top >= top,
other.bottom <= bottom,
];
}
}
/// Parent data for use with [RenderAutoLayout].
class AutoLayoutParentData extends ContainerBoxParentDataMixin<RenderBox> {
/// Creates parent data associated with the given render box.
AutoLayoutParentData(this._renderBox);
final RenderBox _renderBox;
/// Parameters that represent the size and position of the render box.
AutoLayoutRect get rect => _rect;
AutoLayoutRect _rect;
set rect(AutoLayoutRect value) {
if (_rect == value)
return;
if (_rect != null)
_removeImplicitConstraints();
_rect = value;
if (_rect != null)
_addImplicitConstraints();
}
BoxConstraints get _constraintsFromSolver {
return new BoxConstraints.tightFor(
width: _rect.right.value - _rect.left.value,
height: _rect.bottom.value - _rect.top.value
);
}
Offset get _offsetFromSolver {
return new Offset(_rect.left.value, _rect.top.value);
}
List<al.Constraint> _implicitConstraints;
void _addImplicitConstraints() {
assert(_renderBox != null);
if (_renderBox.parent == null || _rect == null)
return;
final List<al.Constraint> implicit = _constructImplicitConstraints();
assert(implicit != null && implicit.isNotEmpty);
assert(_renderBox.parent is RenderAutoLayout);
final RenderAutoLayout parent = _renderBox.parent;
final al.Result result = parent._solver.addConstraints(implicit);
assert(result == al.Result.success);
parent.markNeedsLayout();
_implicitConstraints = implicit;
}
void _removeImplicitConstraints() {
assert(_renderBox != null);
if (_renderBox.parent == null || _implicitConstraints == null || _implicitConstraints.isEmpty)
return;
assert(_renderBox.parent is RenderAutoLayout);
final RenderAutoLayout parent = _renderBox.parent;
final al.Result result = parent._solver.removeConstraints(_implicitConstraints);
assert(result == al.Result.success);
parent.markNeedsLayout();
_implicitConstraints = null;
}
/// Returns the set of implicit constraints that need to be applied to all
/// instances of this class when they are moved into a render object with an
/// active solver. If no implicit constraints needs to be applied, the object
/// may return null.
List<al.Constraint> _constructImplicitConstraints() {
return <al.Constraint>[
_rect.left >= al.cm(0.0), // The left edge must be positive.
_rect.right >= _rect.left, // Width must be positive.
// TODO(chinmay): Check whether we need something similar for the top and
// bottom.
];
}
}
/// Subclass to control the layout of a [RenderAutoLayout].
abstract class AutoLayoutDelegate {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const AutoLayoutDelegate();
/// Returns the constraints to use when computing layout.
///
/// The `parent` argument contains the parameters for the parent's position
/// and size. Typical implementations will return constraints that determine
/// the size and position of each child.
///
/// The delegate interface does not provide a mechanism for obtaining the
/// parameters for children. Subclasses are expected to obtain those
/// parameters through some other mechanism.
List<al.Constraint> getConstraints(AutoLayoutRect parent);
/// Override this method to return true when new constraints need to be generated.
bool shouldUpdateConstraints(@checked AutoLayoutDelegate oldDelegate);
}
/// A render object that uses the cassowary constraint solver to automatically size and position children.
class RenderAutoLayout extends RenderBox
with ContainerRenderObjectMixin<RenderBox, AutoLayoutParentData>,
RenderBoxContainerDefaultsMixin<RenderBox, AutoLayoutParentData> {
/// Creates a render box that automatically sizes and positions its children.
RenderAutoLayout({
AutoLayoutDelegate delegate,
List<RenderBox> children
}) : _delegate = delegate, _needToUpdateConstraints = (delegate != null) {
_solver.addEditVariables(<al.Variable>[
_rect.left.variable,
_rect.right.variable,
_rect.top.variable,
_rect.bottom.variable
], al.Priority.required - 1);
addAll(children);
}
/// The delegate that generates constraints for the layout.
///
/// If the new delegate is the same as the previous one, this does nothing.
///
/// If the new delegate is the same class as the previous one, then the new
/// delegate has its [AutoLayoutDelegate.shouldUpdateConstraints] called; if
/// the result is `true`, then the delegate will be called.
///
/// If the new delegate is a different class than the previous one, then the
/// delegate will be called.
///
/// If the delgate is null, the layout is unconstrained.
AutoLayoutDelegate get delegate => _delegate;
AutoLayoutDelegate _delegate;
set delegate(AutoLayoutDelegate newDelegate) {
if (_delegate == newDelegate)
return;
AutoLayoutDelegate oldDelegate = _delegate;
_delegate = newDelegate;
if (newDelegate == null) {
assert(oldDelegate != null);
_needToUpdateConstraints = true;
markNeedsLayout();
} else if (oldDelegate == null ||
newDelegate.runtimeType != oldDelegate.runtimeType ||
newDelegate.shouldUpdateConstraints(oldDelegate)) {
_needToUpdateConstraints = true;
markNeedsLayout();
}
}
bool _needToUpdateConstraints;
final AutoLayoutRect _rect = new AutoLayoutRect();
final al.Solver _solver = new al.Solver();
final List<al.Constraint> _explicitConstraints = new List<al.Constraint>();
void _setExplicitConstraints(List<al.Constraint> constraints) {
assert(constraints != null);
if (constraints.isEmpty)
return;
if (_solver.addConstraints(constraints) == al.Result.success)
_explicitConstraints.addAll(constraints);
}
void _clearExplicitConstraints() {
if (_explicitConstraints.isEmpty)
return;
if (_solver.removeConstraints(_explicitConstraints) == al.Result.success)
_explicitConstraints.clear();
}
@override
void adoptChild(RenderObject child) {
// Make sure to call super first to setup the parent data
super.adoptChild(child);
final AutoLayoutParentData childParentData = child.parentData;
childParentData._addImplicitConstraints();
assert(child.parentData == childParentData);
}
@override
void dropChild(RenderObject child) {
final AutoLayoutParentData childParentData = child.parentData;
childParentData._removeImplicitConstraints();
assert(child.parentData == childParentData);
super.dropChild(child);
}
@override
void setupParentData(RenderObject child) {
if (child.parentData is! AutoLayoutParentData)
child.parentData = new AutoLayoutParentData(child);
}
@override
bool get sizedByParent => true;
@override
void performResize() {
size = constraints.biggest;
}
Size _previousSize;
@override
void performLayout() {
bool needToFlushUpdates = false;
if (_needToUpdateConstraints) {
_clearExplicitConstraints();
if (_delegate != null)
_setExplicitConstraints(_delegate.getConstraints(_rect));
_needToUpdateConstraints = false;
needToFlushUpdates = true;
}
if (size != _previousSize) {
_solver
..suggestValueForVariable(_rect.left.variable, 0.0)
..suggestValueForVariable(_rect.top.variable, 0.0)
..suggestValueForVariable(_rect.bottom.variable, size.height)
..suggestValueForVariable(_rect.right.variable, size.width);
_previousSize = size;
needToFlushUpdates = true;
}
if (needToFlushUpdates)
_solver.flushUpdates();
RenderBox child = firstChild;
while (child != null) {
final AutoLayoutParentData childParentData = child.parentData;
child.layout(childParentData._constraintsFromSolver);
childParentData.offset = childParentData._offsetFromSolver;
assert(child.parentData == childParentData);
child = childParentData.nextSibling;
}
}
@override
bool hitTestChildren(HitTestResult result, { Point position }) {
return defaultHitTestChildren(result, position: position);
}
@override
void paint(PaintingContext context, Offset offset) {
defaultPaint(context, offset);
}
}
// Copyright 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 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'framework.dart';
export 'package:flutter/rendering.dart' show
AutoLayoutRect,
AutoLayoutDelegate;
/// A widget that uses the cassowary constraint solver to automatically size and position children.
class AutoLayout extends MultiChildRenderObjectWidget {
/// Creates a widget that uses the cassowary constraint solver to automatically size and position children.
AutoLayout({
Key key,
this.delegate,
List<Widget> children: const <Widget>[]
}) : super(key: key, children: children);
/// The delegate that generates constraints for the layout.
///
/// If the delgate is null, the layout is unconstrained.
final AutoLayoutDelegate delegate;
@override
RenderAutoLayout createRenderObject(BuildContext context) => new RenderAutoLayout(delegate: delegate);
@override
void updateRenderObject(BuildContext context, RenderAutoLayout renderObject) {
renderObject.delegate = delegate;
}
}
/// A widget that provides constraints for a child of an [AutoLayout] widget.
///
/// An [AutoLayoutChild] widget must be a descendant of an [AutoLayout], and
/// the path from the [AutoLayoutChild] widget to its enclosing [AutoLayout]
/// must contain only [StatelessWidget]s or [StatefulWidget]s (not other kinds
/// of widgets, like [RenderObjectWidget]s).
class AutoLayoutChild extends ParentDataWidget<AutoLayout> {
/// Creates a widget that provides constraints for a child of an [AutoLayout] widget.
///
/// The object identity of the [rect] argument must be unique among children
/// of a given [AutoLayout] widget.
AutoLayoutChild({
AutoLayoutRect rect,
@required Widget child
}) : rect = rect,
super(key: rect != null ? new ObjectKey(rect) : null, child: child);
/// The constraints to use for this child.
///
/// The object identity of the [rect] object must be unique among children of
/// a given [AutoLayout] widget.
///
/// If null, the child's size and position are unconstrained.
final AutoLayoutRect rect;
@override
void applyParentData(RenderObject renderObject) {
assert(renderObject.parentData is AutoLayoutParentData);
final AutoLayoutParentData parentData = renderObject.parentData;
// AutoLayoutParentData filters out redundant writes and marks needs layout
// as appropriate.
parentData.rect = rect;
}
}
......@@ -10,7 +10,6 @@ library widgets;
export 'src/widgets/animated_cross_fade.dart';
export 'src/widgets/animated_size.dart';
export 'src/widgets/app.dart';
export 'src/widgets/auto_layout.dart';
export 'src/widgets/banner.dart';
export 'src/widgets/basic.dart';
export 'src/widgets/binding.dart';
......
// Copyright 2016 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 'package:test/test.dart';
import 'package:flutter/cassowary.dart';
void main() {
test('variable', () {
Param v = new Param(22.0);
expect(v.value, 22);
});
test('variable1', () {
Param v = new Param(22.0);
expect((v + cm(22.0)).value, 44.0);
expect((v - cm(20.0)).value, 2.0);
});
test('term', () {
Term t = new Term(new Variable(22.0), 2.0);
expect(t.value, 44);
});
test('expression', () {
List<Term> terms = <Term>[
new Term(new Variable(22.0), 2.0),
new Term(new Variable(1.0), 1.0),
];
Expression e = new Expression(terms, 40.0);
expect(e.value, 85.0);
});
test('expression1', () {
Param v1 = new Param(10.0);
Param v2 = new Param(10.0);
Param v3 = new Param(22.0);
expect(v1 is Param, true);
expect(v1 + cm(20.0) is Expression, true);
expect(v1 + v2 is Expression, true);
expect((v1 + v2).value, 20.0);
expect((v1 - v2).value, 0.0);
expect((v1 + v2 + v3) is Expression, true);
expect((v1 + v2 + v3).value, 42.0);
});
test('expression2', () {
Expression e = new Param(10.0) + cm(5.0);
expect(e.value, 15.0);
expect(e is Expression, true);
// Constant
expect((e + cm(2.0)) is Expression, true);
expect((e + cm(2.0)).value, 17.0);
expect((e - cm(2.0)) is Expression, true);
expect((e - cm(2.0)).value, 13.0);
expect(e.value, 15.0);
// Param
Param v = new Param(2.0);
expect((e + v) is Expression, true);
expect((e + v).value, 17.0);
expect((e - v) is Expression, true);
expect((e - v).value, 13.0);
expect(e.value, 15.0);
// Term
Term t = new Term(v.variable, 2.0);
expect((e + t) is Expression, true);
expect((e + t).value, 19.0);
expect((e - t) is Expression, true);
expect((e - t).value, 11.0);
expect(e.value, 15.0);
// Expression
Expression e2 = new Param(7.0) + new Param(3.0);
expect((e + e2) is Expression, true);
expect((e + e2).value, 25.0);
expect((e - e2) is Expression, true);
expect((e - e2).value, 5.0);
expect(e.value, 15.0);
});
test('term2', () {
Term t = new Term(new Variable(12.0), 1.0);
// Constant
ConstantMember c = cm(2.0);
expect((t + c) is Expression, true);
expect((t + c).value, 14.0);
expect((t - c) is Expression, true);
expect((t - c).value, 10.0);
// Variable
Param v = new Param(2.0);
expect((t + v) is Expression, true);
expect((t + v).value, 14.0);
expect((t - v) is Expression, true);
expect((t - v).value, 10.0);
// Term
Term t2 = new Term(new Variable(1.0), 2.0);
expect((t + t2) is Expression, true);
expect((t + t2).value, 14.0);
expect((t - t2) is Expression, true);
expect((t - t2).value, 10.0);
// Expression
Expression exp = new Param(1.0) + cm(1.0);
expect((t + exp) is Expression, true);
expect((t + exp).value, 14.0);
expect((t - exp) is Expression, true);
expect((t - exp).value, 10.0);
});
test('variable3', () {
Param v = new Param(3.0);
// Constant
ConstantMember c = cm(2.0);
expect((v + c) is Expression, true);
expect((v + c).value, 5.0);
expect((v - c) is Expression, true);
expect((v - c).value, 1.0);
// Variable
Param v2 = new Param(2.0);
expect((v + v2) is Expression, true);
expect((v + v2).value, 5.0);
expect((v - v2) is Expression, true);
expect((v - v2).value, 1.0);
// Term
Term t2 = new Term(new Variable(1.0), 2.0);
expect((v + t2) is Expression, true);
expect((v + t2).value, 5.0);
expect((v - t2) is Expression, true);
expect((v - t2).value, 1.0);
// Expression
Expression exp = new Param(1.0) + cm(1.0);
expect(exp.terms.length, 1);
expect((v + exp) is Expression, true);
expect((v + exp).value, 5.0);
expect((v - exp) is Expression, true);
expect((v - exp).value, 1.0);
});
test('constantmember', () {
ConstantMember c = cm(3.0);
// Constant
ConstantMember c2 = cm(2.0);
expect((c + c2) is Expression, true);
expect((c + c2).value, 5.0);
expect((c - c2) is Expression, true);
expect((c - c2).value, 1.0);
// Variable
Param v2 = new Param(2.0);
expect((c + v2) is Expression, true);
expect((c + v2).value, 5.0);
expect((c - v2) is Expression, true);
expect((c - v2).value, 1.0);
// Term
Term t2 = new Term(new Variable(1.0), 2.0);
expect((c + t2) is Expression, true);
expect((c + t2).value, 5.0);
expect((c - t2) is Expression, true);
expect((c - t2).value, 1.0);
// Expression
Expression exp = new Param(1.0) + cm(1.0);
expect((c + exp) is Expression, true);
expect((c + exp).value, 5.0);
expect((c - exp) is Expression, true);
expect((c - exp).value, 1.0);
});
test('constraint2', () {
Param left = new Param(10.0);
Param right = new Param(100.0);
Constraint c = right - left >= cm(25.0);
expect(c is Constraint, true);
});
test('simple_multiplication', () {
// Constant
ConstantMember c = cm(20.0);
expect((c * cm(2.0)).value, 40.0);
// Variable
Param v = new Param(20.0);
expect((v * cm(2.0)).value, 40.0);
// Term
Term t = new Term(v.variable, 1.0);
expect((t * cm(2.0)).value, 40.0);
// Expression
Expression e = new Expression(<Term>[t], 0.0);
expect((e * cm(2.0)).value, 40.0);
});
test('simple_division', () {
// Constant
ConstantMember c = cm(20.0);
expect((c / cm(2.0)).value, 10.0);
// Variable
Param v = new Param(20.0);
expect((v / cm(2.0)).value, 10.0);
// Term
Term t = new Term(v.variable, 1.0);
expect((t / cm(2.0)).value, 10.0);
// Expression
Expression e = new Expression(<Term>[t], 0.0);
expect((e / cm(2.0)).value, 10.0);
});
test('full_constraints_setup', () {
Param left = new Param(2.0);
Param right = new Param(10.0);
Constraint c1 = right - left >= cm(20.0);
expect(c1 is Constraint, true);
expect(c1.expression.constant, -20.0);
expect(c1.relation, Relation.greaterThanOrEqualTo);
Constraint c2 = (right - left).equals(cm(30.0));
expect(c2 is Constraint, true);
expect(c2.expression.constant, -30.0);
expect(c2.relation, Relation.equalTo);
Constraint c3 = right - left <= cm(30.0);
expect(c3 is Constraint, true);
expect(c3.expression.constant, -30.0);
expect(c3.relation, Relation.lessThanOrEqualTo);
});
test('constraint_strength_update', () {
Param left = new Param(2.0);
Param right = new Param(10.0);
Constraint c = (right - left >= cm(200.0)) | 750.0;
expect(c is Constraint, true);
expect(c.expression.terms.length, 2);
expect(c.expression.constant, -200.0);
expect(c.priority, 750.0);
});
test('solver', () {
Solver s = new Solver();
Param left = new Param(2.0);
Param right = new Param(100.0);
Constraint c1 = right - left >= cm(200.0);
expect((right >= left) is Constraint, true);
expect(s.addConstraint(c1), Result.success);
});
test('constraint_complex', () {
Expression e = new Param(200.0) - new Param(100.0);
// Constant
Constraint c1 = e >= cm(50.0);
expect(c1 is Constraint, true);
expect(c1.expression.terms.length, 2);
expect(c1.expression.constant, -50.0);
// Variable
Constraint c2 = e >= new Param(2.0);
expect(c2 is Constraint, true);
expect(c2.expression.terms.length, 3);
expect(c2.expression.constant, 0.0);
// Term
Constraint c3 = e >= new Term(new Variable(2.0), 1.0);
expect(c3 is Constraint, true);
expect(c3.expression.terms.length, 3);
expect(c3.expression.constant, 0.0);
// Expression
Constraint c4 = e >= new Expression(<Term>[new Term(new Variable(2.0), 1.0)], 20.0);
expect(c4 is Constraint, true);
expect(c4.expression.terms.length, 3);
expect(c4.expression.constant, -20.0);
});
test('constraint_complex_non_exprs', () {
// Constant
Constraint c1 = cm(100.0) >= cm(50.0);
expect(c1 is Constraint, true);
expect(c1.expression.terms.length, 0);
expect(c1.expression.constant, 50.0);
// Variable
Constraint c2 = new Param(100.0) >= new Param(2.0);
expect(c2 is Constraint, true);
expect(c2.expression.terms.length, 2);
expect(c2.expression.constant, 0.0);
// Term
Term t = new Term(new Variable(100.0), 1.0);
Constraint c3 = t >= new Term(new Variable(2.0), 1.0);
expect(c3 is Constraint, true);
expect(c3.expression.terms.length, 2);
expect(c3.expression.constant, 0.0);
// Expression
Expression e = new Expression(<Term>[t], 0.0);
Constraint c4 = e >= new Expression(<Term>[new Term(new Variable(2.0), 1.0)], 20.0);
expect(c4 is Constraint, true);
expect(c4.expression.terms.length, 2);
expect(c4.expression.constant, -20.0);
});
test('constraint_update_in_solver', () {
Solver s = new Solver();
Param left = new Param(2.0);
Param right = new Param(100.0);
Constraint c1 = right - left >= cm(200.0);
Constraint c2 = right >= right;
expect(s.addConstraint(c1), Result.success);
expect(s.addConstraint(c1), Result.duplicateConstraint);
expect(s.removeConstraint(c2), Result.unknownConstraint);
expect(s.removeConstraint(c1), Result.success);
expect(s.removeConstraint(c1), Result.unknownConstraint);
});
test('test_multiplication_division_override', () {
ConstantMember c = cm(10.0);
Param v = new Param(c.value);
Term t = new Term(v.variable, 1.0);
Expression e = new Expression(<Term>[t], 0.0);
// Constant
expect((c * cm(10.0)).value, 100);
// Variable
expect((v * cm(10.0)).value, 100);
// Term
expect((t * cm(10.0)).value, 100);
// Expression
expect((e * cm(10.0)).value, 100);
// Constant
expect((c / cm(10.0)).value, 1);
// Variable
expect((v / cm(10.0)).value, 1);
// Term
expect((t / cm(10.0)).value, 1);
// Expression
expect((e / cm(10.0)).value, 1);
});
test('test_multiplication_division_exceptions', () {
ConstantMember c = cm(10.0);
Param v = new Param(c.value);
Term t = new Term(v.variable, 1.0);
Expression e = new Expression(<Term>[t], 0.0);
expect((c * c).value, 100);
expect(() => v * v, throwsA(const isInstanceOf<ParserException>()));
expect(() => v / v, throwsA(const isInstanceOf<ParserException>()));
expect(() => v * t, throwsA(const isInstanceOf<ParserException>()));
expect(() => v / t, throwsA(const isInstanceOf<ParserException>()));
expect(() => v * e, throwsA(const isInstanceOf<ParserException>()));
expect(() => v / e, throwsA(const isInstanceOf<ParserException>()));
expect(() => v * c, returnsNormally);
expect(() => v / c, returnsNormally);
});
test('edit_updates', () {
Solver s = new Solver();
Param left = new Param(0.0);
Param right = new Param(100.0);
Param mid = new Param(0.0);
Constraint c = left + right >= cm(2.0) * mid;
expect(s.addConstraint(c), Result.success);
expect(s.addEditVariable(mid.variable, 999.0), Result.success);
expect(
s.addEditVariable(mid.variable, 999.0), Result.duplicateEditVariable);
expect(s.removeEditVariable(mid.variable), Result.success);
expect(s.removeEditVariable(mid.variable), Result.unknownEditVariable);
});
test('bug1', () {
Param left = new Param(0.0);
Param right = new Param(100.0);
Param mid = new Param(0.0);
expect(((left + right) >= (cm(2.0) * mid)) is Constraint, true);
});
test('single_item', () {
Param left = new Param(-20.0);
Solver s = new Solver();
s.addConstraint(left >= cm(0.0));
s.flushUpdates();
expect(left.value, 0.0);
});
test('midpoints', () {
Param left = new Param(0.0)..name = "left";
Param right = new Param(0.0)..name = "right";
Param mid = new Param(0.0)..name = "mid";
Solver s = new Solver();
expect(s.addConstraint((right + left).equals(mid * cm(2.0))),
Result.success);
expect(s.addConstraint(right - left >= cm(100.0)), Result.success);
expect(s.addConstraint(left >= cm(0.0)), Result.success);
s.flushUpdates();
expect(left.value, 0.0);
expect(mid.value, 50.0);
expect(right.value, 100.0);
});
test('addition_of_multiple', () {
Param left = new Param(0.0);
Param right = new Param(0.0);
Param mid = new Param(0.0);
Solver s = new Solver();
Constraint c = (left >= cm(0.0));
expect(s.addConstraints(<Constraint>[
(left + right).equals(cm(2.0) * mid),
(right - left >= cm(100.0)),
c
]), Result.success);
expect(s.addConstraints(<Constraint>[(right >= cm(-20.0)), c]),
Result.duplicateConstraint);
});
test('edit_constraints', () {
Param left = new Param(0.0)..name = "left";
Param right = new Param(0.0)..name = "right";
Param mid = new Param(0.0)..name = "mid";
Solver s = new Solver();
expect(s.addConstraint((right + left).equals(mid * cm(2.0))),
Result.success);
expect(s.addConstraint(right - left >= cm(100.0)), Result.success);
expect(s.addConstraint(left >= cm(0.0)), Result.success);
expect(s.addEditVariable(mid.variable, Priority.strong), Result.success);
expect(s.suggestValueForVariable(mid.variable, 300.0), Result.success);
s.flushUpdates();
expect(left.value, 0.0);
expect(mid.value, 300.0);
expect(right.value, 600.0);
});
test('test_description', () {
Param left = new Param(0.0);
Param right = new Param(100.0);
Constraint c1 = right >= left;
Constraint c2 = right <= left;
Constraint c3 = right.equals(left);
Solver s = new Solver();
expect(s.addConstraint(c1), Result.success);
expect(s.addConstraint(c2), Result.success);
expect(s.addConstraint(c3), Result.success);
expect(s.toString() != null, true);
});
test('solution_with_optimize', () {
Param p1 = new Param();
Param p2 = new Param();
Param p3 = new Param();
Param container = new Param();
Solver solver = new Solver();
solver.addEditVariable(container.variable, Priority.strong);
solver.suggestValueForVariable(container.variable, 100.0);
solver.addConstraint((p1 >= cm(30.0)) | Priority.strong);
solver.addConstraint(p1.equals(p3) | Priority.medium);
solver.addConstraint(p2.equals(cm(2.0) * p1));
solver.addConstraint(container.equals(p1 + p2 + p3));
solver.flushUpdates();
expect(container.value, 100.0);
expect(p1.value, 30.0);
expect(p2.value, 60.0);
expect(p3.value, 10.0);
});
test('test_updates_collection', () {
Param left = new Param.withContext("left");
Param mid = new Param.withContext("mid");
Param right = new Param.withContext("right");
Solver s = new Solver();
expect(s.addEditVariable(mid.variable, Priority.strong), Result.success);
expect(s.addConstraint((mid * cm(2.0)).equals(left + right)),
Result.success);
expect(s.addConstraint(left >= cm(0.0)), Result.success);
expect(s.suggestValueForVariable(mid.variable, 50.0), Result.success);
Set<dynamic> updates = s.flushUpdates();
expect(updates.length, 2);
expect(left.value, 0.0);
expect(mid.value, 50.0);
expect(right.value, 100.0);
});
test('test_updates_collection_is_set', () {
Param left = new Param.withContext("a");
Param mid = new Param.withContext("a");
Param right = new Param.withContext("a");
Solver s = new Solver();
expect(s.addEditVariable(mid.variable, Priority.strong), Result.success);
expect(s.addConstraint((mid * cm(2.0)).equals(left + right)),
Result.success);
expect(s.addConstraint(left >= cm(10.0)), Result.success);
expect(s.suggestValueForVariable(mid.variable, 50.0), Result.success);
Set<dynamic> updates = s.flushUpdates();
expect(updates.length, 1);
expect(left.value, 10.0);
expect(mid.value, 50.0);
expect(right.value, 90.0);
});
test('param_context_non_final', () {
Param p = new Param.withContext("a");
p.context = "b";
expect(p.context, "b");
});
test('check_type_of_eq_result', () {
Param left = new Param();
Param right = new Param();
expect(left.equals(right).runtimeType, Constraint);
});
test('bulk_add_edit_variables', () {
Solver s = new Solver();
Param left = new Param(0.0);
Param right = new Param(100.0);
Param mid = new Param(0.0);
expect(s.addEditVariables(
<Variable>[left.variable, right.variable, mid.variable], 999.0), Result.success);
});
test('bulk_remove_constraints_and_variables', () {
Solver s = new Solver();
Param left = new Param(0.0);
Param right = new Param(100.0);
Param mid = new Param(0.0);
expect(s.addEditVariables(
<Variable>[left.variable, right.variable, mid.variable], 999.0), Result.success);
Constraint c1 = left <= mid;
Constraint c2 = mid <= right;
expect(s.addConstraints(<Constraint>[c1, c2]), Result.success);
expect(s.removeConstraints(<Constraint>[c1, c2]), Result.success);
expect(s.removeEditVariables(
<Variable>[left.variable, right.variable, mid.variable]), Result.success);
});
}
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