diff --git a/packages/flutter/lib/src/cassowary/constant_member.dart b/packages/flutter/lib/src/cassowary/constant_member.dart index 2d077656db..b33afc3b1a 100644 --- a/packages/flutter/lib/src/cassowary/constant_member.dart +++ b/packages/flutter/lib/src/cassowary/constant_member.dart @@ -6,6 +6,8 @@ import 'expression.dart'; import 'equation_member.dart'; import 'term.dart'; +/// A member of a [Constraint] [Expression] that represent a constant at the +/// time the [Constraint] is added to the solver. class ConstantMember extends EquationMember { /// Creates a [ConstantMember] object. /// diff --git a/packages/flutter/lib/src/cassowary/constraint.dart b/packages/flutter/lib/src/cassowary/constraint.dart index 7936e7d30e..b6fb44f718 100644 --- a/packages/flutter/lib/src/cassowary/constraint.dart +++ b/packages/flutter/lib/src/cassowary/constraint.dart @@ -5,21 +5,52 @@ import 'priority.dart'; import 'expression.dart'; +/// Relationships between [Constraint] expressions. +/// +/// A [Constraint] is created by specifying a relationship between two +/// expressions. The [Solver] tries to satisfy this relationship after the +/// [Constraint] has been added to it at a set priority. enum Relation { + /// The relationship between the left and right hand sides of the expression + /// is `==`, (lhs == rhs). equalTo, + /// The relationship between the left and right hand sides of the expression + /// is `<=`, (lhs <= rhs). lessThanOrEqualTo, + /// The relationship between the left and right hand sides of the expression + /// is `>=`, (lhs => rhs). greaterThanOrEqualTo, } +/// A relationship between two expressions (represented by [Expression]) that +/// the [Solver] tries to hold true. In case of ambiguities, the [Solver] will +/// use priorities to determine [Constraint] precedence. Once a [Constraint] is +/// added to the [Solver], this [Priority] cannot be changed. class Constraint { + /// Creates a new [Constraint] by specifying a single [Expression]. This + /// assumes that the right hand side [Expression] is the constant zero. + /// (` <0>`) Constraint(this.expression, this.relation); + /// The [Relation] between a [Constraint] [Expression] and zero. final Relation relation; + /// The [Constraint] [Expression]. The [Expression] on the right hand side of + /// constraint must be zero. If the [Expression] on the right is not zero, + /// it must be negated from the left hand [Expression] before a [Constraint] + /// can be created. final Expression expression; + /// The [Constraint] [Priority]. The [Priority] can only be modified when the + /// [Constraint] is being created. Once it is added to the solver, + /// modifications to the [Constraint] [Priority] will have no effect on the + /// how the solver evaluates the constraint. double priority = Priority.required; + /// The operator `|` is overloaded as a convenience so that constraint + /// priorities can be specifed along with the [Constraint] expression. + /// + /// For example: `ax + by + cx <= 0 | Priority.weak`. See [Priority]. Constraint operator |(double p) => this..priority = p; @override diff --git a/packages/flutter/lib/src/cassowary/equation_member.dart b/packages/flutter/lib/src/cassowary/equation_member.dart index 55cf5726be..618e29c4cd 100644 --- a/packages/flutter/lib/src/cassowary/equation_member.dart +++ b/packages/flutter/lib/src/cassowary/equation_member.dart @@ -5,25 +5,95 @@ import 'expression.dart'; import 'constraint.dart'; -/// Base class for the various parts of cassowary equations. +/// A member that can be used to construct an [Expression] that may be +/// used to create a constraint. This is to facilitate the easy creation of +/// constraints. The use of the operator overloads is completely optional and +/// is only meant as a convenience. The [Constraint] expressions can be created +/// by manually creating instance of [Constraint] variables, then terms and +/// combining those to create expression. abstract class EquationMember { + /// The representation of this member after it is hoisted to be an + /// expression. Expression asExpression(); + /// Returns if this member is a constant. Constant members can be combined + /// more easily without making the expression non-linear. This makes them + /// easier to use with multiplication and division operators. Constant + /// expression that have zero value may also eliminate other expressions from + /// the solver when used with the multiplication operator. bool get isConstant; + /// The current constant value of this member. After a [Solver] flush, this is + /// value read by entities outside the [Solver]. double get value; + /// Creates a [Constraint] by using this member as the left hand side + /// expression and the argument as the right hand side [Expression] of a + /// [Constraint] with a [Relation.greaterThanOrEqualTo] relationship between + /// the two. + /// + /// For example: `right - left >= cm(200.0)` would read, "the width of the + /// object is at least 200." Constraint operator >=(EquationMember m) => asExpression() >= m; + /// Creates a [Constraint] by using this member as the left hand side + /// expression and the argument as the right hand side [Expression] of a + /// [Constraint] with a [Relation.lessThanOrEqualTo] relationship between the + /// two. + /// + /// For example: `rightEdgeOfA <= leftEdgeOfB` would read, "the entities A and + /// B are stacked left to right." Constraint operator <=(EquationMember m) => asExpression() <= m; + /// Creates a [Constraint] by using this member as the left hand side + /// expression and the argument as the right hand side [Expression] of a + /// [Constraint] with a [Relation.equalTo] relationship between the two. + /// + /// For example: `topEdgeOfBoxA + cm(10.0) == topEdgeOfBoxB` woud read, + /// "the entities A and B have a padding on top of 10." Constraint equals(EquationMember m) => asExpression().equals(m); + /// Creates a [Expression] by adding this member with the argument. Both + /// members may need to be hoisted to expressions themselves before this can + /// occur. + /// + /// For example: `(left + right) / cm(2.0)` can be used as an [Expression] + /// equivalent of the `midPointX` property. Expression operator +(EquationMember m) => asExpression() + m; + /// Creates a [Expression] by subtracting the argument from this member. Both + /// members may need to be hoisted to expressions themselves before this can + /// occur. + /// + /// For example: `right - left` can be used as an [Expression] + /// equivalent of the `width` property. Expression operator -(EquationMember m) => asExpression() - m; + /// Creates a [Expression] by multiplying this member with the argument. Both + /// members may need to be hoisted to expressions themselves before this can + /// occur. + /// + /// Warning: This operation may throw a [ParserException] if the resulting + /// expression is no longer linear. This is because a non-linear [Expression] + /// may not be used to create a constraint. At least one of the [Expression] + /// members must evaluate to a constant. + /// + /// For example: `((left + right) >= (cm(2.0) * mid)` declares a `midpoint` + /// constraint. Notice that at least one the members of the right hand + /// `Expression` is a constant. Expression operator *(EquationMember m) => asExpression() * m; + /// Creates a [Expression] by dividing this member by the argument. Both + /// members may need to be hoisted to expressions themselves before this can + /// occur. + /// + /// Warning: This operation may throw a [ParserException] if the resulting + /// expression is no longer linear. This is because a non-linear [Expression] + /// may not be used to create a constraint. The divisor (i.e. the argument) + /// must evaluate to a constant. + /// + /// For example: `((left + right) / cm(2.0) >= mid` declares a `midpoint` + /// constraint. Notice that the divisor of the left hand [Expression] is a + /// constant. Expression operator /(EquationMember m) => asExpression() / m; } diff --git a/packages/flutter/lib/src/cassowary/expression.dart b/packages/flutter/lib/src/cassowary/expression.dart index 4b36ee1a27..cb894b1023 100644 --- a/packages/flutter/lib/src/cassowary/expression.dart +++ b/packages/flutter/lib/src/cassowary/expression.dart @@ -15,15 +15,24 @@ class _Multiplication { final double multiplicand; } +/// The representation of a linear [Expression] that can be used to create a +/// constraint. class Expression extends EquationMember { + /// Creates a new linear [Expression] using the given terms and constant. Expression(this.terms, this.constant); + /// Creates a new linear [Expression] by copying the terms and constant of + /// another expression. Expression.fromExpression(Expression expr) : this.terms = new List.from(expr.terms), this.constant = expr.constant; + /// The list of terms in this linear expression. Terms in a an [Expression] + /// must have only one [Variable] (indeterminate) and a degree of 1. final List terms; + /// The constant portion of this linear expression. This is just another + /// [Term] with no [Variable]. final double constant; @override diff --git a/packages/flutter/lib/src/cassowary/param.dart b/packages/flutter/lib/src/cassowary/param.dart index a1bd12be1f..2d85df0480 100644 --- a/packages/flutter/lib/src/cassowary/param.dart +++ b/packages/flutter/lib/src/cassowary/param.dart @@ -6,45 +6,70 @@ import 'equation_member.dart'; import 'expression.dart'; import 'term.dart'; +/// A [Variable] inside the layout [Solver]. It represents an indeterminate +/// in the [Expression] that is used to create the [Constraint]. If any entity +/// is interested in watching updates to the value of this indeterminate, +/// it can assign a watcher as the `owner`. class Variable { static int _total = 0; + /// Creates a new [Variable] with the given constant value. Variable(this.value) : _tick = _total++; final int _tick; + /// The current value of the variable. double value; + /// An optional name given to the variable. This is useful in debugging + /// [Solver] state. String name; + /// Variables represent state inside the solver. This state is usually of + /// interest to some entity outside the solver. Such entities can (optionally) + /// associate themselves with these variables. This means that when solver + /// is flushed, it is easy to obtain a reference to the entity the variable + /// is associated with. Param get owner => _owner; + Param _owner; + /// Used by the [Solver] to apply updates to this variable. Only updated + /// variables show up in [Solver] flush results. bool applyUpdate(double updated) { bool res = updated != value; value = updated; return res; } + /// The name used for this [Variable] when debugging the internal state of the + /// solver. String get debugName => name ?? 'variable$_tick'; @override String toString() => debugName; } +/// A [Param] wraps a [Variable] and makes it suitable to be used in an +/// expression. class Param extends EquationMember { + /// Creates a new [Param] with the specified constant value. Param([double value = 0.0]) : variable = new Variable(value) { variable._owner = this; } + /// Creates a new [Param] with the specified constant value that is tied + /// to some object outside the solver. Param.withContext(dynamic context, [double value = 0.0]) : variable = new Variable(value), context = context { variable._owner = this; } + /// The [Variable] associated with this [Param]. final Variable variable; + /// Some object outside the [Solver] that is associated with this Param. dynamic context; @override @@ -56,6 +81,9 @@ class Param extends EquationMember { @override double get value => variable.value; + /// The name of the [Variable] associated with this [Param]. String get name => variable.name; + + /// Set the name of the [Variable] associated with this [Param]. set name(String name) { variable.name = name; } } diff --git a/packages/flutter/lib/src/cassowary/parser_exception.dart b/packages/flutter/lib/src/cassowary/parser_exception.dart index 543b61004b..9a3fe9f45c 100644 --- a/packages/flutter/lib/src/cassowary/parser_exception.dart +++ b/packages/flutter/lib/src/cassowary/parser_exception.dart @@ -4,11 +4,26 @@ import 'equation_member.dart'; +/// Exception thrown when attempting to create a non-linear expression. +/// +/// During the creation of constraints or expressions using the overloaded +/// operators, it may be possible to end up with non-linear expressions. Such +/// expressions are not suitable for [Constraint] creation because the [Solver] +/// will reject the same. A [ParserException] is thrown when a developer tries +/// to create such an expression. +/// +/// The only cases where this is possible is when trying to multiply two +/// expressions where at least one of them is not a constant expression, or, +/// when trying to divide two expressions where the divisor is not constant. class ParserException implements Exception { + /// Creates a new [ParserException] with a given message and a list of the + /// offending member for debugging purposes. ParserException(this.message, this.members); + /// A detailed message describing the exception. final String message; + /// The members that caused the exception. List members; @override diff --git a/packages/flutter/lib/src/cassowary/priority.dart b/packages/flutter/lib/src/cassowary/priority.dart index ab500f3834..c7618c4a94 100644 --- a/packages/flutter/lib/src/cassowary/priority.dart +++ b/packages/flutter/lib/src/cassowary/priority.dart @@ -10,19 +10,24 @@ import 'dart:math'; /// between 0 and 1,000,000,000. These numbers can be created by using the /// [Priority.create] static method. class Priority { - /// The priority level that, by convention, is the highest allowed priority level (1,000,000,000). + /// The [Priority] level that, by convention, is the highest allowed + /// [Priority] level (1,000,000,000). static final double required = create(1e3, 1e3, 1e3); - /// A priority level that is below the [required] level but still near it (1,000,000). + /// A [Priority] level that is below the [required] level but still near it + /// (1,000,000). static final double strong = create(1.0, 0.0, 0.0); - /// A priority level logarithmically in the middle of [strong] and [weak] (1,000). + /// A [Priority] level logarithmically in the middle of [strong] and [weak] + /// (1,000). static final double medium = create(0.0, 1.0, 0.0); - /// A priority level that, by convention, is the lowest allowed priority level (1). + /// A [Priority] level that, by convention, is the lowest allowed [Priority] + /// level (1). static final double weak = create(0.0, 0.0, 1.0); - /// Computes a priority level by combining three numbers in the range 0..1000. + /// Computes a [Priority] level by combining three numbers in the range + /// 0..1000. /// /// The first number is a multiple of [strong]. /// @@ -30,7 +35,8 @@ class Priority { /// /// The third number is a multiple of [weak]. /// - /// By convention, at least one of these numbers should be equal to or greater than 1. + /// By convention, at least one of these numbers should be equal to or greater + /// than 1. static double create(double a, double b, double c) { double result = 0.0; result += max(0.0, min(1e3, a)) * 1e6; diff --git a/packages/flutter/lib/src/cassowary/result.dart b/packages/flutter/lib/src/cassowary/result.dart index 4352816937..2ca42feff3 100644 --- a/packages/flutter/lib/src/cassowary/result.dart +++ b/packages/flutter/lib/src/cassowary/result.dart @@ -17,24 +17,39 @@ class Result { /// Whether this [Result] represents an error (true) or not (false). final bool error; + /// The result when the operation was successful. static const Result success = const Result._('Success', isError: false); + /// The result when the [Constraint] could not be added to the [Solver] + /// because it was already present in the solver. static const Result duplicateConstraint = const Result._('Duplicate constraint'); + /// The result when the [Constraint] could not be added to the [Solver] + /// because it was unsatisfiable. Try lowering the [Priority] of the + /// [Constraint] and try again. static const Result unsatisfiableConstraint = const Result._('Unsatisfiable constraint'); + /// The result when the [Constraint] could not be removed from the solver + /// because it was not present in the [Solver] to begin with. static const Result unknownConstraint = const Result._('Unknown constraint'); + /// The result when could not add the edit [Variable] to the [Solver] because + /// it was already added to the [Solver] previously. static const Result duplicateEditVariable = const Result._('Duplicate edit variable'); + /// The result when the [Constraint] constraint was added at an invalid + /// priority or an edit [Variable] was added at an invalid or required + /// priority. static const Result badRequiredStrength = const Result._('Bad required strength'); + /// The result when the edit [Variable] could not be removed from the solver + /// because it was not present in the [Solver] to begin with. static const Result unknownEditVariable = const Result._('Unknown edit variable'); } diff --git a/packages/flutter/lib/src/cassowary/solver.dart b/packages/flutter/lib/src/cassowary/solver.dart index dcbc227f2d..4da09c3c88 100644 --- a/packages/flutter/lib/src/cassowary/solver.dart +++ b/packages/flutter/lib/src/cassowary/solver.dart @@ -158,6 +158,20 @@ class Solver { /// Attempts to add the constraints in the list to the solver. If it cannot /// add any for some reason, a cleanup is attempted so that either all /// constraints will be added or none. + /// + /// Check the [Result] returned to make sure the operation succeeded. Any + /// errors will be reported via the `message` property on the [Result]. + /// + /// Possible [Result]s: + /// + /// * [Result.success]: All constraints successfully added. + /// * [Result.duplicateConstraint]: One of the constraints in the list was + /// already in the solver or the same constraint was specified multiple + /// times in the argument list. Remove the duplicates and try again. + /// * [Result.unsatisfiableConstraint]: One or more constraints were at + /// [Priority.required] but could not added because of conflicts with other + /// constraints at the same priority. Lower the priority of these + /// constraints and try again. Result addConstraints(List constraints) { _SolverBulkUpdate applier = (Constraint c) => addConstraint(c); _SolverBulkUpdate undoer = (Constraint c) => removeConstraint(c); @@ -165,6 +179,20 @@ class Solver { return _bulkEdit(constraints, applier, undoer); } + /// Attempts to add an individual [Constraint] to the solver. + /// + /// Check the [Result] returned to make sure the operation succeeded. Any + /// errors will be reported via the `message` property on the [Result]. + /// + /// Possible [Result]s: + /// + /// * [Result.success]: The constraint was successfully added. + /// * [Result.duplicateConstraint]: The constraint was already present in the + /// solver. + /// * [Result.unsatisfiableConstraint]: The constraint was at + /// [Priority.required] but could not be added because of a conflict with + /// another constraint at that priority already in the solver. Try lowering + /// the priority of the constraint and try again. Result addConstraint(Constraint constraint) { if (_constraints.containsKey(constraint)) return Result.duplicateConstraint; @@ -198,6 +226,21 @@ class Solver { return _optimizeObjectiveRow(_objective); } + /// Attempts to remove a list of constraints from the solver. Either all + /// constraints are removed or none. If more fine-grained control over the + /// removal is required (for example, not failing on removal of constraints + /// not already present in the solver), try removing the each [Constraint] + /// individually and check the result on each attempt. + /// + /// Check the [Result] returned to make sure the operation succeeded. Any + /// errors will be reported via the `message` property on the [Result]. + /// + /// Possible [Result]s: + /// + /// * [Result.success]: The constraints were successfully removed from the + /// solver. + /// * [Result.unknownConstraint]: One or more constraints in the list were + /// not in the solver. So there was nothing to remove. Result removeConstraints(List constraints) { _SolverBulkUpdate applier = (Constraint c) => removeConstraint(c); _SolverBulkUpdate undoer = (Constraint c) => addConstraint(c); @@ -205,6 +248,17 @@ class Solver { return _bulkEdit(constraints, applier, undoer); } + /// Attempt to remove an individual [Constraint] from the solver. + /// + /// Check the [Result] returned to make sure the operation succeeded. Any + /// errors will be reported via the `message` property on the [Result]. + /// + /// Possible [Result]s: + /// + /// * [Result.success]: The [Constraint] was successfully removed from the + /// solver. + /// * [Result.unknownConstraint]: The [Constraint] was not in the solver so + /// there was nothing to remove. Result removeConstraint(Constraint constraint) { _Tag tag = _constraints[constraint]; if (tag == null) @@ -231,10 +285,32 @@ class Solver { return _optimizeObjectiveRow(_objective); } + /// Returns whether the given [Constraint] is present in the solver. bool hasConstraint(Constraint constraint) { return _constraints.containsKey(constraint); } + /// Adds a list of edit [Variable]s to the [Solver] at a given priority. + /// Either all edit [Variable] are added or none. No edit variables may be + /// added at `Priority.required`. + /// + /// Check the [Result] returned to make sure the operation succeeded. Any + /// errors will be reported via the `message` property on the [Result]. + /// + /// Possible [Result]s: + /// + /// * [Result.success]: The edit variables were successfully added to [Solver] + /// at the specified priority. + /// * [Result.duplicateEditVariable]: One of more edit variables were already + /// present in the [Solver] or the same edit variables were specified + /// multiple times in the list. Remove the duplicates and try again. + /// * [Result.badRequiredStrength]: The edit variables were added at + /// [Priority.required]. Edit variables are used to + /// suggest values to the solver. Since suggestions can't be mandatory, + /// priorities cannot be [Priority.required]. If variable values need to be + /// fixed at [Priority.required], add that preference as a constraint. This + /// allows the solver to check for satisfiability of the constraint (w.r.t + /// other constraints at [Priority.required]) and check for duplicates. Result addEditVariables(List variables, double priority) { _SolverBulkUpdate applier = (Variable v) => addEditVariable(v, priority); _SolverBulkUpdate undoer = (Variable v) => removeEditVariable(v); @@ -242,6 +318,26 @@ class Solver { return _bulkEdit(variables, applier, undoer); } + /// Attempt to add a single edit [Variable] to the [Solver] at the given + /// priority. No edit variables may be added to the [Solver] at + /// `Priority.required`. + /// + /// Check the [Result] returned to make sure the operation succeeded. Any + /// errors will be reported via the `message` property on the [Result]. + /// + /// Possible [Result]s: + /// + /// * [Result.success]: The edit variable was successfully added to [Solver] + /// at the specified priority. + /// * [Result.duplicateEditVariable]: The edit variable was already present + /// in the [Solver]. + /// * [Result.badRequiredStrength]: The edit variable was added at + /// [Priority.required]. Edit variables are used to + /// suggest values to the solver. Since suggestions can't be mandatory, + /// priorities cannot be [Priority.required]. If variable values need to be + /// fixed at [Priority.required], add that preference as a constraint. This + /// allows the solver to check for satisfiability of the constraint (w.r.t + /// other constraints at [Priority.required]) and check for duplicates. Result addEditVariable(Variable variable, double priority) { if (_edits.containsKey(variable)) return Result.duplicateEditVariable; @@ -267,6 +363,18 @@ class Solver { return Result.success; } + /// Attempt the remove the list of edit [Variable] from the solver. Either + /// all the specified edit variables are removed or none. + /// + /// Check the [Result] returned to make sure the operation succeeded. Any + /// errors will be reported via the `message` property on the [Result]. + /// + /// Possible [Result]s: + /// + /// * [Result.success]: The edit variables were successfully removed from the + /// [Solver]. + /// * [Result.unknownEditVariable]: One of more edit variables were not + /// already present in the solver. Result removeEditVariables(List variables) { _SolverBulkUpdate applier = (Variable v) => removeEditVariable(v); _SolverBulkUpdate undoer = (Variable v) => @@ -275,6 +383,17 @@ class Solver { return _bulkEdit(variables, applier, undoer); } + /// Attempt to remove the specified edit [Variable] from the solver. + /// + /// Check the [Result] returned to make sure the operation succeeded. Any + /// errors will be reported via the `message` property on the [Result]. + /// + /// Possible [Result]s: + /// + /// * [Result.success]: The edit variable was successfully removed from the + /// solver. + /// * [Result.unknownEditVariable]: The edit variable was not present in the + /// solver. There was nothing to remove. Result removeEditVariable(Variable variable) { _EditInfo info = _edits[variable]; if (info == null) @@ -286,10 +405,34 @@ class Solver { return Result.success; } + /// Returns whether the given edit [Variable] is present in the solver. bool hasEditVariable(Variable variable) { return _edits.containsKey(variable); } + /// Suggest an updated value for the edit variable. The edit variable + /// must already be added to the solver. + /// + /// Suggestions update values of variables within the [Solver] but take into + /// account all the constraints already present in the [Solver]. Depending + /// on the constraints, the value of the [Variable] may not actually be the + /// value specified. The actual value can be read after the next + /// `flushUpdates` call. Since these updates are merely "suggestions", they + /// cannot be at `Priority.required`. + /// + /// + /// Check the [Result] returned to make sure the operation succeeded. Any + /// errors will be reported via the `message` property on the [Result]. + /// + /// Possible [Result]s: + /// + /// * [Result.success]: The suggestion was successfully applied to the + /// variable within the solver. + /// * [Result.unknownEditVariable]: The edit variable was not already present + /// in the [Solver]. So the suggestion could not be applied. Add this edit + /// variable to the solver and then apply the value again. If you have + /// already added the variable to the [Solver], make sure the [Result] + /// was `Result.success`. Result suggestValueForVariable(Variable variable, double value) { if (!_edits.containsKey(variable)) return Result.unknownEditVariable; @@ -299,6 +442,16 @@ class Solver { return _dualOptimize(); } + /// Flush the results of solver. The set of all `context` objects associated + /// with variables in the [Solver] is returned. If a [Variable] does not + /// contain an associated context, its updates are ignored. + /// + /// The addition and removal of constraints and edit variables to and from the + /// [Solver] as well as the application of suggestions to the added edit + /// variables leads to the modification of values on a lot of other variables. + /// External entities that rely on the values of the variables within the + /// [Solver] can read these updates in one shot by "flushing" out these + /// updates. Set flushUpdates() { Set updates = new HashSet(); diff --git a/packages/flutter/lib/src/cassowary/term.dart b/packages/flutter/lib/src/cassowary/term.dart index d161196534..c3812308f2 100644 --- a/packages/flutter/lib/src/cassowary/term.dart +++ b/packages/flutter/lib/src/cassowary/term.dart @@ -6,11 +6,21 @@ import 'equation_member.dart'; import 'expression.dart'; import 'param.dart'; +/// Represents a single term in an expression. This term contains a single +/// indeterminate and has degree 1. class Term extends EquationMember { + /// Creates term with the given [Variable] and coefficient. Term(this.variable, this.coefficient); + /// The [Variable] (or indeterminate) portion of this term. Variables are + /// usually tied to an opaque object (via its `context` property). On a + /// [Solver] flush, these context objects of updated variables are returned by + /// the solver. An external entity can then choose to interpret these values + /// in what manner it sees fit. final Variable variable; + /// The coefficient of this term. Before addition of the [Constraint] to the + /// solver, terms with a zero coefficient are dropped. final double coefficient; @override