internal void RecycleDfDvNode(DfDvNode node) { // In the case of long constraint chains make sure this does not end up as big as the number of constraints in the block. if (this.DfDvRecycleStack.Count < 1024) { DfDvRecycleStack.Push(node); } }
internal DfDvNode Set(DfDvNode parent, Constraint constraintToEval, Variable variableToEval, Variable variableDoneEval) { this.Parent = parent; this.ConstraintToEval = constraintToEval; this.VariableToEval = variableToEval; this.VariableDoneEval = variableDoneEval; this.Depth = 0; this.ChildrenHaveBeenPushed = false; constraintToEval.Lagrangian = 0.0; return this; }
internal DfDvNode Set(DfDvNode parent, Constraint constraintToEval, Variable variableToEval, Variable variableDoneEval) { this.Parent = parent; this.ConstraintToEval = constraintToEval; this.VariableToEval = variableToEval; this.VariableDoneEval = variableDoneEval; this.Depth = 0; this.ChildrenHaveBeenPushed = false; constraintToEval.Lagrangian = 0.0; return(this); }
internal DfDvNode(DfDvNode parent, Constraint constraintToEval, Variable variableToEval, Variable variableDoneEval) { Set(parent, constraintToEval, variableToEval, variableDoneEval); }
void CheckForConstraintPathTarget(DfDvNode node) { if (this.pathTargetVariable == node.VariableToEval) { // Add every variable from pathTargetVariable up the callchain up to but not including initialVarToEval. while (node.Parent != this.dfDvDummyParentNode) { this.constraintPath.Add(new ConstraintDirectionPair(node.ConstraintToEval, node.IsLeftToRight)); node = node.Parent; } this.pathTargetVariable = null; // Path is complete } }
internal void RecurseGetConnectedVariables(List<Variable> lstVars, Variable initialVarToEval, Variable initialVarDoneEval) { // Get all the vars at and to the right of 'var', including backtracking to get all // variables that are connected from the left. This is just like ComputeDfDv except // that in this case we start with the variableDoneEval being the Left variable. Debug.Assert(0 == this.allConstraints.DfDvStack.Count, "Leftovers in ComputeDfDvStack"); this.allConstraints.DfDvStack.Clear(); Debug.Assert(0 == lstVars.Count, "Leftovers in lstVars"); // Variables for initializing the first node. var dummyConstraint = new Constraint(initialVarToEval); dfDvDummyParentNode = new DfDvNode(dummyConstraint); this.allConstraints.DfDvStack.Push(GetDfDvNode(dfDvDummyParentNode, dummyConstraint, initialVarToEval, initialVarDoneEval)); lstVars.Add(initialVarToEval); // Do a pre-order tree traversal (process the constraint before its children), for consistency // with prior behaviour. while (this.allConstraints.DfDvStack.Count > 0) { // Leave the node on the stack until we've processed all of its children. var node = this.allConstraints.DfDvStack.Peek(); int prevStackCount = this.allConstraints.DfDvStack.Count; if (!node.ChildrenHaveBeenPushed) { node.ChildrenHaveBeenPushed = true; foreach (var constraint in node.VariableToEval.LeftConstraints) { if (constraint.IsActive && (constraint.Right != node.VariableDoneEval)) { // If the node has no constraints other than the one we're now processing, it's a leaf // and we don't need the overhead of pushing to and popping from the stack. if (1 == constraint.Right.ActiveConstraintCount) { Debug_CycleCheck(constraint); Debug_MarkForCycleCheck(constraint); lstVars.Add(constraint.Right); } else { // variableToEval is now considered "done" AddVariableAndPushDfDvNode(lstVars, GetDfDvNode(node, constraint, constraint.Right, node.VariableToEval)); } } } foreach (var constraint in node.VariableToEval.RightConstraints) { if (constraint.IsActive && (constraint.Left != node.VariableDoneEval)) { // See comments in .LeftConstraints if (1 == constraint.Left.ActiveConstraintCount) { Debug_CycleCheck(constraint); Debug_MarkForCycleCheck(constraint); lstVars.Add(constraint.Left); } else { AddVariableAndPushDfDvNode(lstVars, GetDfDvNode(node, constraint, constraint.Left, node.VariableToEval)); } } } } // endif !node.ChildrenHaveBeenPushed // If we just pushed one or more nodes, loop back up and "recurse" into them. if (this.allConstraints.DfDvStack.Count > prevStackCount) { continue; } // We are at a non-leaf node and have "recursed" through all its descendents, so we're done with it. Debug.Assert(this.allConstraints.DfDvStack.Peek() == node, "DfDvStack.Peek() should be 'node'"); this.allConstraints.RecycleDfDvNode(this.allConstraints.DfDvStack.Pop()); } // endwhile stack is not empty }
// Called by RecurseGetConnectedVariables. void AddVariableAndPushDfDvNode(List<Variable> lstVars, DfDvNode node) { Debug_CycleCheck(node.ConstraintToEval); lstVars.Add(node.VariableToEval); PushOnDfDvStack(node); }
void PushOnDfDvStack(DfDvNode node) { Debug_MarkForCycleCheck(node.ConstraintToEval); this.allConstraints.DfDvStack.Push(node); }
// Called by ComputeDfDv. void PushDfDvNode(DfDvNode node) { #if VERBOSE Console.WriteLine("ComputeDfDv depth {0} pushing non-leaf {1}Constraint: {2}", node.Depth, node.IsLeftToRight ? "Left" : "Right", node.ConstraintToEval); #endif // VERBOSE Debug_CycleCheck(node.ConstraintToEval); PushOnDfDvStack(node); }
DfDvNode GetDfDvNode(DfDvNode parent, Constraint constraintToEval, Variable variableToEval, Variable variableDoneEval) { DfDvNode node = (this.allConstraints.DfDvRecycleStack.Count > 0) ? this.allConstraints.DfDvRecycleStack.Pop().Set(parent, constraintToEval, variableToEval, variableDoneEval) : new DfDvNode(parent, constraintToEval, variableToEval, variableDoneEval); node.Depth = node.Parent.Depth + 1; if (this.allConstraints.MaxConstraintTreeDepth < node.Depth) { this.allConstraints.MaxConstraintTreeDepth = node.Depth; } return node; }
// Directly evaluate a leaf node rather than defer it to stack push/pop. void ProcessDfDvLeafNodeDirectly(DfDvNode node) { #if VERBOSE Console.WriteLine("ComputeDfDv directly processing the following leaf constraint:"); #endif // VERBOSE Debug_MarkForCycleCheck(node.ConstraintToEval); ProcessDfDvLeafNode(node); }
} // end ComputeDfDv() void ProcessDfDvLeafNode(DfDvNode node) { #if VERBOSE Console.WriteLine(" ComputeDfDv depth {0} evaluating {1}Constraint: {2}", node.Depth, node.IsLeftToRight ? "Left" : "Right", node.ConstraintToEval); // If pathTargetVariable is non-null, we haven't found it yet, so this constraint is a dead end. if (null != this.pathTargetVariable) { Console.WriteLine(" {0} dead end: {1}", node.IsLeftToRight ? "LtoR" : "RtoL", node.ConstraintToEval); } #endif // VERBOSE double dfdv = node.VariableToEval.DfDv; // Add dfdv to constraint.Lagrangian if we are going left-to-right, else subtract it ("negative slope"); // similarly, add it to or subtract it from the parent's Lagrangian. if (node.IsLeftToRight) { node.ConstraintToEval.Lagrangian += dfdv; node.Parent.ConstraintToEval.Lagrangian += node.ConstraintToEval.Lagrangian; } else { // Any child constraints have already put their values into the current constraint // according to whether they were left-to-right or right-to-left. This is the equivalent // to the sum of return values in the recursive approach in the paper. However, the paper // negates this return value when setting it into a right-to-left parent's Lagrangian; // we're that right-to-left parent now so do that first (negate the sum of children). node.ConstraintToEval.Lagrangian = -(node.ConstraintToEval.Lagrangian + dfdv); node.Parent.ConstraintToEval.Lagrangian -= node.ConstraintToEval.Lagrangian; } #if VERBOSE Console.WriteLine(" ComputeDfDv: incremental {0} dfdv = {1:F5}", node.IsLeftToRight ? "LToR" : "RToL", dfdv); Console.WriteLine(" Constraint {0} Lagrangian: {1:F5} (scale when {2} parent = {3:F5})", node.ConstraintToEval.Id, node.ConstraintToEval.Lagrangian, node.IsLeftToRight ? "added to" : "subtracted from", node.IsLeftToRight ? node.ConstraintToEval.Left.Scale : node.ConstraintToEval.Right.Scale); Console.WriteLine(" Parent {0} Lagrangian: {1:F5}", node.Parent.ConstraintToEval.Id, node.Parent.ConstraintToEval.Lagrangian); #endif // VERBOSE // See if this node found the target variable. CheckForConstraintPathTarget(node); // If this active constraint is violated, record it. Debug_CheckForViolatedActiveConstraint(node.ConstraintToEval); // We're done with this node. this.allConstraints.RecycleDfDvNode(node); }
internal void ComputeDfDv(Variable initialVarToEval) { #if COMPARE_RECURSIVE_DFDV var recursiveDfDv = Recursive_DfDv(initialVarToEval, null, 0); #endif // COMPARE_RECURSIVE_DFDV #if DEBUG Debug.Assert(0 != this.idDfDv, "idDfDv should not be 0"); #endif // DEBUG #if VERBOSE Console.WriteLine("ComputeDfDv initialVarToEval: [{0}]", initialVarToEval); #endif // VERBOSE // Compute the derivative of the spanning tree (comprised of our active constraints) at the // point of variableToEval (with "change" being the difference between "Desired" position and the calculated // position for the current pass), for all paths that do not include the edge variableToEval->variableDoneEval. // Recursiteratively process all outgoing paths from variableToEval to its right (i.e. where it is constraint.Left), // but don't include variableDoneEval because it's already been evaluated. // At each variable on the rightward traversal, we'll also process leftward paths (incoming to) that // variable (via the following constraint loop) before returning here. // variableToEval and variableDoneEval (if not null) are guaranteed to be in this Block, since they're co-located // in an active Constraint in this Block. // // For Expand, we want to find the constraint path from violatedConstraint.Left to violatedConstraint.Right; // the latter is in pathTargetVariable. This is ComputePath from the doc. The logic there is: // Do the iterations of ComputeDvDv // If we find the target, then traverse the parent chain to populate the list bottom-up Debug.Assert(0 == this.allConstraints.DfDvStack.Count, "Leftovers in ComputeDfDvStack"); this.allConstraints.DfDvStack.Clear(); // Variables for initializing the first node. var dummyConstraint = new Constraint(initialVarToEval); dfDvDummyParentNode = new DfDvNode(dummyConstraint); var firstNode = GetDfDvNode(dfDvDummyParentNode, dummyConstraint, initialVarToEval, null /*no "done" var yet*/); this.allConstraints.DfDvStack.Push(firstNode); // Iteratively recurse, processing all children of a constraint before the constraint itself. // Loop termination is by testing for completion based on node==firstNode which is faster than // (non-inlined) Stack.Count. for (; ; ) { // Leave the node on the stack until we've processed all of its children. var node = this.allConstraints.DfDvStack.Peek(); int prevStackCount = this.allConstraints.DfDvStack.Count; #if VERBOSE Console.WriteLine("ComputeDfDv peeking at node {0}: varDoneEval {1}, {2}Constraint: {3}", node, node.VariableDoneEval, node.IsLeftToRight ? "Left" : "Right", node.ConstraintToEval); #endif // VERBOSE if (!node.ChildrenHaveBeenPushed) { node.ChildrenHaveBeenPushed = true; foreach (var constraint in node.VariableToEval.LeftConstraints) { // Direct violations (a -> b -> a) are not caught by the constraint-based cycle detection // because VariableDoneEval prevents them from being entered (b -> a is not entered because a is // VariableDoneEval). These cycles should be caught by the null-minLagrangian IsUnsatisfiable // setting in Block.Expand (but assert with IsActive not IsUnsatisfiable, as the constraint // may not have been encountered yet). Test_Unsatisfiable_Cycle_InDirect_With_SingleConstraint_Var. Debug.Assert(!constraint.IsActive || !(node.IsLeftToRight && (constraint.Right == node.VariableDoneEval)), "this cycle should not happen"); if (constraint.IsActive && (constraint.Right != node.VariableDoneEval)) { // variableToEval is now considered "done" var childNode = GetDfDvNode(node, constraint, constraint.Right, node.VariableToEval); // If the node has no constraints other than the one we're now processing, it's a leaf // and we don't need the overhead of pushing to and popping from the stack. if (1 == constraint.Right.ActiveConstraintCount) { ProcessDfDvLeafNodeDirectly(childNode); } else { PushDfDvNode(childNode); } } } foreach (var constraint in node.VariableToEval.RightConstraints) { // See comments in .LeftConstraints. Debug.Assert(!constraint.IsActive || !(!node.IsLeftToRight && (constraint.Left == node.VariableDoneEval)), "this cycle should not happen"); if (constraint.IsActive && (constraint.Left != node.VariableDoneEval)) { var childNode = GetDfDvNode(node, constraint, constraint.Left, node.VariableToEval); if (1 == constraint.Left.ActiveConstraintCount) { ProcessDfDvLeafNodeDirectly(childNode); } else { PushDfDvNode(childNode); } } } // If we just pushed one or more nodes, loop back up and "recurse" into them. if (this.allConstraints.DfDvStack.Count > prevStackCount) { continue; } } // endif !node.ChildrenHaveBeenPushed // We are at a non-leaf node and have "recursed" through all its descendents; therefore pop it off // the stack and process it. If it's the initial node, we've already updated DummyConstraint.Lagrangian // from all child nodes, and it's in the DummyParentNode as well so this will add the final dfdv. #if VERBOSE Console.WriteLine("ComputeDfDv: Node has no children or all have been processed"); #endif // VERBOSE Debug.Assert(this.allConstraints.DfDvStack.Peek() == node, "DfDvStack.Peek() should be 'node'"); this.allConstraints.DfDvStack.Pop(); ProcessDfDvLeafNode(node); if (node == firstNode) { Debug.Assert(0 == this.allConstraints.DfDvStack.Count, "Leftovers in DfDvStack on completion of loop"); break; } } // endwhile stack is not empty #if VERBOSE Console.WriteLine("ComputeDfDv result: {0:F5}", dummyConstraint.Lagrangian); #endif // VERBOSE #if DEBUG // From the definition of the optimal position of all variables that satisfies the constraints, the // final value of this should be zero. Think of the constraints as rigid rods and the variables as // the attachment points of the rods. Also think of those attachment points as having springs connecting // them to their ideal positions. In order for the whole system to be at rest (i.e. at the optimal // position) the net force on the right-hand side of each rod must be equal and opposite to the net // force on the left-hand side. Thus, the final return value of compute_dfdv is the sum of the entire // left-hand side and right-hand side - which should cancel (within rounding error). //// // The dummyConstraint "rolls up" to its parent which is itself, thus it will be twice the leftover. DebugVerifyFinalDfDvValue(dummyConstraint.Lagrangian / 2.0, String.Format(CultureInfo.InvariantCulture, "nonzero final ComputeDfDv value ({0})", dummyConstraint.Lagrangian)); #if COMPARE_RECURSIVE_DFDV DebugVerifyFinalDfDvValue((dummyConstraint.Lagrangian / 2.0) - recursiveDfDv, String.Format(CultureInfo.InvariantCulture, "Unequal DfDv values; Recursive = {0}, iterative = {1}", recursiveDfDv, dummyConstraint.Lagrangian)); #endif // COMPARE_RECURSIVE_DFDV #endif // DEBUG } // end ComputeDfDv()