From f6a323620ec75cdea4d20a61fef8b0ee3707a278 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Mon, 22 Jun 2015 10:49:09 -0700 Subject: [PATCH 01/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] 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/40] `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/40] 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/40] 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/40] 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/40] 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 e0f38529ed6e26ebb2db09aec1f6480937b97a1c Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 7 Jul 2015 13:19:36 -0700 Subject: [PATCH 36/40] `==` 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 37/40] 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 38/40] 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 39/40] 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 40/40] 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);