internal void Add(Block block) { block.VectorIndex = Vector.Count; Vector.Add(block); //Debug_AssertConsistency(); Debug.Assert(Vector[block.VectorIndex] == block, "Inconsistent block.VectorIndex"); }
internal bool FilterBlock(Block blockToFilter) { // Note: The cache does not try to retain strict accordance with highest violation. // Doing so lowers the hit rate, probably because if LastModifiedBlock has enough variables, // then it has enough high violations to flush all other blocks out of the cache, and // thus the next call to FilterBlock removes all for the current block (which per the following // paragraph results in calling SearchAllConstraints). As it turns out, it doesn't // really matter what order we process the constraints in, other than the perf benefit of // doing the largest violations first, so using the max violation in LastModifiedBlock in this // situation seems to be good enough to win the tradeoff. // // If it becomes necessary to maintain strict "cache always contains the highest violations" // compliance, then we would have to return false if the filtering removed all elements of // the cache, because then we wouldn't know if there were any non-blockToFilter-related constraints // with a higher violation (currently we return true in that case because it is good enough to know // there is a good chance that this is true). Also, SearchViolationCache would need a verification in // at least VERIFY mode to verify there are no higher violations in allConstraints. // Iterate in reverse to remove constraints belonging to LastModifiedBlock. // Note: Enumerators and .Where are not used because they are much slower. LowViolation = double.MaxValue; bool fRet = this.numConstraints > 0; for (int ii = this.numConstraints - 1; ii >= 0; --ii) { var constraint = this.constraints[ii]; // Also remove any constraint that may have been activated by MergeBlocks or marked unsatisfiable // by Block.Expand. if ((constraint.Left.Block == blockToFilter) || (constraint.Right.Block == blockToFilter) || constraint.IsActive || constraint.IsUnsatisfiable) { // If there are any items after this one, then they are ones we want to keep, // so swap in the last one in the array before decrementing the count. if (ii < (this.numConstraints - 1)) { this.constraints[ii] = this.constraints[this.numConstraints - 1]; } --this.numConstraints; } else { #if Inline_Violation double violation = constraint.Violation; // Inline_Violation #else // Inline_Violation double violation = (constraint.Left.ActualPos * constraint.Left.Scale) + constraint.Gap - (constraint.Right.ActualPos * constraint.Right.Scale); Debug.Assert(constraint.Violation == violation, "LeftConstraints: constraint.Violation must == violation"); #endif // Inline_Violation if (violation < LowViolation) { LowViolation = violation; } } } if (0 == this.numConstraints) { LowViolation = 0.0; } return fRet; }
internal void Remove(Block block) { Debug.Assert(Vector[block.VectorIndex] == block, "Inconsistent block.VectorIndex"); Block swapBlock = Vector[Vector.Count - 1]; Vector[block.VectorIndex] = swapBlock; swapBlock.VectorIndex = block.VectorIndex; Vector.RemoveAt(Vector.Count - 1); //Debug_AssertConsistency(); Debug.Assert((0 == Vector.Count) || (block == swapBlock) || (Vector[swapBlock.VectorIndex] == swapBlock), "Inconsistent swapBlock.VectorIndex"); Debug.Assert((0 == Vector.Count) || (Vector[Vector.Count - 1].VectorIndex == (Vector.Count - 1)), "Inconsistent finalBlock.VectorIndex"); }
private bool Project() { if (0 == this.numberOfConstraints) { // We are here for the neighbours-only case. return false; } // Get the maximum violation (the Constraint with the biggest difference between the // required gap between its two variables vs. their actual relative positions). // If there is no violation, we're done (although SplitBlocks may change things so // we have to go again). this.violationCache.Clear(); this.lastModifiedBlock = null; bool useViolationCache = this.allBlocks.Count > this.violationCacheMinBlockCutoff; // The first iteration gets the first violated constraint. int cIterations = 1; double maxViolation; Constraint maxViolatedConstraint = GetMaxViolatedConstraint(out maxViolation, useViolationCache); if (null == maxViolatedConstraint) { #if VERBOSE Console.WriteLine("Project() found no violations"); #endif // VERBOSE return false; } // We have at least one violation, so process them until there are no more. while (null != maxViolatedConstraint) { Debug.Assert(!maxViolatedConstraint.IsUnsatisfiable, "maxViolatedConstraint should not be unsatisfiable"); Debug.Assert(!maxViolatedConstraint.IsEquality, "maxViolatedConstraint should not be equality"); #if VERBOSE Console.WriteLine("MaxVio: {0}", maxViolatedConstraint); // Write a line in satisfy_inc format to compare: // most violated is: (45=9.73427)+3<=(51=0.456531)(-12.2777) [chop off lm as it's uninitialized in satisfy_inc output] Console.WriteLine(" most violated is: ({0}={1:F6})+{2:F0}<=({3}={4:F6})({5:F6})", maxViolatedConstraint.Left.Name, maxViolatedConstraint.Left.ActualPos, maxViolatedConstraint.Gap, maxViolatedConstraint.Right.Name, maxViolatedConstraint.Right.ActualPos, maxViolatedConstraint.Violation); #endif // VERBOSE // Perf note: Variables (and Blocks) use the default Object.Equals implementation, which is // simply ReferenceEquals for reference types. if (maxViolatedConstraint.Left.Block == maxViolatedConstraint.Right.Block) { maxViolatedConstraint.Left.Block.Expand(maxViolatedConstraint); if (maxViolatedConstraint.IsUnsatisfiable) { this.violationCache.Clear(); // We're confusing the lineage of lastModifiedBlock } this.lastModifiedBlock = maxViolatedConstraint.Left.Block; } else { // The variables are in different blocks so merge the blocks. this.lastModifiedBlock = MergeBlocks(maxViolatedConstraint); } #if VERBOSE DumpCost(); #endif // VERBOSE // Note that aborting here does not guarantee a feasible state. if (this.solverParams.InnerProjectIterationsLimit > 0) { if (cIterations >= this.solverParams.InnerProjectIterationsLimit) { #if VERBOSE Console.WriteLine("PostProject aborting due to max inner iterations: max {0}", solverParams.InnerProjectIterationsLimit); #endif // VERBOSE this.solverSolution.InnerProjectIterationsLimitExceeded = true; break; } } // Now we've potentially changed one or many variables' positions so recalculate the max violation. useViolationCache = this.allBlocks.Count > this.violationCacheMinBlockCutoff; if (!useViolationCache) { this.violationCache.Clear(); } ++cIterations; maxViolatedConstraint = GetMaxViolatedConstraint(out maxViolation, useViolationCache); } // endwhile violations exist this.solverSolution.InnerProjectIterationsTotal += cIterations; if (this.solverSolution.MaxInnerProjectIterations < cIterations) { this.solverSolution.MaxInnerProjectIterations = cIterations; } if (this.solverSolution.MinInnerProjectIterations > cIterations) { this.solverSolution.MinInnerProjectIterations = cIterations; } // If we got here, we had at least one violation. this.allConstraints.Debug_AssertConsistency(); return true; } // end Project()
private void ReinitializeBlocks() { // For Qpsc we want to discard the previous block structure, because it did not consider // neighbors, and the gradient may want to pull things in an entirely different way. // We must also do this for a re-Solve that updated the gap of an equality constraint. var oldBlocks = this.allBlocks.Vector.ToArray(); this.allBlocks.Vector.Clear(); #if VERIFY || VERBOSE this.nextNewBlockOrdinal = 0; #endif // VERIFY || VERBOSE foreach (Block oldBlock in oldBlocks) { foreach (Variable variable in oldBlock.Variables) { variable.Reinitialize(); var newBlock = new Block(variable, this.allConstraints #if VERIFY || VERBOSE , ref this.nextNewBlockOrdinal #endif // VERIFY || VERBOSE ); this.allBlocks.Add(newBlock); } } this.allConstraints.Reinitialize(); this.violationCache.Clear(); }
/// <summary> /// Add a Variable (for example, wrapping a node on one axis of the graph) to the Solver. /// </summary> /// <param name="userData">a tag or other user data - can be null</param> /// <param name="desiredPos">The position of the variable, such as the coordinate of a node along one axis.</param> /// <param name="weight">The weight of the variable (makes it less likely to move if the weight is high).</param> /// <param name="scale">The scale of the variable, for improving convergence.</param> /// <returns>The created variable</returns> public Variable AddVariable(Object userData, double desiredPos, double weight, double scale) { // @@DCR "Incremental Solving": For now we disallow this; if we support it, we'll need to // retain loadedVariablesAndConstraintLists, store up the added Variables (TryGetValue and if that fails add // the existing variable, then iterate through variables with new Constraints and replace the arrays. // Also remember to check for emptyConstraintList - don't add to it. if (!this.allConstraints.IsEmpty) { throw new InvalidOperationException( #if DEBUG "Cannot add Variables or Constraints once Solve() has been called" #endif // DEBUG ); } var varNew = new Variable(this.nextVariableOrdinal++, userData, desiredPos, weight, scale); var block = new Block(varNew, this.allConstraints #if VERIFY || VERBOSE , ref this.nextNewBlockOrdinal #endif // VERIFY || VERBOSE ); varNew.Block = block; this.allBlocks.Add(block); ++this.numberOfVariables; // Initialize the variable in the dictionary with a null list and zero left constraints. this.loadedVariablesAndConstraintLists[varNew] = new ConstraintListForVariable(new List<Constraint>(), 0); return varNew; } // end AddVariable()
internal Block SplitOnConstraint(Constraint constraintToSplit #if VERIFY || VERBOSE , ref uint blockId #endif // VERIFY || VERBOSE ) { // We have a split point. Remove that constraint from our active list and transfer it and all // variables to its right to a new block. As mentioned above, all variables and associated // constraints in the block are active, and the block split and recalc of reference positions // doesn't change the actual positions of any variables. this.allConstraints.DeactivateConstraint(constraintToSplit); var newSplitBlock = new Block(null, this.allConstraints #if VERIFY || VERBOSE , ref blockId #endif // VERIFY || VERBOSE ); // Transfer the connected variables. This has the side-effect of moving the associated active // constraints as well (because they are carried in the variables' LeftConstraints). // This must include not only minLagrangianConstraint.Right and those to its right, but also // those to its left that are connected to it by active constraints - because connected variables // must be within a single a block. Since we are splitting the constraint, there will be at least // one variable (minLagrangianConstraint.Left) in the current block when we're done. Because the active // constraints form a tree, we won't have a situation where minLagrangianConstraint.Left is // also the .Right of a constraint of a variable to the left of varRight. // minLagrangianConstraint.Left is "already evaluated" because we don't want the path evaluation to // back up to it (because we're splitting minLagrangianConstraint by deactivating it). this.DebugVerifyBlockConnectivity(); this.TransferConnectedVariables(newSplitBlock, constraintToSplit.Right, constraintToSplit.Left); if (newSplitBlock.Variables.Count > 0) { // We may have removed the first variable so fully recalculate the reference position. this.UpdateReferencePos(); // The new block's sums were not updated as its variables were added directly to its // variables list, so fully recalculate. newSplitBlock.UpdateReferencePos(); this.DebugVerifyBlockConnectivity(); newSplitBlock.DebugVerifyBlockConnectivity(); #if VERBOSE Console.WriteLine("Block.Split Result: {0} {1}", this, newSplitBlock); Console.WriteLine("Old block: {0}", this); this.DumpState(null /* no prefix */); Console.WriteLine("New block: {0}", newSplitBlock); newSplitBlock.DumpState(null /* no prefix */); #endif // VERBOSE } else { // If there were unsatisfiable constraints, we may have tried to transfer all variables; // in that case we simply ignored the transfer operation and left all variables in 'this' block. // Return NULL so Solver.SplitBlocks knows we didn't split. newSplitBlock = null; #if VERBOSE Console.WriteLine("Block.Split Result: all variables would be moved, so skipping"); this.DumpState(null /* no prefix */); #endif // VERBOSE } return newSplitBlock; } // end Split()
internal void Debug_PostMerge(Block blockFrom) { #if DEBUG // If blockFrom's DfDv-cycle detection value was higher than ours, we need to set ours to // that value, to avoid running into stale values. if (blockFrom.idDfDv > this.idDfDv) { this.idDfDv = blockFrom.idDfDv; } #endif // DEBUG }
void TransferConnectedVariables(Block newSplitBlock, Variable varToEval, Variable varDoneEval) { GetConnectedVariables(newSplitBlock.Variables, varToEval, varDoneEval); int numVarsToMove = newSplitBlock.Variables.Count; // cache for perf // The constraints transferred to the new block need to have any stale cycle-detection values cleared out. newSplitBlock.Debug_ClearDfDv(true /* forceFull */); // Avoid the creation of an inner loop on List<T>.Remove (which does linear scan and shift // to preserve the order of members). We don't care about variable ordering within the block // so we can just repeatedly swap in the end one over whichever we're removing. for (int moveIndex = 0; moveIndex < numVarsToMove; ++moveIndex) { newSplitBlock.Variables[moveIndex].Block = newSplitBlock; } // Now iterate from the end and swap in the last one we'll keep over the ones we'll remove. int lastKeepIndex = this.Variables.Count - 1; for (int currentIndex = this.Variables.Count - 1; currentIndex >= 0; --currentIndex) { Variable currentVariable = this.Variables[currentIndex]; if (currentVariable.Block == newSplitBlock) { if (currentIndex < lastKeepIndex) { // Swap in the one from the end. this.Variables[currentIndex] = this.Variables[lastKeepIndex]; } --lastKeepIndex; } } // end for each var to keep // Now remove the end slots we're not keeping. lastKeepIndex is -1 if we are removing all variables. Debug.Assert(numVarsToMove == this.Variables.Count - lastKeepIndex - 1, "variable should not be found twice (probable cycle-detection problem"); this.Variables.RemoveRange(lastKeepIndex + 1, this.Variables.Count - lastKeepIndex - 1); if (0 == this.Variables.Count) { // This is probably due to unsatisfiable constraints; we've transferred all the variables, // so just don't split at all; move the variables back into the current block rather than // leaving an empty block in the list. Caller will detect the empty newSplitBlock and ignore it. for (int moveIndex = 0; moveIndex < numVarsToMove; ++moveIndex) { var variableToMove = newSplitBlock.Variables[moveIndex]; this.Variables.Add(variableToMove); variableToMove.Block = this; } newSplitBlock.Variables.Clear(); } } // end TransferConnectedVariables()