Merge branch 'cassowary'
This commit is contained in:
commit
c7dfbc0451
22
packages/cassowary/lib/cassowary.dart
Normal file
22
packages/cassowary/lib/cassowary.dart
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
part 'constraint.dart';
|
||||||
|
part 'expression.dart';
|
||||||
|
part 'term.dart';
|
||||||
|
part 'variable.dart';
|
||||||
|
part 'equation_member.dart';
|
||||||
|
part 'constant_member.dart';
|
||||||
|
part 'solver.dart';
|
||||||
|
part 'symbol.dart';
|
||||||
|
part 'row.dart';
|
||||||
|
part 'utils.dart';
|
||||||
|
part 'result.dart';
|
||||||
|
part 'parser_exception.dart';
|
||||||
|
part 'param.dart';
|
||||||
|
part 'priority.dart';
|
19
packages/cassowary/lib/constant_member.dart
Normal file
19
packages/cassowary/lib/constant_member.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// 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 {
|
||||||
|
final double value;
|
||||||
|
|
||||||
|
bool get isConstant => true;
|
||||||
|
|
||||||
|
ConstantMember(this.value);
|
||||||
|
|
||||||
|
Expression asExpression() => new Expression([], this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConstantMember cm(double value) {
|
||||||
|
return new ConstantMember(value);
|
||||||
|
}
|
42
packages/cassowary/lib/constraint.dart
Normal file
42
packages/cassowary/lib/constraint.dart
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
part of cassowary;
|
||||||
|
|
||||||
|
enum Relation { equalTo, lessThanOrEqualTo, greaterThanOrEqualTo, }
|
||||||
|
|
||||||
|
class Constraint {
|
||||||
|
final Relation relation;
|
||||||
|
final Expression expression;
|
||||||
|
double priority = Priority.required;
|
||||||
|
|
||||||
|
Constraint(this.expression, this.relation);
|
||||||
|
|
||||||
|
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 (priority == Priority.required) {
|
||||||
|
buffer.write(" (required)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
}
|
30
packages/cassowary/lib/equation_member.dart
Normal file
30
packages/cassowary/lib/equation_member.dart
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
part of cassowary;
|
||||||
|
|
||||||
|
abstract class _EquationMember {
|
||||||
|
Expression asExpression();
|
||||||
|
|
||||||
|
bool get isConstant;
|
||||||
|
|
||||||
|
double get value;
|
||||||
|
|
||||||
|
Constraint operator >=(_EquationMember m) => asExpression() >= m;
|
||||||
|
|
||||||
|
Constraint operator <=(_EquationMember m) => asExpression() <= m;
|
||||||
|
|
||||||
|
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";
|
||||||
|
}
|
172
packages/cassowary/lib/expression.dart
Normal file
172
packages/cassowary/lib/expression.dart
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
// 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<Term> 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);
|
||||||
|
Expression.fromExpression(Expression expr)
|
||||||
|
: this.terms = new List<Term>.from(expr.terms),
|
||||||
|
this.constant = expr.constant;
|
||||||
|
|
||||||
|
Expression asExpression() => this;
|
||||||
|
|
||||||
|
Constraint _createConstraint(
|
||||||
|
_EquationMember /* rhs */ value, Relation relation) {
|
||||||
|
if (value is ConstantMember) {
|
||||||
|
return new Constraint(
|
||||||
|
new Expression(new List.from(terms), constant - value.value),
|
||||||
|
relation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value is Param) {
|
||||||
|
var newTerms = new List<Term>.from(terms)
|
||||||
|
..add(new Term(value.variable, -1.0));
|
||||||
|
return new Constraint(new Expression(newTerms, constant), relation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value is Term) {
|
||||||
|
var newTerms = new List<Term>.from(terms)
|
||||||
|
..add(new Term(value.variable, -value.coefficient));
|
||||||
|
return new Constraint(new Expression(newTerms, constant), relation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value is Expression) {
|
||||||
|
var newTerms = value.terms.fold(new List<Term>.from(terms),
|
||||||
|
(list, t) => list..add(new Term(t.variable, -t.coefficient)));
|
||||||
|
return new Constraint(
|
||||||
|
new Expression(newTerms, constant - value.constant), relation);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(false);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Constraint operator >=(_EquationMember value) =>
|
||||||
|
_createConstraint(value, Relation.greaterThanOrEqualTo);
|
||||||
|
|
||||||
|
Constraint operator <=(_EquationMember value) =>
|
||||||
|
_createConstraint(value, Relation.lessThanOrEqualTo);
|
||||||
|
|
||||||
|
operator ==(_EquationMember value) =>
|
||||||
|
_createConstraint(value, Relation.equalTo);
|
||||||
|
|
||||||
|
Expression operator +(_EquationMember m) {
|
||||||
|
if (m is ConstantMember) {
|
||||||
|
return new Expression(new List.from(terms), constant + m.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m is Param) {
|
||||||
|
return new Expression(
|
||||||
|
new List.from(terms)..add(new Term(m.variable, 1.0)), constant);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m is Term) {
|
||||||
|
return new Expression(new List.from(terms)..add(m), constant);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m is Expression) {
|
||||||
|
return new Expression(
|
||||||
|
new List.from(terms)..addAll(m.terms), constant + m.constant);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(false);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expression operator -(_EquationMember m) {
|
||||||
|
if (m is ConstantMember) {
|
||||||
|
return new Expression(new List.from(terms), constant - m.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m is Param) {
|
||||||
|
return new Expression(
|
||||||
|
new List.from(terms)..add(new Term(m.variable, -1.0)), constant);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m is Term) {
|
||||||
|
return new Expression(new List.from(terms)
|
||||||
|
..add(new Term(m.variable, -m.coefficient)), constant);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m is Expression) {
|
||||||
|
var copiedTerms = new List<Term>.from(terms);
|
||||||
|
m.terms.forEach(
|
||||||
|
(t) => copiedTerms.add(new Term(t.variable, -t.coefficient)));
|
||||||
|
return new Expression(copiedTerms, constant - m.constant);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(false);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_EquationMember _applyMultiplicand(double m) {
|
||||||
|
var newTerms = terms.fold(new List<Term>(), (list, term) => list
|
||||||
|
..add(new Term(term.variable, term.coefficient * m)));
|
||||||
|
return new Expression(newTerms, constant * m);
|
||||||
|
}
|
||||||
|
|
||||||
|
_Pair<Expression, double> _findMulitplierAndMultiplicand(_EquationMember m) {
|
||||||
|
// At least on of the the two members must be constant for the resulting
|
||||||
|
// expression to be linear
|
||||||
|
|
||||||
|
if (!this.isConstant && !m.isConstant) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isConstant) {
|
||||||
|
return new _Pair(m.asExpression(), this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m.isConstant) {
|
||||||
|
return new _Pair(this.asExpression(), m.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(false);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_EquationMember operator *(_EquationMember m) {
|
||||||
|
_Pair<Expression, double> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
29
packages/cassowary/lib/param.dart
Normal file
29
packages/cassowary/lib/param.dart
Normal file
@ -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 Param extends _EquationMember {
|
||||||
|
final Variable variable;
|
||||||
|
dynamic context;
|
||||||
|
|
||||||
|
Param([double value = 0.0]) : variable = new Variable(value) {
|
||||||
|
variable._owner = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Param.withContext(ctx, [double value = 0.0])
|
||||||
|
: variable = new Variable(value),
|
||||||
|
context = ctx {
|
||||||
|
variable._owner = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isConstant => false;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
16
packages/cassowary/lib/parser_exception.dart
Normal file
16
packages/cassowary/lib/parser_exception.dart
Normal file
@ -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<_EquationMember> 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";
|
||||||
|
}
|
||||||
|
}
|
24
packages/cassowary/lib/priority.dart
Normal file
24
packages/cassowary/lib/priority.dart
Normal file
@ -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));
|
||||||
|
}
|
||||||
|
}
|
29
packages/cassowary/lib/result.dart
Normal file
29
packages/cassowary/lib/result.dart
Normal file
@ -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 String message;
|
||||||
|
final bool error;
|
||||||
|
|
||||||
|
const Result(this.message, { bool isError: true }) : error = isError;
|
||||||
|
|
||||||
|
static final Result success = const Result("Success", isError: false);
|
||||||
|
static final Result unimplemented = const Result("Unimplemented");
|
||||||
|
static final Result duplicateConstraint =
|
||||||
|
const Result("Duplicate Constraint");
|
||||||
|
static final Result unsatisfiableConstraint =
|
||||||
|
const Result("Unsatisfiable Constraint");
|
||||||
|
static final Result unknownConstraint =
|
||||||
|
const Result("Unknown Constraint");
|
||||||
|
static final Result duplicateEditVariable =
|
||||||
|
const Result("Duplicate Edit Variable");
|
||||||
|
static final Result badRequiredStrength =
|
||||||
|
const Result("Bad Required Strength");
|
||||||
|
static final Result unknownEditVariable =
|
||||||
|
const Result("Unknown Edit Variable");
|
||||||
|
static final Result internalSolverError =
|
||||||
|
const Result("Internal Solver Error");
|
||||||
|
}
|
78
packages/cassowary/lib/row.dart
Normal file
78
packages/cassowary/lib/row.dart
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
part of cassowary;
|
||||||
|
|
||||||
|
class _Row {
|
||||||
|
final Map<_Symbol, double> cells;
|
||||||
|
double constant = 0.0;
|
||||||
|
|
||||||
|
_Row(this.constant) : this.cells = new Map<_Symbol, double>();
|
||||||
|
_Row.fromRow(_Row row)
|
||||||
|
: this.cells = new Map<_Symbol, double>.from(row.cells),
|
||||||
|
this.constant = row.constant;
|
||||||
|
|
||||||
|
double add(double value) => constant += value;
|
||||||
|
|
||||||
|
void insertSymbol(_Symbol symbol, [double coefficient = 1.0]) {
|
||||||
|
double val = _elvis(cells[symbol], 0.0);
|
||||||
|
|
||||||
|
if (_nearZero(val + coefficient)) {
|
||||||
|
cells.remove(symbol);
|
||||||
|
} else {
|
||||||
|
cells[symbol] = val + coefficient;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void insertRow(_Row other, [double coefficient = 1.0]) {
|
||||||
|
constant += other.constant * coefficient;
|
||||||
|
other.cells.forEach((s, v) => insertSymbol(s, v * coefficient));
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeSymbol(_Symbol symbol) {
|
||||||
|
cells.remove(symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reverseSign() {
|
||||||
|
constant = -constant;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toString() {
|
||||||
|
StringBuffer buffer = new StringBuffer();
|
||||||
|
|
||||||
|
buffer.write(constant);
|
||||||
|
|
||||||
|
cells.forEach((symbol, value) =>
|
||||||
|
buffer.write(" + " + value.toString() + " * " + symbol.toString()));
|
||||||
|
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
}
|
652
packages/cassowary/lib/solver.dart
Normal file
652
packages/cassowary/lib/solver.dart
Normal file
@ -0,0 +1,652 @@
|
|||||||
|
// 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 {
|
||||||
|
final Map<Constraint, _Tag> _constraints = new Map<Constraint, _Tag>();
|
||||||
|
final Map<_Symbol, _Row> _rows = new Map<_Symbol, _Row>();
|
||||||
|
final Map<Variable, _Symbol> _vars = new Map<Variable, _Symbol>();
|
||||||
|
final Map<Variable, _EditInfo> _edits = new Map<Variable, _EditInfo>();
|
||||||
|
final List<_Symbol> _infeasibleRows = new List<_Symbol>();
|
||||||
|
final _Row _objective = new _Row(0.0);
|
||||||
|
_Row _artificial = new _Row(0.0);
|
||||||
|
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
|
||||||
|
/// constraints will be added or none.
|
||||||
|
Result addConstraints(List<Constraint> constraints) {
|
||||||
|
_SolverBulkUpdate applier = (Constraint c) => addConstraint(c);
|
||||||
|
_SolverBulkUpdate undoer = (Constraint c) => removeConstraint(c);
|
||||||
|
|
||||||
|
return _bulkEdit(constraints, applier, undoer);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
return _optimizeObjectiveRow(_objective);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result removeConstraints(List<Constraint> 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) {
|
||||||
|
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<_Symbol, _Row> rowPair = _leavingRowPairForMarkerSymbol(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasConstraint(Constraint constraint) {
|
||||||
|
return _constraints.containsKey(constraint);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result addEditVariables(List<Variable> variables, double priority) {
|
||||||
|
_SolverBulkUpdate applier = (Variable v) => addEditVariable(v, priority);
|
||||||
|
_SolverBulkUpdate undoer = (Variable v) => removeEditVariable(v);
|
||||||
|
|
||||||
|
return _bulkEdit(variables, applier, undoer);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
constraint.priority = priority;
|
||||||
|
|
||||||
|
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 removeEditVariables(List<Variable> variables) {
|
||||||
|
_SolverBulkUpdate applier = (Variable v) => removeEditVariable(v);
|
||||||
|
_SolverBulkUpdate undoer = (Variable v) =>
|
||||||
|
addEditVariable(v, _edits[v].constraint.priority);
|
||||||
|
|
||||||
|
return _bulkEdit(variables, applier, undoer);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasEditVariable(Variable variable) {
|
||||||
|
return _edits.containsKey(variable);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result suggestValueForVariable(Variable variable, double value) {
|
||||||
|
if (!_edits.containsKey(variable)) {
|
||||||
|
return Result.unknownEditVariable;
|
||||||
|
}
|
||||||
|
|
||||||
|
_suggestValueForEditInfoWithoutDualOptimization(_edits[variable], value);
|
||||||
|
|
||||||
|
return _dualOptimize();
|
||||||
|
}
|
||||||
|
|
||||||
|
Set flushUpdates() {
|
||||||
|
Set updates = new Set();
|
||||||
|
|
||||||
|
for (Variable variable in _vars.keys) {
|
||||||
|
_Symbol symbol = _vars[variable];
|
||||||
|
_Row row = _rows[symbol];
|
||||||
|
|
||||||
|
double updatedValue = row == null ? 0.0 : row.constant;
|
||||||
|
|
||||||
|
if (variable._applyUpdate(updatedValue) && variable._owner != null) {
|
||||||
|
dynamic context = variable._owner.context;
|
||||||
|
|
||||||
|
if (context != null) {
|
||||||
|
updates.add(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result _bulkEdit(Iterable 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];
|
||||||
|
|
||||||
|
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 = _symbolForVariable(term.variable);
|
||||||
|
|
||||||
|
_Row foundRow = _rows[symbol];
|
||||||
|
|
||||||
|
if (foundRow != null) {
|
||||||
|
row.insertRow(foundRow, term.coefficient);
|
||||||
|
} else {
|
||||||
|
row.insertSymbol(symbol, term.coefficient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (constraint.relation) {
|
||||||
|
case Relation.lessThanOrEqualTo:
|
||||||
|
case Relation.greaterThanOrEqualTo:
|
||||||
|
{
|
||||||
|
double coefficient =
|
||||||
|
constraint.relation == Relation.lessThanOrEqualTo ? 1.0 : -1.0;
|
||||||
|
|
||||||
|
_Symbol slack = new _Symbol(_SymbolType.slack, tick++);
|
||||||
|
tag.marker = slack;
|
||||||
|
row.insertSymbol(slack, coefficient);
|
||||||
|
|
||||||
|
if (constraint.priority < Priority.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.priority < Priority.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 = _enteringSymbolForObjectiveRow(objective);
|
||||||
|
if (entering.type == _SymbolType.invalid) {
|
||||||
|
return Result.success;
|
||||||
|
}
|
||||||
|
|
||||||
|
_Pair<_Symbol, _Row> leavingPair = _leavingRowForEnteringSymbol(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 _enteringSymbolForObjectiveRow(_Row objective) {
|
||||||
|
Map<_Symbol, double> cells = objective.cells;
|
||||||
|
|
||||||
|
for (_Symbol symbol in cells.keys) {
|
||||||
|
if (symbol.type != _SymbolType.dummy && cells[symbol] < 0.0) {
|
||||||
|
return symbol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new _Symbol(_SymbolType.invalid, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
_Pair<_Symbol, _Row> _leavingRowForEnteringSymbol(_Symbol entering) {
|
||||||
|
double ratio = double.MAX_FINITE;
|
||||||
|
_Pair<_Symbol, _Row> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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<_Symbol, _Row> _leavingRowPairForMarkerSymbol(_Symbol marker) {
|
||||||
|
double r1 = double.MAX_FINITE;
|
||||||
|
double r2 = double.MAX_FINITE;
|
||||||
|
|
||||||
|
_Pair<_Symbol, _Row> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = _dualEnteringSymbolForRow(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 _dualEnteringSymbolForRow(_Row row) {
|
||||||
|
_Symbol entering;
|
||||||
|
|
||||||
|
double ratio = double.MAX_FINITE;
|
||||||
|
|
||||||
|
Map<_Symbol, double> rowCells = row.cells;
|
||||||
|
|
||||||
|
for (_Symbol symbol in rowCells.keys) {
|
||||||
|
double value = rowCells[symbol];
|
||||||
|
|
||||||
|
if (value > 0.0 && symbol.type != _SymbolType.dummy) {
|
||||||
|
double coeff = _objective.coefficientForSymbol(symbol);
|
||||||
|
double r = coeff / value;
|
||||||
|
if (r < ratio) {
|
||||||
|
ratio = r;
|
||||||
|
entering = symbol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _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 {
|
||||||
|
_Symbol marker;
|
||||||
|
_Symbol other;
|
||||||
|
|
||||||
|
_Tag(this.marker, this.other);
|
||||||
|
_Tag.fromTag(_Tag tag)
|
||||||
|
: this.marker = tag.marker,
|
||||||
|
this.other = tag.other;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EditInfo {
|
||||||
|
_Tag tag;
|
||||||
|
Constraint constraint;
|
||||||
|
double constant;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isValidNonRequiredPriority(double priority) {
|
||||||
|
return (priority >= 0.0 && priority < Priority.required);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef Result _SolverBulkUpdate(dynamic item);
|
36
packages/cassowary/lib/symbol.dart
Normal file
36
packages/cassowary/lib/symbol.dart
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
part of cassowary;
|
||||||
|
|
||||||
|
enum _SymbolType { invalid, external, slack, error, dummy, }
|
||||||
|
|
||||||
|
class _Symbol {
|
||||||
|
final _SymbolType type;
|
||||||
|
final int tick;
|
||||||
|
|
||||||
|
_Symbol(this.type, this.tick);
|
||||||
|
|
||||||
|
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}";
|
||||||
|
}
|
||||||
|
}
|
34
packages/cassowary/lib/term.dart
Normal file
34
packages/cassowary/lib/term.dart
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
bool get isConstant => false;
|
||||||
|
|
||||||
|
double get value => coefficient * variable.value;
|
||||||
|
|
||||||
|
Term(this.variable, this.coefficient);
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
21
packages/cassowary/lib/utils.dart
Normal file
21
packages/cassowary/lib/utils.dart
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
class _Pair<X, Y> {
|
||||||
|
X first;
|
||||||
|
Y second;
|
||||||
|
_Pair(this.first, this.second);
|
||||||
|
}
|
27
packages/cassowary/lib/variable.dart
Normal file
27
packages/cassowary/lib/variable.dart
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
part of cassowary;
|
||||||
|
|
||||||
|
class Variable {
|
||||||
|
double value;
|
||||||
|
String name;
|
||||||
|
|
||||||
|
Param _owner;
|
||||||
|
|
||||||
|
final int _tick;
|
||||||
|
static int _total = 0;
|
||||||
|
|
||||||
|
Variable(this.value) : _tick = _total++;
|
||||||
|
|
||||||
|
bool _applyUpdate(double updated) {
|
||||||
|
bool res = updated != value;
|
||||||
|
value = updated;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
String get debugName => _elvis(name, "variable${_tick}");
|
||||||
|
|
||||||
|
String toString() => debugName;
|
||||||
|
}
|
627
packages/cassowary/test/cassowary_test.dart
Normal file
627
packages/cassowary/test/cassowary_test.dart
Normal file
@ -0,0 +1,627 @@
|
|||||||
|
// 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 Param(22.0);
|
||||||
|
expect(v.value, 22);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('variable1', () {
|
||||||
|
var v = new Param(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 Param(10.0);
|
||||||
|
var v2 = new Param(10.0);
|
||||||
|
var v3 = new Param(22.0);
|
||||||
|
|
||||||
|
expect(v1 is Param, true);
|
||||||
|
expect(v1 + cm(20.0) is Expression, true);
|
||||||
|
expect(v1 + v2 is Expression, true);
|
||||||
|
|
||||||
|
expect((v1 + v2).value, 20.0);
|
||||||
|
expect((v1 - v2).value, 0.0);
|
||||||
|
|
||||||
|
expect((v1 + v2 + v3) is Expression, true);
|
||||||
|
expect((v1 + v2 + v3).value, 42.0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('expression2', () {
|
||||||
|
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.value, 15.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);
|
||||||
|
expect((e - v).value, 13.0);
|
||||||
|
|
||||||
|
expect(e.value, 15.0);
|
||||||
|
|
||||||
|
// Term
|
||||||
|
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);
|
||||||
|
expect((e - t).value, 11.0);
|
||||||
|
|
||||||
|
expect(e.value, 15.0);
|
||||||
|
|
||||||
|
// Expression
|
||||||
|
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);
|
||||||
|
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 Param(2.0);
|
||||||
|
expect((t + v) is Expression, true);
|
||||||
|
expect((t + v).value, 14.0);
|
||||||
|
expect((t - v) is Expression, true);
|
||||||
|
expect((t - v).value, 10.0);
|
||||||
|
|
||||||
|
// Term
|
||||||
|
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 Param(1.0) + cm(1.0);
|
||||||
|
expect((t + exp) is Expression, true);
|
||||||
|
expect((t + exp).value, 14.0);
|
||||||
|
expect((t - exp) is Expression, true);
|
||||||
|
expect((t - exp).value, 10.0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('variable3', () {
|
||||||
|
var v = new Param(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 Param(2.0);
|
||||||
|
expect((v + v2) is Expression, true);
|
||||||
|
expect((v + v2).value, 5.0);
|
||||||
|
expect((v - v2) is Expression, true);
|
||||||
|
expect((v - v2).value, 1.0);
|
||||||
|
|
||||||
|
// Term
|
||||||
|
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 Param(1.0) + cm(1.0);
|
||||||
|
expect(exp.terms.length, 1);
|
||||||
|
|
||||||
|
expect((v + exp) is Expression, true);
|
||||||
|
expect((v + exp).value, 5.0);
|
||||||
|
expect((v - exp) is Expression, true);
|
||||||
|
expect((v - exp).value, 1.0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('constantmember', () {
|
||||||
|
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 Param(2.0);
|
||||||
|
expect((c + v2) is Expression, true);
|
||||||
|
expect((c + v2).value, 5.0);
|
||||||
|
expect((c - v2) is Expression, true);
|
||||||
|
expect((c - v2).value, 1.0);
|
||||||
|
|
||||||
|
// Term
|
||||||
|
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 Param(1.0) + cm(1.0);
|
||||||
|
|
||||||
|
expect((c + exp) is Expression, true);
|
||||||
|
expect((c + exp).value, 5.0);
|
||||||
|
expect((c - exp) is Expression, true);
|
||||||
|
expect((c - exp).value, 1.0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('constraint2', () {
|
||||||
|
var left = new Param(10.0);
|
||||||
|
var right = new Param(100.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);
|
||||||
|
|
||||||
|
// Variable
|
||||||
|
var v = new Param(20.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);
|
||||||
|
|
||||||
|
// Expression
|
||||||
|
var e = new Expression([t], 0.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);
|
||||||
|
|
||||||
|
// Variable
|
||||||
|
var v = new Param(20.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);
|
||||||
|
|
||||||
|
// Expression
|
||||||
|
var e = new Expression([t], 0.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);
|
||||||
|
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;
|
||||||
|
expect(c2 is Constraint, true);
|
||||||
|
expect(c2.expression.constant, -30.0);
|
||||||
|
expect(c2.relation, Relation.equalTo);
|
||||||
|
|
||||||
|
var c3 = right - left <= cm(30.0);
|
||||||
|
expect(c3 is Constraint, true);
|
||||||
|
expect(c3.expression.constant, -30.0);
|
||||||
|
expect(c3.relation, Relation.lessThanOrEqualTo);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('constraint_strength_update', () {
|
||||||
|
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);
|
||||||
|
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 Param(2.0);
|
||||||
|
var right = new Param(100.0);
|
||||||
|
|
||||||
|
var c1 = right - left >= cm(200.0);
|
||||||
|
|
||||||
|
expect((right >= left) is Constraint, true);
|
||||||
|
|
||||||
|
expect(s.addConstraint(c1), Result.success);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('constraint_complex', () {
|
||||||
|
var e = new Param(200.0) - new Param(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 Param(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 Param(100.0) >= new Param(2.0);
|
||||||
|
expect(c2 is Constraint, true);
|
||||||
|
expect(c2.expression.terms.length, 2);
|
||||||
|
expect(c2.expression.constant, 0.0);
|
||||||
|
|
||||||
|
// Term
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('constraint_update_in_solver', () {
|
||||||
|
var s = new Solver();
|
||||||
|
|
||||||
|
var left = new Param(2.0);
|
||||||
|
var right = new Param(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);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test_multiplication_division_override', () {
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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 Param(c.value);
|
||||||
|
var t = new Term(v.variable, 1.0);
|
||||||
|
var e = new Expression([t], 0.0);
|
||||||
|
|
||||||
|
expect((c * c).value, 100);
|
||||||
|
expect(() => v * v, throwsA(new isInstanceOf<ParserException>()));
|
||||||
|
expect(() => v / v, throwsA(new isInstanceOf<ParserException>()));
|
||||||
|
expect(() => v * t, throwsA(new isInstanceOf<ParserException>()));
|
||||||
|
expect(() => v / t, throwsA(new isInstanceOf<ParserException>()));
|
||||||
|
expect(() => v * e, throwsA(new isInstanceOf<ParserException>()));
|
||||||
|
expect(() => v / e, throwsA(new isInstanceOf<ParserException>()));
|
||||||
|
expect(() => v * c, returnsNormally);
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('single_item', () {
|
||||||
|
var left = new Param(-20.0);
|
||||||
|
Solver s = new Solver();
|
||||||
|
s.addConstraint(left >= cm(0.0));
|
||||||
|
s.flushUpdates();
|
||||||
|
expect(left.value, 0.0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('midpoints', () {
|
||||||
|
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);
|
||||||
|
|
||||||
|
s.flushUpdates();
|
||||||
|
|
||||||
|
expect(left.value, 0.0);
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
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.flushUpdates();
|
||||||
|
|
||||||
|
expect(left.value, 0.0);
|
||||||
|
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 c1 = right >= left;
|
||||||
|
var c2 = right <= left;
|
||||||
|
var c3 = (right == left) as Constraint;
|
||||||
|
|
||||||
|
Solver s = new Solver();
|
||||||
|
expect(s.addConstraint(c1), Result.success);
|
||||||
|
expect(s.addConstraint(c2), Result.success);
|
||||||
|
expect(s.addConstraint(c3), Result.success);
|
||||||
|
|
||||||
|
expect(s.toString() != null, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('solution_with_optimize', () {
|
||||||
|
Param p1 = new Param();
|
||||||
|
Param p2 = new Param();
|
||||||
|
Param p3 = new Param();
|
||||||
|
|
||||||
|
Param container = new Param();
|
||||||
|
|
||||||
|
Solver solver = new Solver();
|
||||||
|
|
||||||
|
solver.addEditVariable(container.variable, Priority.strong);
|
||||||
|
solver.suggestValueForVariable(container.variable, 100.0);
|
||||||
|
|
||||||
|
solver.addConstraint((p1 >= cm(30.0)) | Priority.strong);
|
||||||
|
solver.addConstraint(((p1 == p3) as Constraint) | Priority.medium);
|
||||||
|
solver.addConstraint((p2 == cm(2.0) * p1) as Constraint);
|
||||||
|
solver.addConstraint((container == (p1 + p2 + p3)) as Constraint);
|
||||||
|
|
||||||
|
solver.flushUpdates();
|
||||||
|
|
||||||
|
expect(container.value, 100.0);
|
||||||
|
|
||||||
|
expect(p1.value, 30.0);
|
||||||
|
expect(p2.value, 60.0);
|
||||||
|
expect(p3.value, 10.0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test_updates_collection', () {
|
||||||
|
Param left = new Param.withContext("left");
|
||||||
|
Param mid = new Param.withContext("mid");
|
||||||
|
Param right = new Param.withContext("right");
|
||||||
|
|
||||||
|
Solver s = new Solver();
|
||||||
|
|
||||||
|
expect(s.addEditVariable(mid.variable, Priority.strong), Result.success);
|
||||||
|
|
||||||
|
expect(s.addConstraint((mid * cm(2.0) == 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.flushUpdates();
|
||||||
|
|
||||||
|
expect(updates.length, 2);
|
||||||
|
|
||||||
|
expect(left.value, 0.0);
|
||||||
|
expect(mid.value, 50.0);
|
||||||
|
expect(right.value, 100.0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('test_updates_collection_is_set', () {
|
||||||
|
Param left = new Param.withContext("a");
|
||||||
|
Param mid = new Param.withContext("a");
|
||||||
|
Param right = new Param.withContext("a");
|
||||||
|
|
||||||
|
Solver s = new Solver();
|
||||||
|
|
||||||
|
expect(s.addEditVariable(mid.variable, Priority.strong), Result.success);
|
||||||
|
|
||||||
|
expect(s.addConstraint((mid * cm(2.0) == 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('param_context_non_final', () {
|
||||||
|
var p = new Param.withContext("a");
|
||||||
|
p.context = "b";
|
||||||
|
expect(p.context, "b");
|
||||||
|
});
|
||||||
|
|
||||||
|
test('check_type_of_eq_result', () {
|
||||||
|
Param left = new Param();
|
||||||
|
Param right = new Param();
|
||||||
|
|
||||||
|
expect((left == 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user