forked from Mirror/wren
Get delta_blue benchmark working in Wren.
This commit is contained in:
914
benchmark/delta_blue.lua.inprogress
Normal file
914
benchmark/delta_blue.lua.inprogress
Normal file
@ -0,0 +1,914 @@
|
||||
-- Copyright 2008 the V8 project authors. All rights reserved.
|
||||
-- Copyright 1996 John Maloney and Mario Wolczko.
|
||||
|
||||
-- This program 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 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- This program 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 this program; if not, write to the Free Software
|
||||
-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
|
||||
-- This implementation of the DeltaBlue benchmark is derived
|
||||
-- from the Smalltalk implementation by John Maloney and Mario
|
||||
-- Wolczko. Some parts have been translated directly, whereas
|
||||
-- others have been modified more aggresively to make it feel
|
||||
-- more like a JavaScript program.
|
||||
|
||||
|
||||
--
|
||||
-- A JavaScript 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.
|
||||
--
|
||||
|
||||
-- From: https://github.com/mraleph/deltablue.lua
|
||||
|
||||
local planner
|
||||
|
||||
--- O b j e c t M o d e l ---
|
||||
|
||||
local function alert (...) print(...) end
|
||||
|
||||
local OrderedCollection = class()
|
||||
|
||||
function OrderedCollection:constructor()
|
||||
self.elms = {}
|
||||
end
|
||||
|
||||
function OrderedCollection:add(elm)
|
||||
self.elms[#self.elms + 1] = elm
|
||||
end
|
||||
|
||||
function OrderedCollection:at (index)
|
||||
return self.elms[index]
|
||||
end
|
||||
|
||||
function OrderedCollection:size ()
|
||||
return #self.elms
|
||||
end
|
||||
|
||||
function OrderedCollection:removeFirst ()
|
||||
local e = self.elms[#self.elms]
|
||||
self.elms[#self.elms] = nil
|
||||
return e
|
||||
end
|
||||
|
||||
function OrderedCollection:remove (elm)
|
||||
local index = 0
|
||||
local skipped = 0
|
||||
|
||||
for i = 1, #self.elms do
|
||||
local value = self.elms[i]
|
||||
if value ~= elm then
|
||||
self.elms[index] = value
|
||||
index = index + 1
|
||||
else
|
||||
skipped = skipped + 1
|
||||
end
|
||||
end
|
||||
|
||||
local l = #self.elms
|
||||
for i = 1, skipped do self.elms[l - i + 1] = nil end
|
||||
end
|
||||
|
||||
--
|
||||
-- S t r e n g t h
|
||||
--
|
||||
|
||||
--
|
||||
-- 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 pointer comparison can be used for value comparison.
|
||||
--
|
||||
|
||||
local Strength = class()
|
||||
|
||||
function Strength:constructor(strengthValue, name)
|
||||
self.strengthValue = strengthValue
|
||||
self.name = name
|
||||
end
|
||||
|
||||
function Strength.stronger (s1, s2)
|
||||
return s1.strengthValue < s2.strengthValue
|
||||
end
|
||||
|
||||
function Strength.weaker (s1, s2)
|
||||
return s1.strengthValue > s2.strengthValue
|
||||
end
|
||||
|
||||
function Strength.weakestOf (s1, s2)
|
||||
return Strength.weaker(s1, s2) and s1 or s2
|
||||
end
|
||||
|
||||
function Strength.strongest (s1, s2)
|
||||
return Strength.stronger(s1, s2) and s1 or s2
|
||||
end
|
||||
|
||||
function Strength:nextWeaker ()
|
||||
local v = self.strengthValue
|
||||
if v == 0 then return Strength.WEAKEST
|
||||
elseif v == 1 then return Strength.WEAK_DEFAULT
|
||||
elseif v == 2 then return Strength.NORMAL
|
||||
elseif v == 3 then return Strength.STRONG_DEFAULT
|
||||
elseif v == 4 then return Strength.PREFERRED
|
||||
elseif v == 5 then return Strength.REQUIRED
|
||||
end
|
||||
end
|
||||
|
||||
-- Strength constants.
|
||||
Strength.REQUIRED = Strength.new(0, "required");
|
||||
Strength.STONG_PREFERRED = Strength.new(1, "strongPreferred");
|
||||
Strength.PREFERRED = Strength.new(2, "preferred");
|
||||
Strength.STRONG_DEFAULT = Strength.new(3, "strongDefault");
|
||||
Strength.NORMAL = Strength.new(4, "normal");
|
||||
Strength.WEAK_DEFAULT = Strength.new(5, "weakDefault");
|
||||
Strength.WEAKEST = Strength.new(6, "weakest");
|
||||
|
||||
--
|
||||
-- C o n s t r a i n t
|
||||
--
|
||||
|
||||
--
|
||||
-- An abstract class representing a system-maintainable relationship
|
||||
-- (or "constraint") between a set of variables. A constraint supplies
|
||||
-- a strength instance variable; concrete subclasses provide a means
|
||||
-- of storing the constrained variables and other information required
|
||||
-- to represent a constraint.
|
||||
--
|
||||
|
||||
local Constraint = class ()
|
||||
|
||||
function Constraint:constructor(strength)
|
||||
self.strength = strength
|
||||
end
|
||||
|
||||
--
|
||||
-- Activate this constraint and attempt to satisfy it.
|
||||
--
|
||||
function Constraint:addConstraint ()
|
||||
self:addToGraph()
|
||||
planner:incrementalAdd(self)
|
||||
end
|
||||
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
function Constraint:satisfy (mark)
|
||||
self:chooseMethod(mark)
|
||||
if not self:isSatisfied() then
|
||||
if self.strength == Strength.REQUIRED then
|
||||
alert("Could not satisfy a required constraint!")
|
||||
end
|
||||
return nil
|
||||
end
|
||||
self:markInputs(mark)
|
||||
local out = self:output()
|
||||
local overridden = out.determinedBy
|
||||
if overridden ~= nil then overridden:markUnsatisfied() end
|
||||
out.determinedBy = self
|
||||
if not planner:addPropagate(self, mark) then alert("Cycle encountered") end
|
||||
out.mark = mark
|
||||
return overridden
|
||||
end
|
||||
|
||||
function Constraint:destroyConstraint ()
|
||||
if self:isSatisfied()
|
||||
then planner:incrementalRemove(self)
|
||||
else self:removeFromGraph()
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
function Constraint:isInput ()
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- U n a r y C o n s t r a i n t
|
||||
--
|
||||
|
||||
--
|
||||
-- Abstract superclass for constraints having a single possible output
|
||||
-- variable.
|
||||
--
|
||||
|
||||
local UnaryConstraint = class(Constraint)
|
||||
|
||||
function UnaryConstraint:constructor (v, strength)
|
||||
UnaryConstraint.super.constructor(self, strength)
|
||||
self.myOutput = v
|
||||
self.satisfied = false
|
||||
self:addConstraint()
|
||||
end
|
||||
|
||||
--
|
||||
-- Adds this constraint to the constraint graph
|
||||
--
|
||||
function UnaryConstraint:addToGraph ()
|
||||
self.myOutput:addConstraint(self)
|
||||
self.satisfied = false
|
||||
end
|
||||
|
||||
--
|
||||
-- Decides if this constraint can be satisfied and records that
|
||||
-- decision.
|
||||
--
|
||||
function UnaryConstraint:chooseMethod (mark)
|
||||
self.satisfied = (self.myOutput.mark ~= mark)
|
||||
and Strength.stronger(self.strength, self.myOutput.walkStrength);
|
||||
end
|
||||
|
||||
--
|
||||
-- Returns true if this constraint is satisfied in the current solution.
|
||||
--
|
||||
function UnaryConstraint:isSatisfied ()
|
||||
return self.satisfied;
|
||||
end
|
||||
|
||||
function UnaryConstraint:markInputs (mark)
|
||||
-- has no inputs
|
||||
end
|
||||
|
||||
--
|
||||
-- Returns the current output variable.
|
||||
--
|
||||
function UnaryConstraint:output ()
|
||||
return self.myOutput
|
||||
end
|
||||
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
function UnaryConstraint:recalculate ()
|
||||
self.myOutput.walkStrength = self.strength
|
||||
self.myOutput.stay = not self:isInput()
|
||||
if self.myOutput.stay then
|
||||
self:execute() -- Stay optimization
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- Records that this constraint is unsatisfied
|
||||
--
|
||||
function UnaryConstraint:markUnsatisfied ()
|
||||
self.satisfied = false
|
||||
end
|
||||
|
||||
function UnaryConstraint:inputsKnown ()
|
||||
return true
|
||||
end
|
||||
|
||||
function UnaryConstraint:removeFromGraph ()
|
||||
if self.myOutput ~= nil then
|
||||
self.myOutput:removeConstraint(self)
|
||||
end
|
||||
self.satisfied = false
|
||||
end
|
||||
|
||||
--
|
||||
-- S t a y C o n s t r a i n t
|
||||
--
|
||||
|
||||
--
|
||||
-- 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".
|
||||
--
|
||||
|
||||
local StayConstraint = class(UnaryConstraint)
|
||||
|
||||
function StayConstraint:constructor(v, str)
|
||||
StayConstraint.super.constructor(self, v, str)
|
||||
end
|
||||
|
||||
function StayConstraint:execute ()
|
||||
-- Stay constraints do nothing
|
||||
end
|
||||
|
||||
--
|
||||
-- E d i t C o n s t r a i n t
|
||||
--
|
||||
|
||||
--
|
||||
-- A unary input constraint used to mark a variable that the client
|
||||
-- wishes to change.
|
||||
--
|
||||
|
||||
local EditConstraint = class (UnaryConstraint)
|
||||
|
||||
function EditConstraint:constructor(v, str)
|
||||
EditConstraint.super.constructor(self, v, str)
|
||||
end
|
||||
|
||||
--
|
||||
-- Edits indicate that a variable is to be changed by imperative code.
|
||||
--
|
||||
function EditConstraint:isInput ()
|
||||
return true
|
||||
end
|
||||
|
||||
function EditConstraint:execute ()
|
||||
-- Edit constraints do nothing
|
||||
end
|
||||
|
||||
--
|
||||
-- B i n a r y C o n s t r a i n t
|
||||
--
|
||||
|
||||
local Direction = {}
|
||||
Direction.NONE = 0
|
||||
Direction.FORWARD = 1
|
||||
Direction.BACKWARD = -1
|
||||
|
||||
--
|
||||
-- Abstract superclass for constraints having two possible output
|
||||
-- variables.
|
||||
--
|
||||
|
||||
local BinaryConstraint = class(Constraint)
|
||||
|
||||
function BinaryConstraint:constructor(var1, var2, strength)
|
||||
BinaryConstraint.super.constructor(self, strength);
|
||||
self.v1 = var1
|
||||
self.v2 = var2
|
||||
self.direction = Direction.NONE
|
||||
self:addConstraint()
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
function BinaryConstraint:chooseMethod (mark)
|
||||
if self.v1.mark == mark then
|
||||
self.direction = (self.v2.mark ~= mark and Strength.stronger(self.strength, self.v2.walkStrength)) and Direction.FORWARD or Direction.NONE
|
||||
end
|
||||
if self.v2.mark == mark then
|
||||
self.direction = (self.v1.mark ~= mark and Strength.stronger(self.strength, self.v1.walkStrength)) and Direction.BACKWARD or Direction.NONE
|
||||
end
|
||||
if Strength.weaker(self.v1.walkStrength, self.v2.walkStrength) then
|
||||
self.direction = Strength.stronger(self.strength, self.v1.walkStrength) and Direction.BACKWARD or Direction.NONE
|
||||
else
|
||||
self.direction = Strength.stronger(self.strength, self.v2.walkStrength) and Direction.FORWARD or Direction.BACKWARD
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- Add this constraint to the constraint graph
|
||||
--
|
||||
function BinaryConstraint:addToGraph ()
|
||||
self.v1:addConstraint(self)
|
||||
self.v2:addConstraint(self)
|
||||
self.direction = Direction.NONE
|
||||
end
|
||||
|
||||
--
|
||||
-- Answer true if this constraint is satisfied in the current solution.
|
||||
--
|
||||
function BinaryConstraint:isSatisfied ()
|
||||
return self.direction ~= Direction.NONE
|
||||
end
|
||||
|
||||
--
|
||||
-- Mark the input variable with the given mark.
|
||||
--
|
||||
function BinaryConstraint:markInputs (mark)
|
||||
self:input().mark = mark
|
||||
end
|
||||
|
||||
--
|
||||
-- Returns the current input variable
|
||||
--
|
||||
function BinaryConstraint:input ()
|
||||
return (self.direction == Direction.FORWARD) and self.v1 or self.v2
|
||||
end
|
||||
|
||||
--
|
||||
-- Returns the current output variable
|
||||
--
|
||||
function BinaryConstraint:output ()
|
||||
return (self.direction == Direction.FORWARD) and self.v2 or self.v1
|
||||
end
|
||||
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
function BinaryConstraint:recalculate ()
|
||||
local ihn = self:input()
|
||||
local out = self:output()
|
||||
out.walkStrength = Strength.weakestOf(self.strength, ihn.walkStrength);
|
||||
out.stay = ihn.stay
|
||||
if out.stay then self:execute() end
|
||||
end
|
||||
|
||||
--
|
||||
-- Record the fact that self constraint is unsatisfied.
|
||||
--
|
||||
function BinaryConstraint:markUnsatisfied ()
|
||||
self.direction = Direction.NONE
|
||||
end
|
||||
|
||||
function BinaryConstraint:inputsKnown (mark)
|
||||
local i = self:input()
|
||||
return i.mark == mark or i.stay or i.determinedBy == nil
|
||||
end
|
||||
|
||||
function BinaryConstraint:removeFromGraph ()
|
||||
if (self.v1 ~= nil) then self.v1:removeConstraint(self) end
|
||||
if (self.v2 ~= nil) then self.v2:removeConstraint(self) end
|
||||
self.direction = Direction.NONE
|
||||
end
|
||||
|
||||
--
|
||||
-- S c a l e C o n s t r a i n t
|
||||
--
|
||||
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
|
||||
local ScaleConstraint = class (BinaryConstraint)
|
||||
|
||||
function ScaleConstraint:constructor(src, scale, offset, dest, strength)
|
||||
self.direction = Direction.NONE
|
||||
self.scale = scale
|
||||
self.offset = offset
|
||||
ScaleConstraint.super.constructor(self, src, dest, strength)
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Adds this constraint to the constraint graph.
|
||||
--
|
||||
function ScaleConstraint:addToGraph ()
|
||||
ScaleConstraint.super.addToGraph(self)
|
||||
self.scale:addConstraint(self)
|
||||
self.offset:addConstraint(self)
|
||||
end
|
||||
|
||||
function ScaleConstraint:removeFromGraph ()
|
||||
ScaleConstraint.super.removeFromGraph(self)
|
||||
if (self.scale ~= nil) then self.scale:removeConstraint(self) end
|
||||
if (self.offset ~= nil) then self.offset:removeConstraint(self) end
|
||||
end
|
||||
|
||||
function ScaleConstraint:markInputs (mark)
|
||||
ScaleConstraint.super.markInputs(self, mark);
|
||||
self.offset.mark = mark
|
||||
self.scale.mark = mark
|
||||
end
|
||||
|
||||
--
|
||||
-- Enforce this constraint. Assume that it is satisfied.
|
||||
--
|
||||
function ScaleConstraint:execute ()
|
||||
if self.direction == Direction.FORWARD then
|
||||
self.v2.value = self.v1.value * self.scale.value + self.offset.value
|
||||
else
|
||||
self.v1.value = (self.v2.value - self.offset.value) / self.scale.value
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
function ScaleConstraint:recalculate ()
|
||||
local ihn = self:input()
|
||||
local out = self:output()
|
||||
out.walkStrength = Strength.weakestOf(self.strength, ihn.walkStrength)
|
||||
out.stay = ihn.stay and self.scale.stay and self.offset.stay
|
||||
if out.stay then self:execute() end
|
||||
end
|
||||
|
||||
--
|
||||
-- E q u a l i t y C o n s t r a i n t
|
||||
--
|
||||
|
||||
--
|
||||
-- Constrains two variables to have the same value.
|
||||
--
|
||||
|
||||
local EqualityConstraint = class (BinaryConstraint)
|
||||
|
||||
function EqualityConstraint:constructor(var1, var2, strength)
|
||||
EqualityConstraint.super.constructor(self, var1, var2, strength)
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- Enforce this constraint. Assume that it is satisfied.
|
||||
--
|
||||
function EqualityConstraint:execute ()
|
||||
self:output().value = self:input().value
|
||||
end
|
||||
|
||||
--
|
||||
-- V a r i a b l e
|
||||
--
|
||||
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
local Variable = class ()
|
||||
|
||||
function Variable:constructor(name, initialValue)
|
||||
self.value = initialValue or 0
|
||||
self.constraints = OrderedCollection.new()
|
||||
self.determinedBy = nil
|
||||
self.mark = 0
|
||||
self.walkStrength = Strength.WEAKEST
|
||||
self.stay = true
|
||||
self.name = name
|
||||
end
|
||||
|
||||
--
|
||||
-- Add the given constraint to the set of all constraints that refer
|
||||
-- this variable.
|
||||
--
|
||||
function Variable:addConstraint (c)
|
||||
self.constraints:add(c)
|
||||
end
|
||||
|
||||
--
|
||||
-- Removes all traces of c from this variable.
|
||||
--
|
||||
function Variable:removeConstraint (c)
|
||||
self.constraints:remove(c)
|
||||
if self.determinedBy == c then
|
||||
self.determinedBy = nil
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- P l a n n e r
|
||||
--
|
||||
|
||||
--
|
||||
-- The DeltaBlue planner
|
||||
--
|
||||
local Planner = class()
|
||||
function Planner:constructor()
|
||||
self.currentMark = 0
|
||||
end
|
||||
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
function Planner:incrementalAdd (c)
|
||||
local mark = self:newMark()
|
||||
local overridden = c:satisfy(mark)
|
||||
while overridden ~= nil do
|
||||
overridden = overridden:satisfy(mark)
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
function Planner:incrementalRemove (c)
|
||||
local out = c:output()
|
||||
c:markUnsatisfied()
|
||||
c:removeFromGraph()
|
||||
local unsatisfied = self:removePropagateFrom(out)
|
||||
local strength = Strength.REQUIRED
|
||||
repeat
|
||||
for i = 1, unsatisfied:size() do
|
||||
local u = unsatisfied:at(i)
|
||||
if u.strength == strength then
|
||||
self:incrementalAdd(u)
|
||||
end
|
||||
end
|
||||
strength = strength:nextWeaker()
|
||||
until strength == Strength.WEAKEST
|
||||
end
|
||||
|
||||
--
|
||||
-- Select a previously unused mark value.
|
||||
--
|
||||
function Planner:newMark ()
|
||||
self.currentMark = self.currentMark + 1
|
||||
return self.currentMark
|
||||
end
|
||||
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
local Plan -- FORWARD DECLARATION
|
||||
function Planner:makePlan (sources)
|
||||
local mark = self:newMark()
|
||||
local plan = Plan.new()
|
||||
local todo = sources
|
||||
while todo:size() > 0 do
|
||||
local c = todo:removeFirst()
|
||||
if c:output().mark ~= mark and c:inputsKnown(mark) then
|
||||
plan:addConstraint(c)
|
||||
c:output().mark = mark
|
||||
self:addConstraintsConsumingTo(c:output(), todo)
|
||||
end
|
||||
end
|
||||
return plan
|
||||
end
|
||||
|
||||
--
|
||||
-- Extract a plan for resatisfying starting from the output of the
|
||||
-- given constraints, usually a set of input constraints.
|
||||
--
|
||||
function Planner:extractPlanFromConstraints (constraints)
|
||||
local sources = OrderedCollection.new()
|
||||
for i = 1, constraints:size() do
|
||||
local c = constraints:at(i)
|
||||
if c:isInput() and c:isSatisfied() then
|
||||
-- not in plan already and eligible for inclusion
|
||||
sources:add(c)
|
||||
end
|
||||
end
|
||||
return self:makePlan(sources)
|
||||
end
|
||||
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
function Planner:addPropagate (c, mark)
|
||||
local todo = OrderedCollection.new()
|
||||
todo:add(c)
|
||||
while todo:size() > 0 do
|
||||
local d = todo:removeFirst()
|
||||
if d:output().mark == mark then
|
||||
self:incrementalRemove(c)
|
||||
return false
|
||||
end
|
||||
d:recalculate()
|
||||
self:addConstraintsConsumingTo(d:output(), todo)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
function Planner:removePropagateFrom (out)
|
||||
out.determinedBy = nil
|
||||
out.walkStrength = Strength.WEAKEST
|
||||
out.stay = true
|
||||
local unsatisfied = OrderedCollection.new()
|
||||
local todo = OrderedCollection.new()
|
||||
todo:add(out)
|
||||
while todo:size() > 0 do
|
||||
local v = todo:removeFirst()
|
||||
for i = 1, v.constraints:size() do
|
||||
local c = v.constraints:at(i)
|
||||
if not c:isSatisfied() then unsatisfied:add(c) end
|
||||
end
|
||||
local determining = v.determinedBy
|
||||
for i = 1, v.constraints:size() do
|
||||
local next = v.constraints:at(i);
|
||||
if next ~= determining and next:isSatisfied() then
|
||||
next:recalculate()
|
||||
todo:add(next:output())
|
||||
end
|
||||
end
|
||||
end
|
||||
return unsatisfied
|
||||
end
|
||||
|
||||
function Planner:addConstraintsConsumingTo (v, coll)
|
||||
local determining = v.determinedBy
|
||||
local cc = v.constraints
|
||||
for i = 1, cc:size() do
|
||||
local c = cc:at(i)
|
||||
if c ~= determining and c:isSatisfied() then
|
||||
coll:add(c)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- P l a n
|
||||
--
|
||||
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
Plan = class()
|
||||
function Plan:constructor()
|
||||
self.v = OrderedCollection.new()
|
||||
end
|
||||
|
||||
function Plan:addConstraint (c)
|
||||
self.v:add(c)
|
||||
end
|
||||
|
||||
function Plan:size ()
|
||||
return self.v:size()
|
||||
end
|
||||
|
||||
function Plan:constraintAt (index)
|
||||
return self.v:at(index)
|
||||
end
|
||||
|
||||
function Plan:execute ()
|
||||
for i = 1, self:size() do
|
||||
local c = self:constraintAt(i)
|
||||
c:execute()
|
||||
end
|
||||
end
|
||||
|
||||
--
|
||||
-- M a i n
|
||||
--
|
||||
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
local function chainTest(n)
|
||||
planner = Planner.new()
|
||||
local prev = nil
|
||||
local first = nil
|
||||
local last = nil
|
||||
|
||||
-- Build chain of n equality constraints
|
||||
for i = 0, n do
|
||||
local name = "v" .. i;
|
||||
local v = Variable.new(name)
|
||||
if prev ~= nil then EqualityConstraint.new(prev, v, Strength.REQUIRED) end
|
||||
if i == 0 then first = v end
|
||||
if i == n then last = v end
|
||||
prev = v
|
||||
end
|
||||
|
||||
StayConstraint.new(last, Strength.STRONG_DEFAULT)
|
||||
local edit = EditConstraint.new(first, Strength.PREFERRED)
|
||||
local edits = OrderedCollection.new()
|
||||
edits:add(edit)
|
||||
local plan = planner:extractPlanFromConstraints(edits)
|
||||
for i = 0, 99 do
|
||||
first.value = i
|
||||
plan:execute()
|
||||
if last.value ~= i then
|
||||
alert("Chain test failed.")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function change(v, newValue)
|
||||
local edit = EditConstraint.new(v, Strength.PREFERRED)
|
||||
local edits = OrderedCollection.new()
|
||||
edits:add(edit)
|
||||
local plan = planner:extractPlanFromConstraints(edits)
|
||||
for i = 1, 10 do
|
||||
v.value = newValue
|
||||
plan:execute()
|
||||
end
|
||||
edit:destroyConstraint()
|
||||
end
|
||||
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
local function projectionTest(n)
|
||||
planner = Planner.new();
|
||||
local scale = Variable.new("scale", 10);
|
||||
local offset = Variable.new("offset", 1000);
|
||||
local src = nil
|
||||
local dst = nil;
|
||||
|
||||
local dests = OrderedCollection.new();
|
||||
for i = 0, n - 1 do
|
||||
src = Variable.new("src" .. i, i);
|
||||
dst = Variable.new("dst" .. i, i);
|
||||
dests:add(dst);
|
||||
StayConstraint.new(src, Strength.NORMAL);
|
||||
ScaleConstraint.new(src, scale, offset, dst, Strength.REQUIRED);
|
||||
end
|
||||
|
||||
change(src, 17)
|
||||
if dst.value ~= 1170 then alert("Projection 1 failed") end
|
||||
change(dst, 1050)
|
||||
if src.value ~= 5 then alert("Projection 2 failed") end
|
||||
change(scale, 5)
|
||||
for i = 0, n - 2 do
|
||||
if dests:at(i + 1).value ~= i * 5 + 1000 then
|
||||
alert("Projection 3 failed")
|
||||
end
|
||||
end
|
||||
change(offset, 2000)
|
||||
for i = 0, n - 2 do
|
||||
if dests:at(i + 1).value ~= i * 5 + 2000 then
|
||||
alert("Projection 4 failed")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function deltaBlue()
|
||||
chainTest(100);
|
||||
projectionTest(100);
|
||||
end
|
||||
|
||||
DeltaBlue = BenchmarkSuite.new('DeltaBlue', 66118, {
|
||||
Benchmark.new('DeltaBlue', deltaBlue)
|
||||
})
|
||||
639
benchmark/delta_blue.py
Normal file
639
benchmark/delta_blue.py
Normal file
@ -0,0 +1,639 @@
|
||||
"""
|
||||
deltablue.py
|
||||
============
|
||||
|
||||
Ported for the PyPy project.
|
||||
|
||||
This implementation of the DeltaBlue benchmark was directly ported
|
||||
from the `V8's source code`_, which was in turn derived
|
||||
from the Smalltalk implementation by John Maloney and Mario
|
||||
Wolczko. The original Javascript implementation was licensed under the GPL.
|
||||
|
||||
It's been updated in places to be more idiomatic to Python (for loops over
|
||||
collections, a couple magic methods, ``OrderedCollection`` being a list & things
|
||||
altering those collections changed to the builtin methods) but largely retains
|
||||
the layout & logic from the original. (Ugh.)
|
||||
|
||||
.. _`V8's source code`: (http://code.google.com/p/v8/source/browse/branches/bleeding_edge/benchmarks/deltablue.js)
|
||||
|
||||
From: https://gist.github.com/toastdriven/6408132
|
||||
|
||||
"""
|
||||
from __future__ import print_function
|
||||
import time
|
||||
|
||||
__author__ = 'Daniel Lindsley'
|
||||
__license__ = 'BSD'
|
||||
|
||||
|
||||
# The JS variant implements "OrderedCollection", which basically completely
|
||||
# overlaps with ``list``. So we'll cheat. :D
|
||||
class OrderedCollection(list):
|
||||
pass
|
||||
|
||||
|
||||
class Strength(object):
|
||||
REQUIRED = None
|
||||
STRONG_PREFERRED = None
|
||||
PREFERRED = None
|
||||
STRONG_DEFAULT = None
|
||||
NORMAL = None
|
||||
WEAK_DEFAULT = None
|
||||
WEAKEST = None
|
||||
|
||||
def __init__(self, strength, name):
|
||||
super(Strength, self).__init__()
|
||||
self.strength = strength
|
||||
self.name = name
|
||||
|
||||
@classmethod
|
||||
def stronger(cls, s1, s2):
|
||||
return s1.strength < s2.strength
|
||||
|
||||
@classmethod
|
||||
def weaker(cls, s1, s2):
|
||||
return s1.strength > s2.strength
|
||||
|
||||
@classmethod
|
||||
def weakest_of(cls, s1, s2):
|
||||
if cls.weaker(s1, s2):
|
||||
return s1
|
||||
|
||||
return s2
|
||||
|
||||
@classmethod
|
||||
def strongest(cls, s1, s2):
|
||||
if cls.stronger(s1, s2):
|
||||
return s1
|
||||
|
||||
return s2
|
||||
|
||||
def next_weaker(self):
|
||||
strengths = {
|
||||
0: self.__class__.WEAKEST,
|
||||
1: self.__class__.WEAK_DEFAULT,
|
||||
2: self.__class__.NORMAL,
|
||||
3: self.__class__.STRONG_DEFAULT,
|
||||
4: self.__class__.PREFERRED,
|
||||
# TODO: This looks like a bug in the original code. Shouldn't this be
|
||||
# ``STRONG_PREFERRED? Keeping for porting sake...
|
||||
5: self.__class__.REQUIRED,
|
||||
}
|
||||
return strengths[self.strength]
|
||||
|
||||
|
||||
# This is a terrible pattern IMO, but true to the original JS implementation.
|
||||
Strength.REQUIRED = Strength(0, "required")
|
||||
Strength.STONG_PREFERRED = Strength(1, "strongPreferred")
|
||||
Strength.PREFERRED = Strength(2, "preferred")
|
||||
Strength.STRONG_DEFAULT = Strength(3, "strongDefault")
|
||||
Strength.NORMAL = Strength(4, "normal")
|
||||
Strength.WEAK_DEFAULT = Strength(5, "weakDefault")
|
||||
Strength.WEAKEST = Strength(6, "weakest")
|
||||
|
||||
|
||||
class Constraint(object):
|
||||
def __init__(self, strength):
|
||||
super(Constraint, self).__init__()
|
||||
self.strength = strength
|
||||
|
||||
def add_constraint(self):
|
||||
global planner
|
||||
self.add_to_graph()
|
||||
planner.incremental_add(self)
|
||||
|
||||
def satisfy(self, mark):
|
||||
global planner
|
||||
self.choose_method(mark)
|
||||
|
||||
if not self.is_satisfied():
|
||||
if self.strength == Strength.REQUIRED:
|
||||
print('Could not satisfy a required constraint!')
|
||||
|
||||
return None
|
||||
|
||||
self.mark_inputs(mark)
|
||||
out = self.output()
|
||||
overridden = out.determined_by
|
||||
|
||||
if overridden is not None:
|
||||
overridden.mark_unsatisfied()
|
||||
|
||||
out.determined_by = self
|
||||
|
||||
if not planner.add_propagate(self, mark):
|
||||
print('Cycle encountered')
|
||||
|
||||
out.mark = mark
|
||||
return overridden
|
||||
|
||||
def destroy_constraint(self):
|
||||
global planner
|
||||
if self.is_satisfied():
|
||||
planner.incremental_remove(self)
|
||||
else:
|
||||
self.remove_from_graph()
|
||||
|
||||
def is_input(self):
|
||||
return False
|
||||
|
||||
|
||||
class UrnaryConstraint(Constraint):
|
||||
def __init__(self, v, strength):
|
||||
super(UrnaryConstraint, self).__init__(strength)
|
||||
self.my_output = v
|
||||
self.satisfied = False
|
||||
self.add_constraint()
|
||||
|
||||
def add_to_graph(self):
|
||||
self.my_output.add_constraint(self)
|
||||
self.satisfied = False
|
||||
|
||||
def choose_method(self, mark):
|
||||
if self.my_output.mark != mark and \
|
||||
Strength.stronger(self.strength, self.my_output.walk_strength):
|
||||
self.satisfied = True
|
||||
else:
|
||||
self.satisfied = False
|
||||
|
||||
def is_satisfied(self):
|
||||
return self.satisfied
|
||||
|
||||
def mark_inputs(self, mark):
|
||||
# No-ops.
|
||||
pass
|
||||
|
||||
def output(self):
|
||||
# Ugh. Keeping it for consistency with the original. So much for
|
||||
# "we're all adults here"...
|
||||
return self.my_output
|
||||
|
||||
def recalculate(self):
|
||||
self.my_output.walk_strength = self.strength
|
||||
self.my_output.stay = not self.is_input()
|
||||
|
||||
if self.my_output.stay:
|
||||
self.execute()
|
||||
|
||||
def mark_unsatisfied(self):
|
||||
self.satisfied = False
|
||||
|
||||
def inputs_known(self, mark):
|
||||
return True
|
||||
|
||||
def remove_from_graph(self):
|
||||
if self.my_output is not None:
|
||||
self.my_output.remove_constraint(self)
|
||||
self.satisfied = False
|
||||
|
||||
|
||||
class StayConstraint(UrnaryConstraint):
|
||||
def __init__(self, v, string):
|
||||
super(StayConstraint, self).__init__(v, string)
|
||||
|
||||
def execute(self):
|
||||
# The methods, THEY DO NOTHING.
|
||||
pass
|
||||
|
||||
|
||||
class EditConstraint(UrnaryConstraint):
|
||||
def __init__(self, v, string):
|
||||
super(EditConstraint, self).__init__(v, string)
|
||||
|
||||
def is_input(self):
|
||||
return True
|
||||
|
||||
def execute(self):
|
||||
# This constraint also does nothing.
|
||||
pass
|
||||
|
||||
|
||||
class Direction(object):
|
||||
# Hooray for things that ought to be structs!
|
||||
NONE = 0
|
||||
FORWARD = 1
|
||||
BACKWARD = -1
|
||||
|
||||
|
||||
class BinaryConstraint(Constraint):
|
||||
def __init__(self, v1, v2, strength):
|
||||
super(BinaryConstraint, self).__init__(strength)
|
||||
self.v1 = v1
|
||||
self.v2 = v2
|
||||
self.direction = Direction.NONE
|
||||
self.add_constraint()
|
||||
|
||||
def choose_method(self, mark):
|
||||
if self.v1.mark == mark:
|
||||
if self.v2.mark != mark and Strength.stronger(self.strength, self.v2.walk_strength):
|
||||
self.direction = Direction.FORWARD
|
||||
else:
|
||||
self.direction = Direction.BACKWARD
|
||||
|
||||
if self.v2.mark == mark:
|
||||
if self.v1.mark != mark and Strength.stronger(self.strength, self.v1.walk_strength):
|
||||
self.direction = Direction.BACKWARD
|
||||
else:
|
||||
self.direction = Direction.NONE
|
||||
|
||||
if Strength.weaker(self.v1.walk_strength, self.v2.walk_strength):
|
||||
if Strength.stronger(self.strength, self.v1.walk_strength):
|
||||
self.direction = Direction.BACKWARD
|
||||
else:
|
||||
self.direction = Direction.NONE
|
||||
else:
|
||||
if Strength.stronger(self.strength, self.v2.walk_strength):
|
||||
self.direction = Direction.FORWARD
|
||||
else:
|
||||
self.direction = Direction.BACKWARD
|
||||
|
||||
def add_to_graph(self):
|
||||
self.v1.add_constraint(self)
|
||||
self.v2.add_constraint(self)
|
||||
self.direction = Direction.NONE
|
||||
|
||||
def is_satisfied(self):
|
||||
return self.direction != Direction.NONE
|
||||
|
||||
def mark_inputs(self, mark):
|
||||
self.input().mark = mark
|
||||
|
||||
def input(self):
|
||||
if self.direction == Direction.FORWARD:
|
||||
return self.v1
|
||||
|
||||
return self.v2
|
||||
|
||||
def output(self):
|
||||
if self.direction == Direction.FORWARD:
|
||||
return self.v2
|
||||
|
||||
return self.v1
|
||||
|
||||
def recalculate(self):
|
||||
ihn = self.input()
|
||||
out = self.output()
|
||||
out.walk_strength = Strength.weakest_of(self.strength, ihn.walk_strength)
|
||||
out.stay = ihn.stay
|
||||
|
||||
if out.stay:
|
||||
self.execute()
|
||||
|
||||
def mark_unsatisfied(self):
|
||||
self.direction = Direction.NONE
|
||||
|
||||
def inputs_known(self, mark):
|
||||
i = self.input()
|
||||
return i.mark == mark or i.stay or i.determined_by == None
|
||||
|
||||
def remove_from_graph(self):
|
||||
if self.v1 is not None:
|
||||
self.v1.remove_constraint(self)
|
||||
|
||||
if self.v2 is not None:
|
||||
self.v2.remove_constraint(self)
|
||||
|
||||
self.direction = Direction.NONE
|
||||
|
||||
|
||||
class ScaleConstraint(BinaryConstraint):
|
||||
def __init__(self, src, scale, offset, dest, strength):
|
||||
self.direction = Direction.NONE
|
||||
self.scale = scale
|
||||
self.offset = offset
|
||||
super(ScaleConstraint, self).__init__(src, dest, strength)
|
||||
|
||||
def add_to_graph(self):
|
||||
super(ScaleConstraint, self).add_to_graph()
|
||||
self.scale.add_constraint(self)
|
||||
self.offset.add_constraint(self)
|
||||
|
||||
def remove_from_graph(self):
|
||||
super(ScaleConstraint, self).remove_from_graph()
|
||||
|
||||
if self.scale is not None:
|
||||
self.scale.remove_constraint(self)
|
||||
|
||||
if self.offset is not None:
|
||||
self.offset.remove_constraint(self)
|
||||
|
||||
def mark_inputs(self, mark):
|
||||
super(ScaleConstraint, self).mark_inputs(mark)
|
||||
self.scale.mark = mark
|
||||
self.offset.mark = mark
|
||||
|
||||
def execute(self):
|
||||
if self.direction == Direction.FORWARD:
|
||||
self.v2.value = self.v1.value * self.scale.value + self.offset.value
|
||||
else:
|
||||
self.v1.value = (self.v2.value - self.offset.value) / self.scale.value
|
||||
|
||||
def recalculate(self):
|
||||
ihn = self.input()
|
||||
out = self.output()
|
||||
out.walk_strength = Strength.weakest_of(self.strength, ihn.walk_strength)
|
||||
out.stay = ihn.stay and self.scale.stay and self.offset.stay
|
||||
|
||||
if out.stay:
|
||||
self.execute()
|
||||
|
||||
|
||||
class EqualityConstraint(BinaryConstraint):
|
||||
def execute(self):
|
||||
self.output().value = self.input().value
|
||||
|
||||
|
||||
class Variable(object):
|
||||
def __init__(self, name, initial_value=0):
|
||||
super(Variable, self).__init__()
|
||||
self.name = name
|
||||
self.value = initial_value
|
||||
self.constraints = OrderedCollection()
|
||||
self.determined_by = None
|
||||
self.mark = 0
|
||||
self.walk_strength = Strength.WEAKEST
|
||||
self.stay = True
|
||||
|
||||
def __repr__(self):
|
||||
# To make debugging this beast from pdb easier...
|
||||
return '<Variable: %s - %s>' % (
|
||||
self.name,
|
||||
self.value
|
||||
)
|
||||
|
||||
def add_constraint(self, constraint):
|
||||
self.constraints.append(constraint)
|
||||
|
||||
def remove_constraint(self, constraint):
|
||||
self.constraints.remove(constraint)
|
||||
|
||||
if self.determined_by == constraint:
|
||||
self.determined_by = None
|
||||
|
||||
|
||||
class Planner(object):
|
||||
def __init__(self):
|
||||
super(Planner, self).__init__()
|
||||
self.current_mark = 0
|
||||
|
||||
def incremental_add(self, constraint):
|
||||
mark = self.new_mark()
|
||||
overridden = constraint.satisfy(mark)
|
||||
|
||||
while overridden is not None:
|
||||
overridden = overridden.satisfy(mark)
|
||||
|
||||
def incremental_remove(self, constraint):
|
||||
out = constraint.output()
|
||||
constraint.mark_unsatisfied()
|
||||
constraint.remove_from_graph()
|
||||
unsatisfied = self.remove_propagate_from(out)
|
||||
strength = Strength.REQUIRED
|
||||
# Do-while, the Python way.
|
||||
repeat = True
|
||||
|
||||
while repeat:
|
||||
for u in unsatisfied:
|
||||
if u.strength == strength:
|
||||
self.incremental_add(u)
|
||||
|
||||
strength = strength.next_weaker()
|
||||
|
||||
repeat = strength != Strength.WEAKEST
|
||||
|
||||
def new_mark(self):
|
||||
self.current_mark += 1
|
||||
return self.current_mark
|
||||
|
||||
def make_plan(self, sources):
|
||||
mark = self.new_mark()
|
||||
plan = Plan()
|
||||
todo = sources
|
||||
|
||||
while len(todo):
|
||||
c = todo.pop(0)
|
||||
|
||||
if c.output().mark != mark and c.inputs_known(mark):
|
||||
plan.add_constraint(c)
|
||||
c.output().mark = mark
|
||||
self.add_constraints_consuming_to(c.output(), todo)
|
||||
|
||||
return plan
|
||||
|
||||
def extract_plan_from_constraints(self, constraints):
|
||||
sources = OrderedCollection()
|
||||
|
||||
for c in constraints:
|
||||
if c.is_input() and c.is_satisfied():
|
||||
sources.append(c)
|
||||
|
||||
return self.make_plan(sources)
|
||||
|
||||
def add_propagate(self, c, mark):
|
||||
todo = OrderedCollection()
|
||||
todo.append(c)
|
||||
|
||||
while len(todo):
|
||||
d = todo.pop(0)
|
||||
|
||||
if d.output().mark == mark:
|
||||
self.incremental_remove(c)
|
||||
return False
|
||||
|
||||
d.recalculate()
|
||||
self.add_constraints_consuming_to(d.output(), todo)
|
||||
|
||||
return True
|
||||
|
||||
def remove_propagate_from(self, out):
|
||||
out.determined_by = None
|
||||
out.walk_strength = Strength.WEAKEST
|
||||
out.stay = True
|
||||
unsatisfied = OrderedCollection()
|
||||
todo = OrderedCollection()
|
||||
todo.append(out)
|
||||
|
||||
while len(todo):
|
||||
v = todo.pop(0)
|
||||
|
||||
for c in v.constraints:
|
||||
if not c.is_satisfied():
|
||||
unsatisfied.append(c)
|
||||
|
||||
determining = v.determined_by
|
||||
|
||||
for c in v.constraints:
|
||||
if c != determining and c.is_satisfied():
|
||||
c.recalculate()
|
||||
todo.append(c.output())
|
||||
|
||||
return unsatisfied
|
||||
|
||||
def add_constraints_consuming_to(self, v, coll):
|
||||
determining = v.determined_by
|
||||
cc = v.constraints
|
||||
|
||||
for c in cc:
|
||||
if c != determining and c.is_satisfied():
|
||||
# I guess we're just updating a reference (``coll``)? Seems
|
||||
# inconsistent with the rest of the implementation, where they
|
||||
# return the lists...
|
||||
coll.append(c)
|
||||
|
||||
|
||||
class Plan(object):
|
||||
def __init__(self):
|
||||
super(Plan, self).__init__()
|
||||
self.v = OrderedCollection()
|
||||
|
||||
def add_constraint(self, c):
|
||||
self.v.append(c)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.v)
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self.v[index]
|
||||
|
||||
def execute(self):
|
||||
for c in self.v:
|
||||
c.execute()
|
||||
|
||||
|
||||
# Main
|
||||
total = 0
|
||||
|
||||
def chain_test(n):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
global planner
|
||||
global total
|
||||
|
||||
planner = Planner()
|
||||
prev, first, last = None, None, None
|
||||
|
||||
# We need to go up to n inclusively.
|
||||
for i in range(n + 1):
|
||||
name = "v%s" % i
|
||||
v = Variable(name)
|
||||
|
||||
if prev is not None:
|
||||
EqualityConstraint(prev, v, Strength.REQUIRED)
|
||||
|
||||
if i == 0:
|
||||
first = v
|
||||
|
||||
if i == n:
|
||||
last = v
|
||||
|
||||
prev = v
|
||||
|
||||
StayConstraint(last, Strength.STRONG_DEFAULT)
|
||||
edit = EditConstraint(first, Strength.PREFERRED)
|
||||
edits = OrderedCollection()
|
||||
edits.append(edit)
|
||||
plan = planner.extract_plan_from_constraints(edits)
|
||||
|
||||
for i in range(100):
|
||||
first.value = i
|
||||
plan.execute()
|
||||
|
||||
total += last.value
|
||||
if last.value != i:
|
||||
print("Chain test failed.")
|
||||
|
||||
|
||||
def projection_test(n):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
global planner
|
||||
global total
|
||||
|
||||
planner = Planner()
|
||||
scale = Variable("scale", 10)
|
||||
offset = Variable("offset", 1000)
|
||||
src, dest = None, None
|
||||
|
||||
dests = OrderedCollection()
|
||||
|
||||
for i in range(n):
|
||||
src = Variable("src%s" % i, i)
|
||||
dst = Variable("dst%s" % i, i)
|
||||
dests.append(dst)
|
||||
StayConstraint(src, Strength.NORMAL)
|
||||
ScaleConstraint(src, scale, offset, dst, Strength.REQUIRED)
|
||||
|
||||
change(src, 17)
|
||||
|
||||
total += dst.value
|
||||
if dst.value != 1170:
|
||||
print("Projection 1 failed")
|
||||
|
||||
change(dst, 1050)
|
||||
|
||||
total += src.value
|
||||
if src.value != 5:
|
||||
print("Projection 2 failed")
|
||||
|
||||
change(scale, 5)
|
||||
|
||||
for i in range(n - 1):
|
||||
total += dests[i].value
|
||||
if dests[i].value != (i * 5 + 1000):
|
||||
print("Projection 3 failed")
|
||||
|
||||
change(offset, 2000)
|
||||
|
||||
for i in range(n - 1):
|
||||
total += dests[i].value
|
||||
if dests[i].value != (i * 5 + 2000):
|
||||
print("Projection 4 failed")
|
||||
|
||||
|
||||
def change(v, new_value):
|
||||
global planner
|
||||
edit = EditConstraint(v, Strength.PREFERRED)
|
||||
edits = OrderedCollection()
|
||||
edits.append(edit)
|
||||
|
||||
plan = planner.extract_plan_from_constraints(edits)
|
||||
|
||||
for i in range(10):
|
||||
v.value = new_value
|
||||
plan.execute()
|
||||
|
||||
edit.destroy_constraint()
|
||||
|
||||
|
||||
# HOORAY FOR GLOBALS... Oh wait.
|
||||
# In spirit of the original, we'll keep it, but ugh.
|
||||
planner = None
|
||||
|
||||
|
||||
def delta_blue():
|
||||
global total
|
||||
start = time.clock()
|
||||
for i in range(20):
|
||||
chain_test(100)
|
||||
projection_test(100)
|
||||
print(total)
|
||||
print("elapsed: " + str(time.clock() - start))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
delta_blue()
|
||||
735
benchmark/delta_blue.wren
Normal file
735
benchmark/delta_blue.wren
Normal file
@ -0,0 +1,735 @@
|
||||
// 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 {
|
||||
new(value, name) {
|
||||
_value = value
|
||||
_name = name
|
||||
}
|
||||
|
||||
value { return _value }
|
||||
name { return _name }
|
||||
|
||||
nextWeaker { return ORDERED[_value] }
|
||||
|
||||
static stronger(s1, s2) { return s1.value < s2.value }
|
||||
static weaker(s1, s2) { return s1.value > s2.value }
|
||||
|
||||
static weakest(s1, s2) {
|
||||
// TODO: Ternary operator.
|
||||
if (Strength.weaker(s1, s2)) return s1
|
||||
return s2
|
||||
}
|
||||
|
||||
static strongest(s1, s2) {
|
||||
// TODO: Ternary operator.
|
||||
if (Strength.stronger(s1, s2)) return s1
|
||||
return s2
|
||||
}
|
||||
}
|
||||
|
||||
// Compile time computed constants.
|
||||
REQUIRED = new Strength(0, "required")
|
||||
STRONG_REFERRED = new Strength(1, "strongPreferred")
|
||||
PREFERRED = new Strength(2, "preferred")
|
||||
STRONG_DEFAULT = new Strength(3, "strongDefault")
|
||||
NORMAL = new Strength(4, "normal")
|
||||
WEAK_DEFAULT = new Strength(5, "weakDefault")
|
||||
WEAKEST = new Strength(6, "weakest")
|
||||
|
||||
ORDERED = [
|
||||
WEAKEST, WEAK_DEFAULT, NORMAL, STRONG_DEFAULT, PREFERRED, STRONG_REFERRED
|
||||
]
|
||||
|
||||
// TODO: Forward declarations.
|
||||
var planner
|
||||
|
||||
class Constraint {
|
||||
new(strength) {
|
||||
_strength = strength
|
||||
}
|
||||
|
||||
strength { return _strength }
|
||||
|
||||
// Activate this constraint and attempt to satisfy it.
|
||||
addConstraint {
|
||||
this.addToGraph
|
||||
planner.incrementalAdd(this)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
this.chooseMethod(mark)
|
||||
if (!this.isSatisfied) {
|
||||
if (_strength == REQUIRED) {
|
||||
IO.print("Could not satisfy a required constraint!")
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
this.markInputs(mark)
|
||||
var out = this.output
|
||||
var overridden = out.determinedBy
|
||||
if (overridden != null) overridden.markUnsatisfied
|
||||
out.determinedBy = this
|
||||
if (!planner.addPropagate(this, mark)) IO.print("Cycle encountered")
|
||||
out.mark = mark
|
||||
return overridden
|
||||
}
|
||||
|
||||
destroyConstraint {
|
||||
if (this.isSatisfied) planner.incrementalRemove(this)
|
||||
this.removeFromGraph
|
||||
}
|
||||
|
||||
// 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.
|
||||
isInput { return false }
|
||||
}
|
||||
|
||||
// Abstract superclass for constraints having a single possible output variable.
|
||||
class UnaryConstraint is Constraint {
|
||||
new(myOutput, strength) {
|
||||
super(strength)
|
||||
_satisfied = false
|
||||
_myOutput = myOutput
|
||||
this.addConstraint
|
||||
}
|
||||
|
||||
// Adds this constraint to the constraint graph.
|
||||
addToGraph {
|
||||
_myOutput.addConstraint(this)
|
||||
_satisfied = false;
|
||||
}
|
||||
|
||||
// Decides if this constraint can be satisfied and records that decision.
|
||||
chooseMethod(mark) {
|
||||
_satisfied = (_myOutput.mark != mark) &&
|
||||
Strength.stronger(this.strength, _myOutput.walkStrength)
|
||||
}
|
||||
|
||||
// Returns true if this constraint is satisfied in the current solution.
|
||||
isSatisfied { return _satisfied }
|
||||
|
||||
markInputs(mark) {
|
||||
// has no inputs.
|
||||
}
|
||||
|
||||
// Returns the current output variable.
|
||||
output { return _myOutput }
|
||||
|
||||
// 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 {
|
||||
_myOutput.walkStrength = this.strength
|
||||
_myOutput.stay = !this.isInput
|
||||
if (_myOutput.stay) this.execute // Stay optimization.
|
||||
}
|
||||
|
||||
// Records that this constraint is unsatisfied.
|
||||
markUnsatisfied {
|
||||
_satisfied = false
|
||||
}
|
||||
|
||||
inputsKnown(mark) { return true }
|
||||
|
||||
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 {
|
||||
new(variable, strength) {
|
||||
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 {
|
||||
EditConstraint(variable, strength) {
|
||||
super(variable, strength)
|
||||
}
|
||||
|
||||
// Edits indicate that a variable is to be changed by imperative code.
|
||||
isInput { return true }
|
||||
|
||||
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 {
|
||||
new(v1, v2, strength) {
|
||||
super(strength)
|
||||
_v1 = v1
|
||||
_v2 = v2
|
||||
_direction = NONE
|
||||
this.addConstraint
|
||||
}
|
||||
|
||||
direction { return _direction }
|
||||
v1 { return _v1 }
|
||||
v2 { return _v2 }
|
||||
|
||||
// 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 &&
|
||||
Strength.stronger(this.strength, _v2.walkStrength)) {
|
||||
_direction = FORWARD
|
||||
} else {
|
||||
_direction = NONE
|
||||
}
|
||||
}
|
||||
|
||||
if (_v2.mark == mark) {
|
||||
if (_v1.mark != mark &&
|
||||
Strength.stronger(this.strength, _v1.walkStrength)) {
|
||||
_direction = BACKWARD
|
||||
} else {
|
||||
_direction = NONE
|
||||
}
|
||||
}
|
||||
|
||||
if (Strength.weaker(_v1.walkStrength, _v2.walkStrength)) {
|
||||
if (Strength.stronger(this.strength, _v1.walkStrength)) {
|
||||
_direction = BACKWARD
|
||||
} else {
|
||||
_direction = NONE
|
||||
}
|
||||
} else {
|
||||
if (Strength.stronger(this.strength, _v2.walkStrength)) {
|
||||
_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.
|
||||
isSatisfied { return _direction != NONE }
|
||||
|
||||
// Mark the input variable with the given mark.
|
||||
markInputs(mark) {
|
||||
this.input.mark = mark
|
||||
}
|
||||
|
||||
// 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 {
|
||||
var ihn = this.input
|
||||
var out = this.output
|
||||
out.walkStrength = Strength.weakest(this.strength, ihn.walkStrength)
|
||||
out.stay = ihn.stay
|
||||
if (out.stay) this.execute
|
||||
}
|
||||
|
||||
// Record the fact that this constraint is unsatisfied.
|
||||
markUnsatisfied {
|
||||
_direction = NONE
|
||||
}
|
||||
|
||||
inputsKnown(mark) {
|
||||
var i = this.input
|
||||
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 {
|
||||
new(src, scale, offset, dest, strength) {
|
||||
_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 {
|
||||
if (this.direction == FORWARD) {
|
||||
this.v2.value = this.v1.value * _scale.value + _offset.value;
|
||||
} else {
|
||||
// TODO: Is this the same semantics as ~/?
|
||||
this.v1.value = ((this.v2.value - _offset.value) / _scale.value).floor;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
var ihn = this.input
|
||||
var out = this.output
|
||||
out.walkStrength = Strength.weakest(this.strength, ihn.walkStrength)
|
||||
out.stay = ihn.stay && _scale.stay && _offset.stay
|
||||
if (out.stay) this.execute
|
||||
}
|
||||
}
|
||||
|
||||
// Constrains two variables to have the same value.
|
||||
class EqualityConstraint is BinaryConstraint {
|
||||
new(v1, v2, strength) {
|
||||
super(v1, v2, strength)
|
||||
}
|
||||
|
||||
// Enforce this constraint. Assume that it is satisfied.
|
||||
execute {
|
||||
this.output.value = this.input.value
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
new(name, value) {
|
||||
_constraints = []
|
||||
_determinedBy = null
|
||||
_mark = 0
|
||||
_walkStrength = WEAKEST
|
||||
_stay = true
|
||||
_name = name
|
||||
_value = value
|
||||
}
|
||||
|
||||
constraints { return _constraints }
|
||||
determinedBy { return _determinedBy }
|
||||
determinedBy = value { return _determinedBy = value }
|
||||
mark { return _mark }
|
||||
mark = value { return _mark = value }
|
||||
walkStrength { return _walkStrength }
|
||||
walkStrength = value { return _walkStrength = value }
|
||||
stay { return _stay }
|
||||
stay = value { return _stay = value }
|
||||
value { return _value }
|
||||
value = newValue { return _value = newValue }
|
||||
|
||||
// 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) {
|
||||
// TODO: Better way to filter list.
|
||||
var i = 0
|
||||
while (i < _constraints.count) {
|
||||
if (_constraints[i] == constraint) {
|
||||
_constraints.removeAt(i)
|
||||
} else {
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
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 {
|
||||
new {
|
||||
_list = []
|
||||
}
|
||||
|
||||
addConstraint(constraint) {
|
||||
_list.add(constraint)
|
||||
}
|
||||
|
||||
size { return _list.count }
|
||||
|
||||
execute {
|
||||
for (constraint in _list) {
|
||||
constraint.execute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Planner {
|
||||
new {
|
||||
_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) {
|
||||
var mark = this.newMark
|
||||
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
|
||||
var unsatisfied = this.removePropagateFrom(out)
|
||||
var strength = REQUIRED
|
||||
while (true) {
|
||||
for (i in 0...unsatisfied.count) {
|
||||
var u = unsatisfied[i]
|
||||
if (u.strength == strength) this.incrementalAdd(u)
|
||||
}
|
||||
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) {
|
||||
var mark = this.newMark
|
||||
var plan = new Plan
|
||||
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
|
||||
this.addConstraintsConsumingTo(constraint.output, todo)
|
||||
}
|
||||
}
|
||||
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 = []
|
||||
for (i in 0...constraints.count) {
|
||||
var constraint = constraints[i]
|
||||
// if not in plan already and eligible for inclusion.
|
||||
if (constraint.isInput && constraint.isSatisfied) sources.add(constraint)
|
||||
}
|
||||
return this.makePlan(sources)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
this.incrementalRemove(constraint)
|
||||
return false
|
||||
}
|
||||
|
||||
d.recalculate
|
||||
this.addConstraintsConsumingTo(d.output, todo)
|
||||
}
|
||||
|
||||
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)
|
||||
for (i in 0...v.constraints.count) {
|
||||
var constraint = v.constraints[i]
|
||||
if (!constraint.isSatisfied) unsatisfied.add(constraint)
|
||||
}
|
||||
|
||||
var determining = v.determinedBy
|
||||
for (i in 0...v.constraints.count) {
|
||||
var next = v.constraints[i]
|
||||
if (next != determining && next.isSatisfied) {
|
||||
next.recalculate
|
||||
todo.add(next.output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return unsatisfied
|
||||
}
|
||||
|
||||
addConstraintsConsumingTo(v, coll) {
|
||||
var determining = v.determinedBy
|
||||
for (i in 0...v.constraints.count) {
|
||||
var constraint = v.constraints[i]
|
||||
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.
|
||||
var chainTest = fn(n) {
|
||||
planner = new Planner
|
||||
var prev = null
|
||||
var first = null
|
||||
var last = null
|
||||
|
||||
// Build chain of n equality constraints.
|
||||
for (i in 0..n) {
|
||||
var v = new Variable("v", 0)
|
||||
if (prev != null) new EqualityConstraint(prev, v, REQUIRED)
|
||||
if (i == 0) first = v
|
||||
if (i == n) last = v
|
||||
prev = v
|
||||
}
|
||||
|
||||
new StayConstraint(last, STRONG_DEFAULT)
|
||||
var edit = new EditConstraint(first, PREFERRED)
|
||||
var plan = planner.extractPlanFromConstraints([edit])
|
||||
for (i in 0...100) {
|
||||
first.value = i
|
||||
plan.execute
|
||||
total = total + last.value
|
||||
}
|
||||
}
|
||||
|
||||
var change = fn(v, newValue) {
|
||||
var edit = new EditConstraint(v, PREFERRED)
|
||||
var plan = planner.extractPlanFromConstraints([edit])
|
||||
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.
|
||||
var projectionTest = fn(n) {
|
||||
planner = new Planner
|
||||
var scale = new Variable("scale", 10)
|
||||
var offset = new Variable("offset", 1000)
|
||||
var src = null
|
||||
var dst = null
|
||||
|
||||
var dests = []
|
||||
for (i in 0...n) {
|
||||
src = new Variable("src", i)
|
||||
dst = new Variable("dst", i)
|
||||
dests.add(dst)
|
||||
new StayConstraint(src, NORMAL)
|
||||
new ScaleConstraint(src, scale, offset, dst, REQUIRED)
|
||||
}
|
||||
|
||||
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)
|
||||
IO.print("elapsed: " + (IO.clock - start).toString)
|
||||
|
||||
@ -29,6 +29,8 @@ BENCHMARK("binary_trees", """stretch tree of depth 13 check: -1
|
||||
32 trees of depth 12 check: -32
|
||||
long lived tree of depth 12 check: -1""")
|
||||
|
||||
BENCHMARK("delta_blue", "7032700")
|
||||
|
||||
BENCHMARK("fib", r"""317811
|
||||
317811
|
||||
317811
|
||||
|
||||
Reference in New Issue
Block a user