2014-02-10 07:56:11 -08:00
|
|
|
// Copyright 2011 Google Inc. All Rights Reserved.
|
|
|
|
|
// Copyright 1996 John Maloney and Mario Wolczko
|
|
|
|
|
//
|
|
|
|
|
// This file is part of GNU Smalltalk.
|
|
|
|
|
//
|
|
|
|
|
// GNU Smalltalk is free software; you can redistribute it and/or modify it
|
|
|
|
|
// under the terms of the GNU General Public License as published by the Free
|
|
|
|
|
// Software Foundation; either version 2, or (at your option) any later version.
|
|
|
|
|
//
|
|
|
|
|
// GNU Smalltalk is distributed in the hope that it will be useful, but WITHOUT
|
|
|
|
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
|
|
|
// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
|
|
|
// details.
|
|
|
|
|
//
|
|
|
|
|
// You should have received a copy of the GNU General Public License along with
|
|
|
|
|
// GNU Smalltalk; see the file COPYING. If not, write to the Free Software
|
|
|
|
|
// Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
//
|
|
|
|
|
// Translated first from Smalltalk to JavaScript, and finally to
|
|
|
|
|
// Dart by Google 2008-2010.
|
|
|
|
|
//
|
|
|
|
|
// Translated to Wren by Bob Nystrom 2014.
|
|
|
|
|
|
|
|
|
|
// A Wren implementation of the DeltaBlue constraint-solving
|
|
|
|
|
// algorithm, as described in:
|
|
|
|
|
//
|
|
|
|
|
// "The DeltaBlue Algorithm: An Incremental Constraint Hierarchy Solver"
|
|
|
|
|
// Bjorn N. Freeman-Benson and John Maloney
|
|
|
|
|
// January 1990 Communications of the ACM,
|
|
|
|
|
// also available as University of Washington TR 89-08-06.
|
|
|
|
|
//
|
|
|
|
|
// Beware: this benchmark is written in a grotesque style where
|
|
|
|
|
// the constraint model is built by side-effects from constructors.
|
|
|
|
|
// I've kept it this way to avoid deviating too much from the original
|
|
|
|
|
// implementation.
|
|
|
|
|
|
|
|
|
|
// TODO: Support forward declarations of globals.
|
|
|
|
|
var REQUIRED = null
|
|
|
|
|
var STRONG_REFERRED = null
|
|
|
|
|
var PREFERRED = null
|
|
|
|
|
var STRONG_DEFAULT = null
|
|
|
|
|
var NORMAL = null
|
|
|
|
|
var WEAK_DEFAULT = null
|
|
|
|
|
var WEAKEST = null
|
|
|
|
|
|
|
|
|
|
var ORDERED = null
|
|
|
|
|
|
|
|
|
|
// Strengths are used to measure the relative importance of constraints.
|
|
|
|
|
// New strengths may be inserted in the strength hierarchy without
|
|
|
|
|
// disrupting current constraints. Strengths cannot be created outside
|
|
|
|
|
// this class, so == can be used for value comparison.
|
|
|
|
|
class Strength {
|
2015-07-21 07:24:53 -07:00
|
|
|
construct new(value, name) {
|
2014-02-10 07:56:11 -08:00
|
|
|
_value = value
|
|
|
|
|
_name = name
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-03 07:48:19 -07:00
|
|
|
value { _value }
|
|
|
|
|
name { _name }
|
2014-02-10 07:56:11 -08:00
|
|
|
|
2014-04-03 07:48:19 -07:00
|
|
|
nextWeaker { ORDERED[_value] }
|
2014-02-10 07:56:11 -08:00
|
|
|
|
2014-04-03 07:48:19 -07:00
|
|
|
static stronger(s1, s2) { s1.value < s2.value }
|
|
|
|
|
static weaker(s1, s2) { s1.value > s2.value }
|
|
|
|
|
static weakest(s1, s2) { Strength.weaker(s1, s2) ? s1 : s2 }
|
|
|
|
|
static strongest(s1, s2) { Strength.stronger(s1, s2) ? s1 : s2 }
|
2014-02-10 07:56:11 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compile time computed constants.
|
2015-07-10 09:18:22 -07:00
|
|
|
REQUIRED = Strength.new(0, "required")
|
|
|
|
|
STRONG_REFERRED = Strength.new(1, "strongPreferred")
|
|
|
|
|
PREFERRED = Strength.new(2, "preferred")
|
|
|
|
|
STRONG_DEFAULT = Strength.new(3, "strongDefault")
|
|
|
|
|
NORMAL = Strength.new(4, "normal")
|
|
|
|
|
WEAK_DEFAULT = Strength.new(5, "weakDefault")
|
|
|
|
|
WEAKEST = Strength.new(6, "weakest")
|
2014-02-10 07:56:11 -08:00
|
|
|
|
|
|
|
|
ORDERED = [
|
|
|
|
|
WEAKEST, WEAK_DEFAULT, NORMAL, STRONG_DEFAULT, PREFERRED, STRONG_REFERRED
|
|
|
|
|
]
|
|
|
|
|
|
2015-01-14 23:02:12 -08:00
|
|
|
var ThePlanner
|
2014-02-10 07:56:11 -08:00
|
|
|
|
|
|
|
|
class Constraint {
|
2015-07-21 07:24:53 -07:00
|
|
|
construct new(strength) {
|
2014-02-10 07:56:11 -08:00
|
|
|
_strength = strength
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-03 07:48:19 -07:00
|
|
|
strength { _strength }
|
2014-02-10 07:56:11 -08:00
|
|
|
|
|
|
|
|
// Activate this constraint and attempt to satisfy it.
|
|
|
|
|
addConstraint {
|
2014-02-12 17:33:35 -08:00
|
|
|
addToGraph
|
2015-01-14 23:02:12 -08:00
|
|
|
ThePlanner.incrementalAdd(this)
|
2014-02-10 07:56:11 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Attempt to find a way to enforce this constraint. If successful,
|
|
|
|
|
// record the solution, perhaps modifying the current dataflow
|
|
|
|
|
// graph. Answer the constraint that this constraint overrides, if
|
|
|
|
|
// there is one, or nil, if there isn't.
|
|
|
|
|
// Assume: I am not already satisfied.
|
|
|
|
|
satisfy(mark) {
|
2014-02-12 17:33:35 -08:00
|
|
|
chooseMethod(mark)
|
|
|
|
|
if (!isSatisfied) {
|
2014-02-10 07:56:11 -08:00
|
|
|
if (_strength == REQUIRED) {
|
|
|
|
|
IO.print("Could not satisfy a required constraint!")
|
|
|
|
|
}
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-12 17:33:35 -08:00
|
|
|
markInputs(mark)
|
|
|
|
|
var out = output
|
2014-02-10 07:56:11 -08:00
|
|
|
var overridden = out.determinedBy
|
|
|
|
|
if (overridden != null) overridden.markUnsatisfied
|
|
|
|
|
out.determinedBy = this
|
2015-01-14 23:02:12 -08:00
|
|
|
if (!ThePlanner.addPropagate(this, mark)) IO.print("Cycle encountered")
|
2014-02-10 07:56:11 -08:00
|
|
|
out.mark = mark
|
|
|
|
|
return overridden
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
destroyConstraint {
|
2015-01-14 23:02:12 -08:00
|
|
|
if (isSatisfied) ThePlanner.incrementalRemove(this)
|
2014-02-12 17:33:35 -08:00
|
|
|
removeFromGraph
|
2014-02-10 07:56:11 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Normal constraints are not input constraints. An input constraint
|
|
|
|
|
// is one that depends on external state, such as the mouse, the
|
|
|
|
|
// keybord, a clock, or some arbitraty piece of imperative code.
|
2014-04-03 07:48:19 -07:00
|
|
|
isInput { false }
|
2014-02-10 07:56:11 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Abstract superclass for constraints having a single possible output variable.
|
|
|
|
|
class UnaryConstraint is Constraint {
|
2015-07-21 07:24:53 -07:00
|
|
|
construct new(myOutput, strength) {
|
2014-02-10 07:56:11 -08:00
|
|
|
super(strength)
|
|
|
|
|
_satisfied = false
|
|
|
|
|
_myOutput = myOutput
|
2014-02-12 17:33:35 -08:00
|
|
|
addConstraint
|
2014-02-10 07:56:11 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Adds this constraint to the constraint graph.
|
|
|
|
|
addToGraph {
|
|
|
|
|
_myOutput.addConstraint(this)
|
2015-01-07 08:06:44 +13:00
|
|
|
_satisfied = false
|
2014-02-10 07:56:11 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Decides if this constraint can be satisfied and records that decision.
|
|
|
|
|
chooseMethod(mark) {
|
|
|
|
|
_satisfied = (_myOutput.mark != mark) &&
|
2014-02-12 17:33:35 -08:00
|
|
|
Strength.stronger(strength, _myOutput.walkStrength)
|
2014-02-10 07:56:11 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Returns true if this constraint is satisfied in the current solution.
|
2014-04-03 07:48:19 -07:00
|
|
|
isSatisfied { _satisfied }
|
2014-02-10 07:56:11 -08:00
|
|
|
|
|
|
|
|
markInputs(mark) {
|
|
|
|
|
// has no inputs.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Returns the current output variable.
|
2014-04-03 07:48:19 -07:00
|
|
|
output { _myOutput }
|
2014-02-10 07:56:11 -08:00
|
|
|
|
|
|
|
|
// Calculate the walkabout strength, the stay flag, and, if it is
|
|
|
|
|
// 'stay', the value for the current output of this constraint. Assume
|
|
|
|
|
// this constraint is satisfied.
|
|
|
|
|
recalculate {
|
2014-02-12 17:33:35 -08:00
|
|
|
_myOutput.walkStrength = strength
|
|
|
|
|
_myOutput.stay = !isInput
|
|
|
|
|
if (_myOutput.stay) execute // Stay optimization.
|
2014-02-10 07:56:11 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Records that this constraint is unsatisfied.
|
|
|
|
|
markUnsatisfied {
|
|
|
|
|
_satisfied = false
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-03 07:48:19 -07:00
|
|
|
inputsKnown(mark) { true }
|
2014-02-10 07:56:11 -08:00
|
|
|
|
|
|
|
|
removeFromGraph {
|
|
|
|
|
if (_myOutput != null) _myOutput.removeConstraint(this)
|
|
|
|
|
_satisfied = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Variables that should, with some level of preference, stay the same.
|
|
|
|
|
// Planners may exploit the fact that instances, if satisfied, will not
|
|
|
|
|
// change their output during plan execution. This is called "stay
|
|
|
|
|
// optimization".
|
|
|
|
|
class StayConstraint is UnaryConstraint {
|
2015-07-21 07:24:53 -07:00
|
|
|
construct new(variable, strength) {
|
2014-02-10 07:56:11 -08:00
|
|
|
super(variable, strength)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
execute {
|
|
|
|
|
// Stay constraints do nothing.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A unary input constraint used to mark a variable that the client
|
|
|
|
|
// wishes to change.
|
|
|
|
|
class EditConstraint is UnaryConstraint {
|
2015-07-21 07:24:53 -07:00
|
|
|
construct new(variable, strength) {
|
2014-02-10 07:56:11 -08:00
|
|
|
super(variable, strength)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Edits indicate that a variable is to be changed by imperative code.
|
2014-04-03 07:48:19 -07:00
|
|
|
isInput { true }
|
2014-02-10 07:56:11 -08:00
|
|
|
|
|
|
|
|
execute {
|
|
|
|
|
// Edit constraints do nothing.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Directions.
|
|
|
|
|
var NONE = 1
|
|
|
|
|
var FORWARD = 2
|
|
|
|
|
var BACKWARD = 0
|
|
|
|
|
|
|
|
|
|
// Abstract superclass for constraints having two possible output
|
|
|
|
|
// variables.
|
|
|
|
|
class BinaryConstraint is Constraint {
|
2015-07-21 07:24:53 -07:00
|
|
|
construct new(v1, v2, strength) {
|
2014-02-10 07:56:11 -08:00
|
|
|
super(strength)
|
|
|
|
|
_v1 = v1
|
|
|
|
|
_v2 = v2
|
|
|
|
|
_direction = NONE
|
2014-02-12 17:33:35 -08:00
|
|
|
addConstraint
|
2014-02-10 07:56:11 -08:00
|
|
|
}
|
|
|
|
|
|
2014-04-03 07:48:19 -07:00
|
|
|
direction { _direction }
|
|
|
|
|
v1 { _v1 }
|
|
|
|
|
v2 { _v2 }
|
2014-02-10 07:56:11 -08:00
|
|
|
|
|
|
|
|
// Decides if this constraint can be satisfied and which way it
|
|
|
|
|
// should flow based on the relative strength of the variables related,
|
|
|
|
|
// and record that decision.
|
|
|
|
|
chooseMethod(mark) {
|
|
|
|
|
if (_v1.mark == mark) {
|
|
|
|
|
if (_v2.mark != mark &&
|
2014-02-12 17:33:35 -08:00
|
|
|
Strength.stronger(strength, _v2.walkStrength)) {
|
2014-02-10 07:56:11 -08:00
|
|
|
_direction = FORWARD
|
|
|
|
|
} else {
|
|
|
|
|
_direction = NONE
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_v2.mark == mark) {
|
|
|
|
|
if (_v1.mark != mark &&
|
2014-02-12 17:33:35 -08:00
|
|
|
Strength.stronger(strength, _v1.walkStrength)) {
|
2014-02-10 07:56:11 -08:00
|
|
|
_direction = BACKWARD
|
|
|
|
|
} else {
|
|
|
|
|
_direction = NONE
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Strength.weaker(_v1.walkStrength, _v2.walkStrength)) {
|
2014-02-12 17:33:35 -08:00
|
|
|
if (Strength.stronger(strength, _v1.walkStrength)) {
|
2014-02-10 07:56:11 -08:00
|
|
|
_direction = BACKWARD
|
|
|
|
|
} else {
|
|
|
|
|
_direction = NONE
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2014-02-12 17:33:35 -08:00
|
|
|
if (Strength.stronger(strength, _v2.walkStrength)) {
|
2014-02-10 07:56:11 -08:00
|
|
|
_direction = FORWARD
|
|
|
|
|
} else {
|
|
|
|
|
_direction = BACKWARD
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add this constraint to the constraint graph.
|
|
|
|
|
addToGraph {
|
|
|
|
|
_v1.addConstraint(this)
|
|
|
|
|
_v2.addConstraint(this)
|
|
|
|
|
_direction = NONE
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Answer true if this constraint is satisfied in the current solution.
|
2014-04-03 07:48:19 -07:00
|
|
|
isSatisfied { _direction != NONE }
|
2014-02-10 07:56:11 -08:00
|
|
|
|
|
|
|
|
// Mark the input variable with the given mark.
|
|
|
|
|
markInputs(mark) {
|
2014-02-12 17:33:35 -08:00
|
|
|
input.mark = mark
|
2014-02-10 07:56:11 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Returns the current input variable
|
|
|
|
|
input {
|
|
|
|
|
if (_direction == FORWARD) return _v1
|
|
|
|
|
return _v2
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Returns the current output variable.
|
|
|
|
|
output {
|
|
|
|
|
if (_direction == FORWARD) return _v2
|
|
|
|
|
return _v1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate the walkabout strength, the stay flag, and, if it is
|
|
|
|
|
// 'stay', the value for the current output of this
|
|
|
|
|
// constraint. Assume this constraint is satisfied.
|
|
|
|
|
recalculate {
|
2014-02-12 17:33:35 -08:00
|
|
|
var ihn = input
|
|
|
|
|
var out = output
|
|
|
|
|
out.walkStrength = Strength.weakest(strength, ihn.walkStrength)
|
2014-02-10 07:56:11 -08:00
|
|
|
out.stay = ihn.stay
|
2014-02-12 17:33:35 -08:00
|
|
|
if (out.stay) execute
|
2014-02-10 07:56:11 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Record the fact that this constraint is unsatisfied.
|
|
|
|
|
markUnsatisfied {
|
|
|
|
|
_direction = NONE
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inputsKnown(mark) {
|
2014-02-12 17:33:35 -08:00
|
|
|
var i = input
|
2014-02-10 07:56:11 -08:00
|
|
|
return i.mark == mark || i.stay || i.determinedBy == null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
removeFromGraph {
|
|
|
|
|
if (_v1 != null) _v1.removeConstraint(this)
|
|
|
|
|
if (_v2 != null) _v2.removeConstraint(this)
|
|
|
|
|
_direction = NONE
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Relates two variables by the linear scaling relationship: "v2 =
|
|
|
|
|
// (v1 * scale) + offset". Either v1 or v2 may be changed to maintain
|
|
|
|
|
// this relationship but the scale factor and offset are considered
|
|
|
|
|
// read-only.
|
|
|
|
|
class ScaleConstraint is BinaryConstraint {
|
2015-07-21 07:24:53 -07:00
|
|
|
construct new(src, scale, offset, dest, strength) {
|
2014-02-10 07:56:11 -08:00
|
|
|
_scale = scale
|
|
|
|
|
_offset = offset
|
|
|
|
|
super(src, dest, strength)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Adds this constraint to the constraint graph.
|
|
|
|
|
addToGraph {
|
|
|
|
|
super.addToGraph
|
|
|
|
|
_scale.addConstraint(this)
|
|
|
|
|
_offset.addConstraint(this)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
removeFromGraph {
|
|
|
|
|
super.removeFromGraph
|
|
|
|
|
if (_scale != null) _scale.removeConstraint(this)
|
|
|
|
|
if (_offset != null) _offset.removeConstraint(this)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
markInputs(mark) {
|
|
|
|
|
super.markInputs(mark)
|
|
|
|
|
_scale.mark = _offset.mark = mark
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Enforce this constraint. Assume that it is satisfied.
|
|
|
|
|
execute {
|
2014-02-12 17:33:35 -08:00
|
|
|
if (direction == FORWARD) {
|
2015-01-07 08:06:44 +13:00
|
|
|
v2.value = v1.value * _scale.value + _offset.value
|
2014-02-10 07:56:11 -08:00
|
|
|
} else {
|
|
|
|
|
// TODO: Is this the same semantics as ~/?
|
2015-01-07 08:06:44 +13:00
|
|
|
v1.value = ((v2.value - _offset.value) / _scale.value).floor
|
2014-02-10 07:56:11 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate the walkabout strength, the stay flag, and, if it is
|
|
|
|
|
// 'stay', the value for the current output of this constraint. Assume
|
|
|
|
|
// this constraint is satisfied.
|
|
|
|
|
recalculate {
|
2014-02-12 17:33:35 -08:00
|
|
|
var ihn = input
|
|
|
|
|
var out = output
|
|
|
|
|
out.walkStrength = Strength.weakest(strength, ihn.walkStrength)
|
2014-02-10 07:56:11 -08:00
|
|
|
out.stay = ihn.stay && _scale.stay && _offset.stay
|
2014-02-12 17:33:35 -08:00
|
|
|
if (out.stay) execute
|
2014-02-10 07:56:11 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Constrains two variables to have the same value.
|
|
|
|
|
class EqualityConstraint is BinaryConstraint {
|
2015-07-21 07:24:53 -07:00
|
|
|
construct new(v1, v2, strength) {
|
2014-02-10 07:56:11 -08:00
|
|
|
super(v1, v2, strength)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Enforce this constraint. Assume that it is satisfied.
|
|
|
|
|
execute {
|
2014-02-12 17:33:35 -08:00
|
|
|
output.value = input.value
|
2014-02-10 07:56:11 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A constrained variable. In addition to its value, it maintain the
|
|
|
|
|
// structure of the constraint graph, the current dataflow graph, and
|
|
|
|
|
// various parameters of interest to the DeltaBlue incremental
|
|
|
|
|
// constraint solver.
|
|
|
|
|
class Variable {
|
2015-07-21 07:24:53 -07:00
|
|
|
construct new(name, value) {
|
2014-02-10 07:56:11 -08:00
|
|
|
_constraints = []
|
|
|
|
|
_determinedBy = null
|
|
|
|
|
_mark = 0
|
|
|
|
|
_walkStrength = WEAKEST
|
|
|
|
|
_stay = true
|
|
|
|
|
_name = name
|
|
|
|
|
_value = value
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-03 07:48:19 -07:00
|
|
|
constraints { _constraints }
|
|
|
|
|
determinedBy { _determinedBy }
|
2014-04-08 21:40:31 -07:00
|
|
|
determinedBy=(value) { _determinedBy = value }
|
2014-04-03 07:48:19 -07:00
|
|
|
mark { _mark }
|
2014-04-08 21:40:31 -07:00
|
|
|
mark=(value) { _mark = value }
|
2014-04-03 07:48:19 -07:00
|
|
|
walkStrength { _walkStrength }
|
2014-04-08 21:40:31 -07:00
|
|
|
walkStrength=(value) { _walkStrength = value }
|
2014-04-03 07:48:19 -07:00
|
|
|
stay { _stay }
|
2014-04-08 21:40:31 -07:00
|
|
|
stay=(value) { _stay = value }
|
2014-04-03 07:48:19 -07:00
|
|
|
value { _value }
|
2014-04-08 21:40:31 -07:00
|
|
|
value=(newValue) { _value = newValue }
|
2014-02-10 07:56:11 -08:00
|
|
|
|
|
|
|
|
// Add the given constraint to the set of all constraints that refer
|
|
|
|
|
// this variable.
|
|
|
|
|
addConstraint(constraint) {
|
|
|
|
|
_constraints.add(constraint)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Removes all traces of c from this variable.
|
|
|
|
|
removeConstraint(constraint) {
|
2014-04-02 17:42:30 -07:00
|
|
|
_constraints = _constraints.where { |c| c != constraint }
|
2014-02-10 07:56:11 -08:00
|
|
|
if (_determinedBy == constraint) _determinedBy = null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A Plan is an ordered list of constraints to be executed in sequence
|
|
|
|
|
// to resatisfy all currently satisfiable constraints in the face of
|
|
|
|
|
// one or more changing inputs.
|
|
|
|
|
class Plan {
|
2015-07-21 07:24:53 -07:00
|
|
|
construct new() {
|
2014-02-10 07:56:11 -08:00
|
|
|
_list = []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addConstraint(constraint) {
|
|
|
|
|
_list.add(constraint)
|
|
|
|
|
}
|
|
|
|
|
|
2014-04-03 07:48:19 -07:00
|
|
|
size { _list.count }
|
2014-02-10 07:56:11 -08:00
|
|
|
|
|
|
|
|
execute {
|
|
|
|
|
for (constraint in _list) {
|
|
|
|
|
constraint.execute
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class Planner {
|
2015-07-21 07:24:53 -07:00
|
|
|
construct new() {
|
2014-02-10 07:56:11 -08:00
|
|
|
_currentMark = 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Attempt to satisfy the given constraint and, if successful,
|
|
|
|
|
// incrementally update the dataflow graph. Details: If satifying
|
|
|
|
|
// the constraint is successful, it may override a weaker constraint
|
|
|
|
|
// on its output. The algorithm attempts to resatisfy that
|
|
|
|
|
// constraint using some other method. This process is repeated
|
|
|
|
|
// until either a) it reaches a variable that was not previously
|
|
|
|
|
// determined by any constraint or b) it reaches a constraint that
|
|
|
|
|
// is too weak to be satisfied using any of its methods. The
|
|
|
|
|
// variables of constraints that have been processed are marked with
|
|
|
|
|
// a unique mark value so that we know where we've been. This allows
|
|
|
|
|
// the algorithm to avoid getting into an infinite loop even if the
|
|
|
|
|
// constraint graph has an inadvertent cycle.
|
|
|
|
|
incrementalAdd(constraint) {
|
2014-02-12 17:33:35 -08:00
|
|
|
var mark = newMark
|
2014-02-10 07:56:11 -08:00
|
|
|
var overridden = constraint.satisfy(mark)
|
|
|
|
|
while (overridden != null) {
|
|
|
|
|
overridden = overridden.satisfy(mark)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Entry point for retracting a constraint. Remove the given
|
|
|
|
|
// constraint and incrementally update the dataflow graph.
|
|
|
|
|
// Details: Retracting the given constraint may allow some currently
|
|
|
|
|
// unsatisfiable downstream constraint to be satisfied. We therefore collect
|
|
|
|
|
// a list of unsatisfied downstream constraints and attempt to
|
|
|
|
|
// satisfy each one in turn. This list is traversed by constraint
|
|
|
|
|
// strength, strongest first, as a heuristic for avoiding
|
|
|
|
|
// unnecessarily adding and then overriding weak constraints.
|
|
|
|
|
// Assume: [c] is satisfied.
|
|
|
|
|
incrementalRemove(constraint) {
|
|
|
|
|
var out = constraint.output
|
|
|
|
|
constraint.markUnsatisfied
|
|
|
|
|
constraint.removeFromGraph
|
2014-02-12 17:33:35 -08:00
|
|
|
var unsatisfied = removePropagateFrom(out)
|
2014-02-10 07:56:11 -08:00
|
|
|
var strength = REQUIRED
|
|
|
|
|
while (true) {
|
2015-03-04 07:16:56 -08:00
|
|
|
for (u in unsatisfied) {
|
2014-02-12 17:33:35 -08:00
|
|
|
if (u.strength == strength) incrementalAdd(u)
|
2014-02-10 07:56:11 -08:00
|
|
|
}
|
|
|
|
|
strength = strength.nextWeaker
|
|
|
|
|
if (strength == WEAKEST) break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Select a previously unused mark value.
|
|
|
|
|
newMark {
|
|
|
|
|
_currentMark = _currentMark + 1
|
|
|
|
|
return _currentMark
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Extract a plan for resatisfaction starting from the given source
|
|
|
|
|
// constraints, usually a set of input constraints. This method
|
|
|
|
|
// assumes that stay optimization is desired; the plan will contain
|
|
|
|
|
// only constraints whose output variables are not stay. Constraints
|
|
|
|
|
// that do no computation, such as stay and edit constraints, are
|
|
|
|
|
// not included in the plan.
|
|
|
|
|
// Details: The outputs of a constraint are marked when it is added
|
|
|
|
|
// to the plan under construction. A constraint may be appended to
|
|
|
|
|
// the plan when all its input variables are known. A variable is
|
|
|
|
|
// known if either a) the variable is marked (indicating that has
|
|
|
|
|
// been computed by a constraint appearing earlier in the plan), b)
|
|
|
|
|
// the variable is 'stay' (i.e. it is a constant at plan execution
|
|
|
|
|
// time), or c) the variable is not determined by any
|
|
|
|
|
// constraint. The last provision is for past states of history
|
|
|
|
|
// variables, which are not stay but which are also not computed by
|
|
|
|
|
// any constraint.
|
|
|
|
|
// Assume: [sources] are all satisfied.
|
|
|
|
|
makePlan(sources) {
|
2014-02-12 17:33:35 -08:00
|
|
|
var mark = newMark
|
2015-07-10 09:18:22 -07:00
|
|
|
var plan = Plan.new()
|
2014-02-10 07:56:11 -08:00
|
|
|
var todo = sources
|
|
|
|
|
while (todo.count > 0) {
|
|
|
|
|
var constraint = todo.removeAt(-1)
|
|
|
|
|
if (constraint.output.mark != mark && constraint.inputsKnown(mark)) {
|
|
|
|
|
plan.addConstraint(constraint)
|
|
|
|
|
constraint.output.mark = mark
|
2014-02-12 17:33:35 -08:00
|
|
|
addConstraintsConsumingTo(constraint.output, todo)
|
2014-02-10 07:56:11 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return plan
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Extract a plan for resatisfying starting from the output of the
|
|
|
|
|
// given [constraints], usually a set of input constraints.
|
|
|
|
|
extractPlanFromConstraints(constraints) {
|
|
|
|
|
var sources = []
|
2015-03-04 07:16:56 -08:00
|
|
|
for (constraint in constraints) {
|
2014-02-10 07:56:11 -08:00
|
|
|
// if not in plan already and eligible for inclusion.
|
|
|
|
|
if (constraint.isInput && constraint.isSatisfied) sources.add(constraint)
|
|
|
|
|
}
|
2014-02-12 17:33:35 -08:00
|
|
|
return makePlan(sources)
|
2014-02-10 07:56:11 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Recompute the walkabout strengths and stay flags of all variables
|
|
|
|
|
// downstream of the given constraint and recompute the actual
|
|
|
|
|
// values of all variables whose stay flag is true. If a cycle is
|
|
|
|
|
// detected, remove the given constraint and answer
|
|
|
|
|
// false. Otherwise, answer true.
|
|
|
|
|
// Details: Cycles are detected when a marked variable is
|
|
|
|
|
// encountered downstream of the given constraint. The sender is
|
|
|
|
|
// assumed to have marked the inputs of the given constraint with
|
|
|
|
|
// the given mark. Thus, encountering a marked node downstream of
|
|
|
|
|
// the output constraint means that there is a path from the
|
|
|
|
|
// constraint's output to one of its inputs.
|
|
|
|
|
addPropagate(constraint, mark) {
|
|
|
|
|
var todo = [constraint]
|
|
|
|
|
while (todo.count > 0) {
|
|
|
|
|
var d = todo.removeAt(-1)
|
|
|
|
|
if (d.output.mark == mark) {
|
2014-02-12 17:33:35 -08:00
|
|
|
incrementalRemove(constraint)
|
2014-02-10 07:56:11 -08:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
d.recalculate
|
2014-02-12 17:33:35 -08:00
|
|
|
addConstraintsConsumingTo(d.output, todo)
|
2014-02-10 07:56:11 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update the walkabout strengths and stay flags of all variables
|
|
|
|
|
// downstream of the given constraint. Answer a collection of
|
|
|
|
|
// unsatisfied constraints sorted in order of decreasing strength.
|
|
|
|
|
removePropagateFrom(out) {
|
|
|
|
|
out.determinedBy = null
|
|
|
|
|
out.walkStrength = WEAKEST
|
|
|
|
|
out.stay = true
|
|
|
|
|
var unsatisfied = []
|
|
|
|
|
var todo = [out]
|
|
|
|
|
while (todo.count > 0) {
|
|
|
|
|
var v = todo.removeAt(-1)
|
2015-03-04 07:16:56 -08:00
|
|
|
for (constraint in v.constraints) {
|
2014-02-10 07:56:11 -08:00
|
|
|
if (!constraint.isSatisfied) unsatisfied.add(constraint)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var determining = v.determinedBy
|
2015-03-04 07:16:56 -08:00
|
|
|
for (next in v.constraints) {
|
2014-02-10 07:56:11 -08:00
|
|
|
if (next != determining && next.isSatisfied) {
|
|
|
|
|
next.recalculate
|
|
|
|
|
todo.add(next.output)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return unsatisfied
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addConstraintsConsumingTo(v, coll) {
|
|
|
|
|
var determining = v.determinedBy
|
2015-03-04 07:16:56 -08:00
|
|
|
for (constraint in v.constraints) {
|
2014-02-10 07:56:11 -08:00
|
|
|
if (constraint != determining && constraint.isSatisfied) {
|
|
|
|
|
coll.add(constraint)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var total = 0
|
|
|
|
|
|
|
|
|
|
// This is the standard DeltaBlue benchmark. A long chain of equality
|
|
|
|
|
// constraints is constructed with a stay constraint on one end. An
|
|
|
|
|
// edit constraint is then added to the opposite end and the time is
|
|
|
|
|
// measured for adding and removing this constraint, and extracting
|
|
|
|
|
// and executing a constraint satisfaction plan. There are two cases.
|
|
|
|
|
// In case 1, the added constraint is stronger than the stay
|
|
|
|
|
// constraint and values must propagate down the entire length of the
|
|
|
|
|
// chain. In case 2, the added constraint is weaker than the stay
|
|
|
|
|
// constraint so it cannot be accomodated. The cost in this case is,
|
|
|
|
|
// of course, very low. Typical situations lie somewhere between these
|
|
|
|
|
// two extremes.
|
2015-07-10 09:18:22 -07:00
|
|
|
var chainTest = Fn.new {|n|
|
|
|
|
|
ThePlanner = Planner.new()
|
2014-02-10 07:56:11 -08:00
|
|
|
var prev = null
|
|
|
|
|
var first = null
|
|
|
|
|
var last = null
|
|
|
|
|
|
|
|
|
|
// Build chain of n equality constraints.
|
|
|
|
|
for (i in 0..n) {
|
2015-07-10 09:18:22 -07:00
|
|
|
var v = Variable.new("v", 0)
|
|
|
|
|
if (prev != null) EqualityConstraint.new(prev, v, REQUIRED)
|
2014-02-10 07:56:11 -08:00
|
|
|
if (i == 0) first = v
|
|
|
|
|
if (i == n) last = v
|
|
|
|
|
prev = v
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-10 09:18:22 -07:00
|
|
|
StayConstraint.new(last, STRONG_DEFAULT)
|
|
|
|
|
var edit = EditConstraint.new(first, PREFERRED)
|
2015-01-14 23:02:12 -08:00
|
|
|
var plan = ThePlanner.extractPlanFromConstraints([edit])
|
2014-02-10 07:56:11 -08:00
|
|
|
for (i in 0...100) {
|
|
|
|
|
first.value = i
|
|
|
|
|
plan.execute
|
|
|
|
|
total = total + last.value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-10 09:18:22 -07:00
|
|
|
var change = Fn.new {|v, newValue|
|
|
|
|
|
var edit = EditConstraint.new(v, PREFERRED)
|
2015-01-14 23:02:12 -08:00
|
|
|
var plan = ThePlanner.extractPlanFromConstraints([edit])
|
2014-02-10 07:56:11 -08:00
|
|
|
for (i in 0...10) {
|
|
|
|
|
v.value = newValue
|
|
|
|
|
plan.execute
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
edit.destroyConstraint
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// This test constructs a two sets of variables related to each
|
|
|
|
|
// other by a simple linear transformation (scale and offset). The
|
|
|
|
|
// time is measured to change a variable on either side of the
|
|
|
|
|
// mapping and to change the scale and offset factors.
|
2015-07-10 09:18:22 -07:00
|
|
|
var projectionTest = Fn.new {|n|
|
|
|
|
|
ThePlanner = Planner.new()
|
|
|
|
|
var scale = Variable.new("scale", 10)
|
|
|
|
|
var offset = Variable.new("offset", 1000)
|
2014-02-10 07:56:11 -08:00
|
|
|
var src = null
|
|
|
|
|
var dst = null
|
|
|
|
|
|
|
|
|
|
var dests = []
|
|
|
|
|
for (i in 0...n) {
|
2015-07-10 09:18:22 -07:00
|
|
|
src = Variable.new("src", i)
|
|
|
|
|
dst = Variable.new("dst", i)
|
2014-02-10 07:56:11 -08:00
|
|
|
dests.add(dst)
|
2015-07-10 09:18:22 -07:00
|
|
|
StayConstraint.new(src, NORMAL)
|
|
|
|
|
ScaleConstraint.new(src, scale, offset, dst, REQUIRED)
|
2014-02-10 07:56:11 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
change.call(src, 17)
|
|
|
|
|
total = total + dst.value
|
|
|
|
|
if (dst.value != 1170) IO.print("Projection 1 failed")
|
|
|
|
|
|
|
|
|
|
change.call(dst, 1050)
|
|
|
|
|
|
|
|
|
|
total = total + src.value
|
|
|
|
|
if (src.value != 5) IO.print("Projection 2 failed")
|
|
|
|
|
|
|
|
|
|
change.call(scale, 5)
|
|
|
|
|
for (i in 0...n - 1) {
|
|
|
|
|
total = total + dests[i].value
|
|
|
|
|
if (dests[i].value != i * 5 + 1000) IO.print("Projection 3 failed")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
change.call(offset, 2000)
|
|
|
|
|
for (i in 0...n - 1) {
|
|
|
|
|
total = total + dests[i].value
|
|
|
|
|
if (dests[i].value != i * 5 + 2000) IO.print("Projection 4 failed")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var start = IO.clock
|
|
|
|
|
for (i in 0...20) {
|
|
|
|
|
chainTest.call(100)
|
|
|
|
|
projectionTest.call(100)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IO.print(total)
|
2014-04-09 07:48:35 -07:00
|
|
|
IO.print("elapsed: ", IO.clock - start)
|