From f6a323620ec75cdea4d20a61fef8b0ee3707a278 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 22 Jun 2015 10:49:09 -0700 Subject: [PATCH 01/62] Initial Commit --- packages/cassowary/lib/cassowary.dart | 12 + packages/cassowary/lib/constant_member.dart | 66 +++++ packages/cassowary/lib/constraint.dart | 15 ++ packages/cassowary/lib/equation_member.dart | 7 + packages/cassowary/lib/expression.dart | 88 +++++++ packages/cassowary/lib/term.dart | 66 +++++ packages/cassowary/lib/variable.dart | 65 +++++ packages/cassowary/test/cassowary_test.dart | 262 ++++++++++++++++++++ 8 files changed, 581 insertions(+) create mode 100644 packages/cassowary/lib/cassowary.dart create mode 100644 packages/cassowary/lib/constant_member.dart create mode 100644 packages/cassowary/lib/constraint.dart create mode 100644 packages/cassowary/lib/equation_member.dart create mode 100644 packages/cassowary/lib/expression.dart create mode 100644 packages/cassowary/lib/term.dart create mode 100644 packages/cassowary/lib/variable.dart create mode 100644 packages/cassowary/test/cassowary_test.dart diff --git a/packages/cassowary/lib/cassowary.dart b/packages/cassowary/lib/cassowary.dart new file mode 100644 index 0000000000..da634c02aa --- /dev/null +++ b/packages/cassowary/lib/cassowary.dart @@ -0,0 +1,12 @@ +// Copyright (c) 2015, . All rights reserved. Use of this source code +// is governed by a BSD-style license that can be found in the LICENSE file. + +/// The cassowary library. +library cassowary; + +part 'constraint.dart'; +part 'expression.dart'; +part 'term.dart'; +part 'variable.dart'; +part 'equation_member.dart'; +part 'constant_member.dart'; diff --git a/packages/cassowary/lib/constant_member.dart b/packages/cassowary/lib/constant_member.dart new file mode 100644 index 0000000000..6e594ed4b9 --- /dev/null +++ b/packages/cassowary/lib/constant_member.dart @@ -0,0 +1,66 @@ +// 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 ConstantMember extends EquationMember { + double value = 0.0; + ConstantMember(this.value); + + Expression operator +(EquationMember m) { + if (m is ConstantMember) { + return new Expression([], this.value + m.value); + } + + if (m is Variable) { + return new Expression([new Term(m, 1.0)], this.value); + } + + if (m is Term) { + return new Expression([m], this.value); + } + + if (m is Expression) { + return new Expression(new List.from(m.terms), this.value + m.constant); + } + + assert(false); + return null; + } + + Expression operator -(EquationMember m) { + if (m is ConstantMember) { + return new Expression([], this.value - m.value); + } + + if (m is Variable) { + return new Expression([new Term(m, -1.0)], this.value); + } + + if (m is Term) { + return new Expression([new Term(m.variable, -m.coefficient)], this.value); + } + + if (m is Expression) { + var negatedTerms = m.terms.fold(new List(), (list, term) => list + ..add(new Term(term.variable, -term.coefficient))); + return new Expression(negatedTerms, this.value - m.constant); + } + + assert(false); + return null; + } + + EquationMember operator *(double m) { + return new ConstantMember(this.value * m); + } + + EquationMember operator /(double m) { + return new ConstantMember(this.value / m); + } +} + +ConstantMember CM(num value) { + return new ConstantMember(value); +} diff --git a/packages/cassowary/lib/constraint.dart b/packages/cassowary/lib/constraint.dart new file mode 100644 index 0000000000..2b97d663e0 --- /dev/null +++ b/packages/cassowary/lib/constraint.dart @@ -0,0 +1,15 @@ +// 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 Relation { equalTo, lessThanOrEqualTo, greaterThanOrEqualTo, } + +class Constraint { + final Relation relation; + final Expression expression; + double priority = 1000.0; + + Constraint(this.expression, this.relation); +} diff --git a/packages/cassowary/lib/equation_member.dart b/packages/cassowary/lib/equation_member.dart new file mode 100644 index 0000000000..db87d2887b --- /dev/null +++ b/packages/cassowary/lib/equation_member.dart @@ -0,0 +1,7 @@ +// 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 {} diff --git a/packages/cassowary/lib/expression.dart b/packages/cassowary/lib/expression.dart new file mode 100644 index 0000000000..8dedb5ff2c --- /dev/null +++ b/packages/cassowary/lib/expression.dart @@ -0,0 +1,88 @@ +// 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 Expression extends EquationMember { + final List terms; + final double constant; + double get value => terms.fold(constant, (value, term) => value + term.value); + + Expression(this.terms, this.constant); + + Constraint _createConstraint(double value, Relation relation) { + return new Constraint( + new Expression(this.terms, this.constant + value), relation); + } + + Constraint operator >=(double value) => + _createConstraint(-value, Relation.greaterThanOrEqualTo); + + Constraint operator <=(double value) => + _createConstraint(-value, Relation.lessThanOrEqualTo); + + operator ==(double value) => _createConstraint(-value, Relation.equalTo); + + Expression operator +(EquationMember m) { + if (m is ConstantMember) { + return new Expression(new List.from(this.terms), this.constant + m.value); + } + + if (m is Variable) { + return new Expression( + new List.from(this.terms)..add(new Term(m, 1.0)), this.constant); + } + + if (m is Term) { + return new Expression(new List.from(this.terms)..add(m), this.constant); + } + + if (m is Expression) { + return new Expression(new List.from(this.terms)..addAll(m.terms), + this.constant + m.constant); + } + + assert(false); + return null; + } + + Expression operator -(EquationMember m) { + if (m is ConstantMember) { + return new Expression(new List.from(this.terms), this.constant - m.value); + } + + if (m is Variable) { + return new Expression( + new List.from(this.terms)..add(new Term(m, -1.0)), this.constant); + } + + if (m is Term) { + return new Expression(new List.from(this.terms) + ..add(new Term(m.variable, -m.coefficient)), this.constant); + } + + if (m is Expression) { + var copiedTerms = new List.from(this.terms); + m.terms.forEach( + (t) => copiedTerms.add(new Term(t.variable, -t.coefficient))); + return new Expression(copiedTerms, this.constant - m.constant); + } + + assert(false); + return null; + } + + EquationMember operator *(double m) { + var terms = this.terms.fold(new List(), (list, term) => list + ..add(new Term(term.variable, term.coefficient * m))); + return new Expression(terms, this.constant); + } + + // TODO(csg): Figure out how to dry this up. + EquationMember operator /(double m) { + var terms = this.terms.fold(new List(), (list, term) => list + ..add(new Term(term.variable, term.coefficient / m))); + return new Expression(terms, this.constant); + } +} diff --git a/packages/cassowary/lib/term.dart b/packages/cassowary/lib/term.dart new file mode 100644 index 0000000000..460f903723 --- /dev/null +++ b/packages/cassowary/lib/term.dart @@ -0,0 +1,66 @@ +// 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 Term extends EquationMember { + final Variable variable; + final double coefficient; + double get value => coefficient * variable.value; + + Term(this.variable, this.coefficient); + + Expression operator +(EquationMember m) { + if (m is ConstantMember) { + return new Expression([this], m.value); + } + + if (m is Variable) { + return new Expression([this, new Term(m, 1.0)], 0.0); + } + + if (m is Term) { + return new Expression([this, m], 0.0); + } + + if (m is Expression) { + return new Expression( + new List.from(m.terms)..insert(0, this), m.constant); + } + + assert(false); + return null; + } + + Expression operator -(EquationMember m) { + if (m is ConstantMember) { + return new Expression([this], -m.value); + } + + if (m is Variable) { + return new Expression([this, new Term(m, -1.0)], 0.0); + } + + if (m is Term) { + return new Expression([this, new Term(m.variable, -m.coefficient)], 0.0); + } + + if (m is Expression) { + var negatedTerms = m.terms.fold(new List(), + (list, t) => list..add(new Term(t.variable, -t.coefficient))); + return new Expression(negatedTerms..insert(0, this), -m.constant); + } + + assert(false); + return null; + } + + EquationMember operator *(double m) { + return new Term(this.variable, this.coefficient * m); + } + + EquationMember operator /(double m) { + return new Term(this.variable, this.coefficient / m); + } +} diff --git a/packages/cassowary/lib/variable.dart b/packages/cassowary/lib/variable.dart new file mode 100644 index 0000000000..dbe9fa5f9d --- /dev/null +++ b/packages/cassowary/lib/variable.dart @@ -0,0 +1,65 @@ +// 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 extends EquationMember { + double value = 0.0; + Variable(this.value); + + Expression operator +(EquationMember m) { + if (m is ConstantMember) { + return new Expression([new Term(this, 1.0)], m.value); + } + + if (m is Variable) { + return new Expression([new Term(this, 1.0), new Term(m, 1.0)], 0.0); + } + + if (m is Term) { + return new Expression([new Term(this, 1.0), m], 0.0); + } + + if (m is Expression) { + return new Expression( + new List.from(m.terms)..insert(0, new Term(this, 1.0)), m.constant); + } + + assert(false); + return null; + } + + Expression operator -(EquationMember m) { + if (m is ConstantMember) { + return new Expression([new Term(this, 1.0)], -m.value); + } + + if (m is Variable) { + return new Expression([new Term(this, 1.0), new Term(m, -1.0)], 0.0); + } + + if (m is Term) { + return new Expression( + [new Term(this, 1.0), new Term(m.variable, -m.coefficient)], 0.0); + } + + if (m is Expression) { + var negatedTerms = m.terms.fold(new List(), + (list, t) => list..add(new Term(t.variable, -t.coefficient))); + negatedTerms.insert(0, new Term(this, 1.0)); + return new Expression(negatedTerms, -m.constant); + } + + assert(false); + return null; + } + + EquationMember operator *(double m) { + return new Term(this, m); + } + + EquationMember operator /(double m) { + return new Term(this, 1.0 / m); + } +} diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart new file mode 100644 index 0000000000..e91a34a137 --- /dev/null +++ b/packages/cassowary/test/cassowary_test.dart @@ -0,0 +1,262 @@ +// 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. + +library cassowary.test; + +import 'package:test/test.dart'; + +import 'package:cassowary/cassowary.dart'; + +void main() { + test('variable', () { + var v = new Variable(22.0); + expect(v.value, 22); + }); + + test('variable1', () { + var v = new Variable(22.0); + expect((v + CM(22.0)).value, 44.0); + expect((v - CM(20.0)).value, 2.0); + }); + + test('term', () { + var t = new Term(new Variable(22.0), 2.0); + expect(t.value, 44); + }); + + test('expression', () { + var terms = [ + new Term(new Variable(22.0), 2.0), + new Term(new Variable(1.0), 1.0), + ]; + var e = new Expression(terms, 40.0); + expect(e.value, 85.0); + }); + + test('expression1', () { + var v1 = new Variable(10.0); + var v2 = new Variable(10.0); + var v3 = new Variable(22.0); + + expect(v1 is Variable, 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', () { + var e = new Variable(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); + + // Variable + var v = new Variable(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 + var t = new Term(v, 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 + var e2 = new Variable(7.0) + new Variable(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', () { + var t = new Term(new Variable(12.0), 1.0); + + // Constant + var 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 + var v = new Variable(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 + var 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 + var exp = new Variable(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', () { + var v = new Variable(3.0); + + // Constant + var 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 + var v2 = new Variable(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 + var 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 + var exp = new Variable(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', () { + var c = CM(3.0); + + // Constant + var 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 + var v2 = new Variable(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 + var 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 + var exp = new Variable(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', () { + var left = new Variable(10.0); + var right = new Variable(100.0); + + var c = right - left >= 25.0; + expect(c is Constraint, true); + }); + + // TODO(csg): Address API inconsistency where the multipliers and divisors + // are doubles instead of equation members + + test('simple_multiplication', () { + // Constant + var c = CM(20.0); + expect((c * 2.0).value, 40.0); + + // Variable + var v = new Variable(20.0); + expect((v * 2.0).value, 40.0); + + // Term + var t = new Term(v, 1.0); + expect((t * 2.0).value, 40.0); + + // Expression + var e = new Expression([t], 0.0); + expect((e * 2.0).value, 40.0); + }); + + test('simple_division', () { + // Constant + var c = CM(20.0); + expect((c / 2.0).value, 10.0); + + // Variable + var v = new Variable(20.0); + expect((v / 2.0).value, 10.0); + + // Term + var t = new Term(v, 1.0); + expect((t / 2.0).value, 10.0); + + // Expression + var e = new Expression([t], 0.0); + expect((e / 2.0).value, 10.0); + }); + + // TODO: Support and test cases where the multipliers and divisors are more + // than just simple constants. + + test('full_constraints_setup', () { + var left = new Variable(2.0); + var right = new Variable(10.0); + + var c1 = right - left >= 20.0; + expect(c1 is Constraint, true); + expect(c1.expression.constant, -20.0); + expect(c1.relation, Relation.greaterThanOrEqualTo); + + var c2 = (right - left == 30.0) as Constraint; + expect(c2 is Constraint, true); + expect(c2.expression.constant, -30.0); + expect(c2.relation, Relation.equalTo); + + var c3 = right - left <= 30.0; + expect(c3 is Constraint, true); + expect(c3.expression.constant, -30.0); + expect(c3.relation, Relation.lessThanOrEqualTo); + }); +} From 2152de9a51319fd3bc42d369f05cf7bea4bbe063 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 22 Jun 2015 12:29:33 -0700 Subject: [PATCH 02/62] Minor: Add support for priority updates on constraints --- packages/cassowary/lib/constraint.dart | 2 ++ packages/cassowary/lib/expression.dart | 1 + packages/cassowary/test/cassowary_test.dart | 11 +++++++++++ 3 files changed, 14 insertions(+) diff --git a/packages/cassowary/lib/constraint.dart b/packages/cassowary/lib/constraint.dart index 2b97d663e0..eb1e42071e 100644 --- a/packages/cassowary/lib/constraint.dart +++ b/packages/cassowary/lib/constraint.dart @@ -12,4 +12,6 @@ class Constraint { double priority = 1000.0; Constraint(this.expression, this.relation); + + Constraint operator |(double p) => this..priority = p; } diff --git a/packages/cassowary/lib/expression.dart b/packages/cassowary/lib/expression.dart index 8dedb5ff2c..237d15494d 100644 --- a/packages/cassowary/lib/expression.dart +++ b/packages/cassowary/lib/expression.dart @@ -6,6 +6,7 @@ part of cassowary; class Expression extends EquationMember { final List terms; + final double constant; double get value => terms.fold(constant, (value, term) => value + term.value); diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index e91a34a137..6853b1e21e 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -259,4 +259,15 @@ void main() { expect(c3.expression.constant, -30.0); expect(c3.relation, Relation.lessThanOrEqualTo); }); + + test('constraint_strength_update', () { + var left = new Variable(2.0); + var right = new Variable(10.0); + + var c = (right - left >= 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); + }); } From a8e6ea06981b2b08384d85a75f2931d99940398d Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 22 Jun 2015 14:31:46 -0700 Subject: [PATCH 03/62] Constraints can be setup directly from non-expression via operator overrides --- packages/cassowary/lib/cassowary.dart | 1 + packages/cassowary/lib/constant_member.dart | 8 +++ packages/cassowary/lib/expression.dart | 42 ++++++++--- packages/cassowary/lib/solver.dart | 17 +++++ packages/cassowary/lib/term.dart | 9 +++ packages/cassowary/lib/variable.dart | 8 +++ packages/cassowary/test/cassowary_test.dart | 79 +++++++++++++++++++-- 7 files changed, 151 insertions(+), 13 deletions(-) create mode 100644 packages/cassowary/lib/solver.dart diff --git a/packages/cassowary/lib/cassowary.dart b/packages/cassowary/lib/cassowary.dart index da634c02aa..39365bc641 100644 --- a/packages/cassowary/lib/cassowary.dart +++ b/packages/cassowary/lib/cassowary.dart @@ -10,3 +10,4 @@ part 'term.dart'; part 'variable.dart'; part 'equation_member.dart'; part 'constant_member.dart'; +part 'solver.dart'; diff --git a/packages/cassowary/lib/constant_member.dart b/packages/cassowary/lib/constant_member.dart index 6e594ed4b9..2443c0dbe9 100644 --- a/packages/cassowary/lib/constant_member.dart +++ b/packages/cassowary/lib/constant_member.dart @@ -8,6 +8,14 @@ class ConstantMember extends EquationMember { double value = 0.0; ConstantMember(this.value); + Expression _asExpression() => new Expression([], this.value); + + Constraint operator >=(EquationMember m) => _asExpression() >= m; + + Constraint operator <=(EquationMember m) => _asExpression() <= m; + + operator ==(EquationMember m) => _asExpression() == m; + Expression operator +(EquationMember m) { if (m is ConstantMember) { return new Expression([], this.value + m.value); diff --git a/packages/cassowary/lib/expression.dart b/packages/cassowary/lib/expression.dart index 237d15494d..a3b33c89df 100644 --- a/packages/cassowary/lib/expression.dart +++ b/packages/cassowary/lib/expression.dart @@ -12,18 +12,44 @@ class Expression extends EquationMember { Expression(this.terms, this.constant); - Constraint _createConstraint(double value, Relation relation) { - return new Constraint( - new Expression(this.terms, this.constant + value), relation); + Constraint _createConstraint( + EquationMember /* rhs */ value, Relation relation) { + if (value is ConstantMember) { + return new Constraint(new Expression( + new List.from(this.terms), this.constant - value.value), relation); + } + + if (value is Variable) { + var newTerms = new List.from(this.terms) + ..add(new Term(value, -1.0)); + return new Constraint(new Expression(newTerms, this.constant), relation); + } + + if (value is Term) { + var newTerms = new List.from(this.terms) + ..add(new Term(value.variable, -value.coefficient)); + return new Constraint(new Expression(newTerms, this.constant), relation); + } + + if (value is Expression) { + var newTerms = value.terms.fold(new List.from(this.terms), + (list, t) => list..add(new Term(t.variable, -t.coefficient))); + return new Constraint( + new Expression(newTerms, this.constant - value.constant), relation); + } + + assert(false); + return null; } - Constraint operator >=(double value) => - _createConstraint(-value, Relation.greaterThanOrEqualTo); + Constraint operator >=(EquationMember value) => + _createConstraint(value, Relation.greaterThanOrEqualTo); - Constraint operator <=(double value) => - _createConstraint(-value, Relation.lessThanOrEqualTo); + Constraint operator <=(EquationMember value) => + _createConstraint(value, Relation.lessThanOrEqualTo); - operator ==(double value) => _createConstraint(-value, Relation.equalTo); + operator ==(EquationMember value) => + _createConstraint(value, Relation.equalTo); Expression operator +(EquationMember m) { if (m is ConstantMember) { diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart new file mode 100644 index 0000000000..9038887c2e --- /dev/null +++ b/packages/cassowary/lib/solver.dart @@ -0,0 +1,17 @@ +// Copyright (c) 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of cassowary; + +class Solver { + bool addConstraint(Constraint c) { + return false; + } + + bool removeContraint(Constraint c) { + return false; + } + + Solver operator <<(Constraint c) => this..addConstraint(c); +} diff --git a/packages/cassowary/lib/term.dart b/packages/cassowary/lib/term.dart index 460f903723..410e724ec9 100644 --- a/packages/cassowary/lib/term.dart +++ b/packages/cassowary/lib/term.dart @@ -11,6 +11,15 @@ class Term extends EquationMember { Term(this.variable, this.coefficient); + Expression _asExpression() => + new Expression([new Term(this.variable, this.coefficient)], 0.0); + + Constraint operator >=(EquationMember m) => _asExpression() >= m; + + Constraint operator <=(EquationMember m) => _asExpression() <= m; + + operator ==(EquationMember m) => _asExpression() == m; + Expression operator +(EquationMember m) { if (m is ConstantMember) { return new Expression([this], m.value); diff --git a/packages/cassowary/lib/variable.dart b/packages/cassowary/lib/variable.dart index dbe9fa5f9d..23a538cb04 100644 --- a/packages/cassowary/lib/variable.dart +++ b/packages/cassowary/lib/variable.dart @@ -8,6 +8,14 @@ class Variable extends EquationMember { double value = 0.0; Variable(this.value); + Expression _asExpression() => new Expression([new Term(this, 1.0)], 0.0); + + Constraint operator >=(EquationMember m) => _asExpression() >= m; + + Constraint operator <=(EquationMember m) => _asExpression() <= m; + + operator ==(EquationMember m) => _asExpression() == m; + Expression operator +(EquationMember m) { if (m is ConstantMember) { return new Expression([new Term(this, 1.0)], m.value); diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index 6853b1e21e..662e0dfc6d 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -194,7 +194,7 @@ void main() { var left = new Variable(10.0); var right = new Variable(100.0); - var c = right - left >= 25.0; + var c = right - left >= CM(25.0); expect(c is Constraint, true); }); @@ -244,17 +244,17 @@ void main() { var left = new Variable(2.0); var right = new Variable(10.0); - var c1 = right - left >= 20.0; + var c1 = right - left >= CM(20.0); expect(c1 is Constraint, true); expect(c1.expression.constant, -20.0); expect(c1.relation, Relation.greaterThanOrEqualTo); - var c2 = (right - left == 30.0) as Constraint; + var c2 = (right - left == CM(30.0)) as Constraint; expect(c2 is Constraint, true); expect(c2.expression.constant, -30.0); expect(c2.relation, Relation.equalTo); - var c3 = right - left <= 30.0; + var c3 = right - left <= CM(30.0); expect(c3 is Constraint, true); expect(c3.expression.constant, -30.0); expect(c3.relation, Relation.lessThanOrEqualTo); @@ -264,10 +264,79 @@ void main() { var left = new Variable(2.0); var right = new Variable(10.0); - var c = (right - left >= 200.0) | 750.0; + var 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', () { + var s = new Solver(); + + var left = new Variable(2.0); + var right = new Variable(100.0); + + var c1 = right - left >= CM(200.0); + var c2 = right + left >= CM(0.0); + + // TODO: Add assertions for this + s << c1 << c2; + }); + + test('constraint_complex', () { + var e = new Variable(200.0) - new Variable(100.0); + + // Constant + var c1 = e >= CM(50.0); + expect(c1 is Constraint, true); + expect(c1.expression.terms.length, 2); + expect(c1.expression.constant, -50.0); + + // Variable + var c2 = e >= new Variable(2.0); + expect(c2 is Constraint, true); + expect(c2.expression.terms.length, 3); + expect(c2.expression.constant, 0.0); + + // Term + var 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 + var c4 = e >= new Expression([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 + var 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 + var c2 = new Variable(100.0) >= new Variable(2.0); + expect(c2 is Constraint, true); + expect(c2.expression.terms.length, 2); + expect(c2.expression.constant, 0.0); + + // Term + var t = new Term(new Variable(100.0), 1.0); + var 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 + var e = new Expression([t], 0.0); + var c4 = e >= new Expression([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); + }); } From 5288d466ab2692623d9f549b244491617f00439a Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 22 Jun 2015 14:39:55 -0700 Subject: [PATCH 04/62] Dry up incremental expression construction from constants, variable, terms and other expressions --- packages/cassowary/lib/constant_member.dart | 52 +------------------ packages/cassowary/lib/equation_member.dart | 14 +++++- packages/cassowary/lib/expression.dart | 2 + packages/cassowary/lib/term.dart | 53 +------------------- packages/cassowary/lib/variable.dart | 55 +-------------------- 5 files changed, 18 insertions(+), 158 deletions(-) diff --git a/packages/cassowary/lib/constant_member.dart b/packages/cassowary/lib/constant_member.dart index 2443c0dbe9..58457d2e7d 100644 --- a/packages/cassowary/lib/constant_member.dart +++ b/packages/cassowary/lib/constant_member.dart @@ -8,57 +8,7 @@ class ConstantMember extends EquationMember { double value = 0.0; ConstantMember(this.value); - Expression _asExpression() => new Expression([], this.value); - - Constraint operator >=(EquationMember m) => _asExpression() >= m; - - Constraint operator <=(EquationMember m) => _asExpression() <= m; - - operator ==(EquationMember m) => _asExpression() == m; - - Expression operator +(EquationMember m) { - if (m is ConstantMember) { - return new Expression([], this.value + m.value); - } - - if (m is Variable) { - return new Expression([new Term(m, 1.0)], this.value); - } - - if (m is Term) { - return new Expression([m], this.value); - } - - if (m is Expression) { - return new Expression(new List.from(m.terms), this.value + m.constant); - } - - assert(false); - return null; - } - - Expression operator -(EquationMember m) { - if (m is ConstantMember) { - return new Expression([], this.value - m.value); - } - - if (m is Variable) { - return new Expression([new Term(m, -1.0)], this.value); - } - - if (m is Term) { - return new Expression([new Term(m.variable, -m.coefficient)], this.value); - } - - if (m is Expression) { - var negatedTerms = m.terms.fold(new List(), (list, term) => list - ..add(new Term(term.variable, -term.coefficient))); - return new Expression(negatedTerms, this.value - m.constant); - } - - assert(false); - return null; - } + Expression asExpression() => new Expression([], this.value); EquationMember operator *(double m) { return new ConstantMember(this.value * m); diff --git a/packages/cassowary/lib/equation_member.dart b/packages/cassowary/lib/equation_member.dart index db87d2887b..54fb04e7ae 100644 --- a/packages/cassowary/lib/equation_member.dart +++ b/packages/cassowary/lib/equation_member.dart @@ -4,4 +4,16 @@ part of cassowary; -abstract class EquationMember {} +abstract class EquationMember { + Expression asExpression(); + + Constraint operator >=(EquationMember m) => asExpression() >= m; + + Constraint operator <=(EquationMember m) => asExpression() <= m; + + operator ==(EquationMember m) => asExpression() == m; + + Expression operator +(EquationMember m) => asExpression() + m; + + Expression operator -(EquationMember m) => asExpression() - m; +} diff --git a/packages/cassowary/lib/expression.dart b/packages/cassowary/lib/expression.dart index a3b33c89df..b00a3a0f23 100644 --- a/packages/cassowary/lib/expression.dart +++ b/packages/cassowary/lib/expression.dart @@ -12,6 +12,8 @@ class Expression extends EquationMember { Expression(this.terms, this.constant); + Expression asExpression() => this; + Constraint _createConstraint( EquationMember /* rhs */ value, Relation relation) { if (value is ConstantMember) { diff --git a/packages/cassowary/lib/term.dart b/packages/cassowary/lib/term.dart index 410e724ec9..e20c714280 100644 --- a/packages/cassowary/lib/term.dart +++ b/packages/cassowary/lib/term.dart @@ -11,60 +11,9 @@ class Term extends EquationMember { Term(this.variable, this.coefficient); - Expression _asExpression() => + Expression asExpression() => new Expression([new Term(this.variable, this.coefficient)], 0.0); - Constraint operator >=(EquationMember m) => _asExpression() >= m; - - Constraint operator <=(EquationMember m) => _asExpression() <= m; - - operator ==(EquationMember m) => _asExpression() == m; - - Expression operator +(EquationMember m) { - if (m is ConstantMember) { - return new Expression([this], m.value); - } - - if (m is Variable) { - return new Expression([this, new Term(m, 1.0)], 0.0); - } - - if (m is Term) { - return new Expression([this, m], 0.0); - } - - if (m is Expression) { - return new Expression( - new List.from(m.terms)..insert(0, this), m.constant); - } - - assert(false); - return null; - } - - Expression operator -(EquationMember m) { - if (m is ConstantMember) { - return new Expression([this], -m.value); - } - - if (m is Variable) { - return new Expression([this, new Term(m, -1.0)], 0.0); - } - - if (m is Term) { - return new Expression([this, new Term(m.variable, -m.coefficient)], 0.0); - } - - if (m is Expression) { - var negatedTerms = m.terms.fold(new List(), - (list, t) => list..add(new Term(t.variable, -t.coefficient))); - return new Expression(negatedTerms..insert(0, this), -m.constant); - } - - assert(false); - return null; - } - EquationMember operator *(double m) { return new Term(this.variable, this.coefficient * m); } diff --git a/packages/cassowary/lib/variable.dart b/packages/cassowary/lib/variable.dart index 23a538cb04..d4d955580e 100644 --- a/packages/cassowary/lib/variable.dart +++ b/packages/cassowary/lib/variable.dart @@ -8,60 +8,7 @@ class Variable extends EquationMember { double value = 0.0; Variable(this.value); - Expression _asExpression() => new Expression([new Term(this, 1.0)], 0.0); - - Constraint operator >=(EquationMember m) => _asExpression() >= m; - - Constraint operator <=(EquationMember m) => _asExpression() <= m; - - operator ==(EquationMember m) => _asExpression() == m; - - Expression operator +(EquationMember m) { - if (m is ConstantMember) { - return new Expression([new Term(this, 1.0)], m.value); - } - - if (m is Variable) { - return new Expression([new Term(this, 1.0), new Term(m, 1.0)], 0.0); - } - - if (m is Term) { - return new Expression([new Term(this, 1.0), m], 0.0); - } - - if (m is Expression) { - return new Expression( - new List.from(m.terms)..insert(0, new Term(this, 1.0)), m.constant); - } - - assert(false); - return null; - } - - Expression operator -(EquationMember m) { - if (m is ConstantMember) { - return new Expression([new Term(this, 1.0)], -m.value); - } - - if (m is Variable) { - return new Expression([new Term(this, 1.0), new Term(m, -1.0)], 0.0); - } - - if (m is Term) { - return new Expression( - [new Term(this, 1.0), new Term(m.variable, -m.coefficient)], 0.0); - } - - if (m is Expression) { - var negatedTerms = m.terms.fold(new List(), - (list, t) => list..add(new Term(t.variable, -t.coefficient))); - negatedTerms.insert(0, new Term(this, 1.0)); - return new Expression(negatedTerms, -m.constant); - } - - assert(false); - return null; - } + Expression asExpression() => new Expression([new Term(this, 1.0)], 0.0); EquationMember operator *(double m) { return new Term(this, m); From 7eb8322315f1d58986aaf20bec404f2571b03904 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 22 Jun 2015 15:02:31 -0700 Subject: [PATCH 05/62] Dry up multiplication and division of equation members --- packages/cassowary/lib/constant_member.dart | 8 -------- packages/cassowary/lib/equation_member.dart | 4 ++++ packages/cassowary/lib/expression.dart | 5 ++--- packages/cassowary/lib/term.dart | 8 -------- packages/cassowary/lib/variable.dart | 8 -------- 5 files changed, 6 insertions(+), 27 deletions(-) diff --git a/packages/cassowary/lib/constant_member.dart b/packages/cassowary/lib/constant_member.dart index 58457d2e7d..ce10d39263 100644 --- a/packages/cassowary/lib/constant_member.dart +++ b/packages/cassowary/lib/constant_member.dart @@ -9,14 +9,6 @@ class ConstantMember extends EquationMember { ConstantMember(this.value); Expression asExpression() => new Expression([], this.value); - - EquationMember operator *(double m) { - return new ConstantMember(this.value * m); - } - - EquationMember operator /(double m) { - return new ConstantMember(this.value / m); - } } ConstantMember CM(num value) { diff --git a/packages/cassowary/lib/equation_member.dart b/packages/cassowary/lib/equation_member.dart index 54fb04e7ae..50ea95a8a4 100644 --- a/packages/cassowary/lib/equation_member.dart +++ b/packages/cassowary/lib/equation_member.dart @@ -16,4 +16,8 @@ abstract class EquationMember { Expression operator +(EquationMember m) => asExpression() + m; Expression operator -(EquationMember m) => asExpression() - m; + + Expression operator *(double m) => asExpression() * m; + + Expression operator /(double m) => asExpression() / m; } diff --git a/packages/cassowary/lib/expression.dart b/packages/cassowary/lib/expression.dart index b00a3a0f23..972ea1d7fd 100644 --- a/packages/cassowary/lib/expression.dart +++ b/packages/cassowary/lib/expression.dart @@ -105,13 +105,12 @@ class Expression extends EquationMember { EquationMember operator *(double m) { var terms = this.terms.fold(new List(), (list, term) => list ..add(new Term(term.variable, term.coefficient * m))); - return new Expression(terms, this.constant); + return new Expression(terms, this.constant * m); } - // TODO(csg): Figure out how to dry this up. EquationMember operator /(double m) { var terms = this.terms.fold(new List(), (list, term) => list ..add(new Term(term.variable, term.coefficient / m))); - return new Expression(terms, this.constant); + return new Expression(terms, this.constant / m); } } diff --git a/packages/cassowary/lib/term.dart b/packages/cassowary/lib/term.dart index e20c714280..e4f7b24d6a 100644 --- a/packages/cassowary/lib/term.dart +++ b/packages/cassowary/lib/term.dart @@ -13,12 +13,4 @@ class Term extends EquationMember { Expression asExpression() => new Expression([new Term(this.variable, this.coefficient)], 0.0); - - EquationMember operator *(double m) { - return new Term(this.variable, this.coefficient * m); - } - - EquationMember operator /(double m) { - return new Term(this.variable, this.coefficient / m); - } } diff --git a/packages/cassowary/lib/variable.dart b/packages/cassowary/lib/variable.dart index d4d955580e..5168290e9d 100644 --- a/packages/cassowary/lib/variable.dart +++ b/packages/cassowary/lib/variable.dart @@ -9,12 +9,4 @@ class Variable extends EquationMember { Variable(this.value); Expression asExpression() => new Expression([new Term(this, 1.0)], 0.0); - - EquationMember operator *(double m) { - return new Term(this, m); - } - - EquationMember operator /(double m) { - return new Term(this, 1.0 / m); - } } From e788fe538f0b506ee26eb71bc1563be6284e39b9 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 22 Jun 2015 15:07:02 -0700 Subject: [PATCH 06/62] Minor: Match style guide --- packages/cassowary/lib/expression.dart | 48 +++++++++++++------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/packages/cassowary/lib/expression.dart b/packages/cassowary/lib/expression.dart index 972ea1d7fd..9e0449a62b 100644 --- a/packages/cassowary/lib/expression.dart +++ b/packages/cassowary/lib/expression.dart @@ -17,27 +17,27 @@ class Expression extends EquationMember { Constraint _createConstraint( EquationMember /* rhs */ value, Relation relation) { if (value is ConstantMember) { - return new Constraint(new Expression( - new List.from(this.terms), this.constant - value.value), relation); + return new Constraint( + new Expression(new List.from(terms), constant - value.value), + relation); } if (value is Variable) { - var newTerms = new List.from(this.terms) - ..add(new Term(value, -1.0)); - return new Constraint(new Expression(newTerms, this.constant), relation); + var newTerms = new List.from(terms)..add(new Term(value, -1.0)); + return new Constraint(new Expression(newTerms, constant), relation); } if (value is Term) { - var newTerms = new List.from(this.terms) + var newTerms = new List.from(terms) ..add(new Term(value.variable, -value.coefficient)); - return new Constraint(new Expression(newTerms, this.constant), relation); + return new Constraint(new Expression(newTerms, constant), relation); } if (value is Expression) { - var newTerms = value.terms.fold(new List.from(this.terms), + var newTerms = value.terms.fold(new List.from(terms), (list, t) => list..add(new Term(t.variable, -t.coefficient))); return new Constraint( - new Expression(newTerms, this.constant - value.constant), relation); + new Expression(newTerms, constant - value.constant), relation); } assert(false); @@ -55,21 +55,21 @@ class Expression extends EquationMember { Expression operator +(EquationMember m) { if (m is ConstantMember) { - return new Expression(new List.from(this.terms), this.constant + m.value); + return new Expression(new List.from(terms), constant + m.value); } if (m is Variable) { return new Expression( - new List.from(this.terms)..add(new Term(m, 1.0)), this.constant); + new List.from(terms)..add(new Term(m, 1.0)), constant); } if (m is Term) { - return new Expression(new List.from(this.terms)..add(m), this.constant); + return new Expression(new List.from(terms)..add(m), constant); } if (m is Expression) { - return new Expression(new List.from(this.terms)..addAll(m.terms), - this.constant + m.constant); + return new Expression( + new List.from(terms)..addAll(m.terms), constant + m.constant); } assert(false); @@ -78,24 +78,24 @@ class Expression extends EquationMember { Expression operator -(EquationMember m) { if (m is ConstantMember) { - return new Expression(new List.from(this.terms), this.constant - m.value); + return new Expression(new List.from(terms), constant - m.value); } if (m is Variable) { return new Expression( - new List.from(this.terms)..add(new Term(m, -1.0)), this.constant); + new List.from(terms)..add(new Term(m, -1.0)), constant); } if (m is Term) { - return new Expression(new List.from(this.terms) - ..add(new Term(m.variable, -m.coefficient)), this.constant); + return new Expression(new List.from(terms) + ..add(new Term(m.variable, -m.coefficient)), constant); } if (m is Expression) { - var copiedTerms = new List.from(this.terms); + var copiedTerms = new List.from(terms); m.terms.forEach( (t) => copiedTerms.add(new Term(t.variable, -t.coefficient))); - return new Expression(copiedTerms, this.constant - m.constant); + return new Expression(copiedTerms, constant - m.constant); } assert(false); @@ -103,14 +103,14 @@ class Expression extends EquationMember { } EquationMember operator *(double m) { - var terms = this.terms.fold(new List(), (list, term) => list + var newTerms = terms.fold(new List(), (list, term) => list ..add(new Term(term.variable, term.coefficient * m))); - return new Expression(terms, this.constant * m); + return new Expression(newTerms, constant * m); } EquationMember operator /(double m) { - var terms = this.terms.fold(new List(), (list, term) => list + var newTerms = terms.fold(new List(), (list, term) => list ..add(new Term(term.variable, term.coefficient / m))); - return new Expression(terms, this.constant / m); + return new Expression(newTerms, constant / m); } } From 306c795c2194673011b977ba1336eb26ea274f2a Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 23 Jun 2015 09:50:52 -0700 Subject: [PATCH 07/62] Minor: Add stubs for the symbol and solver --- packages/cassowary/lib/cassowary.dart | 1 + packages/cassowary/lib/constant_member.dart | 1 + packages/cassowary/lib/constraint.dart | 14 +++++++++++-- packages/cassowary/lib/solver.dart | 22 +++++++++++++++++++++ packages/cassowary/lib/symbol.dart | 13 ++++++++++++ packages/cassowary/test/cassowary_test.dart | 2 ++ 6 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 packages/cassowary/lib/symbol.dart diff --git a/packages/cassowary/lib/cassowary.dart b/packages/cassowary/lib/cassowary.dart index 39365bc641..c3cea2d866 100644 --- a/packages/cassowary/lib/cassowary.dart +++ b/packages/cassowary/lib/cassowary.dart @@ -11,3 +11,4 @@ part 'variable.dart'; part 'equation_member.dart'; part 'constant_member.dart'; part 'solver.dart'; +part 'symbol.dart'; diff --git a/packages/cassowary/lib/constant_member.dart b/packages/cassowary/lib/constant_member.dart index ce10d39263..de6afb422d 100644 --- a/packages/cassowary/lib/constant_member.dart +++ b/packages/cassowary/lib/constant_member.dart @@ -6,6 +6,7 @@ part of cassowary; class ConstantMember extends EquationMember { double value = 0.0; + ConstantMember(this.value); Expression asExpression() => new Expression([], this.value); diff --git a/packages/cassowary/lib/constraint.dart b/packages/cassowary/lib/constraint.dart index eb1e42071e..114ad219e8 100644 --- a/packages/cassowary/lib/constraint.dart +++ b/packages/cassowary/lib/constraint.dart @@ -9,9 +9,19 @@ enum Relation { equalTo, lessThanOrEqualTo, greaterThanOrEqualTo, } class Constraint { final Relation relation; final Expression expression; - double priority = 1000.0; + final bool required; - Constraint(this.expression, this.relation); + static const double requiredPriority = 1000.0; + double _priority = requiredPriority - 1.0; + + Constraint(this.expression, this.relation) : this.required = false; + Constraint.Required(this.expression, this.relation) : this.required = true { + this.priority = requiredPriority; + } + + double get priority => required ? requiredPriority : _priority; + set priority(double p) => _priority = + required ? requiredPriority : p.clamp(0.0, requiredPriority - 1.0); Constraint operator |(double p) => this..priority = p; } diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index 9038887c2e..b2a9f344d3 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -13,5 +13,27 @@ class Solver { return false; } + bool hasConstraint(Constraint c) { + return false; + } + + bool addEditVariable(Variable v, double priority) { + return false; + } + + bool removeEditVariable(Variable v) { + return false; + } + + bool hasEditVariable(Variable v) { + return false; + } + + bool suggestVariable(Variable v, double value) { + return false; + } + + void updateVariable() {} + Solver operator <<(Constraint c) => this..addConstraint(c); } diff --git a/packages/cassowary/lib/symbol.dart b/packages/cassowary/lib/symbol.dart new file mode 100644 index 0000000000..7c37eca6eb --- /dev/null +++ b/packages/cassowary/lib/symbol.dart @@ -0,0 +1,13 @@ +// 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 { + SymbolType type; + + Symbol(this.type); +} diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index 662e0dfc6d..cce4618fd9 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -280,6 +280,8 @@ void main() { var c1 = right - left >= CM(200.0); var c2 = right + left >= CM(0.0); + expect((right >= left) is Constraint, true); + // TODO: Add assertions for this s << c1 << c2; }); From 530700a8c17e8712d68830794905056cbaaa9463 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 23 Jun 2015 13:22:48 -0700 Subject: [PATCH 08/62] Implement row.dart and some other minor utility methods --- packages/cassowary/lib/cassowary.dart | 2 + packages/cassowary/lib/row.dart | 62 +++++++++++++++++++++++++++ packages/cassowary/lib/solver.dart | 21 +++++++++ packages/cassowary/lib/utils.dart | 15 +++++++ 4 files changed, 100 insertions(+) create mode 100644 packages/cassowary/lib/row.dart create mode 100644 packages/cassowary/lib/utils.dart diff --git a/packages/cassowary/lib/cassowary.dart b/packages/cassowary/lib/cassowary.dart index c3cea2d866..1666b0dd44 100644 --- a/packages/cassowary/lib/cassowary.dart +++ b/packages/cassowary/lib/cassowary.dart @@ -12,3 +12,5 @@ part 'equation_member.dart'; part 'constant_member.dart'; part 'solver.dart'; part 'symbol.dart'; +part 'row.dart'; +part 'utils.dart'; diff --git a/packages/cassowary/lib/row.dart b/packages/cassowary/lib/row.dart new file mode 100644 index 0000000000..8fb61568e4 --- /dev/null +++ b/packages/cassowary/lib/row.dart @@ -0,0 +1,62 @@ +// 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 _cells = new Map(); + double _constant = 0.0; + + double get constant => _constant; + Map get cells => _cells; + + double add(double value) => _constant += value; + + void insertSymbol(Symbol symbol, [double coefficient = 1.0]) { + double val = _elvis(_cells[symbol], 0.0) + coefficient; + + if (_nearZero(val)) { + _cells.remove(symbol); + } else { + _cells[symbol] = val + coefficient; + } + } + + void insertRow(Row other, [double coefficient = 1.0]) { + _constant += other.constant * coefficient; + other.cells.forEach((s, v) => insertSymbol(s, v * coefficient)); + } + + void removeSymbol(Symbol symbol) { + _cells.remove(symbol); + } + + void reverseSign() => _cells.forEach((s, 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((s, v) => _cells[s] = v * coefficient); + } + + void solveForSymbols(Symbol lhs, Symbol rhs) { + insertSymbol(lhs, -1.0); + solveForSymbol(rhs); + } + + double coefficientForSymbol(Symbol symbol) => _elvis(_cells[symbol], 0.0); + + void substitute(Symbol symbol, Row row) { + double coefficient = _cells[symbol]; + + if (coefficient == null) { + return; + } + + _cells.remove(symbol); + insertRow(row, coefficient); + } +} diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index b2a9f344d3..3d9c91ab66 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -5,6 +5,14 @@ part of cassowary; class Solver { + final Map _constraints = new Map(); + final Map _rows = new Map(); + final Map _vars = new Map(); + final Map _edits = new Map(); + final List _infeasibleRows = new List(); + final Row _objective = new Row(); + final Row _artificial = new Row(); + bool addConstraint(Constraint c) { return false; } @@ -37,3 +45,16 @@ class Solver { Solver operator <<(Constraint c) => this..addConstraint(c); } + +class Tag { + Symbol marker; + Symbol other; + + Tag(this.marker, this.other); +} + +class EditInfo { + Tag tag; + Constraint constraint; + double constant; +} diff --git a/packages/cassowary/lib/utils.dart b/packages/cassowary/lib/utils.dart new file mode 100644 index 0000000000..b003269dd9 --- /dev/null +++ b/packages/cassowary/lib/utils.dart @@ -0,0 +1,15 @@ +// 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; +} + +// Workaround for the lack of a null coalescing operator. Uses a ternary +// instead. Sadly, due the lack of generic types on functions, we have to use +// dynamic instead. +_elvis(a, b) => a != null ? a : b; From af67d08746d7a90f5a1b4e6cc92002c661cd3964 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 23 Jun 2015 13:58:44 -0700 Subject: [PATCH 09/62] Minor: Add result types for known failure cases --- packages/cassowary/lib/cassowary.dart | 1 + packages/cassowary/lib/result.dart | 29 +++++++++++++++++++++++++++ packages/cassowary/lib/solver.dart | 28 +++++++++++++------------- 3 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 packages/cassowary/lib/result.dart diff --git a/packages/cassowary/lib/cassowary.dart b/packages/cassowary/lib/cassowary.dart index 1666b0dd44..40f58941e6 100644 --- a/packages/cassowary/lib/cassowary.dart +++ b/packages/cassowary/lib/cassowary.dart @@ -14,3 +14,4 @@ part 'solver.dart'; part 'symbol.dart'; part 'row.dart'; part 'utils.dart'; +part 'result.dart'; diff --git a/packages/cassowary/lib/result.dart b/packages/cassowary/lib/result.dart new file mode 100644 index 0000000000..20aee96b5b --- /dev/null +++ b/packages/cassowary/lib/result.dart @@ -0,0 +1,29 @@ +// 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 bool error; + final String message; + + const Result(this.message, this.error); + + static final Result success = const Result("Success", false); + static final Result unimplemented = const Result("Unimplemented", true); + static final Result duplicateConstraint = + const Result("Duplicate Constraint", true); + static final Result unsatisfiableConstraint = + const Result("Unsatisfiable Constraint", true); + static final Result unknownConstraint = + const Result("Unknown Constraint", true); + static final Result duplicateEditVariable = + const Result("Duplicate Edit Variable", true); + static final Result badRequiredStrength = + const Result("Bad Required Strength", true); + static final Result unknownEditVariable = + const Result("Unknown Edit Variable", true); + static final Result internalSolverError = + const Result("Internal Solver Error", true); +} diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index 3d9c91ab66..e4a20863ce 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -13,32 +13,32 @@ class Solver { final Row _objective = new Row(); final Row _artificial = new Row(); - bool addConstraint(Constraint c) { - return false; + Result addConstraint(Constraint c) { + return Result.unimplemented; } - bool removeContraint(Constraint c) { - return false; + Result removeContraint(Constraint c) { + return Result.unimplemented; } - bool hasConstraint(Constraint c) { - return false; + Result hasConstraint(Constraint c) { + return Result.unimplemented; } - bool addEditVariable(Variable v, double priority) { - return false; + Result addEditVariable(Variable v, double priority) { + return Result.unimplemented; } - bool removeEditVariable(Variable v) { - return false; + Result removeEditVariable(Variable v) { + return Result.unimplemented; } - bool hasEditVariable(Variable v) { - return false; + Result hasEditVariable(Variable v) { + return Result.unimplemented; } - bool suggestVariable(Variable v, double value) { - return false; + Result suggestVariable(Variable v, double value) { + return Result.unimplemented; } void updateVariable() {} From b78b35d723d681190b58de445343c8d1bd4c5f2b Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 23 Jun 2015 18:01:17 -0700 Subject: [PATCH 10/62] Implement addition of constraints to the solver --- packages/cassowary/lib/expression.dart | 3 + packages/cassowary/lib/row.dart | 40 ++-- packages/cassowary/lib/solver.dart | 274 ++++++++++++++++++++++++- packages/cassowary/lib/symbol.dart | 5 +- packages/cassowary/lib/utils.dart | 6 + 5 files changed, 303 insertions(+), 25 deletions(-) diff --git a/packages/cassowary/lib/expression.dart b/packages/cassowary/lib/expression.dart index 9e0449a62b..366ea625b7 100644 --- a/packages/cassowary/lib/expression.dart +++ b/packages/cassowary/lib/expression.dart @@ -11,6 +11,9 @@ class Expression extends EquationMember { double get value => terms.fold(constant, (value, term) => value + term.value); Expression(this.terms, this.constant); + Expression.fromExpression(Expression expr) + : this.terms = new List.from(expr.terms), + this.constant = expr.constant; Expression asExpression() => this; diff --git a/packages/cassowary/lib/row.dart b/packages/cassowary/lib/row.dart index 8fb61568e4..44a28c02fb 100644 --- a/packages/cassowary/lib/row.dart +++ b/packages/cassowary/lib/row.dart @@ -5,41 +5,43 @@ part of cassowary; class Row { - final Map _cells = new Map(); - double _constant = 0.0; + final Map cells; + double constant = 0.0; - double get constant => _constant; - Map get cells => _cells; + Row(this.constant) : this.cells = new Map(); + Row.fromRow(Row row) + : this.cells = new Map.from(row.cells), + this.constant = row.constant; - double add(double value) => _constant += value; + double add(double value) => constant += value; void insertSymbol(Symbol symbol, [double coefficient = 1.0]) { - double val = _elvis(_cells[symbol], 0.0) + coefficient; + double val = _elvis(cells[symbol], 0.0) + coefficient; if (_nearZero(val)) { - _cells.remove(symbol); + cells.remove(symbol); } else { - _cells[symbol] = val + coefficient; + cells[symbol] = val + coefficient; } } void insertRow(Row other, [double coefficient = 1.0]) { - _constant += other.constant * coefficient; + constant += other.constant * coefficient; other.cells.forEach((s, v) => insertSymbol(s, v * coefficient)); } void removeSymbol(Symbol symbol) { - _cells.remove(symbol); + cells.remove(symbol); } - void reverseSign() => _cells.forEach((s, v) => _cells[s] = -v); + void reverseSign() => cells.forEach((s, 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((s, v) => _cells[s] = v * coefficient); + assert(cells.containsKey(symbol)); + double coefficient = -1.0 / cells[symbol]; + cells.remove(symbol); + constant *= coefficient; + cells.forEach((s, v) => cells[s] = v * coefficient); } void solveForSymbols(Symbol lhs, Symbol rhs) { @@ -47,16 +49,16 @@ class Row { solveForSymbol(rhs); } - double coefficientForSymbol(Symbol symbol) => _elvis(_cells[symbol], 0.0); + double coefficientForSymbol(Symbol symbol) => _elvis(cells[symbol], 0.0); void substitute(Symbol symbol, Row row) { - double coefficient = _cells[symbol]; + double coefficient = cells[symbol]; if (coefficient == null) { return; } - _cells.remove(symbol); + cells.remove(symbol); insertRow(row, coefficient); } } diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index e4a20863ce..cbe5721ff8 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -10,11 +10,45 @@ class Solver { final Map _vars = new Map(); final Map _edits = new Map(); final List _infeasibleRows = new List(); - final Row _objective = new Row(); - final Row _artificial = new Row(); + final Row _objective = new Row(0.0); + Row _artificial = new Row(0.0); + int tick = 0; - Result addConstraint(Constraint c) { - return Result.unimplemented; + Result addConstraint(Constraint constraint) { + if (_constraints.containsKey(constraint)) { + return Result.duplicateConstraint; + } + + Tag tag = new Tag( + new Symbol(SymbolType.invalid, 0), new Symbol(SymbolType.invalid, 0)); + + Row row = _createRow(constraint, tag); + + Symbol subject = _chooseSubjectForRow(row, tag); + + if (subject.type == SymbolType.invalid && _allDummiesInRow(row)) { + if (!_nearZero(row.constant)) { + return Result.unsatisfiableConstraint; + } else { + subject = tag.marker; + } + } + + if (subject.type == SymbolType.invalid) { + if (!_addWithArtificialVariableOnRow(row)) { + return Result.unsatisfiableConstraint; + } + } else { + row.solveForSymbol(subject); + _substitute(subject, row); + _rows[subject] = row; + } + + _constraints[constraint] = tag; + + _optimizeObjectiveRow(_objective); + + return Result.success; } Result removeContraint(Constraint c) { @@ -44,6 +78,238 @@ class Solver { void updateVariable() {} Solver operator <<(Constraint c) => this..addConstraint(c); + + Symbol _getSymbolForVariable(Variable variable) { + Symbol symbol = _vars[variable]; + + if (symbol != null) { + return symbol; + } + + symbol = new Symbol(SymbolType.external, tick++); + _vars[variable] = symbol; + + return symbol; + } + + Row _createRow(Constraint constraint, Tag tag) { + Expression expr = new Expression.fromExpression(constraint.expression); + Row row = new Row(expr.constant); + + expr.terms.forEach((term) { + if (!_nearZero(term.coefficient)) { + Symbol symbol = _getSymbolForVariable(term.variable); + + Row foundRow = _rows[symbol]; + + if (foundRow != null) { + row.insertRow(foundRow, term.coefficient); + } else { + row.insertSymbol(symbol, term.coefficient); + } + } + }); + + switch (constraint.relation) { + case Relation.lessThanOrEqualTo: + case Relation.greaterThanOrEqualTo: + { + double coefficient = + constraint.relation == Relation.lessThanOrEqualTo ? 1.0 : -1.0; + + Symbol slack = new Symbol(SymbolType.slack, tick++); + tag.marker = slack; + row.insertSymbol(slack, coefficient); + + if (!constraint.required) { + Symbol error = new Symbol(SymbolType.error, tick++); + tag.other = error; + row.insertSymbol(error, -coefficient); + _objective.insertSymbol(error, constraint.priority); + } + } + break; + case Relation.equalTo: + if (!constraint.required) { + Symbol errPlus = new Symbol(SymbolType.error, tick++); + Symbol errMinus = new Symbol(SymbolType.error, tick++); + tag.marker = errPlus; + tag.other = errMinus; + row.insertSymbol(errPlus, -1.0); + row.insertSymbol(errMinus, 1.0); + _objective.insertSymbol(errPlus, constraint.priority); + _objective.insertSymbol(errMinus, constraint.priority); + } else { + Symbol dummy = new Symbol(SymbolType.dummy, tick++); + tag.marker = dummy; + row.insertSymbol(dummy); + } + break; + } + + if (row.constant < 0.0) { + row.reverseSign(); + } + + return row; + } + + Symbol _chooseSubjectForRow(Row row, Tag tag) { + for (Symbol symbol in row.cells.keys) { + if (symbol.type == SymbolType.external) { + return symbol; + } + } + + if (tag.marker.type == SymbolType.slack || + tag.marker.type == SymbolType.error) { + if (row.coefficientForSymbol(tag.marker) < 0.0) { + return tag.marker; + } + } + + if (tag.other.type == SymbolType.slack || + tag.other.type == SymbolType.error) { + if (row.coefficientForSymbol(tag.other) < 0.0) { + return tag.other; + } + } + + return new Symbol(SymbolType.invalid, 0); + } + + bool _allDummiesInRow(Row row) { + for (Symbol symbol in row.cells.keys) { + if (symbol.type != SymbolType.dummy) { + return false; + } + } + return true; + } + + bool _addWithArtificialVariableOnRow(Row row) { + Symbol artificial = new Symbol(SymbolType.slack, tick++); + _rows[artificial] = new Row.fromRow(row); + _artificial = new Row.fromRow(row); + + Result result = _optimizeObjectiveRow(_artificial); + + if (result.error) { + // FIXME(csg): Propagate this up! + return false; + } + + bool success = _nearZero(_artificial.constant); + _artificial = new Row(0.0); + + Row foundRow = _rows[artificial]; + if (foundRow != null) { + _rows.remove(artificial); + if (foundRow.cells.isEmpty) { + return success; + } + + Symbol entering = _anyPivotableSymbol(foundRow); + if (entering.type == SymbolType.invalid) { + return false; + } + + foundRow.solveForSymbols(artificial, entering); + _substitute(entering, foundRow); + _rows[entering] = foundRow; + } + + for (Row row in _rows.values) { + row.removeSymbol(artificial); + } + _objective.removeSymbol(artificial); + return success; + } + + Result _optimizeObjectiveRow(Row objective) { + while (true) { + Symbol entering = _getEnteringSymbolForObjectiveRow(objective); + if (entering.type == SymbolType.invalid) { + return Result.success; + } + + _Pair leavingPair = + _getLeavingRowForEnteringSymbol(entering); + + if (leavingPair == null) { + return Result.internalSolverError; + } + + Symbol leaving = leavingPair.first; + Row row = leavingPair.second; + _rows.remove(leavingPair.first); + row.solveForSymbols(leaving, entering); + _substitute(entering, row); + _rows[entering] = row; + } + } + + Symbol _getEnteringSymbolForObjectiveRow(Row objective) { + Map cells = objective.cells; + + for (Symbol symbol in cells.keys) { + if (symbol.type != SymbolType.dummy && cells[symbol] < 0.0) { + return symbol; + } + } + + return new Symbol(SymbolType.invalid, 0); + } + + _Pair _getLeavingRowForEnteringSymbol(Symbol entering) { + double ratio = double.MAX_FINITE; + _Pair result = new _Pair(null, null); + + _rows.forEach((symbol, row) { + if (symbol.type != SymbolType.external) { + double temp = row.coefficientForSymbol(entering); + + if (temp < 0.0) { + double temp_ratio = -row.constant / temp; + + if (temp_ratio < ratio) { + ratio = temp_ratio; + result.first = symbol; + result.second = row; + } + } + } + }); + + if (result.first == null || result.second == null) { + return null; + } + + return result; + } + + void _substitute(Symbol symbol, Row row) { + _rows.forEach((first, second) { + second.substitute(symbol, row); + if (first.type != SymbolType.external && second.constant < 0.0) { + _infeasibleRows.add(first); + } + }); + + _objective.substitute(symbol, row); + if (_artificial != null) { + _artificial.substitute(symbol, row); + } + } + + Symbol _anyPivotableSymbol(Row row) { + for (Symbol symbol in row.cells.keys) { + if (symbol.type == SymbolType.slack || symbol.type == SymbolType.error) { + return symbol; + } + } + return new Symbol(SymbolType.invalid, 0); + } } class Tag { diff --git a/packages/cassowary/lib/symbol.dart b/packages/cassowary/lib/symbol.dart index 7c37eca6eb..aed9e8e7d0 100644 --- a/packages/cassowary/lib/symbol.dart +++ b/packages/cassowary/lib/symbol.dart @@ -7,7 +7,8 @@ part of cassowary; enum SymbolType { invalid, external, slack, error, dummy, } class Symbol { - SymbolType type; + final SymbolType type; + int tick; - Symbol(this.type); + Symbol(this.type, this.tick); } diff --git a/packages/cassowary/lib/utils.dart b/packages/cassowary/lib/utils.dart index b003269dd9..699a6ae902 100644 --- a/packages/cassowary/lib/utils.dart +++ b/packages/cassowary/lib/utils.dart @@ -13,3 +13,9 @@ bool _nearZero(double value) { // instead. Sadly, due the lack of generic types on functions, we have to use // dynamic instead. _elvis(a, b) => a != null ? a : b; + +class _Pair { + X first; + Y second; + _Pair(this.first, this.second); +} From 9beb28618076975ae5d750b5632ad00636aed09c Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 23 Jun 2015 18:07:56 -0700 Subject: [PATCH 11/62] Account for the result of optimization when adding constraints --- packages/cassowary/lib/solver.dart | 4 +--- packages/cassowary/test/cassowary_test.dart | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index cbe5721ff8..51da4b5930 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -46,9 +46,7 @@ class Solver { _constraints[constraint] = tag; - _optimizeObjectiveRow(_objective); - - return Result.success; + return _optimizeObjectiveRow(_objective); } Result removeContraint(Constraint c) { diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index cce4618fd9..2e41a9ce34 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -282,8 +282,7 @@ void main() { expect((right >= left) is Constraint, true); - // TODO: Add assertions for this - s << c1 << c2; + expect(s.addConstraint(c1), Result.success); }); test('constraint_complex', () { From 8187c6852b56893b4621174eccfa96b2c0d4c33e Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 24 Jun 2015 12:41:54 -0700 Subject: [PATCH 12/62] Allow removal of constraints from the solver --- packages/cassowary/lib/solver.dart | 93 ++++++++++++++++++++- packages/cassowary/test/cassowary_test.dart | 17 +++- 2 files changed, 107 insertions(+), 3 deletions(-) diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index 51da4b5930..a22b079acd 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -49,8 +49,37 @@ class Solver { return _optimizeObjectiveRow(_objective); } - Result removeContraint(Constraint c) { - return Result.unimplemented; + Result removeConstraint(Constraint constraint) { + Tag tag = _constraints[constraint]; + if (tag == null) { + return Result.unknownConstraint; + } + + tag = new Tag.fromTag(tag); + _constraints.remove(constraint); + + _removeConstraintEffects(constraint, tag); + + Row row = _rows[tag.marker]; + if (row != null) { + _rows.remove(tag.marker); + } else { + _Pair rowPair = + _getLeavingRowPairForMarkerSymbol(tag.marker); + + if (rowPair == null) { + return Result.internalSolverError; + } + + Symbol leaving = rowPair.first; + row = rowPair.second; + var removed = _rows.remove(rowPair.first); + assert(removed != null); + row.solveForSymbols(leaving, tag.marker); + _substitute(tag.marker, row); + } + + return _optimizeObjectiveRow(_objective); } Result hasConstraint(Constraint c) { @@ -308,6 +337,63 @@ class Solver { } return new Symbol(SymbolType.invalid, 0); } + + void _removeConstraintEffects(Constraint cn, Tag tag) { + if (tag.marker.type == SymbolType.error) { + _removeMarkerEffects(tag.marker, cn.priority); + } + if (tag.other.type == SymbolType.error) { + _removeMarkerEffects(tag.other, cn.priority); + } + } + + void _removeMarkerEffects(Symbol marker, double strength) { + Row row = _rows[marker]; + if (row != null) { + _objective.insertRow(row, -strength); + } else { + _objective.insertSymbol(marker, -strength); + } + } + + _Pair _getLeavingRowPairForMarkerSymbol(Symbol marker) { + double r1 = double.MAX_FINITE; + double r2 = double.MAX_FINITE; + + _Pair first, second, third; + + _rows.forEach((symbol, row) { + double c = row.coefficientForSymbol(marker); + + if (c == 0.0) { + return; + } + + if (symbol.type == SymbolType.external) { + third = new _Pair(symbol, row); + } else if (c < 0.0) { + double r = -row.constant / c; + if (r < r1) { + r1 = r; + first = new _Pair(symbol, row); + } + } else { + double r = row.constant / c; + if (r < r2) { + r2 = r; + second = new _Pair(symbol, row); + } + } + }); + + if (first != null) { + return first; + } + if (second != null) { + return second; + } + return third; + } } class Tag { @@ -315,6 +401,9 @@ class Tag { Symbol other; Tag(this.marker, this.other); + Tag.fromTag(Tag tag) + : this.marker = tag.marker, + this.other = tag.other; } class EditInfo { diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index 2e41a9ce34..4e9bd76e6f 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -278,7 +278,6 @@ void main() { var right = new Variable(100.0); var c1 = right - left >= CM(200.0); - var c2 = right + left >= CM(0.0); expect((right >= left) is Constraint, true); @@ -340,4 +339,20 @@ void main() { expect(c4.expression.terms.length, 2); expect(c4.expression.constant, -20.0); }); + + test('constraint_update_in_solver', () { + var s = new Solver(); + + var left = new Variable(2.0); + var right = new Variable(100.0); + + var c1 = right - left >= CM(200.0); + var 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); + }); } From 5137e03c9db979b9769fc1d17a84771cbc787bae Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 24 Jun 2015 13:23:31 -0700 Subject: [PATCH 13/62] Add support for updating edits --- packages/cassowary/lib/solver.dart | 52 +++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index a22b079acd..0a2b96086b 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -82,20 +82,52 @@ class Solver { return _optimizeObjectiveRow(_objective); } - Result hasConstraint(Constraint c) { - return Result.unimplemented; + bool hasConstraint(Constraint constraint) { + return _constraints.containsKey(constraint); } - Result addEditVariable(Variable v, double priority) { - return Result.unimplemented; + Result addEditVariable(Variable variable, double priority) { + if (_edits.containsKey(variable)) { + return Result.duplicateEditVariable; + } + + if (!_isValidNonRequiredPriority(priority)) { + return Result.badRequiredStrength; + } + + Constraint constraint = new Constraint( + new Expression([new Term(variable, 1.0)], 0.0), Relation.equalTo); + + if (addConstraint(constraint) != Result.success) { + return Result.internalSolverError; + } + + EditInfo info = new EditInfo(); + info.tag = _constraints[constraint]; + info.constraint = constraint; + info.constant = 0.0; + + _edits[variable] = info; + + return Result.success; } - Result removeEditVariable(Variable v) { - return Result.unimplemented; + Result removeEditVariable(Variable variable) { + EditInfo info = _edits[variable]; + if (info == null) { + return Result.unknownEditVariable; + } + + if (removeConstraint(info.constraint) != Result.success) { + return Result.internalSolverError; + } + + _edits.remove(variable); + return Result.success; } - Result hasEditVariable(Variable v) { - return Result.unimplemented; + bool hasEditVariable(Variable variable) { + return _edits.containsKey(variable); } Result suggestVariable(Variable v, double value) { @@ -411,3 +443,7 @@ class EditInfo { Constraint constraint; double constant; } + +bool _isValidNonRequiredPriority(double priority) { + return (priority >= 0.0 && priority < Constraint.requiredPriority); +} From 9ea8abd5afb17a9ba3afe340b92a37bb39a82a80 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 24 Jun 2015 14:52:46 -0700 Subject: [PATCH 14/62] Allow constraint creation from multiplication and division when at least one argument is a constant expression --- packages/cassowary/lib/cassowary.dart | 1 + packages/cassowary/lib/constant_member.dart | 2 + packages/cassowary/lib/equation_member.dart | 8 ++- packages/cassowary/lib/expression.dart | 49 +++++++++++++-- packages/cassowary/lib/parser_exception.dart | 16 +++++ packages/cassowary/lib/term.dart | 3 + packages/cassowary/lib/variable.dart | 2 + packages/cassowary/test/cassowary_test.dart | 65 +++++++++++++++++--- 8 files changed, 131 insertions(+), 15 deletions(-) create mode 100644 packages/cassowary/lib/parser_exception.dart diff --git a/packages/cassowary/lib/cassowary.dart b/packages/cassowary/lib/cassowary.dart index 40f58941e6..e55c291a5a 100644 --- a/packages/cassowary/lib/cassowary.dart +++ b/packages/cassowary/lib/cassowary.dart @@ -15,3 +15,4 @@ part 'symbol.dart'; part 'row.dart'; part 'utils.dart'; part 'result.dart'; +part 'parser_exception.dart'; diff --git a/packages/cassowary/lib/constant_member.dart b/packages/cassowary/lib/constant_member.dart index de6afb422d..629f1b9e27 100644 --- a/packages/cassowary/lib/constant_member.dart +++ b/packages/cassowary/lib/constant_member.dart @@ -7,6 +7,8 @@ part of cassowary; class ConstantMember extends EquationMember { double value = 0.0; + bool get isConstant => true; + ConstantMember(this.value); Expression asExpression() => new Expression([], this.value); diff --git a/packages/cassowary/lib/equation_member.dart b/packages/cassowary/lib/equation_member.dart index 50ea95a8a4..aa59021308 100644 --- a/packages/cassowary/lib/equation_member.dart +++ b/packages/cassowary/lib/equation_member.dart @@ -7,6 +7,10 @@ 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; @@ -17,7 +21,7 @@ abstract class EquationMember { Expression operator -(EquationMember m) => asExpression() - m; - Expression operator *(double m) => asExpression() * m; + Expression operator *(EquationMember m) => asExpression() * m; - Expression operator /(double m) => asExpression() / m; + Expression operator /(EquationMember m) => asExpression() / m; } diff --git a/packages/cassowary/lib/expression.dart b/packages/cassowary/lib/expression.dart index 366ea625b7..a4c2b15445 100644 --- a/packages/cassowary/lib/expression.dart +++ b/packages/cassowary/lib/expression.dart @@ -8,6 +8,9 @@ class Expression extends EquationMember { final List terms; final double constant; + + bool get isConstant => terms.length == 0; + double get value => terms.fold(constant, (value, term) => value + term.value); Expression(this.terms, this.constant); @@ -105,15 +108,51 @@ class Expression extends EquationMember { return null; } - EquationMember operator *(double m) { + EquationMember _applyMultiplicand(double m) { var newTerms = terms.fold(new List(), (list, term) => list ..add(new Term(term.variable, term.coefficient * m))); return new Expression(newTerms, constant * m); } - EquationMember operator /(double m) { - var newTerms = terms.fold(new List(), (list, term) => list - ..add(new Term(term.variable, term.coefficient / m))); - return new Expression(newTerms, constant / m); + _Pair _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(m.asExpression(), this.value); + } + + if (m.isConstant) { + return new _Pair(this.asExpression(), m.value); + } + + assert(false); + return null; + } + + EquationMember operator *(EquationMember m) { + _Pair args = _findMulitplierAndMultiplicand(m); + + if (args == null) { + throw new ParserException( + "Could not find constant multiplicand or multiplier", [this, m]); + return null; + } + + return args.first._applyMultiplicand(args.second); + } + + EquationMember operator /(EquationMember m) { + if (!m.isConstant) { + throw new ParserException( + "The divisor was not a constant expression", [this, m]); + return null; + } + + return this._applyMultiplicand(1.0 / m.value); } } diff --git a/packages/cassowary/lib/parser_exception.dart b/packages/cassowary/lib/parser_exception.dart new file mode 100644 index 0000000000..94494a5431 --- /dev/null +++ b/packages/cassowary/lib/parser_exception.dart @@ -0,0 +1,16 @@ +// 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 ParserException implements Exception { + final String message; + List members; + ParserException(this.message, this.members); + + String toString() { + if (message == null) return "Error while parsing constraint or expression"; + return "Error: '$message' while trying to parse constraint or expression"; + } +} diff --git a/packages/cassowary/lib/term.dart b/packages/cassowary/lib/term.dart index e4f7b24d6a..8a88148025 100644 --- a/packages/cassowary/lib/term.dart +++ b/packages/cassowary/lib/term.dart @@ -7,6 +7,9 @@ part of cassowary; class Term extends EquationMember { final Variable variable; final double coefficient; + + bool get isConstant => false; + double get value => coefficient * variable.value; Term(this.variable, this.coefficient); diff --git a/packages/cassowary/lib/variable.dart b/packages/cassowary/lib/variable.dart index 5168290e9d..0a9262e6ef 100644 --- a/packages/cassowary/lib/variable.dart +++ b/packages/cassowary/lib/variable.dart @@ -8,5 +8,7 @@ class Variable extends EquationMember { double value = 0.0; Variable(this.value); + bool get isConstant => false; + Expression asExpression() => new Expression([new Term(this, 1.0)], 0.0); } diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index 4e9bd76e6f..310f0d116b 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -204,37 +204,37 @@ void main() { test('simple_multiplication', () { // Constant var c = CM(20.0); - expect((c * 2.0).value, 40.0); + expect((c * CM(2.0)).value, 40.0); // Variable var v = new Variable(20.0); - expect((v * 2.0).value, 40.0); + expect((v * CM(2.0)).value, 40.0); // Term var t = new Term(v, 1.0); - expect((t * 2.0).value, 40.0); + expect((t * CM(2.0)).value, 40.0); // Expression var e = new Expression([t], 0.0); - expect((e * 2.0).value, 40.0); + expect((e * CM(2.0)).value, 40.0); }); test('simple_division', () { // Constant var c = CM(20.0); - expect((c / 2.0).value, 10.0); + expect((c / CM(2.0)).value, 10.0); // Variable var v = new Variable(20.0); - expect((v / 2.0).value, 10.0); + expect((v / CM(2.0)).value, 10.0); // Term var t = new Term(v, 1.0); - expect((t / 2.0).value, 10.0); + expect((t / CM(2.0)).value, 10.0); // Expression var e = new Expression([t], 0.0); - expect((e / 2.0).value, 10.0); + expect((e / CM(2.0)).value, 10.0); }); // TODO: Support and test cases where the multipliers and divisors are more @@ -355,4 +355,53 @@ void main() { expect(s.removeConstraint(c1), Result.success); expect(s.removeConstraint(c1), Result.unknownConstraint); }); + + test('test_multiplication_division_override', () { + var c = CM(10.0); + var v = new Variable(c.value); + var t = new Term(v, 1.0); + var e = new Expression([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', () { + var c = CM(10.0); + var v = new Variable(c.value); + var t = new Term(v, 1.0); + var e = new Expression([t], 0.0); + + expect((c * c).value, 100); + expect(() => v * v, throwsA(new isInstanceOf())); + expect(() => v / v, throwsA(new isInstanceOf())); + expect(() => v * t, throwsA(new isInstanceOf())); + expect(() => v / t, throwsA(new isInstanceOf())); + expect(() => v * e, throwsA(new isInstanceOf())); + expect(() => v / e, throwsA(new isInstanceOf())); + expect(() => v * c, returnsNormally); + expect(() => v / c, returnsNormally); + }); + } From 436f272a094f564a557b10cf1b1b417d655230cb Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 24 Jun 2015 16:48:02 -0700 Subject: [PATCH 15/62] Avoid using variables as equation members --- packages/cassowary/lib/cassowary.dart | 1 + packages/cassowary/lib/equation_member.dart | 5 +- packages/cassowary/lib/expression.dart | 13 +-- packages/cassowary/lib/param.dart | 18 ++++ packages/cassowary/lib/variable.dart | 6 +- packages/cassowary/test/cassowary_test.dart | 108 ++++++++++++-------- 6 files changed, 95 insertions(+), 56 deletions(-) create mode 100644 packages/cassowary/lib/param.dart diff --git a/packages/cassowary/lib/cassowary.dart b/packages/cassowary/lib/cassowary.dart index e55c291a5a..2445e9ad3e 100644 --- a/packages/cassowary/lib/cassowary.dart +++ b/packages/cassowary/lib/cassowary.dart @@ -16,3 +16,4 @@ part 'row.dart'; part 'utils.dart'; part 'result.dart'; part 'parser_exception.dart'; +part 'param.dart'; diff --git a/packages/cassowary/lib/equation_member.dart b/packages/cassowary/lib/equation_member.dart index aa59021308..243aa712bc 100644 --- a/packages/cassowary/lib/equation_member.dart +++ b/packages/cassowary/lib/equation_member.dart @@ -15,7 +15,7 @@ abstract class EquationMember { Constraint operator <=(EquationMember m) => asExpression() <= m; - operator ==(EquationMember m) => asExpression() == m; + /* Constraint */ operator ==(EquationMember m) => asExpression() == m; Expression operator +(EquationMember m) => asExpression() + m; @@ -24,4 +24,7 @@ abstract class EquationMember { Expression operator *(EquationMember m) => asExpression() * m; Expression operator /(EquationMember m) => asExpression() / m; + + int get hashCode => + throw "An equation member is not comparable and cannot be added to collections"; } diff --git a/packages/cassowary/lib/expression.dart b/packages/cassowary/lib/expression.dart index a4c2b15445..3af04db3fe 100644 --- a/packages/cassowary/lib/expression.dart +++ b/packages/cassowary/lib/expression.dart @@ -28,8 +28,9 @@ class Expression extends EquationMember { relation); } - if (value is Variable) { - var newTerms = new List.from(terms)..add(new Term(value, -1.0)); + if (value is Param) { + var newTerms = new List.from(terms) + ..add(new Term(value.variable, -1.0)); return new Constraint(new Expression(newTerms, constant), relation); } @@ -64,9 +65,9 @@ class Expression extends EquationMember { return new Expression(new List.from(terms), constant + m.value); } - if (m is Variable) { + if (m is Param) { return new Expression( - new List.from(terms)..add(new Term(m, 1.0)), constant); + new List.from(terms)..add(new Term(m.variable, 1.0)), constant); } if (m is Term) { @@ -87,9 +88,9 @@ class Expression extends EquationMember { return new Expression(new List.from(terms), constant - m.value); } - if (m is Variable) { + if (m is Param) { return new Expression( - new List.from(terms)..add(new Term(m, -1.0)), constant); + new List.from(terms)..add(new Term(m.variable, -1.0)), constant); } if (m is Term) { diff --git a/packages/cassowary/lib/param.dart b/packages/cassowary/lib/param.dart new file mode 100644 index 0000000000..22a7da28a1 --- /dev/null +++ b/packages/cassowary/lib/param.dart @@ -0,0 +1,18 @@ +// 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 Param extends EquationMember { + final Variable variable; + + Param.withVariable(this.variable); + Param(double value) : this.variable = new Variable(value); + + bool get isConstant => false; + + double get value => variable.value; + + Expression asExpression() => new Expression([new Term(variable, 1.0)], 0.0); +} diff --git a/packages/cassowary/lib/variable.dart b/packages/cassowary/lib/variable.dart index 0a9262e6ef..0d22967d3c 100644 --- a/packages/cassowary/lib/variable.dart +++ b/packages/cassowary/lib/variable.dart @@ -4,11 +4,9 @@ part of cassowary; -class Variable extends EquationMember { +class Variable { double value = 0.0; Variable(this.value); - bool get isConstant => false; - - Expression asExpression() => new Expression([new Term(this, 1.0)], 0.0); + // TODO(csg): Add external variable update callbacks here } diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index 310f0d116b..f5eb34d02d 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -10,12 +10,12 @@ import 'package:cassowary/cassowary.dart'; void main() { test('variable', () { - var v = new Variable(22.0); + var v = new Param(22.0); expect(v.value, 22); }); test('variable1', () { - var v = new Variable(22.0); + var v = new Param(22.0); expect((v + CM(22.0)).value, 44.0); expect((v - CM(20.0)).value, 2.0); }); @@ -35,11 +35,11 @@ void main() { }); test('expression1', () { - var v1 = new Variable(10.0); - var v2 = new Variable(10.0); - var v3 = new Variable(22.0); + var v1 = new Param(10.0); + var v2 = new Param(10.0); + var v3 = new Param(22.0); - expect(v1 is Variable, true); + expect(v1 is Param, true); expect(v1 + CM(20.0) is Expression, true); expect(v1 + v2 is Expression, true); @@ -51,7 +51,7 @@ void main() { }); test('expression2', () { - var e = new Variable(10.0) + CM(5.0); + var e = new Param(10.0) + CM(5.0); expect(e.value, 15.0); expect(e is Expression, true); @@ -63,8 +63,8 @@ void main() { expect(e.value, 15.0); - // Variable - var v = new Variable(2.0); + // Param + var v = new Param(2.0); expect((e + v) is Expression, true); expect((e + v).value, 17.0); expect((e - v) is Expression, true); @@ -73,7 +73,7 @@ void main() { expect(e.value, 15.0); // Term - var t = new Term(v, 2.0); + var 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); @@ -82,7 +82,7 @@ void main() { expect(e.value, 15.0); // Expression - var e2 = new Variable(7.0) + new Variable(3.0); + var 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); @@ -102,7 +102,7 @@ void main() { expect((t - c).value, 10.0); // Variable - var v = new Variable(2.0); + var v = new Param(2.0); expect((t + v) is Expression, true); expect((t + v).value, 14.0); expect((t - v) is Expression, true); @@ -116,7 +116,7 @@ void main() { expect((t - t2).value, 10.0); // Expression - var exp = new Variable(1.0) + CM(1.0); + var 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); @@ -124,7 +124,7 @@ void main() { }); test('variable3', () { - var v = new Variable(3.0); + var v = new Param(3.0); // Constant var c = CM(2.0); @@ -134,7 +134,7 @@ void main() { expect((v - c).value, 1.0); // Variable - var v2 = new Variable(2.0); + var v2 = new Param(2.0); expect((v + v2) is Expression, true); expect((v + v2).value, 5.0); expect((v - v2) is Expression, true); @@ -148,7 +148,7 @@ void main() { expect((v - t2).value, 1.0); // Expression - var exp = new Variable(1.0) + CM(1.0); + var exp = new Param(1.0) + CM(1.0); expect(exp.terms.length, 1); expect((v + exp) is Expression, true); @@ -168,7 +168,7 @@ void main() { expect((c - c2).value, 1.0); // Variable - var v2 = new Variable(2.0); + var v2 = new Param(2.0); expect((c + v2) is Expression, true); expect((c + v2).value, 5.0); expect((c - v2) is Expression, true); @@ -182,7 +182,7 @@ void main() { expect((c - t2).value, 1.0); // Expression - var exp = new Variable(1.0) + CM(1.0); + var exp = new Param(1.0) + CM(1.0); expect((c + exp) is Expression, true); expect((c + exp).value, 5.0); @@ -191,27 +191,24 @@ void main() { }); test('constraint2', () { - var left = new Variable(10.0); - var right = new Variable(100.0); + var left = new Param(10.0); + var right = new Param(100.0); var c = right - left >= CM(25.0); expect(c is Constraint, true); }); - // TODO(csg): Address API inconsistency where the multipliers and divisors - // are doubles instead of equation members - test('simple_multiplication', () { // Constant var c = CM(20.0); expect((c * CM(2.0)).value, 40.0); // Variable - var v = new Variable(20.0); + var v = new Param(20.0); expect((v * CM(2.0)).value, 40.0); // Term - var t = new Term(v, 1.0); + var t = new Term(v.variable, 1.0); expect((t * CM(2.0)).value, 40.0); // Expression @@ -225,11 +222,11 @@ void main() { expect((c / CM(2.0)).value, 10.0); // Variable - var v = new Variable(20.0); + var v = new Param(20.0); expect((v / CM(2.0)).value, 10.0); // Term - var t = new Term(v, 1.0); + var t = new Term(v.variable, 1.0); expect((t / CM(2.0)).value, 10.0); // Expression @@ -237,12 +234,9 @@ void main() { expect((e / CM(2.0)).value, 10.0); }); - // TODO: Support and test cases where the multipliers and divisors are more - // than just simple constants. - test('full_constraints_setup', () { - var left = new Variable(2.0); - var right = new Variable(10.0); + var left = new Param(2.0); + var right = new Param(10.0); var c1 = right - left >= CM(20.0); expect(c1 is Constraint, true); @@ -261,8 +255,8 @@ void main() { }); test('constraint_strength_update', () { - var left = new Variable(2.0); - var right = new Variable(10.0); + var left = new Param(2.0); + var right = new Param(10.0); var c = (right - left >= CM(200.0)) | 750.0; expect(c is Constraint, true); @@ -274,8 +268,8 @@ void main() { test('solver', () { var s = new Solver(); - var left = new Variable(2.0); - var right = new Variable(100.0); + var left = new Param(2.0); + var right = new Param(100.0); var c1 = right - left >= CM(200.0); @@ -285,7 +279,7 @@ void main() { }); test('constraint_complex', () { - var e = new Variable(200.0) - new Variable(100.0); + var e = new Param(200.0) - new Param(100.0); // Constant var c1 = e >= CM(50.0); @@ -294,7 +288,7 @@ void main() { expect(c1.expression.constant, -50.0); // Variable - var c2 = e >= new Variable(2.0); + var c2 = e >= new Param(2.0); expect(c2 is Constraint, true); expect(c2.expression.terms.length, 3); expect(c2.expression.constant, 0.0); @@ -320,7 +314,7 @@ void main() { expect(c1.expression.constant, 50.0); // Variable - var c2 = new Variable(100.0) >= new Variable(2.0); + var 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); @@ -343,8 +337,8 @@ void main() { test('constraint_update_in_solver', () { var s = new Solver(); - var left = new Variable(2.0); - var right = new Variable(100.0); + var left = new Param(2.0); + var right = new Param(100.0); var c1 = right - left >= CM(200.0); var c2 = right >= right; @@ -358,8 +352,8 @@ void main() { test('test_multiplication_division_override', () { var c = CM(10.0); - var v = new Variable(c.value); - var t = new Term(v, 1.0); + var v = new Param(c.value); + var t = new Term(v.variable, 1.0); var e = new Expression([t], 0.0); // Constant @@ -389,8 +383,8 @@ void main() { test('test_multiplication_division_exceptions', () { var c = CM(10.0); - var v = new Variable(c.value); - var t = new Term(v, 1.0); + var v = new Param(c.value); + var t = new Term(v.variable, 1.0); var e = new Expression([t], 0.0); expect((c * c).value, 100); @@ -404,4 +398,28 @@ void main() { expect(() => v / c, returnsNormally); }); + test('edit_updates', () { + Solver s = new Solver(); + + var left = new Param(0.0); + var right = new Param(100.0); + var 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', () { + var left = new Param(0.0); + var right = new Param(100.0); + var mid = new Param(0.0); + + expect(((left + right) >= (CM(2.0) * mid)) is Constraint, true); + }); } From d4a67499e9acec32ddccdc2f39073e6f9b4d874f Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 24 Jun 2015 17:53:41 -0700 Subject: [PATCH 16/62] Implement Solver.suggestValue --- packages/cassowary/lib/solver.dart | 93 +++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 2 deletions(-) diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index 0a2b96086b..45eb62b904 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -130,8 +130,14 @@ class Solver { return _edits.containsKey(variable); } - Result suggestVariable(Variable v, double value) { - return Result.unimplemented; + Result suggestValueForVariable(Variable variable, double value) { + if (!_edits.containsKey(variable)) { + return Result.unknownEditVariable; + } + + _suggestValueForEditInfoWithoutDualOptimization(_edits[variable], value); + + return _dualOptimize(); } void updateVariable() {} @@ -426,6 +432,89 @@ class Solver { } return third; } + + void _suggestValueForEditInfoWithoutDualOptimization( + EditInfo info, double value) { + double delta = value - info.constant; + info.constant = value; + + { + Symbol symbol = info.tag.marker; + Row row = _rows[info.tag.marker]; + + if (row != null) { + if (row.add(-delta) < 0.0) { + _infeasibleRows.add(symbol); + } + return; + } + + symbol = info.tag.other; + row = _rows[info.tag.other]; + + if (row != null) { + if (row.add(delta) < 0.0) { + _infeasibleRows.add(symbol); + } + return; + } + } + + for (Symbol symbol in _rows.keys) { + Row row = _rows[symbol]; + double coeff = row.coefficientForSymbol(info.tag.marker); + if (coeff != 0.0 && + row.add(delta * coeff) < 0.0 && + symbol.type != SymbolType.external) { + _infeasibleRows.add(symbol); + } + } + } + + Result _dualOptimize() { + while (_infeasibleRows.length != 0) { + Symbol leaving = _infeasibleRows.removeLast(); + Row row = _rows[leaving]; + + if (row != null && row.constant < 0.0) { + Symbol entering = _getDualEnteringSymbolForRow(row); + + if (entering.type == SymbolType.invalid) { + return Result.internalSolverError; + } + + _rows.remove(leaving); + + row.solveForSymbols(leaving, entering); + _substitute(entering, row); + _rows[entering] = row; + } + } + return Result.success; + } + + Symbol _getDualEnteringSymbolForRow(Row row) { + Symbol entering; + + double ratio = double.MAX_FINITE; + + Map rowCells = row.cells; + + for (Symbol symbol in rowCells.keys) { + double value = rowCells[symbol]; + + if (value > 0.0 && symbol.type != SymbolType.dummy) { + double coeff = _objective.coefficientForSymbol(symbol); + double r = coeff / value; + if (r < ratio) { + ratio = r; + entering = symbol; + } + } + } + + return _elvis(entering, new Symbol(SymbolType.invalid, 0)); + } } class Tag { From 891085b74bfb3ac996536eae18e7621a57493db0 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 24 Jun 2015 18:03:21 -0700 Subject: [PATCH 17/62] Allow updating external variables from the solver --- packages/cassowary/lib/solver.dart | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index 45eb62b904..77d5afa008 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -140,7 +140,17 @@ class Solver { return _dualOptimize(); } - void updateVariable() {} + void updateVariable() { + for (Variable variable in _vars.keys) { + Symbol symbol = _vars[variable]; + Row row = _rows[symbol]; + if (row == null) { + variable.value = 0.0; + } else { + variable.value = row.constant; + } + } + } Solver operator <<(Constraint c) => this..addConstraint(c); From 7dcd8115c3d2b97c636b50d21e2331321c3dbe26 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 24 Jun 2015 18:18:58 -0700 Subject: [PATCH 18/62] Avoid exposing internal classes from the cassowary library --- packages/cassowary/lib/cassowary.dart | 6 +- packages/cassowary/lib/constant_member.dart | 2 +- packages/cassowary/lib/equation_member.dart | 16 +- packages/cassowary/lib/expression.dart | 22 +-- packages/cassowary/lib/param.dart | 2 +- packages/cassowary/lib/parser_exception.dart | 2 +- packages/cassowary/lib/row.dart | 24 +-- packages/cassowary/lib/solver.dart | 170 +++++++++---------- packages/cassowary/lib/symbol.dart | 4 +- packages/cassowary/lib/term.dart | 2 +- 10 files changed, 125 insertions(+), 125 deletions(-) diff --git a/packages/cassowary/lib/cassowary.dart b/packages/cassowary/lib/cassowary.dart index 2445e9ad3e..21ae5b8fc2 100644 --- a/packages/cassowary/lib/cassowary.dart +++ b/packages/cassowary/lib/cassowary.dart @@ -1,7 +1,7 @@ -// Copyright (c) 2015, . All rights reserved. Use of this source code -// is governed by a BSD-style license that can be found in the LICENSE file. +// Copyright (c) 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. -/// The cassowary library. library cassowary; part 'constraint.dart'; diff --git a/packages/cassowary/lib/constant_member.dart b/packages/cassowary/lib/constant_member.dart index 629f1b9e27..09490d1131 100644 --- a/packages/cassowary/lib/constant_member.dart +++ b/packages/cassowary/lib/constant_member.dart @@ -4,7 +4,7 @@ part of cassowary; -class ConstantMember extends EquationMember { +class ConstantMember extends _EquationMember { double value = 0.0; bool get isConstant => true; diff --git a/packages/cassowary/lib/equation_member.dart b/packages/cassowary/lib/equation_member.dart index 243aa712bc..0e33640c05 100644 --- a/packages/cassowary/lib/equation_member.dart +++ b/packages/cassowary/lib/equation_member.dart @@ -4,26 +4,26 @@ part of cassowary; -abstract class EquationMember { +abstract class _EquationMember { Expression asExpression(); bool get isConstant; double get value; - Constraint operator >=(EquationMember m) => asExpression() >= m; + Constraint operator >=(_EquationMember m) => asExpression() >= m; - Constraint operator <=(EquationMember m) => asExpression() <= m; + Constraint operator <=(_EquationMember m) => asExpression() <= m; - /* Constraint */ operator ==(EquationMember m) => asExpression() == m; + /* Constraint */ operator ==(_EquationMember m) => asExpression() == 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; - Expression operator *(EquationMember m) => asExpression() * m; + Expression operator *(_EquationMember m) => asExpression() * m; - Expression operator /(EquationMember m) => asExpression() / m; + Expression operator /(_EquationMember m) => asExpression() / m; int get hashCode => throw "An equation member is not comparable and cannot be added to collections"; diff --git a/packages/cassowary/lib/expression.dart b/packages/cassowary/lib/expression.dart index 3af04db3fe..8dc82f7157 100644 --- a/packages/cassowary/lib/expression.dart +++ b/packages/cassowary/lib/expression.dart @@ -4,7 +4,7 @@ part of cassowary; -class Expression extends EquationMember { +class Expression extends _EquationMember { final List terms; final double constant; @@ -21,7 +21,7 @@ class Expression extends EquationMember { Expression asExpression() => this; Constraint _createConstraint( - EquationMember /* rhs */ value, Relation relation) { + _EquationMember /* rhs */ value, Relation relation) { if (value is ConstantMember) { return new Constraint( new Expression(new List.from(terms), constant - value.value), @@ -51,16 +51,16 @@ class Expression extends EquationMember { return null; } - Constraint operator >=(EquationMember value) => + Constraint operator >=(_EquationMember value) => _createConstraint(value, Relation.greaterThanOrEqualTo); - Constraint operator <=(EquationMember value) => + Constraint operator <=(_EquationMember value) => _createConstraint(value, Relation.lessThanOrEqualTo); - operator ==(EquationMember value) => + operator ==(_EquationMember value) => _createConstraint(value, Relation.equalTo); - Expression operator +(EquationMember m) { + Expression operator +(_EquationMember m) { if (m is ConstantMember) { return new Expression(new List.from(terms), constant + m.value); } @@ -83,7 +83,7 @@ class Expression extends EquationMember { return null; } - Expression operator -(EquationMember m) { + Expression operator -(_EquationMember m) { if (m is ConstantMember) { return new Expression(new List.from(terms), constant - m.value); } @@ -109,13 +109,13 @@ class Expression extends EquationMember { return null; } - EquationMember _applyMultiplicand(double m) { + _EquationMember _applyMultiplicand(double m) { var newTerms = terms.fold(new List(), (list, term) => list ..add(new Term(term.variable, term.coefficient * m))); return new Expression(newTerms, constant * m); } - _Pair _findMulitplierAndMultiplicand(EquationMember m) { + _Pair _findMulitplierAndMultiplicand(_EquationMember m) { // At least on of the the two members must be constant for the resulting // expression to be linear @@ -135,7 +135,7 @@ class Expression extends EquationMember { return null; } - EquationMember operator *(EquationMember m) { + _EquationMember operator *(_EquationMember m) { _Pair args = _findMulitplierAndMultiplicand(m); if (args == null) { @@ -147,7 +147,7 @@ class Expression extends EquationMember { return args.first._applyMultiplicand(args.second); } - EquationMember operator /(EquationMember m) { + _EquationMember operator /(_EquationMember m) { if (!m.isConstant) { throw new ParserException( "The divisor was not a constant expression", [this, m]); diff --git a/packages/cassowary/lib/param.dart b/packages/cassowary/lib/param.dart index 22a7da28a1..d6710ef15f 100644 --- a/packages/cassowary/lib/param.dart +++ b/packages/cassowary/lib/param.dart @@ -4,7 +4,7 @@ part of cassowary; -class Param extends EquationMember { +class Param extends _EquationMember { final Variable variable; Param.withVariable(this.variable); diff --git a/packages/cassowary/lib/parser_exception.dart b/packages/cassowary/lib/parser_exception.dart index 94494a5431..979533caa4 100644 --- a/packages/cassowary/lib/parser_exception.dart +++ b/packages/cassowary/lib/parser_exception.dart @@ -6,7 +6,7 @@ part of cassowary; class ParserException implements Exception { final String message; - List members; + List<_EquationMember> members; ParserException(this.message, this.members); String toString() { diff --git a/packages/cassowary/lib/row.dart b/packages/cassowary/lib/row.dart index 44a28c02fb..c178caed9b 100644 --- a/packages/cassowary/lib/row.dart +++ b/packages/cassowary/lib/row.dart @@ -4,18 +4,18 @@ part of cassowary; -class Row { - final Map cells; +class _Row { + final Map<_Symbol, double> cells; double constant = 0.0; - Row(this.constant) : this.cells = new Map(); - Row.fromRow(Row row) - : this.cells = new Map.from(row.cells), + _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]) { + void insertSymbol(_Symbol symbol, [double coefficient = 1.0]) { double val = _elvis(cells[symbol], 0.0) + coefficient; if (_nearZero(val)) { @@ -25,18 +25,18 @@ class Row { } } - void insertRow(Row other, [double coefficient = 1.0]) { + void insertRow(_Row other, [double coefficient = 1.0]) { constant += other.constant * coefficient; other.cells.forEach((s, v) => insertSymbol(s, v * coefficient)); } - void removeSymbol(Symbol symbol) { + void removeSymbol(_Symbol symbol) { cells.remove(symbol); } void reverseSign() => cells.forEach((s, v) => cells[s] = -v); - void solveForSymbol(Symbol symbol) { + void solveForSymbol(_Symbol symbol) { assert(cells.containsKey(symbol)); double coefficient = -1.0 / cells[symbol]; cells.remove(symbol); @@ -44,14 +44,14 @@ class Row { cells.forEach((s, v) => cells[s] = v * coefficient); } - void solveForSymbols(Symbol lhs, Symbol rhs) { + void solveForSymbols(_Symbol lhs, _Symbol rhs) { insertSymbol(lhs, -1.0); solveForSymbol(rhs); } - double coefficientForSymbol(Symbol symbol) => _elvis(cells[symbol], 0.0); + double coefficientForSymbol(_Symbol symbol) => _elvis(cells[symbol], 0.0); - void substitute(Symbol symbol, Row row) { + void substitute(_Symbol symbol, _Row row) { double coefficient = cells[symbol]; if (coefficient == null) { diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index 77d5afa008..3ad6c4b00a 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -5,13 +5,13 @@ part of cassowary; class Solver { - final Map _constraints = new Map(); - final Map _rows = new Map(); - final Map _vars = new Map(); - final Map _edits = new Map(); - final List _infeasibleRows = new List(); - final Row _objective = new Row(0.0); - Row _artificial = new Row(0.0); + final Map _constraints = new Map(); + final Map<_Symbol, _Row> _rows = new Map<_Symbol, _Row>(); + final Map _vars = new Map(); + final Map _edits = new Map(); + final List<_Symbol> _infeasibleRows = new List<_Symbol>(); + final _Row _objective = new _Row(0.0); + _Row _artificial = new _Row(0.0); int tick = 0; Result addConstraint(Constraint constraint) { @@ -19,12 +19,12 @@ class Solver { return Result.duplicateConstraint; } - Tag tag = new Tag( - new Symbol(SymbolType.invalid, 0), new Symbol(SymbolType.invalid, 0)); + _Tag tag = new _Tag( + new _Symbol(SymbolType.invalid, 0), new _Symbol(SymbolType.invalid, 0)); - Row row = _createRow(constraint, tag); + _Row row = _createRow(constraint, tag); - Symbol subject = _chooseSubjectForRow(row, tag); + _Symbol subject = _chooseSubjectForRow(row, tag); if (subject.type == SymbolType.invalid && _allDummiesInRow(row)) { if (!_nearZero(row.constant)) { @@ -50,28 +50,28 @@ class Solver { } Result removeConstraint(Constraint constraint) { - Tag tag = _constraints[constraint]; + _Tag tag = _constraints[constraint]; if (tag == null) { return Result.unknownConstraint; } - tag = new Tag.fromTag(tag); + tag = new _Tag.fromTag(tag); _constraints.remove(constraint); _removeConstraintEffects(constraint, tag); - Row row = _rows[tag.marker]; + _Row row = _rows[tag.marker]; if (row != null) { _rows.remove(tag.marker); } else { - _Pair rowPair = + _Pair<_Symbol, _Row> rowPair = _getLeavingRowPairForMarkerSymbol(tag.marker); if (rowPair == null) { return Result.internalSolverError; } - Symbol leaving = rowPair.first; + _Symbol leaving = rowPair.first; row = rowPair.second; var removed = _rows.remove(rowPair.first); assert(removed != null); @@ -102,7 +102,7 @@ class Solver { return Result.internalSolverError; } - EditInfo info = new EditInfo(); + _EditInfo info = new _EditInfo(); info.tag = _constraints[constraint]; info.constraint = constraint; info.constant = 0.0; @@ -113,7 +113,7 @@ class Solver { } Result removeEditVariable(Variable variable) { - EditInfo info = _edits[variable]; + _EditInfo info = _edits[variable]; if (info == null) { return Result.unknownEditVariable; } @@ -142,8 +142,8 @@ class Solver { void updateVariable() { for (Variable variable in _vars.keys) { - Symbol symbol = _vars[variable]; - Row row = _rows[symbol]; + _Symbol symbol = _vars[variable]; + _Row row = _rows[symbol]; if (row == null) { variable.value = 0.0; } else { @@ -154,28 +154,28 @@ class Solver { Solver operator <<(Constraint c) => this..addConstraint(c); - Symbol _getSymbolForVariable(Variable variable) { - Symbol symbol = _vars[variable]; + _Symbol _getSymbolForVariable(Variable variable) { + _Symbol symbol = _vars[variable]; if (symbol != null) { return symbol; } - symbol = new Symbol(SymbolType.external, tick++); + symbol = new _Symbol(SymbolType.external, tick++); _vars[variable] = symbol; return symbol; } - Row _createRow(Constraint constraint, Tag tag) { + _Row _createRow(Constraint constraint, _Tag tag) { Expression expr = new Expression.fromExpression(constraint.expression); - Row row = new Row(expr.constant); + _Row row = new _Row(expr.constant); expr.terms.forEach((term) { if (!_nearZero(term.coefficient)) { - Symbol symbol = _getSymbolForVariable(term.variable); + _Symbol symbol = _getSymbolForVariable(term.variable); - Row foundRow = _rows[symbol]; + _Row foundRow = _rows[symbol]; if (foundRow != null) { row.insertRow(foundRow, term.coefficient); @@ -192,12 +192,12 @@ class Solver { double coefficient = constraint.relation == Relation.lessThanOrEqualTo ? 1.0 : -1.0; - Symbol slack = new Symbol(SymbolType.slack, tick++); + _Symbol slack = new _Symbol(SymbolType.slack, tick++); tag.marker = slack; row.insertSymbol(slack, coefficient); if (!constraint.required) { - Symbol error = new Symbol(SymbolType.error, tick++); + _Symbol error = new _Symbol(SymbolType.error, tick++); tag.other = error; row.insertSymbol(error, -coefficient); _objective.insertSymbol(error, constraint.priority); @@ -206,8 +206,8 @@ class Solver { break; case Relation.equalTo: if (!constraint.required) { - Symbol errPlus = new Symbol(SymbolType.error, tick++); - Symbol errMinus = new Symbol(SymbolType.error, tick++); + _Symbol errPlus = new _Symbol(SymbolType.error, tick++); + _Symbol errMinus = new _Symbol(SymbolType.error, tick++); tag.marker = errPlus; tag.other = errMinus; row.insertSymbol(errPlus, -1.0); @@ -215,7 +215,7 @@ class Solver { _objective.insertSymbol(errPlus, constraint.priority); _objective.insertSymbol(errMinus, constraint.priority); } else { - Symbol dummy = new Symbol(SymbolType.dummy, tick++); + _Symbol dummy = new _Symbol(SymbolType.dummy, tick++); tag.marker = dummy; row.insertSymbol(dummy); } @@ -229,8 +229,8 @@ class Solver { return row; } - Symbol _chooseSubjectForRow(Row row, Tag tag) { - for (Symbol symbol in row.cells.keys) { + _Symbol _chooseSubjectForRow(_Row row, _Tag tag) { + for (_Symbol symbol in row.cells.keys) { if (symbol.type == SymbolType.external) { return symbol; } @@ -250,11 +250,11 @@ class Solver { } } - return new Symbol(SymbolType.invalid, 0); + return new _Symbol(SymbolType.invalid, 0); } - bool _allDummiesInRow(Row row) { - for (Symbol symbol in row.cells.keys) { + bool _allDummiesInRow(_Row row) { + for (_Symbol symbol in row.cells.keys) { if (symbol.type != SymbolType.dummy) { return false; } @@ -262,10 +262,10 @@ class Solver { return true; } - bool _addWithArtificialVariableOnRow(Row row) { - Symbol artificial = new Symbol(SymbolType.slack, tick++); - _rows[artificial] = new Row.fromRow(row); - _artificial = new Row.fromRow(row); + bool _addWithArtificialVariableOnRow(_Row row) { + _Symbol artificial = new _Symbol(SymbolType.slack, tick++); + _rows[artificial] = new _Row.fromRow(row); + _artificial = new _Row.fromRow(row); Result result = _optimizeObjectiveRow(_artificial); @@ -275,16 +275,16 @@ class Solver { } bool success = _nearZero(_artificial.constant); - _artificial = new Row(0.0); + _artificial = new _Row(0.0); - Row foundRow = _rows[artificial]; + _Row foundRow = _rows[artificial]; if (foundRow != null) { _rows.remove(artificial); if (foundRow.cells.isEmpty) { return success; } - Symbol entering = _anyPivotableSymbol(foundRow); + _Symbol entering = _anyPivotableSymbol(foundRow); if (entering.type == SymbolType.invalid) { return false; } @@ -294,29 +294,29 @@ class Solver { _rows[entering] = foundRow; } - for (Row row in _rows.values) { + for (_Row row in _rows.values) { row.removeSymbol(artificial); } _objective.removeSymbol(artificial); return success; } - Result _optimizeObjectiveRow(Row objective) { + Result _optimizeObjectiveRow(_Row objective) { while (true) { - Symbol entering = _getEnteringSymbolForObjectiveRow(objective); + _Symbol entering = _getEnteringSymbolForObjectiveRow(objective); if (entering.type == SymbolType.invalid) { return Result.success; } - _Pair leavingPair = + _Pair<_Symbol, _Row> leavingPair = _getLeavingRowForEnteringSymbol(entering); if (leavingPair == null) { return Result.internalSolverError; } - Symbol leaving = leavingPair.first; - Row row = leavingPair.second; + _Symbol leaving = leavingPair.first; + _Row row = leavingPair.second; _rows.remove(leavingPair.first); row.solveForSymbols(leaving, entering); _substitute(entering, row); @@ -324,21 +324,21 @@ class Solver { } } - Symbol _getEnteringSymbolForObjectiveRow(Row objective) { - Map cells = objective.cells; + _Symbol _getEnteringSymbolForObjectiveRow(_Row objective) { + Map<_Symbol, double> cells = objective.cells; - for (Symbol symbol in cells.keys) { + for (_Symbol symbol in cells.keys) { if (symbol.type != SymbolType.dummy && cells[symbol] < 0.0) { return symbol; } } - return new Symbol(SymbolType.invalid, 0); + return new _Symbol(SymbolType.invalid, 0); } - _Pair _getLeavingRowForEnteringSymbol(Symbol entering) { + _Pair<_Symbol, _Row> _getLeavingRowForEnteringSymbol(_Symbol entering) { double ratio = double.MAX_FINITE; - _Pair result = new _Pair(null, null); + _Pair<_Symbol, _Row> result = new _Pair(null, null); _rows.forEach((symbol, row) { if (symbol.type != SymbolType.external) { @@ -363,7 +363,7 @@ class Solver { return result; } - void _substitute(Symbol symbol, Row row) { + void _substitute(_Symbol symbol, _Row row) { _rows.forEach((first, second) { second.substitute(symbol, row); if (first.type != SymbolType.external && second.constant < 0.0) { @@ -377,16 +377,16 @@ class Solver { } } - Symbol _anyPivotableSymbol(Row row) { - for (Symbol symbol in row.cells.keys) { + _Symbol _anyPivotableSymbol(_Row row) { + for (_Symbol symbol in row.cells.keys) { if (symbol.type == SymbolType.slack || symbol.type == SymbolType.error) { return symbol; } } - return new Symbol(SymbolType.invalid, 0); + return new _Symbol(SymbolType.invalid, 0); } - void _removeConstraintEffects(Constraint cn, Tag tag) { + void _removeConstraintEffects(Constraint cn, _Tag tag) { if (tag.marker.type == SymbolType.error) { _removeMarkerEffects(tag.marker, cn.priority); } @@ -395,8 +395,8 @@ class Solver { } } - void _removeMarkerEffects(Symbol marker, double strength) { - Row row = _rows[marker]; + void _removeMarkerEffects(_Symbol marker, double strength) { + _Row row = _rows[marker]; if (row != null) { _objective.insertRow(row, -strength); } else { @@ -404,11 +404,11 @@ class Solver { } } - _Pair _getLeavingRowPairForMarkerSymbol(Symbol marker) { + _Pair<_Symbol, _Row> _getLeavingRowPairForMarkerSymbol(_Symbol marker) { double r1 = double.MAX_FINITE; double r2 = double.MAX_FINITE; - _Pair first, second, third; + _Pair<_Symbol, _Row> first, second, third; _rows.forEach((symbol, row) { double c = row.coefficientForSymbol(marker); @@ -444,13 +444,13 @@ class Solver { } void _suggestValueForEditInfoWithoutDualOptimization( - EditInfo info, double value) { + _EditInfo info, double value) { double delta = value - info.constant; info.constant = value; { - Symbol symbol = info.tag.marker; - Row row = _rows[info.tag.marker]; + _Symbol symbol = info.tag.marker; + _Row row = _rows[info.tag.marker]; if (row != null) { if (row.add(-delta) < 0.0) { @@ -470,8 +470,8 @@ class Solver { } } - for (Symbol symbol in _rows.keys) { - Row row = _rows[symbol]; + 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 && @@ -483,11 +483,11 @@ class Solver { Result _dualOptimize() { while (_infeasibleRows.length != 0) { - Symbol leaving = _infeasibleRows.removeLast(); - Row row = _rows[leaving]; + _Symbol leaving = _infeasibleRows.removeLast(); + _Row row = _rows[leaving]; if (row != null && row.constant < 0.0) { - Symbol entering = _getDualEnteringSymbolForRow(row); + _Symbol entering = _getDualEnteringSymbolForRow(row); if (entering.type == SymbolType.invalid) { return Result.internalSolverError; @@ -503,14 +503,14 @@ class Solver { return Result.success; } - Symbol _getDualEnteringSymbolForRow(Row row) { - Symbol entering; + _Symbol _getDualEnteringSymbolForRow(_Row row) { + _Symbol entering; double ratio = double.MAX_FINITE; - Map rowCells = row.cells; + Map<_Symbol, double> rowCells = row.cells; - for (Symbol symbol in rowCells.keys) { + for (_Symbol symbol in rowCells.keys) { double value = rowCells[symbol]; if (value > 0.0 && symbol.type != SymbolType.dummy) { @@ -523,22 +523,22 @@ class Solver { } } - return _elvis(entering, new Symbol(SymbolType.invalid, 0)); + return _elvis(entering, new _Symbol(SymbolType.invalid, 0)); } } -class Tag { - Symbol marker; - Symbol other; +class _Tag { + _Symbol marker; + _Symbol other; - Tag(this.marker, this.other); - Tag.fromTag(Tag tag) + _Tag(this.marker, this.other); + _Tag.fromTag(_Tag tag) : this.marker = tag.marker, this.other = tag.other; } -class EditInfo { - Tag tag; +class _EditInfo { + _Tag tag; Constraint constraint; double constant; } diff --git a/packages/cassowary/lib/symbol.dart b/packages/cassowary/lib/symbol.dart index aed9e8e7d0..cd7be7ebd3 100644 --- a/packages/cassowary/lib/symbol.dart +++ b/packages/cassowary/lib/symbol.dart @@ -6,9 +6,9 @@ part of cassowary; enum SymbolType { invalid, external, slack, error, dummy, } -class Symbol { +class _Symbol { final SymbolType type; int tick; - Symbol(this.type, this.tick); + _Symbol(this.type, this.tick); } diff --git a/packages/cassowary/lib/term.dart b/packages/cassowary/lib/term.dart index 8a88148025..264b16c6f1 100644 --- a/packages/cassowary/lib/term.dart +++ b/packages/cassowary/lib/term.dart @@ -4,7 +4,7 @@ part of cassowary; -class Term extends EquationMember { +class Term extends _EquationMember { final Variable variable; final double coefficient; From 8be3c640cc8ad775d722e620df3fc63433aad7df Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Thu, 25 Jun 2015 10:25:33 -0700 Subject: [PATCH 19/62] Minor: Refactor -> Rename internal private methods in the solver --- packages/cassowary/lib/solver.dart | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index 3ad6c4b00a..5284ef0a7d 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -65,7 +65,7 @@ class Solver { _rows.remove(tag.marker); } else { _Pair<_Symbol, _Row> rowPair = - _getLeavingRowPairForMarkerSymbol(tag.marker); + _leavingRowPairForMarkerSymbol(tag.marker); if (rowPair == null) { return Result.internalSolverError; @@ -154,7 +154,7 @@ class Solver { Solver operator <<(Constraint c) => this..addConstraint(c); - _Symbol _getSymbolForVariable(Variable variable) { + _Symbol _symbolForVariable(Variable variable) { _Symbol symbol = _vars[variable]; if (symbol != null) { @@ -173,7 +173,7 @@ class Solver { expr.terms.forEach((term) { if (!_nearZero(term.coefficient)) { - _Symbol symbol = _getSymbolForVariable(term.variable); + _Symbol symbol = _symbolForVariable(term.variable); _Row foundRow = _rows[symbol]; @@ -303,13 +303,13 @@ class Solver { Result _optimizeObjectiveRow(_Row objective) { while (true) { - _Symbol entering = _getEnteringSymbolForObjectiveRow(objective); + _Symbol entering = _enteringSymbolForObjectiveRow(objective); if (entering.type == SymbolType.invalid) { return Result.success; } _Pair<_Symbol, _Row> leavingPair = - _getLeavingRowForEnteringSymbol(entering); + _leavingRowForEnteringSymbol(entering); if (leavingPair == null) { return Result.internalSolverError; @@ -324,7 +324,7 @@ class Solver { } } - _Symbol _getEnteringSymbolForObjectiveRow(_Row objective) { + _Symbol _enteringSymbolForObjectiveRow(_Row objective) { Map<_Symbol, double> cells = objective.cells; for (_Symbol symbol in cells.keys) { @@ -336,7 +336,7 @@ class Solver { return new _Symbol(SymbolType.invalid, 0); } - _Pair<_Symbol, _Row> _getLeavingRowForEnteringSymbol(_Symbol entering) { + _Pair<_Symbol, _Row> _leavingRowForEnteringSymbol(_Symbol entering) { double ratio = double.MAX_FINITE; _Pair<_Symbol, _Row> result = new _Pair(null, null); @@ -404,7 +404,7 @@ class Solver { } } - _Pair<_Symbol, _Row> _getLeavingRowPairForMarkerSymbol(_Symbol marker) { + _Pair<_Symbol, _Row> _leavingRowPairForMarkerSymbol(_Symbol marker) { double r1 = double.MAX_FINITE; double r2 = double.MAX_FINITE; @@ -487,7 +487,7 @@ class Solver { _Row row = _rows[leaving]; if (row != null && row.constant < 0.0) { - _Symbol entering = _getDualEnteringSymbolForRow(row); + _Symbol entering = _dualEnteringSymbolForRow(row); if (entering.type == SymbolType.invalid) { return Result.internalSolverError; @@ -503,7 +503,7 @@ class Solver { return Result.success; } - _Symbol _getDualEnteringSymbolForRow(_Row row) { + _Symbol _dualEnteringSymbolForRow(_Row row) { _Symbol entering; double ratio = double.MAX_FINITE; From 882a17f75bd0c0c153f236c7bf97f6a7d269d8e7 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Thu, 25 Jun 2015 11:45:47 -0700 Subject: [PATCH 20/62] Minor: Add more tests --- packages/cassowary/lib/solver.dart | 2 +- packages/cassowary/test/cassowary_test.dart | 26 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index 5284ef0a7d..17dfa75f0f 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -140,7 +140,7 @@ class Solver { return _dualOptimize(); } - void updateVariable() { + void flushVariableUpdates() { for (Variable variable in _vars.keys) { _Symbol symbol = _vars[variable]; _Row row = _rows[symbol]; diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index f5eb34d02d..85ec92bf66 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -422,4 +422,30 @@ void main() { expect(((left + right) >= (CM(2.0) * mid)) is Constraint, true); }); + + test('single_item', () { + var left = new Param(-20.0); + Solver s = new Solver(); + s << (left >= CM(0.0)); + s.flushVariableUpdates(); + expect(left.value, 0.0); + }); + + test('midpoints', () { + var left = new Param(0.0); + var right = new Param(0.0); + var mid = new Param(0.0); + + Solver s = new Solver(); + + s << ((left + right == CM(2.0) * mid) as Constraint); + s << (right - left >= CM(100.0)); + s << (left >= CM(0.0)); + + s.flushVariableUpdates(); + + expect(left.value, 0.0); + expect(mid.value, 50.0); + expect(right.value, 100.0); + }); } From a029c93ed1a12935d9a9173c8d0b7015fb8fb021 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Thu, 25 Jun 2015 12:11:00 -0700 Subject: [PATCH 21/62] Remove the << overload on solver. Operator precendence rules made it awkward to use anyway --- packages/cassowary/lib/solver.dart | 103 ++++++++++++-------- packages/cassowary/lib/symbol.dart | 4 +- packages/cassowary/test/cassowary_test.dart | 9 +- 3 files changed, 71 insertions(+), 45 deletions(-) diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index 17dfa75f0f..d094d62c69 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -14,19 +14,47 @@ class Solver { _Row _artificial = new _Row(0.0); int tick = 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. + Result addConstraints(List constraints) { + List added = new List(constraints.length); + bool needsCleanup = false; + + Result result = Result.success; + + for (Constraint constraint in constraints) { + result = addConstraint(constraint); + if (result == Result.success) { + added.add(constraint); + } else { + needsCleanup = true; + break; + } + } + + if (needsCleanup) { + for (Constraint constraint in added) { + removeConstraint(constraint); + } + } + + return result; + } + Result addConstraint(Constraint constraint) { if (_constraints.containsKey(constraint)) { return Result.duplicateConstraint; } - _Tag tag = new _Tag( - new _Symbol(SymbolType.invalid, 0), new _Symbol(SymbolType.invalid, 0)); + _Tag tag = new _Tag(new _Symbol(_SymbolType.invalid, 0), + new _Symbol(_SymbolType.invalid, 0)); _Row row = _createRow(constraint, tag); _Symbol subject = _chooseSubjectForRow(row, tag); - if (subject.type == SymbolType.invalid && _allDummiesInRow(row)) { + if (subject.type == _SymbolType.invalid && _allDummiesInRow(row)) { if (!_nearZero(row.constant)) { return Result.unsatisfiableConstraint; } else { @@ -34,7 +62,7 @@ class Solver { } } - if (subject.type == SymbolType.invalid) { + if (subject.type == _SymbolType.invalid) { if (!_addWithArtificialVariableOnRow(row)) { return Result.unsatisfiableConstraint; } @@ -64,8 +92,7 @@ class Solver { if (row != null) { _rows.remove(tag.marker); } else { - _Pair<_Symbol, _Row> rowPair = - _leavingRowPairForMarkerSymbol(tag.marker); + _Pair<_Symbol, _Row> rowPair = _leavingRowPairForMarkerSymbol(tag.marker); if (rowPair == null) { return Result.internalSolverError; @@ -152,8 +179,6 @@ class Solver { } } - Solver operator <<(Constraint c) => this..addConstraint(c); - _Symbol _symbolForVariable(Variable variable) { _Symbol symbol = _vars[variable]; @@ -161,7 +186,7 @@ class Solver { return symbol; } - symbol = new _Symbol(SymbolType.external, tick++); + symbol = new _Symbol(_SymbolType.external, tick++); _vars[variable] = symbol; return symbol; @@ -192,12 +217,12 @@ class Solver { double coefficient = constraint.relation == Relation.lessThanOrEqualTo ? 1.0 : -1.0; - _Symbol slack = new _Symbol(SymbolType.slack, tick++); + _Symbol slack = new _Symbol(_SymbolType.slack, tick++); tag.marker = slack; row.insertSymbol(slack, coefficient); if (!constraint.required) { - _Symbol error = new _Symbol(SymbolType.error, tick++); + _Symbol error = new _Symbol(_SymbolType.error, tick++); tag.other = error; row.insertSymbol(error, -coefficient); _objective.insertSymbol(error, constraint.priority); @@ -206,8 +231,8 @@ class Solver { break; case Relation.equalTo: if (!constraint.required) { - _Symbol errPlus = new _Symbol(SymbolType.error, tick++); - _Symbol errMinus = new _Symbol(SymbolType.error, tick++); + _Symbol errPlus = new _Symbol(_SymbolType.error, tick++); + _Symbol errMinus = new _Symbol(_SymbolType.error, tick++); tag.marker = errPlus; tag.other = errMinus; row.insertSymbol(errPlus, -1.0); @@ -215,7 +240,7 @@ class Solver { _objective.insertSymbol(errPlus, constraint.priority); _objective.insertSymbol(errMinus, constraint.priority); } else { - _Symbol dummy = new _Symbol(SymbolType.dummy, tick++); + _Symbol dummy = new _Symbol(_SymbolType.dummy, tick++); tag.marker = dummy; row.insertSymbol(dummy); } @@ -231,31 +256,31 @@ 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 (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 (tag.other.type == _SymbolType.slack || + tag.other.type == _SymbolType.error) { if (row.coefficientForSymbol(tag.other) < 0.0) { return tag.other; } } - return new _Symbol(SymbolType.invalid, 0); + 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; } } @@ -263,7 +288,7 @@ class Solver { } bool _addWithArtificialVariableOnRow(_Row row) { - _Symbol artificial = new _Symbol(SymbolType.slack, tick++); + _Symbol artificial = new _Symbol(_SymbolType.slack, tick++); _rows[artificial] = new _Row.fromRow(row); _artificial = new _Row.fromRow(row); @@ -285,7 +310,7 @@ class Solver { } _Symbol entering = _anyPivotableSymbol(foundRow); - if (entering.type == SymbolType.invalid) { + if (entering.type == _SymbolType.invalid) { return false; } @@ -304,12 +329,11 @@ 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); + _Pair<_Symbol, _Row> leavingPair = _leavingRowForEnteringSymbol(entering); if (leavingPair == null) { return Result.internalSolverError; @@ -328,12 +352,12 @@ 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); + return new _Symbol(_SymbolType.invalid, 0); } _Pair<_Symbol, _Row> _leavingRowForEnteringSymbol(_Symbol entering) { @@ -341,7 +365,7 @@ class Solver { _Pair<_Symbol, _Row> result = new _Pair(null, null); _rows.forEach((symbol, row) { - if (symbol.type != SymbolType.external) { + if (symbol.type != _SymbolType.external) { double temp = row.coefficientForSymbol(entering); if (temp < 0.0) { @@ -366,7 +390,7 @@ class Solver { void _substitute(_Symbol symbol, _Row row) { _rows.forEach((first, second) { second.substitute(symbol, row); - if (first.type != SymbolType.external && second.constant < 0.0) { + if (first.type != _SymbolType.external && second.constant < 0.0) { _infeasibleRows.add(first); } }); @@ -379,18 +403,19 @@ class Solver { _Symbol _anyPivotableSymbol(_Row row) { for (_Symbol symbol in row.cells.keys) { - if (symbol.type == SymbolType.slack || symbol.type == SymbolType.error) { + if (symbol.type == _SymbolType.slack || + symbol.type == _SymbolType.error) { return symbol; } } - return new _Symbol(SymbolType.invalid, 0); + return new _Symbol(_SymbolType.invalid, 0); } void _removeConstraintEffects(Constraint cn, _Tag tag) { - if (tag.marker.type == SymbolType.error) { + if (tag.marker.type == _SymbolType.error) { _removeMarkerEffects(tag.marker, cn.priority); } - if (tag.other.type == SymbolType.error) { + if (tag.other.type == _SymbolType.error) { _removeMarkerEffects(tag.other, cn.priority); } } @@ -417,7 +442,7 @@ class Solver { return; } - if (symbol.type == SymbolType.external) { + if (symbol.type == _SymbolType.external) { third = new _Pair(symbol, row); } else if (c < 0.0) { double r = -row.constant / c; @@ -475,7 +500,7 @@ class Solver { double coeff = row.coefficientForSymbol(info.tag.marker); if (coeff != 0.0 && row.add(delta * coeff) < 0.0 && - symbol.type != SymbolType.external) { + symbol.type != _SymbolType.external) { _infeasibleRows.add(symbol); } } @@ -489,7 +514,7 @@ class Solver { if (row != null && row.constant < 0.0) { _Symbol entering = _dualEnteringSymbolForRow(row); - if (entering.type == SymbolType.invalid) { + if (entering.type == _SymbolType.invalid) { return Result.internalSolverError; } @@ -513,7 +538,7 @@ class Solver { for (_Symbol symbol in rowCells.keys) { double value = rowCells[symbol]; - if (value > 0.0 && symbol.type != SymbolType.dummy) { + if (value > 0.0 && symbol.type != _SymbolType.dummy) { double coeff = _objective.coefficientForSymbol(symbol); double r = coeff / value; if (r < ratio) { @@ -523,7 +548,7 @@ class Solver { } } - return _elvis(entering, new _Symbol(SymbolType.invalid, 0)); + return _elvis(entering, new _Symbol(_SymbolType.invalid, 0)); } } diff --git a/packages/cassowary/lib/symbol.dart b/packages/cassowary/lib/symbol.dart index cd7be7ebd3..f6ace47ad7 100644 --- a/packages/cassowary/lib/symbol.dart +++ b/packages/cassowary/lib/symbol.dart @@ -4,10 +4,10 @@ part of cassowary; -enum SymbolType { invalid, external, slack, error, dummy, } +enum _SymbolType { invalid, external, slack, error, dummy, } class _Symbol { - final SymbolType type; + final _SymbolType type; int tick; _Symbol(this.type, this.tick); diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index 85ec92bf66..8bd5287931 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -426,7 +426,7 @@ void main() { test('single_item', () { var left = new Param(-20.0); Solver s = new Solver(); - s << (left >= CM(0.0)); + s.addConstraint(left >= CM(0.0)); s.flushVariableUpdates(); expect(left.value, 0.0); }); @@ -438,9 +438,10 @@ void main() { Solver s = new Solver(); - s << ((left + right == CM(2.0) * mid) as Constraint); - s << (right - left >= CM(100.0)); - s << (left >= CM(0.0)); + expect(s.addConstraint((left + right == CM(2.0) * mid) as Constraint), + Result.success); + expect(s.addConstraint(right - left >= CM(100.0)), Result.success); + expect(s.addConstraint(left >= CM(0.0)), Result.success); s.flushVariableUpdates(); From 9d075adabbf57327f752815ba7fbe686d67c9e5c Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Thu, 25 Jun 2015 12:20:10 -0700 Subject: [PATCH 22/62] Minor: Add tests to check for addition of multiple constraints --- packages/cassowary/lib/solver.dart | 2 +- packages/cassowary/test/cassowary_test.dart | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index d094d62c69..fbf4429ebc 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -18,7 +18,7 @@ class Solver { /// add any for some reason, a cleanup is attempted so that either all /// constraints will be added or none. Result addConstraints(List constraints) { - List added = new List(constraints.length); + List added = new List(); bool needsCleanup = false; Result result = Result.success; diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index 8bd5287931..a32852d8ff 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -449,4 +449,23 @@ void main() { expect(mid.value, 50.0); expect(right.value, 100.0); }); + + test('addition_of_multiple', () { + var left = new Param(0.0); + var right = new Param(0.0); + var mid = new Param(0.0); + + Solver s = new Solver(); + + var c = (left >= CM(0.0)); + + expect(s.addConstraints([ + (left + right == CM(2.0) * mid) as Constraint, + (right - left >= CM(100.0)), + c + ]), Result.success); + + expect(s.addConstraints([(right >= CM(-20.0)), c]), + Result.duplicateConstraint); + }); } From 2f3e5aa70b01e45d3221953214240dfefb0866cd Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Thu, 25 Jun 2015 14:30:37 -0700 Subject: [PATCH 23/62] Add toString() overrides to internal solver members --- packages/cassowary/lib/constraint.dart | 25 ++++++++++++++++++ packages/cassowary/lib/expression.dart | 13 ++++++++++ packages/cassowary/lib/param.dart | 3 +++ packages/cassowary/lib/row.dart | 11 ++++++++ packages/cassowary/lib/solver.dart | 36 ++++++++++++++++++++++++++ packages/cassowary/lib/symbol.dart | 22 ++++++++++++++++ packages/cassowary/lib/term.dart | 15 +++++++++++ packages/cassowary/lib/variable.dart | 11 +++++++- 8 files changed, 135 insertions(+), 1 deletion(-) diff --git a/packages/cassowary/lib/constraint.dart b/packages/cassowary/lib/constraint.dart index 114ad219e8..b036567d30 100644 --- a/packages/cassowary/lib/constraint.dart +++ b/packages/cassowary/lib/constraint.dart @@ -24,4 +24,29 @@ class Constraint { required ? requiredPriority : p.clamp(0.0, requiredPriority - 1.0); Constraint operator |(double p) => this..priority = p; + + 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 (required) { + buffer.write(" (required)"); + } + + return buffer.toString(); + } } diff --git a/packages/cassowary/lib/expression.dart b/packages/cassowary/lib/expression.dart index 8dc82f7157..0c7f1e9ce2 100644 --- a/packages/cassowary/lib/expression.dart +++ b/packages/cassowary/lib/expression.dart @@ -156,4 +156,17 @@ class Expression extends _EquationMember { return this._applyMultiplicand(1.0 / m.value); } + + String toString() { + StringBuffer buffer = new StringBuffer(); + + terms.forEach((t) => buffer.write("${t}")); + + if (constant != 0.0) { + buffer.write(constant.sign > 0.0 ? "+" : "-"); + buffer.write(constant.abs()); + } + + return buffer.toString(); + } } diff --git a/packages/cassowary/lib/param.dart b/packages/cassowary/lib/param.dart index d6710ef15f..735a55a3ab 100644 --- a/packages/cassowary/lib/param.dart +++ b/packages/cassowary/lib/param.dart @@ -14,5 +14,8 @@ class Param extends _EquationMember { double get value => variable.value; + String get name => variable.name; + set name(String name) => variable.name = name; + Expression asExpression() => new Expression([new Term(variable, 1.0)], 0.0); } diff --git a/packages/cassowary/lib/row.dart b/packages/cassowary/lib/row.dart index c178caed9b..54c7e15d84 100644 --- a/packages/cassowary/lib/row.dart +++ b/packages/cassowary/lib/row.dart @@ -61,4 +61,15 @@ class _Row { cells.remove(symbol); insertRow(row, coefficient); } + + String toString() { + StringBuffer buffer = new StringBuffer(); + + buffer.write(constant); + + cells.forEach((symbol, value) => + buffer.write(" + " + value.toString() + " * " + symbol.toString())); + + return buffer.toString(); + } } diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index fbf4429ebc..411d1e69ca 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -550,6 +550,42 @@ class Solver { return _elvis(entering, new _Symbol(_SymbolType.invalid, 0)); } + + String toString() { + StringBuffer buffer = new StringBuffer(); + String separator = "\n~~~~~~~~~"; + + // Objective + buffer.writeln(separator + " Objective"); + buffer.writeln(_objective.toString()); + + // Tableau + buffer.writeln(separator + " Tableau"); + _rows.forEach((symbol, row) { + buffer.write(symbol.toString()); + buffer.write(" | "); + buffer.writeln(row.toString()); + }); + + // Infeasible + buffer.writeln(separator + " Infeasible"); + _infeasibleRows.forEach((symbol) => buffer.writeln(symbol.toString())); + + // Variables + buffer.writeln(separator + " Variables"); + _vars.forEach((variable, symbol) => + buffer.writeln("${variable.toString()} = ${symbol.toString()}")); + + // Edit Variables + buffer.writeln(separator + " Edit Variables"); + _edits.forEach((variable, editinfo) => buffer.writeln(variable)); + + // Constraints + buffer.writeln(separator + " Constraints"); + _constraints.forEach((constraint, _) => buffer.writeln(constraint)); + + return buffer.toString(); + } } class _Tag { diff --git a/packages/cassowary/lib/symbol.dart b/packages/cassowary/lib/symbol.dart index f6ace47ad7..d76ed5a84e 100644 --- a/packages/cassowary/lib/symbol.dart +++ b/packages/cassowary/lib/symbol.dart @@ -11,4 +11,26 @@ class _Symbol { int tick; _Symbol(this.type, this.tick); + + 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}"; + } } diff --git a/packages/cassowary/lib/term.dart b/packages/cassowary/lib/term.dart index 264b16c6f1..5849f65e16 100644 --- a/packages/cassowary/lib/term.dart +++ b/packages/cassowary/lib/term.dart @@ -16,4 +16,19 @@ class Term extends _EquationMember { Expression asExpression() => new Expression([new Term(this.variable, this.coefficient)], 0.0); + + 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(); + } } diff --git a/packages/cassowary/lib/variable.dart b/packages/cassowary/lib/variable.dart index 0d22967d3c..39c12f8833 100644 --- a/packages/cassowary/lib/variable.dart +++ b/packages/cassowary/lib/variable.dart @@ -6,7 +6,16 @@ part of cassowary; class Variable { double value = 0.0; - Variable(this.value); + String name; + + int _tick; + static int _total = 0; + + Variable(this.value) : _tick = _total++; // TODO(csg): Add external variable update callbacks here + + String get debugName => _elvis(name, "variable${_tick}"); + + String toString() => "${debugName}(=${value})"; } From 20908034d58328f561fc6055fa37aefa599044f3 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Thu, 25 Jun 2015 16:24:21 -0700 Subject: [PATCH 24/62] Make constraint priority setup more expressive --- packages/cassowary/lib/cassowary.dart | 3 +++ packages/cassowary/lib/constraint.dart | 18 ++++-------------- packages/cassowary/lib/priority.dart | 24 ++++++++++++++++++++++++ packages/cassowary/lib/row.dart | 4 ++-- packages/cassowary/lib/solver.dart | 6 +++--- packages/cassowary/lib/variable.dart | 2 +- 6 files changed, 37 insertions(+), 20 deletions(-) create mode 100644 packages/cassowary/lib/priority.dart diff --git a/packages/cassowary/lib/cassowary.dart b/packages/cassowary/lib/cassowary.dart index 21ae5b8fc2..913ec48cf2 100644 --- a/packages/cassowary/lib/cassowary.dart +++ b/packages/cassowary/lib/cassowary.dart @@ -4,6 +4,8 @@ library cassowary; +import 'dart:math'; + part 'constraint.dart'; part 'expression.dart'; part 'term.dart'; @@ -17,3 +19,4 @@ part 'utils.dart'; part 'result.dart'; part 'parser_exception.dart'; part 'param.dart'; +part 'priority.dart'; diff --git a/packages/cassowary/lib/constraint.dart b/packages/cassowary/lib/constraint.dart index b036567d30..83a5946cc2 100644 --- a/packages/cassowary/lib/constraint.dart +++ b/packages/cassowary/lib/constraint.dart @@ -9,19 +9,9 @@ enum Relation { equalTo, lessThanOrEqualTo, greaterThanOrEqualTo, } class Constraint { final Relation relation; final Expression expression; - final bool required; + double priority = Priority.required; - static const double requiredPriority = 1000.0; - double _priority = requiredPriority - 1.0; - - Constraint(this.expression, this.relation) : this.required = false; - Constraint.Required(this.expression, this.relation) : this.required = true { - this.priority = requiredPriority; - } - - double get priority => required ? requiredPriority : _priority; - set priority(double p) => _priority = - required ? requiredPriority : p.clamp(0.0, requiredPriority - 1.0); + Constraint(this.expression, this.relation); Constraint operator |(double p) => this..priority = p; @@ -31,7 +21,7 @@ class Constraint { switch (relation) { case Relation.equalTo: - buffer.write(" <= 0 "); + buffer.write(" == 0 "); break; case Relation.greaterThanOrEqualTo: buffer.write(" >= 0 "); @@ -43,7 +33,7 @@ class Constraint { buffer.write(" | priority = ${priority}"); - if (required) { + if (priority == Priority.required) { buffer.write(" (required)"); } diff --git a/packages/cassowary/lib/priority.dart b/packages/cassowary/lib/priority.dart new file mode 100644 index 0000000000..9e432e1df3 --- /dev/null +++ b/packages/cassowary/lib/priority.dart @@ -0,0 +1,24 @@ +// 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 Priority { + static final double required = create(1e3, 1e3, 1e3); + static final double strong = create(1.0, 0.0, 0.0); + static final double medium = create(0.0, 1.0, 0.0); + static final double weak = create(0.0, 0.0, 1.0); + + 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; + } + + static double clamp(double value) { + return max(0.0, min(required, value)); + } +} diff --git a/packages/cassowary/lib/row.dart b/packages/cassowary/lib/row.dart index 54c7e15d84..578c6452a9 100644 --- a/packages/cassowary/lib/row.dart +++ b/packages/cassowary/lib/row.dart @@ -16,9 +16,9 @@ class _Row { double add(double value) => constant += value; void insertSymbol(_Symbol symbol, [double coefficient = 1.0]) { - double val = _elvis(cells[symbol], 0.0) + coefficient; + double val = _elvis(cells[symbol], 0.0); - if (_nearZero(val)) { + if (_nearZero(val + coefficient)) { cells.remove(symbol); } else { cells[symbol] = val + coefficient; diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index 411d1e69ca..d2a9d1f15b 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -221,7 +221,7 @@ class Solver { tag.marker = slack; row.insertSymbol(slack, coefficient); - if (!constraint.required) { + if (constraint.priority < Priority.required) { _Symbol error = new _Symbol(_SymbolType.error, tick++); tag.other = error; row.insertSymbol(error, -coefficient); @@ -230,7 +230,7 @@ class Solver { } break; case Relation.equalTo: - if (!constraint.required) { + if (constraint.priority < Priority.required) { _Symbol errPlus = new _Symbol(_SymbolType.error, tick++); _Symbol errMinus = new _Symbol(_SymbolType.error, tick++); tag.marker = errPlus; @@ -605,5 +605,5 @@ class _EditInfo { } bool _isValidNonRequiredPriority(double priority) { - return (priority >= 0.0 && priority < Constraint.requiredPriority); + return (priority >= 0.0 && priority < Priority.required); } diff --git a/packages/cassowary/lib/variable.dart b/packages/cassowary/lib/variable.dart index 39c12f8833..3192601923 100644 --- a/packages/cassowary/lib/variable.dart +++ b/packages/cassowary/lib/variable.dart @@ -17,5 +17,5 @@ class Variable { String get debugName => _elvis(name, "variable${_tick}"); - String toString() => "${debugName}(=${value})"; + String toString() => debugName; } From 49d14caa696ee8ac246224c63becd1a90d8161e2 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Thu, 25 Jun 2015 17:07:15 -0700 Subject: [PATCH 25/62] Make the midpoints test pass. Fixes incorrect Row.reverseSign --- packages/cassowary/lib/row.dart | 5 ++++- packages/cassowary/lib/solver.dart | 2 +- packages/cassowary/test/cassowary_test.dart | 8 ++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/cassowary/lib/row.dart b/packages/cassowary/lib/row.dart index 578c6452a9..5c49c0c78e 100644 --- a/packages/cassowary/lib/row.dart +++ b/packages/cassowary/lib/row.dart @@ -34,7 +34,10 @@ class _Row { cells.remove(symbol); } - void reverseSign() => cells.forEach((s, v) => cells[s] = -v); + void reverseSign() { + constant = -constant; + cells.forEach((s, v) => cells[s] = -v); + } void solveForSymbol(_Symbol symbol) { assert(cells.containsKey(symbol)); diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index d2a9d1f15b..c70d7544aa 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -12,7 +12,7 @@ class Solver { final List<_Symbol> _infeasibleRows = new List<_Symbol>(); final _Row _objective = new _Row(0.0); _Row _artificial = new _Row(0.0); - int tick = 0; + int tick = 1; /// Attempts to add the constraints in the list to the solver. If it cannot /// add any for some reason, a cleanup is attempted so that either all diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index a32852d8ff..895f26c44e 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -432,13 +432,13 @@ void main() { }); test('midpoints', () { - var left = new Param(0.0); - var right = new Param(0.0); - var mid = new Param(0.0); + var left = new Param(0.0)..name = "left"; + var right = new Param(0.0)..name = "right"; + var mid = new Param(0.0)..name = "mid"; Solver s = new Solver(); - expect(s.addConstraint((left + right == CM(2.0) * mid) as Constraint), + expect(s.addConstraint((right + left == mid * CM(2.0)) as Constraint), Result.success); expect(s.addConstraint(right - left >= CM(100.0)), Result.success); expect(s.addConstraint(left >= CM(0.0)), Result.success); From 97cd09d2a1e60b2e872280b1c750cf63815e0cf5 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Thu, 25 Jun 2015 17:27:42 -0700 Subject: [PATCH 26/62] Avoid adding implicit constraints for edit variables at required priority --- packages/cassowary/lib/solver.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index c70d7544aa..c0ce826c3c 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -124,6 +124,7 @@ class Solver { Constraint constraint = new Constraint( new Expression([new Term(variable, 1.0)], 0.0), Relation.equalTo); + constraint.priority = priority; if (addConstraint(constraint) != Result.success) { return Result.internalSolverError; From d8d07a31dd44cb875491be8117c7e6652ef38941 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Thu, 25 Jun 2015 17:28:00 -0700 Subject: [PATCH 27/62] Add tests edit constraints --- packages/cassowary/test/cassowary_test.dart | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index 895f26c44e..2dfe9b60fa 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -468,4 +468,26 @@ void main() { expect(s.addConstraints([(right >= CM(-20.0)), c]), Result.duplicateConstraint); }); + + test('edit_constraints', () { + var left = new Param(0.0)..name = "left"; + var right = new Param(0.0)..name = "right"; + var mid = new Param(0.0)..name = "mid"; + + Solver s = new Solver(); + + expect(s.addConstraint((right + left == mid * CM(2.0)) as Constraint), + 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.flushVariableUpdates(); + + expect(left.value, 0.0); + expect(mid.value, 300.0); + expect(right.value, 600.0); + }); } From eedbb4f167da7f8fca5226dcbd22399fc4d68d48 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Thu, 25 Jun 2015 18:13:56 -0700 Subject: [PATCH 28/62] Add a simple test for the toString() override so that the coverage tool is not sad --- packages/cassowary/test/cassowary_test.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index 2dfe9b60fa..900e2cee3b 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -490,4 +490,15 @@ void main() { expect(mid.value, 300.0); expect(right.value, 600.0); }); + + test('test_description', () { + var left = new Param(0.0); + var right = new Param(100.0); + var c = right >= left; + + Solver s = new Solver(); + expect(s.addConstraint(c), Result.success); + + expect(s.toString() != null, true); + }); } From e65fd76ead3680bd4720244e3a74da468b939fb0 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Fri, 26 Jun 2015 12:20:02 -0700 Subject: [PATCH 29/62] Since params are usually created to be edited later, make the initial value optional --- packages/cassowary/lib/param.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cassowary/lib/param.dart b/packages/cassowary/lib/param.dart index 735a55a3ab..0705d0e8df 100644 --- a/packages/cassowary/lib/param.dart +++ b/packages/cassowary/lib/param.dart @@ -8,7 +8,7 @@ class Param extends _EquationMember { final Variable variable; Param.withVariable(this.variable); - Param(double value) : this.variable = new Variable(value); + Param([double value = 0.0]) : this.variable = new Variable(value); bool get isConstant => false; From df2eb202cc8756423ec71fafbe707b811e6778d5 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Fri, 26 Jun 2015 12:21:06 -0700 Subject: [PATCH 30/62] Minor: A slightly more contrived test case to verify contraint priorities --- packages/cassowary/test/cassowary_test.dart | 26 +++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index 900e2cee3b..854b23b251 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -501,4 +501,30 @@ void main() { 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 == p3) as Constraint) | Priority.medium); + solver.addConstraint((p2 == CM(2.0) * p1) as Constraint); + solver.addConstraint((container == (p1 + p2 + p3)) as Constraint); + + solver.flushVariableUpdates(); + + expect(container.value, 100.0); + + expect(p1.value, 30.0); + expect(p2.value, 60.0); + expect(p3.value, 10.0); + }); } From 4568b088e07d7384bb5fe13ff1dacf81d7b3e587 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 29 Jun 2015 15:51:29 -0700 Subject: [PATCH 31/62] `Solver.flushParameterUpdates` returns the a collection of updated parameters --- packages/cassowary/lib/param.dart | 9 ++++- packages/cassowary/lib/solver.dart | 14 ++++--- packages/cassowary/lib/variable.dart | 8 +++- packages/cassowary/test/cassowary_test.dart | 41 ++++++++++++++++++--- 4 files changed, 58 insertions(+), 14 deletions(-) diff --git a/packages/cassowary/lib/param.dart b/packages/cassowary/lib/param.dart index 0705d0e8df..0b2b5c14f9 100644 --- a/packages/cassowary/lib/param.dart +++ b/packages/cassowary/lib/param.dart @@ -7,8 +7,13 @@ part of cassowary; class Param extends _EquationMember { final Variable variable; - Param.withVariable(this.variable); - Param([double value = 0.0]) : this.variable = new Variable(value); + Param.withVariable(this.variable) { + variable._owner = this; + } + + Param([double value = 0.0]) : this.variable = new Variable(value) { + variable._owner = this; + } bool get isConstant => false; diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index c0ce826c3c..59ee312bc1 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -168,16 +168,20 @@ class Solver { return _dualOptimize(); } - void flushVariableUpdates() { + List flushParameterUpdates() { + List updates = new List(); + for (Variable variable in _vars.keys) { _Symbol symbol = _vars[variable]; _Row row = _rows[symbol]; - if (row == null) { - variable.value = 0.0; - } else { - variable.value = row.constant; + + double updatedValue = row == null ? 0.0 : row.constant; + + if (variable._applyUpdate(updatedValue) && variable._owner != null) { + updates.add(variable._owner); } } + return updates; } _Symbol _symbolForVariable(Variable variable) { diff --git a/packages/cassowary/lib/variable.dart b/packages/cassowary/lib/variable.dart index 3192601923..40219f77fd 100644 --- a/packages/cassowary/lib/variable.dart +++ b/packages/cassowary/lib/variable.dart @@ -8,12 +8,18 @@ class Variable { double value = 0.0; String name; + Param _owner; + int _tick; static int _total = 0; Variable(this.value) : _tick = _total++; - // TODO(csg): Add external variable update callbacks here + bool _applyUpdate(double updated) { + bool res = updated != value; + value = updated; + return res; + } String get debugName => _elvis(name, "variable${_tick}"); diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index 854b23b251..7c2e60d7aa 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -427,7 +427,7 @@ void main() { var left = new Param(-20.0); Solver s = new Solver(); s.addConstraint(left >= CM(0.0)); - s.flushVariableUpdates(); + s.flushParameterUpdates(); expect(left.value, 0.0); }); @@ -443,7 +443,7 @@ void main() { expect(s.addConstraint(right - left >= CM(100.0)), Result.success); expect(s.addConstraint(left >= CM(0.0)), Result.success); - s.flushVariableUpdates(); + s.flushParameterUpdates(); expect(left.value, 0.0); expect(mid.value, 50.0); @@ -484,7 +484,7 @@ void main() { expect(s.addEditVariable(mid.variable, Priority.strong), Result.success); expect(s.suggestValueForVariable(mid.variable, 300.0), Result.success); - s.flushVariableUpdates(); + s.flushParameterUpdates(); expect(left.value, 0.0); expect(mid.value, 300.0); @@ -494,10 +494,14 @@ void main() { test('test_description', () { var left = new Param(0.0); var right = new Param(100.0); - var c = right >= left; + var c1 = right >= left; + var c2 = right <= left; + var c3 = (right == left) as Constraint; Solver s = new Solver(); - expect(s.addConstraint(c), Result.success); + expect(s.addConstraint(c1), Result.success); + expect(s.addConstraint(c2), Result.success); + expect(s.addConstraint(c3), Result.success); expect(s.toString() != null, true); }); @@ -519,7 +523,7 @@ void main() { solver.addConstraint((p2 == CM(2.0) * p1) as Constraint); solver.addConstraint((container == (p1 + p2 + p3)) as Constraint); - solver.flushVariableUpdates(); + solver.flushParameterUpdates(); expect(container.value, 100.0); @@ -527,4 +531,29 @@ void main() { expect(p2.value, 60.0); expect(p3.value, 10.0); }); + + test('test_updates_collection', () { + Param left = new Param(); + Param mid = new Param(); + Param right = new Param(); + + Solver s = new Solver(); + + expect(s.addEditVariable(mid.variable, Priority.strong), Result.success); + + expect(s.addConstraint((mid * CM(2.0) == left + right) as Constraint), + Result.success); + expect(s.addConstraint(left >= CM(0.0)), Result.success); + + expect(s.suggestValueForVariable(mid.variable, 50.0), Result.success); + + var updates = s.flushParameterUpdates(); + + expect(updates.length, 2); + expect(updates[0] is Param, true); + + expect(left.value, 0.0); + expect(mid.value, 50.0); + expect(right.value, 100.0); + }); } From 704d0174e01345cdb722aadbc0038ffe695ec13e Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 30 Jun 2015 14:05:52 -0700 Subject: [PATCH 32/62] Solver.flush update returns the set of context associated with parameters in play --- packages/cassowary/lib/param.dart | 9 ++++- packages/cassowary/lib/solver.dart | 11 ++++-- packages/cassowary/test/cassowary_test.dart | 41 ++++++++++++++++----- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/packages/cassowary/lib/param.dart b/packages/cassowary/lib/param.dart index 0b2b5c14f9..f296c9d3af 100644 --- a/packages/cassowary/lib/param.dart +++ b/packages/cassowary/lib/param.dart @@ -6,12 +6,17 @@ part of cassowary; class Param extends _EquationMember { final Variable variable; + final dynamic context; - Param.withVariable(this.variable) { + Param([double value = 0.0]) + : variable = new Variable(value), + context = null { variable._owner = this; } - Param([double value = 0.0]) : this.variable = new Variable(value) { + Param.withContext(ctx) + : variable = new Variable(0.0), + context = ctx { variable._owner = this; } diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index 59ee312bc1..f76ea239a5 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -168,8 +168,8 @@ class Solver { return _dualOptimize(); } - List flushParameterUpdates() { - List updates = new List(); + Set flushUpdates() { + Set updates = new Set(); for (Variable variable in _vars.keys) { _Symbol symbol = _vars[variable]; @@ -178,9 +178,14 @@ class Solver { double updatedValue = row == null ? 0.0 : row.constant; if (variable._applyUpdate(updatedValue) && variable._owner != null) { - updates.add(variable._owner); + dynamic context = variable._owner.context; + + if (context != null) { + updates.add(context); + } } } + return updates; } diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index 7c2e60d7aa..1ea67e1373 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -427,7 +427,7 @@ void main() { var left = new Param(-20.0); Solver s = new Solver(); s.addConstraint(left >= CM(0.0)); - s.flushParameterUpdates(); + s.flushUpdates(); expect(left.value, 0.0); }); @@ -443,7 +443,7 @@ void main() { expect(s.addConstraint(right - left >= CM(100.0)), Result.success); expect(s.addConstraint(left >= CM(0.0)), Result.success); - s.flushParameterUpdates(); + s.flushUpdates(); expect(left.value, 0.0); expect(mid.value, 50.0); @@ -484,7 +484,7 @@ void main() { expect(s.addEditVariable(mid.variable, Priority.strong), Result.success); expect(s.suggestValueForVariable(mid.variable, 300.0), Result.success); - s.flushParameterUpdates(); + s.flushUpdates(); expect(left.value, 0.0); expect(mid.value, 300.0); @@ -523,7 +523,7 @@ void main() { solver.addConstraint((p2 == CM(2.0) * p1) as Constraint); solver.addConstraint((container == (p1 + p2 + p3)) as Constraint); - solver.flushParameterUpdates(); + solver.flushUpdates(); expect(container.value, 100.0); @@ -533,9 +533,9 @@ void main() { }); test('test_updates_collection', () { - Param left = new Param(); - Param mid = new Param(); - Param right = new Param(); + Param left = new Param.withContext("left"); + Param mid = new Param.withContext("mid"); + Param right = new Param.withContext("right"); Solver s = new Solver(); @@ -547,13 +547,36 @@ void main() { expect(s.suggestValueForVariable(mid.variable, 50.0), Result.success); - var updates = s.flushParameterUpdates(); + var updates = s.flushUpdates(); expect(updates.length, 2); - expect(updates[0] is Param, true); 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) == left + right) as Constraint), + Result.success); + expect(s.addConstraint(left >= CM(10.0)), Result.success); + + expect(s.suggestValueForVariable(mid.variable, 50.0), Result.success); + + var updates = s.flushUpdates(); + + expect(updates.length, 1); + + expect(left.value, 10.0); + expect(mid.value, 50.0); + expect(right.value, 90.0); + }); } From e66e88834ec219297d586fbaa98abbe732ef1d0e Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 30 Jun 2015 16:49:15 -0700 Subject: [PATCH 33/62] Parameter contexts are non-final --- packages/cassowary/lib/param.dart | 6 ++---- packages/cassowary/test/cassowary_test.dart | 6 ++++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/cassowary/lib/param.dart b/packages/cassowary/lib/param.dart index f296c9d3af..a2e7e176a3 100644 --- a/packages/cassowary/lib/param.dart +++ b/packages/cassowary/lib/param.dart @@ -6,11 +6,9 @@ part of cassowary; class Param extends _EquationMember { final Variable variable; - final dynamic context; + dynamic context; - Param([double value = 0.0]) - : variable = new Variable(value), - context = null { + Param([double value = 0.0]) : variable = new Variable(value) { variable._owner = this; } diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index 1ea67e1373..0eaf130888 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -579,4 +579,10 @@ void main() { expect(mid.value, 50.0); expect(right.value, 90.0); }); + + test('param_context_non_final', () { + var p = new Param.withContext("a"); + p.context = "b"; + expect(p.context, "b"); + }); } From a223491d00b0a861ca6a222127a98fccd4bdc33c Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 1 Jul 2015 12:26:33 -0700 Subject: [PATCH 34/62] Equality override on equation member returns a constraint --- packages/cassowary/lib/equation_member.dart | 2 +- packages/cassowary/test/cassowary_test.dart | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/cassowary/lib/equation_member.dart b/packages/cassowary/lib/equation_member.dart index 0e33640c05..06f35176b2 100644 --- a/packages/cassowary/lib/equation_member.dart +++ b/packages/cassowary/lib/equation_member.dart @@ -15,7 +15,7 @@ abstract class _EquationMember { Constraint operator <=(_EquationMember m) => asExpression() <= m; - /* Constraint */ operator ==(_EquationMember m) => asExpression() == m; + Constraint operator ==(_EquationMember m) => asExpression() == m; Expression operator +(_EquationMember m) => asExpression() + m; diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index 0eaf130888..6d67e5413f 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -585,4 +585,11 @@ void main() { p.context = "b"; expect(p.context, "b"); }); + + test('check_type_of_eq_result', () { + Param left = new Param(); + Param right = new Param(); + + expect((left == right).runtimeType, Constraint); + }); } From 1ad945f8975bb367b922ec55a4740041ab700b7a Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 1 Jul 2015 12:26:46 -0700 Subject: [PATCH 35/62] Make the constant member value final --- packages/cassowary/lib/constant_member.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cassowary/lib/constant_member.dart b/packages/cassowary/lib/constant_member.dart index 09490d1131..db8c7ed7bf 100644 --- a/packages/cassowary/lib/constant_member.dart +++ b/packages/cassowary/lib/constant_member.dart @@ -5,7 +5,7 @@ part of cassowary; class ConstantMember extends _EquationMember { - double value = 0.0; + final double value; bool get isConstant => true; From 5a439792f16f1e9b4263ffa856cc5741b9240660 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 1 Jul 2015 18:07:25 -0700 Subject: [PATCH 36/62] Non composite simulations: Friction, gravity, spring --- packages/newton/README.md | 3 + packages/newton/lib/newton.dart | 17 ++++ packages/newton/lib/src/fall.dart | 8 ++ packages/newton/lib/src/friction.dart | 26 ++++++ packages/newton/lib/src/gravity.dart | 26 ++++++ packages/newton/lib/src/scroll.dart | 8 ++ packages/newton/lib/src/simulation.dart | 26 ++++++ packages/newton/lib/src/spring.dart | 46 +++++++++++ packages/newton/lib/src/spring_solution.dart | 85 ++++++++++++++++++++ packages/newton/lib/src/utils.dart | 12 +++ packages/newton/test/newton_test.dart | 11 +++ 11 files changed, 268 insertions(+) create mode 100644 packages/newton/README.md create mode 100644 packages/newton/lib/newton.dart create mode 100644 packages/newton/lib/src/fall.dart create mode 100644 packages/newton/lib/src/friction.dart create mode 100644 packages/newton/lib/src/gravity.dart create mode 100644 packages/newton/lib/src/scroll.dart create mode 100644 packages/newton/lib/src/simulation.dart create mode 100644 packages/newton/lib/src/spring.dart create mode 100644 packages/newton/lib/src/spring_solution.dart create mode 100644 packages/newton/lib/src/utils.dart create mode 100644 packages/newton/test/newton_test.dart diff --git a/packages/newton/README.md b/packages/newton/README.md new file mode 100644 index 0000000000..0562d23ed7 --- /dev/null +++ b/packages/newton/README.md @@ -0,0 +1,3 @@ +# Newton + +Simple Physics Simulations for Dart. Springs, friction, gravity, etc. diff --git a/packages/newton/lib/newton.dart b/packages/newton/lib/newton.dart new file mode 100644 index 0000000000..bc2de1a90f --- /dev/null +++ b/packages/newton/lib/newton.dart @@ -0,0 +1,17 @@ +// 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. + +library newton; + +import 'dart:math' as Math; + +part 'src/simulation.dart'; +part 'src/utils.dart'; + +part 'src/fall.dart'; +part 'src/friction.dart'; +part 'src/gravity.dart'; +part 'src/scroll.dart'; +part 'src/spring.dart'; +part 'src/spring_solution.dart'; diff --git a/packages/newton/lib/src/fall.dart b/packages/newton/lib/src/fall.dart new file mode 100644 index 0000000000..ca5ec9556b --- /dev/null +++ b/packages/newton/lib/src/fall.dart @@ -0,0 +1,8 @@ +// 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 newton; + +// TODO(csg): Composite simulation +class Fall {} diff --git a/packages/newton/lib/src/friction.dart b/packages/newton/lib/src/friction.dart new file mode 100644 index 0000000000..c0d2320982 --- /dev/null +++ b/packages/newton/lib/src/friction.dart @@ -0,0 +1,26 @@ +// 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 newton; + +class Friction extends Simulation { + final double _drag; + final double _dragNaturalLog; + final double _x; + final double _v; + + Friction(double drag, double position, double velocity) + : _drag = drag, + _dragNaturalLog = Math.log(drag), + _x = position, + _v = velocity; + + double x(double time) => + _x + _v + Math.pow(_drag, time) / _dragNaturalLog - _v / _dragNaturalLog; + + double dx(double time) => _v * Math.pow(_drag, time); + + @override + bool isDone(double time) => dx(time).abs() < 1.0; +} diff --git a/packages/newton/lib/src/gravity.dart b/packages/newton/lib/src/gravity.dart new file mode 100644 index 0000000000..4eb11f69d4 --- /dev/null +++ b/packages/newton/lib/src/gravity.dart @@ -0,0 +1,26 @@ +// 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 newton; + +class Gravity extends Simulation { + final double _x; + final double _v; + final double _a; + final double _end; + + Gravity( + double acceleration, double distance, double endDistance, double velocity) + : _a = acceleration, + _x = distance, + _v = velocity, + _end = endDistance; + + double x(double time) => _x + _v * time + 0.5 * _a * time * time; + + double dx(double time) => _v + time * _a; + + @override + bool isDone(double time) => x(time).abs() >= _end; +} diff --git a/packages/newton/lib/src/scroll.dart b/packages/newton/lib/src/scroll.dart new file mode 100644 index 0000000000..12f9f4957a --- /dev/null +++ b/packages/newton/lib/src/scroll.dart @@ -0,0 +1,8 @@ +// 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 newton; + +// TODO(csg): Composite simulation +class Scroll {} diff --git a/packages/newton/lib/src/simulation.dart b/packages/newton/lib/src/simulation.dart new file mode 100644 index 0000000000..12b6b926c2 --- /dev/null +++ b/packages/newton/lib/src/simulation.dart @@ -0,0 +1,26 @@ +// 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 newton; + +abstract class Simulatable { + /// The current position of the object in the simulation + double x(double time); + + /// The current velocity of the object in the simulation + double dx(double time); +} + +/// The base class for all simulations. The user is meant to instantiate an +/// instance of a simulation and query the same for the position and velocity +/// of the body at a given interval. +/// +/// Note: All operation on subclasses of Simulation are idempotent. Composite +/// simulations are not guaranteed to be idempotent however. FIXME(csg): How do +/// I make this apparent? +abstract class Simulation implements Simulatable { + + /// Returns if the simulation is done at a given time + bool isDone(double time); +} diff --git a/packages/newton/lib/src/spring.dart b/packages/newton/lib/src/spring.dart new file mode 100644 index 0000000000..aee59baea9 --- /dev/null +++ b/packages/newton/lib/src/spring.dart @@ -0,0 +1,46 @@ +// 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 newton; + +class SpringDesc { + /// The mass of the spring (m) + final double mass; + + /// The spring constant (k) + final double springConstant; + + /// The damping coefficient. + /// Note: Not to be confused with the damping ratio (zeta). Use the separate + /// constructor provided for this purpose + final double damping; + + SpringDesc(this.mass, this.springConstant, this.damping); + SpringDesc.withDampingRatio(double mass, double springConstant, double zeta) + : this.mass = mass, + this.springConstant = springConstant, + this.damping = zeta * 2.0 * Math.sqrt(mass * springConstant); +} + +/// Creates a spring simulation. Depending on the spring description, a +/// critically, under or overdamped spring will be created. +class Spring extends Simulation { + final double _endPosition; + + final _SpringSolution _solution; + + /// A spring description with the provided spring description, start distance, + /// end distance and velocity. + Spring(SpringDesc desc, double start, double end, double velocity) + : this._endPosition = end, + _solution = new _SpringSolution(desc, start - end, velocity); + + double x(double time) => _endPosition + _solution.x(time); + + double dx(double time) => _solution.dx(time); + + @override + bool isDone(double time) => + _nearEqual(x(time), _endPosition) && _nearZero(dx(time)); +} diff --git a/packages/newton/lib/src/spring_solution.dart b/packages/newton/lib/src/spring_solution.dart new file mode 100644 index 0000000000..0950da329f --- /dev/null +++ b/packages/newton/lib/src/spring_solution.dart @@ -0,0 +1,85 @@ +// 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 newton; + +abstract class _SpringSolution implements Simulatable { + factory _SpringSolution( + SpringDesc desc, double initialPosition, double initialVelocity) { + double cmk = + desc.damping * desc.damping - 4 * desc.mass * desc.springConstant; + + if (cmk == 0.0) { + return new _CriticalSolution(desc, initialPosition, initialVelocity); + } else if (cmk > 0.0) { + return new _OverdampedSolution(desc, initialPosition, initialVelocity); + } else { + return new _UnderdampedSolution(desc, initialPosition, initialVelocity); + } + + return null; + } +} + +class _CriticalSolution implements _SpringSolution { + double r, c1, c2; + + _CriticalSolution(SpringDesc desc, double distance, double velocity) { + r = -desc.damping / (2.0 * desc.mass); + c1 = distance; + c2 = velocity / (r * distance); + } + + double x(double time) => (c1 + c2 * time) * Math.pow(Math.E, r * time); + + double dx(double time) { + final double power = Math.pow(Math.E, r * time); + return r * (c1 + c2 * time) * power + c2 * power; + } +} + +class _OverdampedSolution implements _SpringSolution { + double r1, r2, c1, c2; + + _OverdampedSolution(SpringDesc desc, double distance, double velocity) { + double cmk = + desc.damping * desc.damping - 4 * desc.mass * desc.springConstant; + + r1 = (-desc.damping - Math.sqrt(cmk)) / (2.0 * desc.mass); + r2 = (-desc.damping + Math.sqrt(cmk)) / (2.0 * desc.mass); + c2 = (velocity - r1 * distance) / (r2 - r1); + c1 = distance - c2; + } + + double x(double time) => + (c1 * Math.pow(Math.E, r1 * time) + c2 * Math.pow(Math.E, r2 * time)); + + double dx(double time) => (c1 * r1 * Math.pow(Math.E, r1 * time) + + c2 * r2 * Math.pow(Math.E, r2 * time)); +} + +class _UnderdampedSolution implements _SpringSolution { + double w, r, c1, c2; + + _UnderdampedSolution(SpringDesc desc, double distance, double velocity) { + w = Math.sqrt(4.0 * desc.mass * desc.springConstant - + desc.damping * desc.damping) / + (2.0 * desc.mass); + r = -(desc.damping / 2.0 * desc.mass); + c1 = distance; + c2 = (velocity - r * distance) / w; + } + + double x(double time) => Math.pow(Math.E, r * time) * + (c1 * Math.cos(w * time) + c2 * Math.sin(w * time)); + + double dx(double time) { + final double power = Math.pow(Math.E, r * time); + final double cosine = Math.cos(w * time); + final double sine = Math.sin(w * time); + + return power * (c2 * w * cosine - c1 * w * sine) + + r * power * (c2 * sine + c1 * cosine); + } +} diff --git a/packages/newton/lib/src/utils.dart b/packages/newton/lib/src/utils.dart new file mode 100644 index 0000000000..f3edfa2ca5 --- /dev/null +++ b/packages/newton/lib/src/utils.dart @@ -0,0 +1,12 @@ +// 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 newton; + +const double _simulationEpsilon = 0.01; + +bool _nearEqual(double a, double b) => + (a > (b - _simulationEpsilon)) && (a < (b + _simulationEpsilon)); + +bool _nearZero(double a) => _nearEqual(a, 0.0); diff --git a/packages/newton/test/newton_test.dart b/packages/newton/test/newton_test.dart new file mode 100644 index 0000000000..fb3c343515 --- /dev/null +++ b/packages/newton/test/newton_test.dart @@ -0,0 +1,11 @@ +// 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. + +library simple_physics.test; + +import 'package:test/test.dart'; + +import 'package:newton/newton.dart'; + +void main() {} From 8c91b9b7c75b431144a3a936c0f0993452360e39 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 6 Jul 2015 10:57:26 -0700 Subject: [PATCH 37/62] Implement simulation groups for kinetic scrolling --- packages/newton/lib/newton.dart | 2 +- packages/newton/lib/src/fall.dart | 8 -- packages/newton/lib/src/scroll.dart | 47 +++++++++- packages/newton/lib/src/simulation.dart | 4 +- packages/newton/lib/src/simulation_group.dart | 42 +++++++++ packages/newton/lib/src/spring_solution.dart | 88 ++++++++++++------- 6 files changed, 145 insertions(+), 46 deletions(-) delete mode 100644 packages/newton/lib/src/fall.dart create mode 100644 packages/newton/lib/src/simulation_group.dart diff --git a/packages/newton/lib/newton.dart b/packages/newton/lib/newton.dart index bc2de1a90f..744fe3135f 100644 --- a/packages/newton/lib/newton.dart +++ b/packages/newton/lib/newton.dart @@ -7,9 +7,9 @@ library newton; import 'dart:math' as Math; part 'src/simulation.dart'; +part 'src/simulation_group.dart'; part 'src/utils.dart'; -part 'src/fall.dart'; part 'src/friction.dart'; part 'src/gravity.dart'; part 'src/scroll.dart'; diff --git a/packages/newton/lib/src/fall.dart b/packages/newton/lib/src/fall.dart deleted file mode 100644 index ca5ec9556b..0000000000 --- a/packages/newton/lib/src/fall.dart +++ /dev/null @@ -1,8 +0,0 @@ -// 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 newton; - -// TODO(csg): Composite simulation -class Fall {} diff --git a/packages/newton/lib/src/scroll.dart b/packages/newton/lib/src/scroll.dart index 12f9f4957a..c4a9fe6a74 100644 --- a/packages/newton/lib/src/scroll.dart +++ b/packages/newton/lib/src/scroll.dart @@ -4,5 +4,48 @@ part of newton; -// TODO(csg): Composite simulation -class Scroll {} +/// Simulates kinetic scrolling behavior between a leading and trailing +/// boundary. Friction is applied within the extends and a spring action applied +/// at the boundaries. This simulation can only step forward. +class Scroll extends SimulationGroup { + final double _leadingExtent; + final double _trailingExtent; + final SpringDesc _springDesc; + final double _drag; + + bool _isSpringing = false; + Simulation _currentSimulation; + + Scroll(double position, double velocity, double leading, double trailing, + SpringDesc spring, double drag) + : _leadingExtent = leading, + _trailingExtent = trailing, + _springDesc = spring, + _drag = drag { + _chooseSimulation(position, velocity); + } + + @override + void step(double time) => _chooseSimulation( + _currentSimulation.x(time), _currentSimulation.dx(time)); + + @override + Simulation get currentSimulation => _currentSimulation; + + void _chooseSimulation(double position, double velocity) { + /// This simulation can only step forward + if (!_isSpringing) { + if (position > _trailingExtent) { + _isSpringing = true; + _currentSimulation = + new Spring(_springDesc, position, _trailingExtent, velocity); + } else if (position < _leadingExtent) { + _isSpringing = true; + _currentSimulation = + new Spring(_springDesc, position, _leadingExtent, velocity); + } + } else if (_currentSimulation == null) { + _currentSimulation = new Friction(_drag, position, velocity); + } + } +} diff --git a/packages/newton/lib/src/simulation.dart b/packages/newton/lib/src/simulation.dart index 12b6b926c2..ca06f4811a 100644 --- a/packages/newton/lib/src/simulation.dart +++ b/packages/newton/lib/src/simulation.dart @@ -16,9 +16,7 @@ abstract class Simulatable { /// instance of a simulation and query the same for the position and velocity /// of the body at a given interval. /// -/// Note: All operation on subclasses of Simulation are idempotent. Composite -/// simulations are not guaranteed to be idempotent however. FIXME(csg): How do -/// I make this apparent? +/// Note: All operations on subclasses of Simulation are idempotent. abstract class Simulation implements Simulatable { /// Returns if the simulation is done at a given time diff --git a/packages/newton/lib/src/simulation_group.dart b/packages/newton/lib/src/simulation_group.dart new file mode 100644 index 0000000000..29f9311b4f --- /dev/null +++ b/packages/newton/lib/src/simulation_group.dart @@ -0,0 +1,42 @@ +// 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 newton; + +/// The abstract base class of all composite simulations. Concrete subclasses +/// must implement the appropriate methods to select the appropriate simulation +/// at a given time interval. The simulation group takes care to call the `step` +/// method at appropriate intervals. If more fine grained control over the the +/// step is necessary, subclasses may override the `Simulatable` methods. +abstract class SimulationGroup extends Simulation { + Simulation get currentSimulation; + + void step(double time); + + double x(double time) { + _stepIfNecessary(time); + return currentSimulation.x(time); + } + + double dx(double time) { + _stepIfNecessary(time); + return currentSimulation.dx(time); + } + + @override + bool isDone(double time) { + _stepIfNecessary(time); + return currentSimulation.isDone(time); + } + + double _lastStep = -1.0; + void _stepIfNecessary(double time) { + if (_nearEqual(_lastStep, time)) { + return; + } + + _lastStep = time; + step(time); + } +} diff --git a/packages/newton/lib/src/spring_solution.dart b/packages/newton/lib/src/spring_solution.dart index 0950da329f..511b32a293 100644 --- a/packages/newton/lib/src/spring_solution.dart +++ b/packages/newton/lib/src/spring_solution.dart @@ -23,63 +23,87 @@ abstract class _SpringSolution implements Simulatable { } class _CriticalSolution implements _SpringSolution { - double r, c1, c2; + final double _r, _c1, _c2; - _CriticalSolution(SpringDesc desc, double distance, double velocity) { - r = -desc.damping / (2.0 * desc.mass); - c1 = distance; - c2 = velocity / (r * distance); + factory _CriticalSolution(SpringDesc desc, double distance, double velocity) { + final double r = -desc.damping / (2.0 * desc.mass); + final double c1 = distance; + final double c2 = velocity / (r * distance); + return new _CriticalSolution.withArgs(r, c1, c2); } - double x(double time) => (c1 + c2 * time) * Math.pow(Math.E, r * time); + _CriticalSolution.withArgs(double r, double c1, double c2) + : _r = r, + _c1 = c1, + _c2 = c2; + + double x(double time) => (_c1 + _c2 * time) * Math.pow(Math.E, _r * time); double dx(double time) { - final double power = Math.pow(Math.E, r * time); - return r * (c1 + c2 * time) * power + c2 * power; + final double power = Math.pow(Math.E, _r * time); + return _r * (_c1 + _c2 * time) * power + _c2 * power; } } class _OverdampedSolution implements _SpringSolution { - double r1, r2, c1, c2; + final double _r1, _r2, _c1, _c2; - _OverdampedSolution(SpringDesc desc, double distance, double velocity) { - double cmk = + factory _OverdampedSolution( + SpringDesc desc, double distance, double velocity) { + final double cmk = desc.damping * desc.damping - 4 * desc.mass * desc.springConstant; - r1 = (-desc.damping - Math.sqrt(cmk)) / (2.0 * desc.mass); - r2 = (-desc.damping + Math.sqrt(cmk)) / (2.0 * desc.mass); - c2 = (velocity - r1 * distance) / (r2 - r1); - c1 = distance - c2; + final double r1 = (-desc.damping - Math.sqrt(cmk)) / (2.0 * desc.mass); + final double r2 = (-desc.damping + Math.sqrt(cmk)) / (2.0 * desc.mass); + final double c2 = (velocity - r1 * distance) / (r2 - r1); + final double c1 = distance - c2; + + return new _OverdampedSolution.withArgs(r1, r2, c1, c2); } - double x(double time) => - (c1 * Math.pow(Math.E, r1 * time) + c2 * Math.pow(Math.E, r2 * time)); + _OverdampedSolution.withArgs(double r1, double r2, double c1, double c2) + : _r1 = r1, + _r2 = r2, + _c1 = c1, + _c2 = c2; - double dx(double time) => (c1 * r1 * Math.pow(Math.E, r1 * time) + - c2 * r2 * Math.pow(Math.E, r2 * time)); + double x(double time) => + (_c1 * Math.pow(Math.E, _r1 * time) + _c2 * Math.pow(Math.E, _r2 * time)); + + double dx(double time) => (_c1 * _r1 * Math.pow(Math.E, _r1 * time) + + _c2 * _r2 * Math.pow(Math.E, _r2 * time)); } class _UnderdampedSolution implements _SpringSolution { - double w, r, c1, c2; + final double _w, _r, _c1, _c2; - _UnderdampedSolution(SpringDesc desc, double distance, double velocity) { - w = Math.sqrt(4.0 * desc.mass * desc.springConstant - + factory _UnderdampedSolution( + SpringDesc desc, double distance, double velocity) { + final double w = Math.sqrt(4.0 * desc.mass * desc.springConstant - desc.damping * desc.damping) / (2.0 * desc.mass); - r = -(desc.damping / 2.0 * desc.mass); - c1 = distance; - c2 = (velocity - r * distance) / w; + final double r = -(desc.damping / 2.0 * desc.mass); + final double c1 = distance; + final double c2 = (velocity - r * distance) / w; + + return new _UnderdampedSolution.withArgs(w, r, c1, c2); } - double x(double time) => Math.pow(Math.E, r * time) * - (c1 * Math.cos(w * time) + c2 * Math.sin(w * time)); + _UnderdampedSolution.withArgs(double w, double r, double c1, double c2) + : _w = w, + _r = r, + _c1 = c1, + _c2 = c2; + + double x(double time) => Math.pow(Math.E, _r * time) * + (_c1 * Math.cos(_w * time) + _c2 * Math.sin(_w * time)); double dx(double time) { - final double power = Math.pow(Math.E, r * time); - final double cosine = Math.cos(w * time); - final double sine = Math.sin(w * time); + final double power = Math.pow(Math.E, _r * time); + final double cosine = Math.cos(_w * time); + final double sine = Math.sin(_w * time); - return power * (c2 * w * cosine - c1 * w * sine) + - r * power * (c2 * sine + c1 * cosine); + return power * (_c2 * _w * cosine - _c1 * _w * sine) + + _r * power * (_c2 * sine + _c1 * cosine); } } From 30965c09842fa04da9942f1479347fde1df9f84a Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 6 Jul 2015 12:19:37 -0700 Subject: [PATCH 38/62] Test Friction simulation --- packages/newton/lib/src/friction.dart | 2 +- packages/newton/test/newton_test.dart | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/newton/lib/src/friction.dart b/packages/newton/lib/src/friction.dart index c0d2320982..a055ac7d03 100644 --- a/packages/newton/lib/src/friction.dart +++ b/packages/newton/lib/src/friction.dart @@ -17,7 +17,7 @@ class Friction extends Simulation { _v = velocity; double x(double time) => - _x + _v + Math.pow(_drag, time) / _dragNaturalLog - _v / _dragNaturalLog; + _x + _v * Math.pow(_drag, time) / _dragNaturalLog - _v / _dragNaturalLog; double dx(double time) => _v * Math.pow(_drag, time); diff --git a/packages/newton/test/newton_test.dart b/packages/newton/test/newton_test.dart index fb3c343515..f3d94d2378 100644 --- a/packages/newton/test/newton_test.dart +++ b/packages/newton/test/newton_test.dart @@ -8,4 +8,24 @@ import 'package:test/test.dart'; import 'package:newton/newton.dart'; -void main() {} +typedef bool SimulationTestHandler(int millis); + +void main() { + test('test_friction', () { + var friction = new Friction(0.3, 100.0, 400.0); + + expect(friction.isDone(0.0), false); + expect(friction.x(0.0), 100); + expect(friction.dx(0.0), 400.0); + + expect(friction.x(1.0) > 330 && friction.x(1.0) < 335, true); + + expect(friction.dx(1.0), 120.0); + expect(friction.dx(2.0), 36.0); + expect(friction.dx(3.0), 10.8); + expect(friction.dx(4.0) < 3.5, true); + + expect(friction.isDone(5.0), true); + expect(friction.x(5.0) > 431 && friction.x(5.0) < 432, true); + }); +} From bcf1f8d05e3182360dc00abb9ae72306d874475f Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 6 Jul 2015 12:33:21 -0700 Subject: [PATCH 39/62] Test gravity --- packages/newton/test/newton_test.dart | 32 +++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/newton/test/newton_test.dart b/packages/newton/test/newton_test.dart index f3d94d2378..de86bbe61d 100644 --- a/packages/newton/test/newton_test.dart +++ b/packages/newton/test/newton_test.dart @@ -8,8 +8,6 @@ import 'package:test/test.dart'; import 'package:newton/newton.dart'; -typedef bool SimulationTestHandler(int millis); - void main() { test('test_friction', () { var friction = new Friction(0.3, 100.0, 400.0); @@ -28,4 +26,34 @@ void main() { expect(friction.isDone(5.0), true); expect(friction.x(5.0) > 431 && friction.x(5.0) < 432, true); }); + + test('test_gravity', () { + var gravity = new Gravity(200.0, 100.0, 600.0, 0.0); + + expect(gravity.isDone(0.0), false); + expect(gravity.x(0.0), 100.0); + expect(gravity.dx(0.0), 0.0); + + // Starts at 100 + expect(gravity.x(0.25), 106.25); + expect(gravity.x(0.50), 125); + expect(gravity.x(0.75), 156.25); + expect(gravity.x(1.00), 200); + expect(gravity.x(1.25), 256.25); + expect(gravity.x(1.50), 325); + expect(gravity.x(1.75), 406.25); + + // Starts at 0.0 + expect(gravity.dx(0.25), 50.0); + expect(gravity.dx(0.50), 100); + expect(gravity.dx(0.75), 150.00); + expect(gravity.dx(1.00), 200.0); + expect(gravity.dx(1.25), 250.0); + expect(gravity.dx(1.50), 300); + expect(gravity.dx(1.75), 350); + + expect(gravity.isDone(2.5), true); + expect(gravity.x(2.5), 725); + expect(gravity.dx(2.5), 500.0); + }); } From ab7a6dd6c2e9d69cac19b4317a99a2823d067247 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 6 Jul 2015 12:48:52 -0700 Subject: [PATCH 40/62] Add accessors for spring type --- packages/newton/lib/src/spring.dart | 9 +++++++++ packages/newton/lib/src/spring_solution.dart | 11 +++++++++++ packages/newton/test/newton_test.dart | 18 ++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/packages/newton/lib/src/spring.dart b/packages/newton/lib/src/spring.dart index aee59baea9..f23ce27b13 100644 --- a/packages/newton/lib/src/spring.dart +++ b/packages/newton/lib/src/spring.dart @@ -17,12 +17,19 @@ class SpringDesc { final double damping; SpringDesc(this.mass, this.springConstant, this.damping); + + /// Create a spring given the mass, spring constant and the damping ratio. The + /// damping ratio is especially useful trying to determing the type of spring + /// to create. A ratio of 1.0 creates a critically damped spring, > 1.0 + /// creates an overdamped spring and < 1.0 an underdamped one. SpringDesc.withDampingRatio(double mass, double springConstant, double zeta) : this.mass = mass, this.springConstant = springConstant, this.damping = zeta * 2.0 * Math.sqrt(mass * springConstant); } +enum SpringType { unknown, criticallyDamped, underDamped, overDamped, } + /// Creates a spring simulation. Depending on the spring description, a /// critically, under or overdamped spring will be created. class Spring extends Simulation { @@ -36,6 +43,8 @@ class Spring extends Simulation { : this._endPosition = end, _solution = new _SpringSolution(desc, start - end, velocity); + SpringType get type => _solution.type; + double x(double time) => _endPosition + _solution.x(time); double dx(double time) => _solution.dx(time); diff --git a/packages/newton/lib/src/spring_solution.dart b/packages/newton/lib/src/spring_solution.dart index 511b32a293..fe57f7c08b 100644 --- a/packages/newton/lib/src/spring_solution.dart +++ b/packages/newton/lib/src/spring_solution.dart @@ -20,6 +20,8 @@ abstract class _SpringSolution implements Simulatable { return null; } + + SpringType get type; } class _CriticalSolution implements _SpringSolution { @@ -32,6 +34,9 @@ class _CriticalSolution implements _SpringSolution { return new _CriticalSolution.withArgs(r, c1, c2); } + @override + SpringType get type => SpringType.criticallyDamped; + _CriticalSolution.withArgs(double r, double c1, double c2) : _r = r, _c1 = c1, @@ -67,6 +72,9 @@ class _OverdampedSolution implements _SpringSolution { _c1 = c1, _c2 = c2; + @override + SpringType get type => SpringType.overDamped; + double x(double time) => (_c1 * Math.pow(Math.E, _r1 * time) + _c2 * Math.pow(Math.E, _r2 * time)); @@ -95,6 +103,9 @@ class _UnderdampedSolution implements _SpringSolution { _c1 = c1, _c2 = c2; + @override + SpringType get type => SpringType.underDamped; + double x(double time) => Math.pow(Math.E, _r * time) * (_c1 * Math.cos(_w * time) + _c2 * Math.sin(_w * time)); diff --git a/packages/newton/test/newton_test.dart b/packages/newton/test/newton_test.dart index de86bbe61d..54c7daa223 100644 --- a/packages/newton/test/newton_test.dart +++ b/packages/newton/test/newton_test.dart @@ -56,4 +56,22 @@ void main() { expect(gravity.x(2.5), 725); expect(gravity.dx(2.5), 500.0); }); + + test('spring_types', () { + var crit = new Spring( + new SpringDesc.withDampingRatio(1.0, 100.0, 1.0), 0.0, 300.0, 0.0); + expect(crit.type, SpringType.criticallyDamped); + + var under = new Spring( + new SpringDesc.withDampingRatio(1.0, 100.0, 0.75), 0.0, 300.0, 0.0); + expect(under.type, SpringType.underDamped); + + var over = new Spring( + new SpringDesc.withDampingRatio(1.0, 100.0, 1.25), 0.0, 300.0, 0.0); + expect(over.type, SpringType.overDamped); + + // Just so we don't forget how to create a desc without the ratio. + var other = new Spring(new SpringDesc(1.0, 100.0, 20.0), 0.0, 20.0, 20.0); + expect(other.type, SpringType.criticallyDamped); + }); } From c3aaf8aa338d17c9b940dd8b75d8129a43aa0618 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 6 Jul 2015 13:30:53 -0700 Subject: [PATCH 41/62] Test individial spring types --- packages/newton/lib/src/spring.dart | 2 +- packages/newton/test/newton_test.dart | 58 +++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/packages/newton/lib/src/spring.dart b/packages/newton/lib/src/spring.dart index f23ce27b13..bda8242d20 100644 --- a/packages/newton/lib/src/spring.dart +++ b/packages/newton/lib/src/spring.dart @@ -12,7 +12,7 @@ class SpringDesc { final double springConstant; /// The damping coefficient. - /// Note: Not to be confused with the damping ratio (zeta). Use the separate + /// Note: Not to be confused with the damping ratio. Use the separate /// constructor provided for this purpose final double damping; diff --git a/packages/newton/test/newton_test.dart b/packages/newton/test/newton_test.dart index 54c7daa223..8ef74e357f 100644 --- a/packages/newton/test/newton_test.dart +++ b/packages/newton/test/newton_test.dart @@ -74,4 +74,62 @@ void main() { var other = new Spring(new SpringDesc(1.0, 100.0, 20.0), 0.0, 20.0, 20.0); expect(other.type, SpringType.criticallyDamped); }); + + test('crit_spring', () { + var crit = new Spring( + new SpringDesc.withDampingRatio(1.0, 100.0, 1.0), 0.0, 500.0, 0.0); + expect(crit.type, SpringType.criticallyDamped); + + expect(crit.isDone(0.0), false); + expect(crit.x(0.0), 0.0); + expect(crit.dx(0.0), 5000.0); + + expect(crit.x(0.25).floor(), 458.0); + expect(crit.x(0.50).floor(), 496.0); + expect(crit.x(0.75).floor(), 499.0); + + expect(crit.dx(0.25).floor(), 410); + expect(crit.dx(0.50).floor(), 33); + expect(crit.dx(0.75).floor(), 2); + + expect(crit.isDone(1.50), true); + expect(crit.x(1.5) > 499.0 && crit.x(1.5) < 501.0, true); + expect(crit.dx(1.5) < 0.1, true /* basically within tolerance */); + }); + + test('overdamped_spring', () { + var over = new Spring( + new SpringDesc.withDampingRatio(1.0, 100.0, 1.25), 0.0, 500.0, 0.0); + expect(over.type, SpringType.overDamped); + + expect(over.isDone(0.0), false); + expect(over.x(0.0), 0.0); + + expect(over.x(0.5).floor(), 445.0); + expect(over.x(1.0).floor(), 495.0); + expect(over.x(1.5).floor(), 499.0); + + expect(over.dx(0.5).floor(), 273.0); + expect(over.dx(1.0).floor(), 22.0); + expect(over.dx(1.5).floor(), 1.0); + + expect(over.isDone(3.0), true); + }); + + test('underdamped_spring', () { + var under = new Spring( + new SpringDesc.withDampingRatio(1.0, 100.0, 0.25), 0.0, 300.0, 0.0); + expect(under.type, SpringType.underDamped); + + expect(under.isDone(0.0), false); + + // Overshot with negative velocity + expect(under.x(1.0).floor(), 325); + expect(under.dx(1.0).floor(), -65); + + expect(under.dx(6.0).floor(), 0.0); + expect(under.x(6.0).floor(), 299); + + expect(under.isDone(6.0), true); + }); } From 9932e9f03a6bdfdf39ab611bc8cd9701d039aca4 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 6 Jul 2015 14:03:10 -0700 Subject: [PATCH 42/62] Test kinetic scrolling group simulation --- packages/newton/lib/src/scroll.dart | 24 +++++++++++++++---- packages/newton/lib/src/simulation_group.dart | 15 ++++++++---- packages/newton/lib/src/spring_solution.dart | 3 --- packages/newton/test/newton_test.dart | 12 ++++++++++ 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/packages/newton/lib/src/scroll.dart b/packages/newton/lib/src/scroll.dart index c4a9fe6a74..4b57b9f0af 100644 --- a/packages/newton/lib/src/scroll.dart +++ b/packages/newton/lib/src/scroll.dart @@ -15,6 +15,7 @@ class Scroll extends SimulationGroup { bool _isSpringing = false; Simulation _currentSimulation; + double _offset = 0.0; Scroll(double position, double velocity, double leading, double trailing, SpringDesc spring, double drag) @@ -22,30 +23,43 @@ class Scroll extends SimulationGroup { _trailingExtent = trailing, _springDesc = spring, _drag = drag { - _chooseSimulation(position, velocity); + _chooseSimulation(position, velocity, 0.0); } @override void step(double time) => _chooseSimulation( - _currentSimulation.x(time), _currentSimulation.dx(time)); + _currentSimulation.x(time - _offset), + _currentSimulation.dx(time - _offset), time); @override Simulation get currentSimulation => _currentSimulation; - void _chooseSimulation(double position, double velocity) { - /// This simulation can only step forward + @override + double get currentIntervalOffset => _offset; + + void _chooseSimulation( + double position, double velocity, double intervalOffset) { + + /// This simulation can only step forward. if (!_isSpringing) { if (position > _trailingExtent) { _isSpringing = true; + _offset = intervalOffset; _currentSimulation = new Spring(_springDesc, position, _trailingExtent, velocity); + return; } else if (position < _leadingExtent) { _isSpringing = true; + _offset = intervalOffset; _currentSimulation = new Spring(_springDesc, position, _leadingExtent, velocity); + return; } - } else if (_currentSimulation == null) { + } + + if (_currentSimulation == null) { _currentSimulation = new Friction(_drag, position, velocity); + return; } } } diff --git a/packages/newton/lib/src/simulation_group.dart b/packages/newton/lib/src/simulation_group.dart index 29f9311b4f..9fb6f9c365 100644 --- a/packages/newton/lib/src/simulation_group.dart +++ b/packages/newton/lib/src/simulation_group.dart @@ -4,24 +4,31 @@ part of newton; -/// The abstract base class of all composite simulations. Concrete subclasses +/// The abstract base class for all composite simulations. Concrete subclasses /// must implement the appropriate methods to select the appropriate simulation /// at a given time interval. The simulation group takes care to call the `step` /// method at appropriate intervals. If more fine grained control over the the -/// step is necessary, subclasses may override the `Simulatable` methods. +/// step is necessary, subclasses may override `Simulatable` methods. abstract class SimulationGroup extends Simulation { + + /// The currently active simulation Simulation get currentSimulation; + /// The time offset applied to the currently active simulation; + double get currentIntervalOffset; + + /// Called when a significant change in the interval is detected. Subclasses + /// must decide if the the current simulation must be switched (or updated). void step(double time); double x(double time) { _stepIfNecessary(time); - return currentSimulation.x(time); + return currentSimulation.x(time - currentIntervalOffset); } double dx(double time) { _stepIfNecessary(time); - return currentSimulation.dx(time); + return currentSimulation.dx(time - currentIntervalOffset); } @override diff --git a/packages/newton/lib/src/spring_solution.dart b/packages/newton/lib/src/spring_solution.dart index fe57f7c08b..7363a5d784 100644 --- a/packages/newton/lib/src/spring_solution.dart +++ b/packages/newton/lib/src/spring_solution.dart @@ -34,7 +34,6 @@ class _CriticalSolution implements _SpringSolution { return new _CriticalSolution.withArgs(r, c1, c2); } - @override SpringType get type => SpringType.criticallyDamped; _CriticalSolution.withArgs(double r, double c1, double c2) @@ -72,7 +71,6 @@ class _OverdampedSolution implements _SpringSolution { _c1 = c1, _c2 = c2; - @override SpringType get type => SpringType.overDamped; double x(double time) => @@ -103,7 +101,6 @@ class _UnderdampedSolution implements _SpringSolution { _c1 = c1, _c2 = c2; - @override SpringType get type => SpringType.underDamped; double x(double time) => Math.pow(Math.E, _r * time) * diff --git a/packages/newton/test/newton_test.dart b/packages/newton/test/newton_test.dart index 8ef74e357f..a568848eab 100644 --- a/packages/newton/test/newton_test.dart +++ b/packages/newton/test/newton_test.dart @@ -132,4 +132,16 @@ void main() { expect(under.isDone(6.0), true); }); + + test('test_kinetic_scroll', () { + var spring = new SpringDesc.withDampingRatio(1.0, 50.0, 0.5); + + var scroll = new Scroll(100.0, 800.0, 0.0, 300.0, spring, 0.3); + + expect(scroll.isDone(0.0), false); + expect(scroll.isDone(3.5), true); + + var scroll2 = new Scroll(100.0, -800.0, 0.0, 300.0, spring, 0.3); + expect(scroll2.isDone(4.5), true); + }); } From fc098d8c65d30721d988d6d440adf2a58ac99a57 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 6 Jul 2015 14:11:41 -0700 Subject: [PATCH 43/62] Rename concrete simulation subclasses --- packages/newton/lib/newton.dart | 8 +++---- ...friction.dart => friction_simulation.dart} | 10 ++++---- .../{gravity.dart => gravity_simulation.dart} | 4 ++-- .../{scroll.dart => scroll_simulation.dart} | 16 ++++++------- .../{spring.dart => spring_simulation.dart} | 4 ++-- packages/newton/test/newton_test.dart | 23 ++++++++++--------- 6 files changed, 33 insertions(+), 32 deletions(-) rename packages/newton/lib/src/{friction.dart => friction_simulation.dart} (65%) rename packages/newton/lib/src/{gravity.dart => gravity_simulation.dart} (90%) rename packages/newton/lib/src/{scroll.dart => scroll_simulation.dart} (76%) rename packages/newton/lib/src/{spring.dart => spring_simulation.dart} (93%) diff --git a/packages/newton/lib/newton.dart b/packages/newton/lib/newton.dart index 744fe3135f..03eaa834b4 100644 --- a/packages/newton/lib/newton.dart +++ b/packages/newton/lib/newton.dart @@ -10,8 +10,8 @@ part 'src/simulation.dart'; part 'src/simulation_group.dart'; part 'src/utils.dart'; -part 'src/friction.dart'; -part 'src/gravity.dart'; -part 'src/scroll.dart'; -part 'src/spring.dart'; +part 'src/friction_simulation.dart'; +part 'src/gravity_simulation.dart'; +part 'src/scroll_simulation.dart'; +part 'src/spring_simulation.dart'; part 'src/spring_solution.dart'; diff --git a/packages/newton/lib/src/friction.dart b/packages/newton/lib/src/friction_simulation.dart similarity index 65% rename from packages/newton/lib/src/friction.dart rename to packages/newton/lib/src/friction_simulation.dart index a055ac7d03..3916456e2d 100644 --- a/packages/newton/lib/src/friction.dart +++ b/packages/newton/lib/src/friction_simulation.dart @@ -4,20 +4,20 @@ part of newton; -class Friction extends Simulation { +class FrictionSimulation extends Simulation { final double _drag; - final double _dragNaturalLog; + final double _dragLog; final double _x; final double _v; - Friction(double drag, double position, double velocity) + FrictionSimulation(double drag, double position, double velocity) : _drag = drag, - _dragNaturalLog = Math.log(drag), + _dragLog = Math.log(drag), _x = position, _v = velocity; double x(double time) => - _x + _v * Math.pow(_drag, time) / _dragNaturalLog - _v / _dragNaturalLog; + _x + _v * Math.pow(_drag, time) / _dragLog - _v / _dragLog; double dx(double time) => _v * Math.pow(_drag, time); diff --git a/packages/newton/lib/src/gravity.dart b/packages/newton/lib/src/gravity_simulation.dart similarity index 90% rename from packages/newton/lib/src/gravity.dart rename to packages/newton/lib/src/gravity_simulation.dart index 4eb11f69d4..2f002b74e0 100644 --- a/packages/newton/lib/src/gravity.dart +++ b/packages/newton/lib/src/gravity_simulation.dart @@ -4,13 +4,13 @@ part of newton; -class Gravity extends Simulation { +class GravitySimulation extends Simulation { final double _x; final double _v; final double _a; final double _end; - Gravity( + GravitySimulation( double acceleration, double distance, double endDistance, double velocity) : _a = acceleration, _x = distance, diff --git a/packages/newton/lib/src/scroll.dart b/packages/newton/lib/src/scroll_simulation.dart similarity index 76% rename from packages/newton/lib/src/scroll.dart rename to packages/newton/lib/src/scroll_simulation.dart index 4b57b9f0af..a1f7d59fb5 100644 --- a/packages/newton/lib/src/scroll.dart +++ b/packages/newton/lib/src/scroll_simulation.dart @@ -7,7 +7,7 @@ part of newton; /// Simulates kinetic scrolling behavior between a leading and trailing /// boundary. Friction is applied within the extends and a spring action applied /// at the boundaries. This simulation can only step forward. -class Scroll extends SimulationGroup { +class ScrollSimulation extends SimulationGroup { final double _leadingExtent; final double _trailingExtent; final SpringDesc _springDesc; @@ -17,8 +17,8 @@ class Scroll extends SimulationGroup { Simulation _currentSimulation; double _offset = 0.0; - Scroll(double position, double velocity, double leading, double trailing, - SpringDesc spring, double drag) + ScrollSimulation(double position, double velocity, double leading, + double trailing, SpringDesc spring, double drag) : _leadingExtent = leading, _trailingExtent = trailing, _springDesc = spring, @@ -45,20 +45,20 @@ class Scroll extends SimulationGroup { if (position > _trailingExtent) { _isSpringing = true; _offset = intervalOffset; - _currentSimulation = - new Spring(_springDesc, position, _trailingExtent, velocity); + _currentSimulation = new SpringSimulation( + _springDesc, position, _trailingExtent, velocity); return; } else if (position < _leadingExtent) { _isSpringing = true; _offset = intervalOffset; - _currentSimulation = - new Spring(_springDesc, position, _leadingExtent, velocity); + _currentSimulation = new SpringSimulation( + _springDesc, position, _leadingExtent, velocity); return; } } if (_currentSimulation == null) { - _currentSimulation = new Friction(_drag, position, velocity); + _currentSimulation = new FrictionSimulation(_drag, position, velocity); return; } } diff --git a/packages/newton/lib/src/spring.dart b/packages/newton/lib/src/spring_simulation.dart similarity index 93% rename from packages/newton/lib/src/spring.dart rename to packages/newton/lib/src/spring_simulation.dart index bda8242d20..9d80bf094d 100644 --- a/packages/newton/lib/src/spring.dart +++ b/packages/newton/lib/src/spring_simulation.dart @@ -32,14 +32,14 @@ enum SpringType { unknown, criticallyDamped, underDamped, overDamped, } /// Creates a spring simulation. Depending on the spring description, a /// critically, under or overdamped spring will be created. -class Spring extends Simulation { +class SpringSimulation extends Simulation { final double _endPosition; final _SpringSolution _solution; /// A spring description with the provided spring description, start distance, /// end distance and velocity. - Spring(SpringDesc desc, double start, double end, double velocity) + SpringSimulation(SpringDesc desc, double start, double end, double velocity) : this._endPosition = end, _solution = new _SpringSolution(desc, start - end, velocity); diff --git a/packages/newton/test/newton_test.dart b/packages/newton/test/newton_test.dart index a568848eab..275aa81ca6 100644 --- a/packages/newton/test/newton_test.dart +++ b/packages/newton/test/newton_test.dart @@ -10,7 +10,7 @@ import 'package:newton/newton.dart'; void main() { test('test_friction', () { - var friction = new Friction(0.3, 100.0, 400.0); + var friction = new FrictionSimulation(0.3, 100.0, 400.0); expect(friction.isDone(0.0), false); expect(friction.x(0.0), 100); @@ -28,7 +28,7 @@ void main() { }); test('test_gravity', () { - var gravity = new Gravity(200.0, 100.0, 600.0, 0.0); + var gravity = new GravitySimulation(200.0, 100.0, 600.0, 0.0); expect(gravity.isDone(0.0), false); expect(gravity.x(0.0), 100.0); @@ -58,25 +58,26 @@ void main() { }); test('spring_types', () { - var crit = new Spring( + var crit = new SpringSimulation( new SpringDesc.withDampingRatio(1.0, 100.0, 1.0), 0.0, 300.0, 0.0); expect(crit.type, SpringType.criticallyDamped); - var under = new Spring( + var under = new SpringSimulation( new SpringDesc.withDampingRatio(1.0, 100.0, 0.75), 0.0, 300.0, 0.0); expect(under.type, SpringType.underDamped); - var over = new Spring( + var over = new SpringSimulation( new SpringDesc.withDampingRatio(1.0, 100.0, 1.25), 0.0, 300.0, 0.0); expect(over.type, SpringType.overDamped); // Just so we don't forget how to create a desc without the ratio. - var other = new Spring(new SpringDesc(1.0, 100.0, 20.0), 0.0, 20.0, 20.0); + var other = + new SpringSimulation(new SpringDesc(1.0, 100.0, 20.0), 0.0, 20.0, 20.0); expect(other.type, SpringType.criticallyDamped); }); test('crit_spring', () { - var crit = new Spring( + var crit = new SpringSimulation( new SpringDesc.withDampingRatio(1.0, 100.0, 1.0), 0.0, 500.0, 0.0); expect(crit.type, SpringType.criticallyDamped); @@ -98,7 +99,7 @@ void main() { }); test('overdamped_spring', () { - var over = new Spring( + var over = new SpringSimulation( new SpringDesc.withDampingRatio(1.0, 100.0, 1.25), 0.0, 500.0, 0.0); expect(over.type, SpringType.overDamped); @@ -117,7 +118,7 @@ void main() { }); test('underdamped_spring', () { - var under = new Spring( + var under = new SpringSimulation( new SpringDesc.withDampingRatio(1.0, 100.0, 0.25), 0.0, 300.0, 0.0); expect(under.type, SpringType.underDamped); @@ -136,12 +137,12 @@ void main() { test('test_kinetic_scroll', () { var spring = new SpringDesc.withDampingRatio(1.0, 50.0, 0.5); - var scroll = new Scroll(100.0, 800.0, 0.0, 300.0, spring, 0.3); + var scroll = new ScrollSimulation(100.0, 800.0, 0.0, 300.0, spring, 0.3); expect(scroll.isDone(0.0), false); expect(scroll.isDone(3.5), true); - var scroll2 = new Scroll(100.0, -800.0, 0.0, 300.0, spring, 0.3); + var scroll2 = new ScrollSimulation(100.0, -800.0, 0.0, 300.0, spring, 0.3); expect(scroll2.isDone(4.5), true); }); } From be5e52bc7c79d798d61032820dae7464bb6ebd1a Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 6 Jul 2015 14:31:21 -0700 Subject: [PATCH 44/62] Add build status and coverage badges --- packages/newton/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/newton/README.md b/packages/newton/README.md index 0562d23ed7..731a9abcaa 100644 --- a/packages/newton/README.md +++ b/packages/newton/README.md @@ -1,3 +1,5 @@ # Newton +[![Build Status](https://travis-ci.org/domokit/newton.svg?branch=master)](https://travis-ci.org/domokit/newton) +[![Coverage Status](https://coveralls.io/repos/domokit/newton/badge.svg?branch=master)](https://coveralls.io/r/domokit/newton?branch=master) Simple Physics Simulations for Dart. Springs, friction, gravity, etc. From e0f38529ed6e26ebb2db09aec1f6480937b97a1c Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 7 Jul 2015 13:19:36 -0700 Subject: [PATCH 45/62] `==` operator override on expression returns a constraint --- packages/cassowary/lib/expression.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cassowary/lib/expression.dart b/packages/cassowary/lib/expression.dart index 0c7f1e9ce2..28f6aa6b6f 100644 --- a/packages/cassowary/lib/expression.dart +++ b/packages/cassowary/lib/expression.dart @@ -57,7 +57,7 @@ class Expression extends _EquationMember { Constraint operator <=(_EquationMember value) => _createConstraint(value, Relation.lessThanOrEqualTo); - operator ==(_EquationMember value) => + Constraint operator ==(_EquationMember value) => _createConstraint(value, Relation.equalTo); Expression operator +(_EquationMember m) { From 149a2ca1f8aa9ce8b7b93b9c389990944763f792 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 8 Jul 2015 10:38:42 -0700 Subject: [PATCH 46/62] Get rid of the return type decl on the equality operator override on EquationMember --- packages/cassowary/lib/equation_member.dart | 2 +- packages/cassowary/lib/expression.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cassowary/lib/equation_member.dart b/packages/cassowary/lib/equation_member.dart index 06f35176b2..247710d624 100644 --- a/packages/cassowary/lib/equation_member.dart +++ b/packages/cassowary/lib/equation_member.dart @@ -15,7 +15,7 @@ abstract class _EquationMember { Constraint operator <=(_EquationMember m) => asExpression() <= m; - Constraint operator ==(_EquationMember m) => asExpression() == m; + operator ==(_EquationMember m) => asExpression() == m; Expression operator +(_EquationMember m) => asExpression() + m; diff --git a/packages/cassowary/lib/expression.dart b/packages/cassowary/lib/expression.dart index 28f6aa6b6f..0c7f1e9ce2 100644 --- a/packages/cassowary/lib/expression.dart +++ b/packages/cassowary/lib/expression.dart @@ -57,7 +57,7 @@ class Expression extends _EquationMember { Constraint operator <=(_EquationMember value) => _createConstraint(value, Relation.lessThanOrEqualTo); - Constraint operator ==(_EquationMember value) => + operator ==(_EquationMember value) => _createConstraint(value, Relation.equalTo); Expression operator +(_EquationMember m) { From e9335659939ca486fbf8c901c5f0a88e1686d693 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 8 Jul 2015 10:39:17 -0700 Subject: [PATCH 47/62] Add bulk edit update options to the solver --- packages/cassowary/lib/solver.dart | 60 +++++++++++++-------- packages/cassowary/test/cassowary_test.dart | 11 ++++ 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index f76ea239a5..b4de988764 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -18,28 +18,10 @@ class Solver { /// add any for some reason, a cleanup is attempted so that either all /// constraints will be added or none. Result addConstraints(List constraints) { - List added = new List(); - bool needsCleanup = false; + _SolverBulkUpdate applier = (Constraint c) => addConstraint(c); + _SolverBulkUpdate undoer = (Constraint c) => removeConstraint(c); - Result result = Result.success; - - for (Constraint constraint in constraints) { - result = addConstraint(constraint); - if (result == Result.success) { - added.add(constraint); - } else { - needsCleanup = true; - break; - } - } - - if (needsCleanup) { - for (Constraint constraint in added) { - removeConstraint(constraint); - } - } - - return result; + return _bulkEdit(constraints, applier, undoer); } Result addConstraint(Constraint constraint) { @@ -113,6 +95,13 @@ class Solver { return _constraints.containsKey(constraint); } + Result addEditVariables(List variables, double priority) { + _SolverBulkUpdate applier = (Variable v) => addEditVariable(v, priority); + _SolverBulkUpdate undoer = (Variable v) => removeEditVariable(v); + + return _bulkEdit(variables, applier, undoer); + } + Result addEditVariable(Variable variable, double priority) { if (_edits.containsKey(variable)) { return Result.duplicateEditVariable; @@ -189,6 +178,33 @@ class Solver { return updates; } + Result _bulkEdit(Iterable items, + _SolverBulkUpdate applier, + _SolverBulkUpdate undoer) { + List applied = new List(); + 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]; @@ -617,3 +633,5 @@ class _EditInfo { bool _isValidNonRequiredPriority(double priority) { return (priority >= 0.0 && priority < Priority.required); } + +typedef Result _SolverBulkUpdate(dynamic item); diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index 6d67e5413f..8562974f65 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -592,4 +592,15 @@ void main() { expect((left == right).runtimeType, Constraint); }); + + test('bulk_add_edit_variables', () { + Solver s = new Solver(); + + var left = new Param(0.0); + var right = new Param(100.0); + var mid = new Param(0.0); + + expect(s.addEditVariables( + [left.variable, right.variable, mid.variable], 999.0), Result.success); + }); } From 99dc91eb365c0355726d2d96139a40b80935ecd9 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 8 Jul 2015 12:38:22 -0700 Subject: [PATCH 48/62] Add bulk remove options --- packages/cassowary/lib/solver.dart | 15 +++++++++++++++ packages/cassowary/test/cassowary_test.dart | 21 +++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/packages/cassowary/lib/solver.dart b/packages/cassowary/lib/solver.dart index b4de988764..249560def1 100644 --- a/packages/cassowary/lib/solver.dart +++ b/packages/cassowary/lib/solver.dart @@ -59,6 +59,13 @@ class Solver { return _optimizeObjectiveRow(_objective); } + Result removeConstraints(List constraints) { + _SolverBulkUpdate applier = (Constraint c) => removeConstraint(c); + _SolverBulkUpdate undoer = (Constraint c) => addConstraint(c); + + return _bulkEdit(constraints, applier, undoer); + } + Result removeConstraint(Constraint constraint) { _Tag tag = _constraints[constraint]; if (tag == null) { @@ -129,6 +136,14 @@ class Solver { return Result.success; } + Result removeEditVariables(List variables) { + _SolverBulkUpdate applier = (Variable v) => removeEditVariable(v); + _SolverBulkUpdate undoer = (Variable v) => + addEditVariable(v, _edits[v].constraint.priority); + + return _bulkEdit(variables, applier, undoer); + } + Result removeEditVariable(Variable variable) { _EditInfo info = _edits[variable]; if (info == null) { diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index 8562974f65..23e8f976aa 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -603,4 +603,25 @@ void main() { expect(s.addEditVariables( [left.variable, right.variable, mid.variable], 999.0), Result.success); }); + + test('bulk_remove_constraints_and_variables', () { + Solver s = new Solver(); + + var left = new Param(0.0); + var right = new Param(100.0); + var mid = new Param(0.0); + + expect(s.addEditVariables( + [left.variable, right.variable, mid.variable], 999.0), Result.success); + + var c1 = left <= mid; + var c2 = mid <= right; + + expect(s.addConstraints([c1, c2]), Result.success); + + expect(s.removeConstraints([c1, c2]), Result.success); + + expect(s.removeEditVariables( + [left.variable, right.variable, mid.variable]), Result.success); + }); } From d29a0b526b0da67d609054d831b72519c6af2b67 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 8 Jul 2015 16:40:51 -0700 Subject: [PATCH 49/62] Address initial code review concerns --- packages/cassowary/lib/constant_member.dart | 2 +- packages/cassowary/lib/param.dart | 4 +- packages/cassowary/lib/result.dart | 22 ++-- packages/cassowary/lib/symbol.dart | 2 +- packages/cassowary/lib/variable.dart | 4 +- packages/cassowary/test/cassowary_test.dart | 126 ++++++++++---------- 6 files changed, 80 insertions(+), 80 deletions(-) diff --git a/packages/cassowary/lib/constant_member.dart b/packages/cassowary/lib/constant_member.dart index db8c7ed7bf..fed8d20740 100644 --- a/packages/cassowary/lib/constant_member.dart +++ b/packages/cassowary/lib/constant_member.dart @@ -14,6 +14,6 @@ class ConstantMember extends _EquationMember { Expression asExpression() => new Expression([], this.value); } -ConstantMember CM(num value) { +ConstantMember cm(double value) { return new ConstantMember(value); } diff --git a/packages/cassowary/lib/param.dart b/packages/cassowary/lib/param.dart index a2e7e176a3..6efe14dffa 100644 --- a/packages/cassowary/lib/param.dart +++ b/packages/cassowary/lib/param.dart @@ -12,8 +12,8 @@ class Param extends _EquationMember { variable._owner = this; } - Param.withContext(ctx) - : variable = new Variable(0.0), + Param.withContext(ctx, [double value = 0.0]) + : variable = new Variable(value), context = ctx { variable._owner = this; } diff --git a/packages/cassowary/lib/result.dart b/packages/cassowary/lib/result.dart index 20aee96b5b..36c822bccd 100644 --- a/packages/cassowary/lib/result.dart +++ b/packages/cassowary/lib/result.dart @@ -5,25 +5,25 @@ part of cassowary; class Result { - final bool error; final String message; + final bool error; - const Result(this.message, this.error); + const Result(this.message, { bool isError: true }) : error = isError; - static final Result success = const Result("Success", false); - static final Result unimplemented = const Result("Unimplemented", true); + static final Result success = const Result("Success", isError: false); + static final Result unimplemented = const Result("Unimplemented"); static final Result duplicateConstraint = - const Result("Duplicate Constraint", true); + const Result("Duplicate Constraint"); static final Result unsatisfiableConstraint = - const Result("Unsatisfiable Constraint", true); + const Result("Unsatisfiable Constraint"); static final Result unknownConstraint = - const Result("Unknown Constraint", true); + const Result("Unknown Constraint"); static final Result duplicateEditVariable = - const Result("Duplicate Edit Variable", true); + const Result("Duplicate Edit Variable"); static final Result badRequiredStrength = - const Result("Bad Required Strength", true); + const Result("Bad Required Strength"); static final Result unknownEditVariable = - const Result("Unknown Edit Variable", true); + const Result("Unknown Edit Variable"); static final Result internalSolverError = - const Result("Internal Solver Error", true); + const Result("Internal Solver Error"); } diff --git a/packages/cassowary/lib/symbol.dart b/packages/cassowary/lib/symbol.dart index d76ed5a84e..cd7bcf491c 100644 --- a/packages/cassowary/lib/symbol.dart +++ b/packages/cassowary/lib/symbol.dart @@ -8,7 +8,7 @@ enum _SymbolType { invalid, external, slack, error, dummy, } class _Symbol { final _SymbolType type; - int tick; + final int tick; _Symbol(this.type, this.tick); diff --git a/packages/cassowary/lib/variable.dart b/packages/cassowary/lib/variable.dart index 40219f77fd..93850e716e 100644 --- a/packages/cassowary/lib/variable.dart +++ b/packages/cassowary/lib/variable.dart @@ -5,12 +5,12 @@ part of cassowary; class Variable { - double value = 0.0; + double value; String name; Param _owner; - int _tick; + final int _tick; static int _total = 0; Variable(this.value) : _tick = _total++; diff --git a/packages/cassowary/test/cassowary_test.dart b/packages/cassowary/test/cassowary_test.dart index 23e8f976aa..300f5e85d2 100644 --- a/packages/cassowary/test/cassowary_test.dart +++ b/packages/cassowary/test/cassowary_test.dart @@ -16,8 +16,8 @@ void main() { test('variable1', () { var v = new Param(22.0); - expect((v + CM(22.0)).value, 44.0); - expect((v - CM(20.0)).value, 2.0); + expect((v + cm(22.0)).value, 44.0); + expect((v - cm(20.0)).value, 2.0); }); test('term', () { @@ -40,7 +40,7 @@ void main() { var v3 = new Param(22.0); expect(v1 is Param, true); - expect(v1 + CM(20.0) is Expression, true); + expect(v1 + cm(20.0) is Expression, true); expect(v1 + v2 is Expression, true); expect((v1 + v2).value, 20.0); @@ -51,15 +51,15 @@ void main() { }); test('expression2', () { - var e = new Param(10.0) + CM(5.0); + var 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 + 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); @@ -95,7 +95,7 @@ void main() { var t = new Term(new Variable(12.0), 1.0); // Constant - var c = CM(2.0); + var c = cm(2.0); expect((t + c) is Expression, true); expect((t + c).value, 14.0); expect((t - c) is Expression, true); @@ -116,7 +116,7 @@ void main() { expect((t - t2).value, 10.0); // Expression - var exp = new Param(1.0) + CM(1.0); + var 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); @@ -127,7 +127,7 @@ void main() { var v = new Param(3.0); // Constant - var c = CM(2.0); + var c = cm(2.0); expect((v + c) is Expression, true); expect((v + c).value, 5.0); expect((v - c) is Expression, true); @@ -148,7 +148,7 @@ void main() { expect((v - t2).value, 1.0); // Expression - var exp = new Param(1.0) + CM(1.0); + var exp = new Param(1.0) + cm(1.0); expect(exp.terms.length, 1); expect((v + exp) is Expression, true); @@ -158,10 +158,10 @@ void main() { }); test('constantmember', () { - var c = CM(3.0); + var c = cm(3.0); // Constant - var c2 = CM(2.0); + var c2 = cm(2.0); expect((c + c2) is Expression, true); expect((c + c2).value, 5.0); expect((c - c2) is Expression, true); @@ -182,7 +182,7 @@ void main() { expect((c - t2).value, 1.0); // Expression - var exp = new Param(1.0) + CM(1.0); + var exp = new Param(1.0) + cm(1.0); expect((c + exp) is Expression, true); expect((c + exp).value, 5.0); @@ -194,61 +194,61 @@ void main() { var left = new Param(10.0); var right = new Param(100.0); - var c = right - left >= CM(25.0); + var c = right - left >= cm(25.0); expect(c is Constraint, true); }); test('simple_multiplication', () { // Constant - var c = CM(20.0); - expect((c * CM(2.0)).value, 40.0); + var c = cm(20.0); + expect((c * cm(2.0)).value, 40.0); // Variable var v = new Param(20.0); - expect((v * CM(2.0)).value, 40.0); + expect((v * cm(2.0)).value, 40.0); // Term var t = new Term(v.variable, 1.0); - expect((t * CM(2.0)).value, 40.0); + expect((t * cm(2.0)).value, 40.0); // Expression var e = new Expression([t], 0.0); - expect((e * CM(2.0)).value, 40.0); + expect((e * cm(2.0)).value, 40.0); }); test('simple_division', () { // Constant - var c = CM(20.0); - expect((c / CM(2.0)).value, 10.0); + var c = cm(20.0); + expect((c / cm(2.0)).value, 10.0); // Variable var v = new Param(20.0); - expect((v / CM(2.0)).value, 10.0); + expect((v / cm(2.0)).value, 10.0); // Term var t = new Term(v.variable, 1.0); - expect((t / CM(2.0)).value, 10.0); + expect((t / cm(2.0)).value, 10.0); // Expression var e = new Expression([t], 0.0); - expect((e / CM(2.0)).value, 10.0); + expect((e / cm(2.0)).value, 10.0); }); test('full_constraints_setup', () { var left = new Param(2.0); var right = new Param(10.0); - var c1 = right - left >= CM(20.0); + var c1 = right - left >= cm(20.0); expect(c1 is Constraint, true); expect(c1.expression.constant, -20.0); expect(c1.relation, Relation.greaterThanOrEqualTo); - var c2 = (right - left == CM(30.0)) as Constraint; + var c2 = (right - left == cm(30.0)) as Constraint; expect(c2 is Constraint, true); expect(c2.expression.constant, -30.0); expect(c2.relation, Relation.equalTo); - var c3 = right - left <= CM(30.0); + var c3 = right - left <= cm(30.0); expect(c3 is Constraint, true); expect(c3.expression.constant, -30.0); expect(c3.relation, Relation.lessThanOrEqualTo); @@ -258,7 +258,7 @@ void main() { var left = new Param(2.0); var right = new Param(10.0); - var c = (right - left >= CM(200.0)) | 750.0; + var 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); @@ -271,7 +271,7 @@ void main() { var left = new Param(2.0); var right = new Param(100.0); - var c1 = right - left >= CM(200.0); + var c1 = right - left >= cm(200.0); expect((right >= left) is Constraint, true); @@ -282,7 +282,7 @@ void main() { var e = new Param(200.0) - new Param(100.0); // Constant - var c1 = e >= CM(50.0); + var c1 = e >= cm(50.0); expect(c1 is Constraint, true); expect(c1.expression.terms.length, 2); expect(c1.expression.constant, -50.0); @@ -308,7 +308,7 @@ void main() { test('constraint_complex_non_exprs', () { // Constant - var c1 = CM(100.0) >= CM(50.0); + var c1 = cm(100.0) >= cm(50.0); expect(c1 is Constraint, true); expect(c1.expression.terms.length, 0); expect(c1.expression.constant, 50.0); @@ -340,7 +340,7 @@ void main() { var left = new Param(2.0); var right = new Param(100.0); - var c1 = right - left >= CM(200.0); + var c1 = right - left >= cm(200.0); var c2 = right >= right; expect(s.addConstraint(c1), Result.success); @@ -351,38 +351,38 @@ void main() { }); test('test_multiplication_division_override', () { - var c = CM(10.0); + var c = cm(10.0); var v = new Param(c.value); var t = new Term(v.variable, 1.0); var e = new Expression([t], 0.0); // Constant - expect((c * CM(10.0)).value, 100); + expect((c * cm(10.0)).value, 100); // Variable - expect((v * CM(10.0)).value, 100); + expect((v * cm(10.0)).value, 100); // Term - expect((t * CM(10.0)).value, 100); + expect((t * cm(10.0)).value, 100); // Expression - expect((e * CM(10.0)).value, 100); + expect((e * cm(10.0)).value, 100); // Constant - expect((c / CM(10.0)).value, 1); + expect((c / cm(10.0)).value, 1); // Variable - expect((v / CM(10.0)).value, 1); + expect((v / cm(10.0)).value, 1); // Term - expect((t / CM(10.0)).value, 1); + expect((t / cm(10.0)).value, 1); // Expression - expect((e / CM(10.0)).value, 1); + expect((e / cm(10.0)).value, 1); }); test('test_multiplication_division_exceptions', () { - var c = CM(10.0); + var c = cm(10.0); var v = new Param(c.value); var t = new Term(v.variable, 1.0); var e = new Expression([t], 0.0); @@ -405,7 +405,7 @@ void main() { var right = new Param(100.0); var mid = new Param(0.0); - Constraint c = left + right >= CM(2.0) * mid; + Constraint c = left + right >= cm(2.0) * mid; expect(s.addConstraint(c), Result.success); expect(s.addEditVariable(mid.variable, 999.0), Result.success); @@ -420,13 +420,13 @@ void main() { var right = new Param(100.0); var mid = new Param(0.0); - expect(((left + right) >= (CM(2.0) * mid)) is Constraint, true); + expect(((left + right) >= (cm(2.0) * mid)) is Constraint, true); }); test('single_item', () { var left = new Param(-20.0); Solver s = new Solver(); - s.addConstraint(left >= CM(0.0)); + s.addConstraint(left >= cm(0.0)); s.flushUpdates(); expect(left.value, 0.0); }); @@ -438,10 +438,10 @@ void main() { Solver s = new Solver(); - expect(s.addConstraint((right + left == mid * CM(2.0)) as Constraint), + expect(s.addConstraint((right + left == mid * cm(2.0)) as Constraint), Result.success); - expect(s.addConstraint(right - left >= CM(100.0)), Result.success); - expect(s.addConstraint(left >= CM(0.0)), Result.success); + expect(s.addConstraint(right - left >= cm(100.0)), Result.success); + expect(s.addConstraint(left >= cm(0.0)), Result.success); s.flushUpdates(); @@ -457,15 +457,15 @@ void main() { Solver s = new Solver(); - var c = (left >= CM(0.0)); + var c = (left >= cm(0.0)); expect(s.addConstraints([ - (left + right == CM(2.0) * mid) as Constraint, - (right - left >= CM(100.0)), + (left + right == cm(2.0) * mid) as Constraint, + (right - left >= cm(100.0)), c ]), Result.success); - expect(s.addConstraints([(right >= CM(-20.0)), c]), + expect(s.addConstraints([(right >= cm(-20.0)), c]), Result.duplicateConstraint); }); @@ -476,10 +476,10 @@ void main() { Solver s = new Solver(); - expect(s.addConstraint((right + left == mid * CM(2.0)) as Constraint), + expect(s.addConstraint((right + left == mid * cm(2.0)) as Constraint), Result.success); - expect(s.addConstraint(right - left >= CM(100.0)), Result.success); - expect(s.addConstraint(left >= CM(0.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); @@ -518,9 +518,9 @@ void main() { solver.addEditVariable(container.variable, Priority.strong); solver.suggestValueForVariable(container.variable, 100.0); - solver.addConstraint((p1 >= CM(30.0)) | Priority.strong); + solver.addConstraint((p1 >= cm(30.0)) | Priority.strong); solver.addConstraint(((p1 == p3) as Constraint) | Priority.medium); - solver.addConstraint((p2 == CM(2.0) * p1) as Constraint); + solver.addConstraint((p2 == cm(2.0) * p1) as Constraint); solver.addConstraint((container == (p1 + p2 + p3)) as Constraint); solver.flushUpdates(); @@ -541,9 +541,9 @@ void main() { expect(s.addEditVariable(mid.variable, Priority.strong), Result.success); - expect(s.addConstraint((mid * CM(2.0) == left + right) as Constraint), + expect(s.addConstraint((mid * cm(2.0) == left + right) as Constraint), Result.success); - expect(s.addConstraint(left >= CM(0.0)), Result.success); + expect(s.addConstraint(left >= cm(0.0)), Result.success); expect(s.suggestValueForVariable(mid.variable, 50.0), Result.success); @@ -565,9 +565,9 @@ void main() { expect(s.addEditVariable(mid.variable, Priority.strong), Result.success); - expect(s.addConstraint((mid * CM(2.0) == left + right) as Constraint), + expect(s.addConstraint((mid * cm(2.0) == left + right) as Constraint), Result.success); - expect(s.addConstraint(left >= CM(10.0)), Result.success); + expect(s.addConstraint(left >= cm(10.0)), Result.success); expect(s.suggestValueForVariable(mid.variable, 50.0), Result.success); From 7b77043eba73fd4eaddc16991ef1c6602a886be0 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 8 Jul 2015 17:56:14 -0700 Subject: [PATCH 50/62] Newton: Address initial code review concerns --- packages/newton/lib/newton.dart | 2 +- .../newton/lib/src/friction_simulation.dart | 6 ++-- .../newton/lib/src/scroll_simulation.dart | 4 +-- .../newton/lib/src/spring_simulation.dart | 23 ++++++++---- packages/newton/lib/src/spring_solution.dart | 35 +++++++++--------- packages/newton/test/newton_test.dart | 36 +++++++++++-------- 6 files changed, 61 insertions(+), 45 deletions(-) diff --git a/packages/newton/lib/newton.dart b/packages/newton/lib/newton.dart index 03eaa834b4..47de84a790 100644 --- a/packages/newton/lib/newton.dart +++ b/packages/newton/lib/newton.dart @@ -4,7 +4,7 @@ library newton; -import 'dart:math' as Math; +import 'dart:math' as math; part 'src/simulation.dart'; part 'src/simulation_group.dart'; diff --git a/packages/newton/lib/src/friction_simulation.dart b/packages/newton/lib/src/friction_simulation.dart index 3916456e2d..3aa9ce8d0f 100644 --- a/packages/newton/lib/src/friction_simulation.dart +++ b/packages/newton/lib/src/friction_simulation.dart @@ -12,14 +12,14 @@ class FrictionSimulation extends Simulation { FrictionSimulation(double drag, double position, double velocity) : _drag = drag, - _dragLog = Math.log(drag), + _dragLog = math.log(drag), _x = position, _v = velocity; double x(double time) => - _x + _v * Math.pow(_drag, time) / _dragLog - _v / _dragLog; + _x + _v * math.pow(_drag, time) / _dragLog - _v / _dragLog; - double dx(double time) => _v * Math.pow(_drag, time); + double dx(double time) => _v * math.pow(_drag, time); @override bool isDone(double time) => dx(time).abs() < 1.0; diff --git a/packages/newton/lib/src/scroll_simulation.dart b/packages/newton/lib/src/scroll_simulation.dart index a1f7d59fb5..123e8d1b41 100644 --- a/packages/newton/lib/src/scroll_simulation.dart +++ b/packages/newton/lib/src/scroll_simulation.dart @@ -10,7 +10,7 @@ part of newton; class ScrollSimulation extends SimulationGroup { final double _leadingExtent; final double _trailingExtent; - final SpringDesc _springDesc; + final SpringDescription _springDesc; final double _drag; bool _isSpringing = false; @@ -18,7 +18,7 @@ class ScrollSimulation extends SimulationGroup { double _offset = 0.0; ScrollSimulation(double position, double velocity, double leading, - double trailing, SpringDesc spring, double drag) + double trailing, SpringDescription spring, double drag) : _leadingExtent = leading, _trailingExtent = trailing, _springDesc = spring, diff --git a/packages/newton/lib/src/spring_simulation.dart b/packages/newton/lib/src/spring_simulation.dart index 9d80bf094d..3ca6a846f8 100644 --- a/packages/newton/lib/src/spring_simulation.dart +++ b/packages/newton/lib/src/spring_simulation.dart @@ -4,7 +4,7 @@ part of newton; -class SpringDesc { +class SpringDescription { /// The mass of the spring (m) final double mass; @@ -16,16 +16,24 @@ class SpringDesc { /// constructor provided for this purpose final double damping; - SpringDesc(this.mass, this.springConstant, this.damping); + SpringDescription({double mass, double springConstant, double damping}) + : mass = mass, + springConstant = springConstant, + damping = damping { + assert(mass != null); + assert(springConstant != null); + assert(damping != null); + } /// Create a spring given the mass, spring constant and the damping ratio. The /// damping ratio is especially useful trying to determing the type of spring /// to create. A ratio of 1.0 creates a critically damped spring, > 1.0 /// creates an overdamped spring and < 1.0 an underdamped one. - SpringDesc.withDampingRatio(double mass, double springConstant, double zeta) - : this.mass = mass, - this.springConstant = springConstant, - this.damping = zeta * 2.0 * Math.sqrt(mass * springConstant); + SpringDescription.withDampingRatio( + {double mass, double springConstant, double ratio: 1.0}) + : mass = mass, + springConstant = springConstant, + damping = ratio * 2.0 * math.sqrt(mass * springConstant); } enum SpringType { unknown, criticallyDamped, underDamped, overDamped, } @@ -39,7 +47,8 @@ class SpringSimulation extends Simulation { /// A spring description with the provided spring description, start distance, /// end distance and velocity. - SpringSimulation(SpringDesc desc, double start, double end, double velocity) + SpringSimulation( + SpringDescription desc, double start, double end, double velocity) : this._endPosition = end, _solution = new _SpringSolution(desc, start - end, velocity); diff --git a/packages/newton/lib/src/spring_solution.dart b/packages/newton/lib/src/spring_solution.dart index 7363a5d784..3241311c60 100644 --- a/packages/newton/lib/src/spring_solution.dart +++ b/packages/newton/lib/src/spring_solution.dart @@ -6,7 +6,7 @@ part of newton; abstract class _SpringSolution implements Simulatable { factory _SpringSolution( - SpringDesc desc, double initialPosition, double initialVelocity) { + SpringDescription desc, double initialPosition, double initialVelocity) { double cmk = desc.damping * desc.damping - 4 * desc.mass * desc.springConstant; @@ -27,7 +27,8 @@ abstract class _SpringSolution implements Simulatable { class _CriticalSolution implements _SpringSolution { final double _r, _c1, _c2; - factory _CriticalSolution(SpringDesc desc, double distance, double velocity) { + factory _CriticalSolution( + SpringDescription desc, double distance, double velocity) { final double r = -desc.damping / (2.0 * desc.mass); final double c1 = distance; final double c2 = velocity / (r * distance); @@ -41,10 +42,10 @@ class _CriticalSolution implements _SpringSolution { _c1 = c1, _c2 = c2; - double x(double time) => (_c1 + _c2 * time) * Math.pow(Math.E, _r * time); + double x(double time) => (_c1 + _c2 * time) * math.pow(math.E, _r * time); double dx(double time) { - final double power = Math.pow(Math.E, _r * time); + final double power = math.pow(math.E, _r * time); return _r * (_c1 + _c2 * time) * power + _c2 * power; } } @@ -53,12 +54,12 @@ class _OverdampedSolution implements _SpringSolution { final double _r1, _r2, _c1, _c2; factory _OverdampedSolution( - SpringDesc desc, double distance, double velocity) { + SpringDescription desc, double distance, double velocity) { final double cmk = desc.damping * desc.damping - 4 * desc.mass * desc.springConstant; - final double r1 = (-desc.damping - Math.sqrt(cmk)) / (2.0 * desc.mass); - final double r2 = (-desc.damping + Math.sqrt(cmk)) / (2.0 * desc.mass); + final double r1 = (-desc.damping - math.sqrt(cmk)) / (2.0 * desc.mass); + final double r2 = (-desc.damping + math.sqrt(cmk)) / (2.0 * desc.mass); final double c2 = (velocity - r1 * distance) / (r2 - r1); final double c1 = distance - c2; @@ -74,18 +75,18 @@ class _OverdampedSolution implements _SpringSolution { SpringType get type => SpringType.overDamped; double x(double time) => - (_c1 * Math.pow(Math.E, _r1 * time) + _c2 * Math.pow(Math.E, _r2 * time)); + (_c1 * math.pow(math.E, _r1 * time) + _c2 * math.pow(math.E, _r2 * time)); - double dx(double time) => (_c1 * _r1 * Math.pow(Math.E, _r1 * time) + - _c2 * _r2 * Math.pow(Math.E, _r2 * time)); + double dx(double time) => (_c1 * _r1 * math.pow(math.E, _r1 * time) + + _c2 * _r2 * math.pow(math.E, _r2 * time)); } class _UnderdampedSolution implements _SpringSolution { final double _w, _r, _c1, _c2; factory _UnderdampedSolution( - SpringDesc desc, double distance, double velocity) { - final double w = Math.sqrt(4.0 * desc.mass * desc.springConstant - + SpringDescription desc, double distance, double velocity) { + final double w = math.sqrt(4.0 * desc.mass * desc.springConstant - desc.damping * desc.damping) / (2.0 * desc.mass); final double r = -(desc.damping / 2.0 * desc.mass); @@ -103,13 +104,13 @@ class _UnderdampedSolution implements _SpringSolution { SpringType get type => SpringType.underDamped; - double x(double time) => Math.pow(Math.E, _r * time) * - (_c1 * Math.cos(_w * time) + _c2 * Math.sin(_w * time)); + double x(double time) => math.pow(math.E, _r * time) * + (_c1 * math.cos(_w * time) + _c2 * math.sin(_w * time)); double dx(double time) { - final double power = Math.pow(Math.E, _r * time); - final double cosine = Math.cos(_w * time); - final double sine = Math.sin(_w * time); + final double power = math.pow(math.E, _r * time); + final double cosine = math.cos(_w * time); + final double sine = math.sin(_w * time); return power * (_c2 * _w * cosine - _c1 * _w * sine) + _r * power * (_c2 * sine + _c1 * cosine); diff --git a/packages/newton/test/newton_test.dart b/packages/newton/test/newton_test.dart index 275aa81ca6..323082f169 100644 --- a/packages/newton/test/newton_test.dart +++ b/packages/newton/test/newton_test.dart @@ -58,27 +58,32 @@ void main() { }); test('spring_types', () { - var crit = new SpringSimulation( - new SpringDesc.withDampingRatio(1.0, 100.0, 1.0), 0.0, 300.0, 0.0); + var crit = new SpringSimulation(new SpringDescription.withDampingRatio( + mass: 1.0, springConstant: 100.0), 0.0, 300.0, 0.0); expect(crit.type, SpringType.criticallyDamped); - var under = new SpringSimulation( - new SpringDesc.withDampingRatio(1.0, 100.0, 0.75), 0.0, 300.0, 0.0); + crit = new SpringSimulation(new SpringDescription.withDampingRatio( + mass: 1.0, springConstant: 100.0, ratio: 1.0), 0.0, 300.0, 0.0); + expect(crit.type, SpringType.criticallyDamped); + + var under = new SpringSimulation(new SpringDescription.withDampingRatio( + mass: 1.0, springConstant: 100.0, ratio: 0.75), 0.0, 300.0, 0.0); expect(under.type, SpringType.underDamped); - var over = new SpringSimulation( - new SpringDesc.withDampingRatio(1.0, 100.0, 1.25), 0.0, 300.0, 0.0); + var over = new SpringSimulation(new SpringDescription.withDampingRatio( + mass: 1.0, springConstant: 100.0, ratio: 1.25), 0.0, 300.0, 0.0); expect(over.type, SpringType.overDamped); // Just so we don't forget how to create a desc without the ratio. - var other = - new SpringSimulation(new SpringDesc(1.0, 100.0, 20.0), 0.0, 20.0, 20.0); + var other = new SpringSimulation( + new SpringDescription(mass: 1.0, springConstant: 100.0, damping: 20.0), + 0.0, 20.0, 20.0); expect(other.type, SpringType.criticallyDamped); }); test('crit_spring', () { - var crit = new SpringSimulation( - new SpringDesc.withDampingRatio(1.0, 100.0, 1.0), 0.0, 500.0, 0.0); + var crit = new SpringSimulation(new SpringDescription.withDampingRatio( + mass: 1.0, springConstant: 100.0, ratio: 1.0), 0.0, 500.0, 0.0); expect(crit.type, SpringType.criticallyDamped); expect(crit.isDone(0.0), false); @@ -99,8 +104,8 @@ void main() { }); test('overdamped_spring', () { - var over = new SpringSimulation( - new SpringDesc.withDampingRatio(1.0, 100.0, 1.25), 0.0, 500.0, 0.0); + var over = new SpringSimulation(new SpringDescription.withDampingRatio( + mass: 1.0, springConstant: 100.0, ratio: 1.25), 0.0, 500.0, 0.0); expect(over.type, SpringType.overDamped); expect(over.isDone(0.0), false); @@ -118,8 +123,8 @@ void main() { }); test('underdamped_spring', () { - var under = new SpringSimulation( - new SpringDesc.withDampingRatio(1.0, 100.0, 0.25), 0.0, 300.0, 0.0); + var under = new SpringSimulation(new SpringDescription.withDampingRatio( + mass: 1.0, springConstant: 100.0, ratio: 0.25), 0.0, 300.0, 0.0); expect(under.type, SpringType.underDamped); expect(under.isDone(0.0), false); @@ -135,7 +140,8 @@ void main() { }); test('test_kinetic_scroll', () { - var spring = new SpringDesc.withDampingRatio(1.0, 50.0, 0.5); + var spring = new SpringDescription.withDampingRatio( + mass: 1.0, springConstant: 50.0, ratio: 0.5); var scroll = new ScrollSimulation(100.0, 800.0, 0.0, 300.0, spring, 0.3); From 1633ac7f12028eac67a4745b3631bd08ea938935 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Wed, 8 Jul 2015 18:15:18 -0700 Subject: [PATCH 51/62] Use the more concise syntax for initialization of variables in the constructor --- packages/newton/lib/src/spring_simulation.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/newton/lib/src/spring_simulation.dart b/packages/newton/lib/src/spring_simulation.dart index 3ca6a846f8..0edf828284 100644 --- a/packages/newton/lib/src/spring_simulation.dart +++ b/packages/newton/lib/src/spring_simulation.dart @@ -16,10 +16,8 @@ class SpringDescription { /// constructor provided for this purpose final double damping; - SpringDescription({double mass, double springConstant, double damping}) - : mass = mass, - springConstant = springConstant, - damping = damping { + SpringDescription( + {double this.mass, double this.springConstant, double this.damping}) { assert(mass != null); assert(springConstant != null); assert(damping != null); From be7e02851a12f88667feb82be2649e2628b90413 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Fri, 10 Jul 2015 16:07:59 -0700 Subject: [PATCH 52/62] Add test to verify that composite scroll simulation ends correctly even if the spring was never initialized. --- packages/newton/test/newton_test.dart | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/newton/test/newton_test.dart b/packages/newton/test/newton_test.dart index 323082f169..c7b6323d72 100644 --- a/packages/newton/test/newton_test.dart +++ b/packages/newton/test/newton_test.dart @@ -151,4 +151,29 @@ void main() { var scroll2 = new ScrollSimulation(100.0, -800.0, 0.0, 300.0, spring, 0.3); expect(scroll2.isDone(4.5), true); }); + + test('scroll_with_inf_edge_ends', () { + var spring = new SpringDescription.withDampingRatio( + mass: 1.0, springConstant: 50.0, ratio: 0.5); + + var scroll = + new ScrollSimulation(100.0, 400.0, 0.0, double.INFINITY, spring, 0.3); + + expect(scroll.isDone(0.0), false); + expect(scroll.x(0.0), 100); + expect(scroll.dx(0.0), 400.0); + + expect(scroll.x(1.0) > 330 && scroll.x(1.0) < 335, true); + + expect(scroll.dx(1.0), 120.0); + expect(scroll.dx(2.0), 36.0); + expect(scroll.dx(3.0), 10.8); + expect(scroll.dx(4.0) < 3.5, true); + + expect(scroll.isDone(5.0), true); + expect(scroll.x(5.0) > 431 && scroll.x(5.0) < 432, true); + + // We should never switch + expect(scroll.currentIntervalOffset, 0.0); + }); } From 1f1cd6c279f93165132d332b0cac7b6292161c16 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Fri, 10 Jul 2015 15:54:39 -0700 Subject: [PATCH 53/62] Increase the simulation epsilon We don't need to compute simulations out to that many decimal places because we're working in pixels. R=chinmaygarde@google.com --- packages/newton/lib/src/utils.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/newton/lib/src/utils.dart b/packages/newton/lib/src/utils.dart index f3edfa2ca5..e6f997fe42 100644 --- a/packages/newton/lib/src/utils.dart +++ b/packages/newton/lib/src/utils.dart @@ -4,7 +4,7 @@ part of newton; -const double _simulationEpsilon = 0.01; +const double _simulationEpsilon = 0.2; bool _nearEqual(double a, double b) => (a > (b - _simulationEpsilon)) && (a < (b + _simulationEpsilon)); From 1ad857b53b95ffca1bd75b94bc84d159ea383e27 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 14 Jul 2015 16:00:00 -0700 Subject: [PATCH 54/62] Allow explicitly setting tolerances on simulations --- packages/newton/lib/newton.dart | 1 + .../newton/lib/src/friction_simulation.dart | 2 +- packages/newton/lib/src/scroll_simulation.dart | 12 +++++++----- packages/newton/lib/src/simulation.dart | 3 +-- packages/newton/lib/src/simulation_group.dart | 15 ++++++++++++--- packages/newton/lib/src/spring_simulation.dart | 3 ++- packages/newton/lib/src/tolerance.dart | 17 +++++++++++++++++ packages/newton/lib/src/utils.dart | 8 +++----- packages/newton/test/newton_test.dart | 12 ++++++++++++ 9 files changed, 56 insertions(+), 17 deletions(-) create mode 100644 packages/newton/lib/src/tolerance.dart diff --git a/packages/newton/lib/newton.dart b/packages/newton/lib/newton.dart index 47de84a790..c70ca3cb02 100644 --- a/packages/newton/lib/newton.dart +++ b/packages/newton/lib/newton.dart @@ -8,6 +8,7 @@ import 'dart:math' as math; part 'src/simulation.dart'; part 'src/simulation_group.dart'; +part 'src/tolerance.dart'; part 'src/utils.dart'; part 'src/friction_simulation.dart'; diff --git a/packages/newton/lib/src/friction_simulation.dart b/packages/newton/lib/src/friction_simulation.dart index 3aa9ce8d0f..590c5e408d 100644 --- a/packages/newton/lib/src/friction_simulation.dart +++ b/packages/newton/lib/src/friction_simulation.dart @@ -22,5 +22,5 @@ class FrictionSimulation extends Simulation { double dx(double time) => _v * math.pow(_drag, time); @override - bool isDone(double time) => dx(time).abs() < 1.0; + bool isDone(double time) => dx(time).abs() < this.tolerance.velocity; } diff --git a/packages/newton/lib/src/scroll_simulation.dart b/packages/newton/lib/src/scroll_simulation.dart index 123e8d1b41..b322945414 100644 --- a/packages/newton/lib/src/scroll_simulation.dart +++ b/packages/newton/lib/src/scroll_simulation.dart @@ -27,7 +27,7 @@ class ScrollSimulation extends SimulationGroup { } @override - void step(double time) => _chooseSimulation( + bool step(double time) => _chooseSimulation( _currentSimulation.x(time - _offset), _currentSimulation.dx(time - _offset), time); @@ -37,7 +37,7 @@ class ScrollSimulation extends SimulationGroup { @override double get currentIntervalOffset => _offset; - void _chooseSimulation( + bool _chooseSimulation( double position, double velocity, double intervalOffset) { /// This simulation can only step forward. @@ -47,19 +47,21 @@ class ScrollSimulation extends SimulationGroup { _offset = intervalOffset; _currentSimulation = new SpringSimulation( _springDesc, position, _trailingExtent, velocity); - return; + return true; } else if (position < _leadingExtent) { _isSpringing = true; _offset = intervalOffset; _currentSimulation = new SpringSimulation( _springDesc, position, _leadingExtent, velocity); - return; + return true; } } if (_currentSimulation == null) { _currentSimulation = new FrictionSimulation(_drag, position, velocity); - return; + return true; } + + return false; } } diff --git a/packages/newton/lib/src/simulation.dart b/packages/newton/lib/src/simulation.dart index ca06f4811a..e374c9d5ed 100644 --- a/packages/newton/lib/src/simulation.dart +++ b/packages/newton/lib/src/simulation.dart @@ -15,9 +15,8 @@ abstract class Simulatable { /// The base class for all simulations. The user is meant to instantiate an /// instance of a simulation and query the same for the position and velocity /// of the body at a given interval. -/// -/// Note: All operations on subclasses of Simulation are idempotent. abstract class Simulation implements Simulatable { + Tolerance tolerance = toleranceDefault; /// Returns if the simulation is done at a given time bool isDone(double time); diff --git a/packages/newton/lib/src/simulation_group.dart b/packages/newton/lib/src/simulation_group.dart index 9fb6f9c365..44e7bb8673 100644 --- a/packages/newton/lib/src/simulation_group.dart +++ b/packages/newton/lib/src/simulation_group.dart @@ -19,7 +19,8 @@ abstract class SimulationGroup extends Simulation { /// Called when a significant change in the interval is detected. Subclasses /// must decide if the the current simulation must be switched (or updated). - void step(double time); + /// The result is whether the simulation was switched in this step. + bool step(double time); double x(double time) { _stepIfNecessary(time); @@ -31,6 +32,12 @@ abstract class SimulationGroup extends Simulation { return currentSimulation.dx(time - currentIntervalOffset); } + @override + void set tolerance(Tolerance t) { + this.currentSimulation.tolerance = t; + super.tolerance = t; + } + @override bool isDone(double time) { _stepIfNecessary(time); @@ -39,11 +46,13 @@ abstract class SimulationGroup extends Simulation { double _lastStep = -1.0; void _stepIfNecessary(double time) { - if (_nearEqual(_lastStep, time)) { + if (_nearEqual(_lastStep, time, toleranceDefault.time)) { return; } _lastStep = time; - step(time); + if (step(time)) { + this.currentSimulation.tolerance = this.tolerance; + } } } diff --git a/packages/newton/lib/src/spring_simulation.dart b/packages/newton/lib/src/spring_simulation.dart index 0edf828284..5b267b0daa 100644 --- a/packages/newton/lib/src/spring_simulation.dart +++ b/packages/newton/lib/src/spring_simulation.dart @@ -58,5 +58,6 @@ class SpringSimulation extends Simulation { @override bool isDone(double time) => - _nearEqual(x(time), _endPosition) && _nearZero(dx(time)); + _nearEqual(x(time), _endPosition, this.tolerance.distance) && + _nearZero(dx(time), this.tolerance.velocity); } diff --git a/packages/newton/lib/src/tolerance.dart b/packages/newton/lib/src/tolerance.dart new file mode 100644 index 0000000000..b52d075e9c --- /dev/null +++ b/packages/newton/lib/src/tolerance.dart @@ -0,0 +1,17 @@ +// 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 newton; + +class Tolerance { + final double distance; + final double time; + final double velocity; + + const Tolerance({this.distance: epsilonDefault, this.time: epsilonDefault, + this.velocity: epsilonDefault}); +} + +const double epsilonDefault = 1e-3; +const Tolerance toleranceDefault = const Tolerance(); diff --git a/packages/newton/lib/src/utils.dart b/packages/newton/lib/src/utils.dart index e6f997fe42..75709a3a65 100644 --- a/packages/newton/lib/src/utils.dart +++ b/packages/newton/lib/src/utils.dart @@ -4,9 +4,7 @@ part of newton; -const double _simulationEpsilon = 0.2; +bool _nearEqual(double a, double b, double epsilon) => + (a > (b - epsilon)) && (a < (b + epsilon)); -bool _nearEqual(double a, double b) => - (a > (b - _simulationEpsilon)) && (a < (b + _simulationEpsilon)); - -bool _nearZero(double a) => _nearEqual(a, 0.0); +bool _nearZero(double a, double epsilon) => _nearEqual(a, 0.0, epsilon); diff --git a/packages/newton/test/newton_test.dart b/packages/newton/test/newton_test.dart index c7b6323d72..44c8050114 100644 --- a/packages/newton/test/newton_test.dart +++ b/packages/newton/test/newton_test.dart @@ -12,6 +12,8 @@ void main() { test('test_friction', () { var friction = new FrictionSimulation(0.3, 100.0, 400.0); + friction.tolerance = const Tolerance(velocity: 1.0); + expect(friction.isDone(0.0), false); expect(friction.x(0.0), 100); expect(friction.dx(0.0), 400.0); @@ -84,6 +86,9 @@ void main() { test('crit_spring', () { var crit = new SpringSimulation(new SpringDescription.withDampingRatio( mass: 1.0, springConstant: 100.0, ratio: 1.0), 0.0, 500.0, 0.0); + + crit.tolerance = const Tolerance(distance: 0.01, velocity: 0.01); + expect(crit.type, SpringType.criticallyDamped); expect(crit.isDone(0.0), false); @@ -106,6 +111,9 @@ void main() { test('overdamped_spring', () { var over = new SpringSimulation(new SpringDescription.withDampingRatio( mass: 1.0, springConstant: 100.0, ratio: 1.25), 0.0, 500.0, 0.0); + + over.tolerance = const Tolerance(distance: 0.01, velocity: 0.01); + expect(over.type, SpringType.overDamped); expect(over.isDone(0.0), false); @@ -145,6 +153,8 @@ void main() { var scroll = new ScrollSimulation(100.0, 800.0, 0.0, 300.0, spring, 0.3); + scroll.tolerance = const Tolerance(velocity: 0.01, distance: 0.01); + expect(scroll.isDone(0.0), false); expect(scroll.isDone(3.5), true); @@ -159,6 +169,8 @@ void main() { var scroll = new ScrollSimulation(100.0, 400.0, 0.0, double.INFINITY, spring, 0.3); + scroll.tolerance = const Tolerance(velocity: 1.0); + expect(scroll.isDone(0.0), false); expect(scroll.x(0.0), 100); expect(scroll.dx(0.0), 400.0); From 6c639ee389fe2c267dcf0150c87d728cc0e9499d Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Fri, 24 Jul 2015 15:15:19 -0700 Subject: [PATCH 55/62] optional spring description --- packages/newton/lib/src/scroll_simulation.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/newton/lib/src/scroll_simulation.dart b/packages/newton/lib/src/scroll_simulation.dart index b322945414..c71986148a 100644 --- a/packages/newton/lib/src/scroll_simulation.dart +++ b/packages/newton/lib/src/scroll_simulation.dart @@ -18,11 +18,12 @@ class ScrollSimulation extends SimulationGroup { double _offset = 0.0; ScrollSimulation(double position, double velocity, double leading, - double trailing, SpringDescription spring, double drag) + double trailing, [SpringDescription spring, double drag]) : _leadingExtent = leading, _trailingExtent = trailing, _springDesc = spring, _drag = drag { + assert((spring != null && drag != null) || (spring == null && drag == null)); _chooseSimulation(position, velocity, 0.0); } @@ -40,6 +41,10 @@ class ScrollSimulation extends SimulationGroup { bool _chooseSimulation( double position, double velocity, double intervalOffset) { + if (_springDesc == null && + (position > _trailingExtent || position < _leadingExtent)) + return false; + /// This simulation can only step forward. if (!_isSpringing) { if (position > _trailingExtent) { From 66f064eabf22d58d2827f4e318ef17e89cfe6cb1 Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Fri, 24 Jul 2015 15:51:33 -0700 Subject: [PATCH 56/62] spring can be null drag must not be --- packages/newton/lib/src/scroll_simulation.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/newton/lib/src/scroll_simulation.dart b/packages/newton/lib/src/scroll_simulation.dart index c71986148a..9dece96207 100644 --- a/packages/newton/lib/src/scroll_simulation.dart +++ b/packages/newton/lib/src/scroll_simulation.dart @@ -18,12 +18,11 @@ class ScrollSimulation extends SimulationGroup { double _offset = 0.0; ScrollSimulation(double position, double velocity, double leading, - double trailing, [SpringDescription spring, double drag]) + double trailing, SpringDescription spring, double drag) : _leadingExtent = leading, _trailingExtent = trailing, _springDesc = spring, _drag = drag { - assert((spring != null && drag != null) || (spring == null && drag == null)); _chooseSimulation(position, velocity, 0.0); } From ea1a8ee3c2a2d896174b8289d1e2a7d4bd442cca Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Mon, 27 Jul 2015 09:07:06 -0700 Subject: [PATCH 57/62] added BoundedFrictionSimulation --- .../newton/lib/src/friction_simulation.dart | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/newton/lib/src/friction_simulation.dart b/packages/newton/lib/src/friction_simulation.dart index 590c5e408d..4cca70433b 100644 --- a/packages/newton/lib/src/friction_simulation.dart +++ b/packages/newton/lib/src/friction_simulation.dart @@ -24,3 +24,25 @@ class FrictionSimulation extends Simulation { @override bool isDone(double time) => dx(time).abs() < this.tolerance.velocity; } + +class BoundedFrictionSimulation extends FrictionSimulation { + BoundedFrictionSimulation( + double drag, + double position, + double velocity, + double this._minX, + double this._maxX) : super(drag, position, velocity); + + final double _minX; + final double _maxX; + + double x(double time) { + return super.x(time).clamp(_minX, _maxX); + } + + bool isDone(double time) { + return super.isDone(time) || + (x(time) - _minX).abs() < tolerance.distance || + (x(time) - _maxX).abs() < tolerance.distance; + } +} From 32ff3e59b46ff3e8f957f15ae921dfb339029ef4 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Sat, 19 Sep 2015 08:41:31 -0700 Subject: [PATCH 58/62] Update README.md s/domokit/flutter/ --- packages/newton/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/newton/README.md b/packages/newton/README.md index 731a9abcaa..02942a74f9 100644 --- a/packages/newton/README.md +++ b/packages/newton/README.md @@ -1,5 +1,5 @@ # Newton -[![Build Status](https://travis-ci.org/domokit/newton.svg?branch=master)](https://travis-ci.org/domokit/newton) +[![Build Status](https://travis-ci.org/flutter/newton.svg?branch=master)](https://travis-ci.org/flutter/newton) [![Coverage Status](https://coveralls.io/repos/domokit/newton/badge.svg?branch=master)](https://coveralls.io/r/domokit/newton?branch=master) Simple Physics Simulations for Dart. Springs, friction, gravity, etc. From eaa72f6ef74ebd18d37c4e3984688c61c0c00b84 Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Tue, 22 Sep 2015 12:55:55 -0700 Subject: [PATCH 59/62] No new functionality, just some gratuitous changes. --- packages/newton/README.md | 4 +++ .../newton/lib/src/scroll_simulation.dart | 34 ++++++++----------- packages/newton/test/newton_test.dart | 1 - 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/packages/newton/README.md b/packages/newton/README.md index 02942a74f9..ccd16eaf98 100644 --- a/packages/newton/README.md +++ b/packages/newton/README.md @@ -3,3 +3,7 @@ [![Coverage Status](https://coveralls.io/repos/domokit/newton/badge.svg?branch=master)](https://coveralls.io/r/domokit/newton?branch=master) Simple Physics Simulations for Dart. Springs, friction, gravity, etc. + +To run the tests: +pub get +dart test/newton_test.dart diff --git a/packages/newton/lib/src/scroll_simulation.dart b/packages/newton/lib/src/scroll_simulation.dart index 9dece96207..f45b6724c5 100644 --- a/packages/newton/lib/src/scroll_simulation.dart +++ b/packages/newton/lib/src/scroll_simulation.dart @@ -8,24 +8,25 @@ part of newton; /// boundary. Friction is applied within the extends and a spring action applied /// at the boundaries. This simulation can only step forward. class ScrollSimulation extends SimulationGroup { + ScrollSimulation( + double position, + double velocity, + this._leadingExtent, + this._trailingExtent, + this._spring, + this._drag) { + _chooseSimulation(position, velocity, 0.0); + } + final double _leadingExtent; final double _trailingExtent; - final SpringDescription _springDesc; + final SpringDescription _spring; final double _drag; bool _isSpringing = false; Simulation _currentSimulation; double _offset = 0.0; - ScrollSimulation(double position, double velocity, double leading, - double trailing, SpringDescription spring, double drag) - : _leadingExtent = leading, - _trailingExtent = trailing, - _springDesc = spring, - _drag = drag { - _chooseSimulation(position, velocity, 0.0); - } - @override bool step(double time) => _chooseSimulation( _currentSimulation.x(time - _offset), @@ -37,11 +38,8 @@ class ScrollSimulation extends SimulationGroup { @override double get currentIntervalOffset => _offset; - bool _chooseSimulation( - double position, double velocity, double intervalOffset) { - - if (_springDesc == null && - (position > _trailingExtent || position < _leadingExtent)) + bool _chooseSimulation(double position, double velocity, double intervalOffset) { + if (_spring == null && (position > _trailingExtent || position < _leadingExtent)) return false; /// This simulation can only step forward. @@ -49,14 +47,12 @@ class ScrollSimulation extends SimulationGroup { if (position > _trailingExtent) { _isSpringing = true; _offset = intervalOffset; - _currentSimulation = new SpringSimulation( - _springDesc, position, _trailingExtent, velocity); + _currentSimulation = new SpringSimulation(_spring, position, _trailingExtent, velocity); return true; } else if (position < _leadingExtent) { _isSpringing = true; _offset = intervalOffset; - _currentSimulation = new SpringSimulation( - _springDesc, position, _leadingExtent, velocity); + _currentSimulation = new SpringSimulation(_spring, position, _leadingExtent, velocity); return true; } } diff --git a/packages/newton/test/newton_test.dart b/packages/newton/test/newton_test.dart index 44c8050114..a70a9c7242 100644 --- a/packages/newton/test/newton_test.dart +++ b/packages/newton/test/newton_test.dart @@ -168,7 +168,6 @@ void main() { var scroll = new ScrollSimulation(100.0, 400.0, 0.0, double.INFINITY, spring, 0.3); - scroll.tolerance = const Tolerance(velocity: 1.0); expect(scroll.isDone(0.0), false); From 09099a170350356e93ed3974960d1d8ae431c33f Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Fri, 25 Sep 2015 10:20:16 -0700 Subject: [PATCH 60/62] Added FrictionSimulation.through() constructor --- .../newton/lib/src/friction_simulation.dart | 24 ++++++++++- packages/newton/test/newton_test.dart | 42 +++++++++++++++++++ 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/packages/newton/lib/src/friction_simulation.dart b/packages/newton/lib/src/friction_simulation.dart index 4cca70433b..38671b99d3 100644 --- a/packages/newton/lib/src/friction_simulation.dart +++ b/packages/newton/lib/src/friction_simulation.dart @@ -16,8 +16,28 @@ class FrictionSimulation extends Simulation { _x = position, _v = velocity; - double x(double time) => - _x + _v * math.pow(_drag, time) / _dragLog - _v / _dragLog; + // Return the drag value for a FrictionSimulation whose x() and dx() values pass + // through the specified start and end position/velocity values. + // + // Total time to reach endVelocity is just: (log(endVelocity) / log(startVelocity)) / log(_drag) + // or (log(v1) - log(v0)) / log(D), given v = v0 * D^t per the dx() function below. + // Solving for D given x(time) is trickier. Algebra courtesy of Wolfram Alpha: + // x1 = x0 + (v0 * D^((log(v1) - log(v0)) / log(D))) / log(D) - v0 / log(D), find D + static double _dragFor(double startPosition, double endPosition, double startVelocity, double endVelocity) { + return math.pow(math.E, (startVelocity - endVelocity) / (startPosition - endPosition)); + } + + // A friction simulation that starts and ends at the specified positions + // and velocities. + factory FrictionSimulation.through(double startPosition, double endPosition, double startVelocity, double endVelocity) { + return new FrictionSimulation( + _dragFor(startPosition, endPosition, startVelocity, endVelocity), + startPosition, + startVelocity) + .. tolerance = new Tolerance(velocity: endVelocity.abs()); + } + + double x(double time) => _x + _v * math.pow(_drag, time) / _dragLog - _v / _dragLog; double dx(double time) => _v * math.pow(_drag, time); diff --git a/packages/newton/test/newton_test.dart b/packages/newton/test/newton_test.dart index a70a9c7242..cd302c0e32 100644 --- a/packages/newton/test/newton_test.dart +++ b/packages/newton/test/newton_test.dart @@ -29,6 +29,48 @@ void main() { expect(friction.x(5.0) > 431 && friction.x(5.0) < 432, true); }); + test('test_friction_through', () { + // Use a normal FrictionSimulation to generate start and end + // velocity and positions with drag = 0.025. + var startPosition = 10.0; + var startVelocity = 600.0; + var f = new FrictionSimulation(0.025, startPosition, startVelocity); + var endPosition = f.x(1.0); + var endVelocity = f.dx(1.0); + expect(endPosition, greaterThan(startPosition)); + expect(endVelocity, lessThan(startVelocity)); + + // Verify that that the "through" FrictionSimulation ends up at + // endPosition and endVelocity; implies that it computed the right + // value for _drag. + var friction = new FrictionSimulation.through( + startPosition, endPosition, startVelocity, endVelocity); + expect(friction.isDone(0.0), false); + expect(friction.x(0.0), 10.0); + expect(friction.dx(0.0), 600.0); + + double epsilon = 1e-4; + expect(friction.isDone(1.0 + epsilon), true); + expect(friction.x(1.0), closeTo(endPosition, epsilon)); + expect(friction.dx(1.0), closeTo(endVelocity, epsilon)); + + // Same scenario as above except that the velocities are + // are negative. + startPosition = 1000.0; + startVelocity = -500.0; + f = new FrictionSimulation(0.025, 1000.0, -500.0); + endPosition = f.x(1.0); + endVelocity = f.dx(1.0); + expect(endPosition, lessThan(startPosition)); + expect(endVelocity, greaterThan(startVelocity)); + + friction = new FrictionSimulation.through( + startPosition, endPosition, startVelocity, endVelocity); + expect(friction.isDone(1.0 + epsilon), true); + expect(friction.x(1.0), closeTo(endPosition, epsilon)); + expect(friction.dx(1.0), closeTo(endVelocity, epsilon)); + }); + test('test_gravity', () { var gravity = new GravitySimulation(200.0, 100.0, 600.0, 0.0); From e5a96acac0c4d3d359b89cc623a20336293f456c Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Wed, 14 Oct 2015 16:32:26 -0700 Subject: [PATCH 61/62] Added ScrollSpringSimulation SimulationGroup isDone() now uses currentIntervalOffset --- .../newton/lib/src/scroll_simulation.dart | 4 +-- packages/newton/lib/src/simulation_group.dart | 2 +- .../newton/lib/src/spring_simulation.dart | 20 +++++++++++++ packages/newton/test/newton_test.dart | 28 ++++++++++++++++--- 4 files changed, 47 insertions(+), 7 deletions(-) diff --git a/packages/newton/lib/src/scroll_simulation.dart b/packages/newton/lib/src/scroll_simulation.dart index f45b6724c5..64e0a45ba1 100644 --- a/packages/newton/lib/src/scroll_simulation.dart +++ b/packages/newton/lib/src/scroll_simulation.dart @@ -47,12 +47,12 @@ class ScrollSimulation extends SimulationGroup { if (position > _trailingExtent) { _isSpringing = true; _offset = intervalOffset; - _currentSimulation = new SpringSimulation(_spring, position, _trailingExtent, velocity); + _currentSimulation = new ScrollSpringSimulation(_spring, position, _trailingExtent, velocity); return true; } else if (position < _leadingExtent) { _isSpringing = true; _offset = intervalOffset; - _currentSimulation = new SpringSimulation(_spring, position, _leadingExtent, velocity); + _currentSimulation = new ScrollSpringSimulation(_spring, position, _leadingExtent, velocity); return true; } } diff --git a/packages/newton/lib/src/simulation_group.dart b/packages/newton/lib/src/simulation_group.dart index 44e7bb8673..a7359dac3f 100644 --- a/packages/newton/lib/src/simulation_group.dart +++ b/packages/newton/lib/src/simulation_group.dart @@ -41,7 +41,7 @@ abstract class SimulationGroup extends Simulation { @override bool isDone(double time) { _stepIfNecessary(time); - return currentSimulation.isDone(time); + return currentSimulation.isDone(time - currentIntervalOffset); } double _lastStep = -1.0; diff --git a/packages/newton/lib/src/spring_simulation.dart b/packages/newton/lib/src/spring_simulation.dart index 5b267b0daa..143f96c77f 100644 --- a/packages/newton/lib/src/spring_simulation.dart +++ b/packages/newton/lib/src/spring_simulation.dart @@ -61,3 +61,23 @@ class SpringSimulation extends Simulation { _nearEqual(x(time), _endPosition, this.tolerance.distance) && _nearZero(dx(time), this.tolerance.velocity); } + +/// A SpringSimulation where the value of x() is guaranteed to have exactly the +/// end value when the simulation isDone(). +class ScrollSpringSimulation extends SpringSimulation { + ScrollSpringSimulation(SpringDescription desc, double start, double end, double velocity) + : super(desc, start, end, velocity); + + bool _isDone(double position, double velocity) { + return _nearEqual(position, _endPosition, tolerance.distance) && _nearZero(velocity, tolerance.velocity); + } + + @override + double x(double time) { + double xAtTime = super.x(time); + return _isDone(xAtTime, dx(time)) ? _endPosition : xAtTime; + } + + @override + bool isDone(double time) => _isDone(x(time), dx(time)); +} diff --git a/packages/newton/test/newton_test.dart b/packages/newton/test/newton_test.dart index cd302c0e32..20efa211d2 100644 --- a/packages/newton/test/newton_test.dart +++ b/packages/newton/test/newton_test.dart @@ -194,14 +194,16 @@ void main() { mass: 1.0, springConstant: 50.0, ratio: 0.5); var scroll = new ScrollSimulation(100.0, 800.0, 0.0, 300.0, spring, 0.3); - - scroll.tolerance = const Tolerance(velocity: 0.01, distance: 0.01); - + scroll.tolerance = const Tolerance(velocity: 0.5, distance: 0.1); expect(scroll.isDone(0.0), false); + expect(scroll.isDone(0.5), false); // switch from friction to spring expect(scroll.isDone(3.5), true); var scroll2 = new ScrollSimulation(100.0, -800.0, 0.0, 300.0, spring, 0.3); - expect(scroll2.isDone(4.5), true); + scroll2.tolerance = const Tolerance(velocity: 0.5, distance: 0.1); + expect(scroll2.isDone(0.0), false); + expect(scroll2.isDone(0.5), false); // switch from friction to spring + expect(scroll2.isDone(3.5), true); }); test('scroll_with_inf_edge_ends', () { @@ -229,4 +231,22 @@ void main() { // We should never switch expect(scroll.currentIntervalOffset, 0.0); }); + + test('over/under scroll spring', () { + var spring = new SpringDescription.withDampingRatio(mass: 1.0, springConstant: 170.0, ratio: 1.1); + var scroll = new ScrollSimulation(500.0, -7500.0, 0.0, 1000.0, spring, 0.025); + scroll.tolerance = new Tolerance(velocity: 45.0, distance: 1.5); + + expect(scroll.isDone(0.0), false); + expect(scroll.x(0.0), closeTo(500.0, .0001)); + expect(scroll.dx(0.0), closeTo(-7500.0, .0001)); + + expect(scroll.isDone(0.025), false); + expect(scroll.x(0.025), closeTo(320.0, 1.0)); + expect(scroll.dx(0.25), closeTo(-2982, 1.0)); + + expect(scroll.isDone(2.0), true); + expect(scroll.x(2.0), 0.0); + expect(scroll.dx(2.0), closeTo(0.0, 45.0)); + }); } From 6fea7f5871f7d3aaefc2fb7caea11826e597a7ae Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Mon, 2 Nov 2015 21:54:38 -0800 Subject: [PATCH 62/62] Start building the repository structure --- .travis.yml | 8 ++++++++ packages/cassowary/pubspec.yaml | 11 +++++++++++ packages/newton/pubspec.yaml | 11 +++++++++++ travis/setup.sh | 7 +++++++ travis/test.sh | 5 +++++ 5 files changed, 42 insertions(+) create mode 100644 .travis.yml create mode 100644 packages/cassowary/pubspec.yaml create mode 100644 packages/newton/pubspec.yaml create mode 100755 travis/setup.sh create mode 100755 travis/test.sh diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..169eacda8d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: dart +sudo: false +dart: + - stable +before_script: + - ./travis/setup.sh +script: + - ./travis/test.sh diff --git a/packages/cassowary/pubspec.yaml b/packages/cassowary/pubspec.yaml new file mode 100644 index 0000000000..558387778b --- /dev/null +++ b/packages/cassowary/pubspec.yaml @@ -0,0 +1,11 @@ +name: cassowary +description: Cassowary Constraint Solving Toolkit +version: 0.1.7 +author: Flutter Authors +homepage: https://github.com/flutter/flutter/tree/master/packages/cassowary +environment: + sdk: '>=1.0.0 <2.0.0' +dev_dependencies: + test: '>=0.12.0 <0.13.0' + test_runner: '<=0.2.16' + dart_coveralls: '<=0.3.0' diff --git a/packages/newton/pubspec.yaml b/packages/newton/pubspec.yaml new file mode 100644 index 0000000000..801aac31b4 --- /dev/null +++ b/packages/newton/pubspec.yaml @@ -0,0 +1,11 @@ +name: newton +description: Simple Physics Simulations for Dart +version: 0.1.5 +author: Flutter Authors +homepage: https://github.com/flutter/flutter/tree/master/packages/newton +environment: + sdk: '>=1.0.0 <2.0.0' +dev_dependencies: + test: '>=0.12.0 <0.13.0' + test_runner: '<=0.2.16' + dart_coveralls: '<=0.3.0' diff --git a/travis/setup.sh b/travis/setup.sh new file mode 100755 index 0000000000..f135a26940 --- /dev/null +++ b/travis/setup.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -ex + +(cd packages/cassowary; pub get) +(cd packages/newton; pub get) + +pub global activate tuneup diff --git a/travis/test.sh b/travis/test.sh new file mode 100755 index 0000000000..134c465c1a --- /dev/null +++ b/travis/test.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -ex + +(cd packages/cassowary; pub global run tuneup check; pub run test -j1) +(cd packages/newton; pub global run tuneup check; pub run test -j1)