Exemple #1
0
        /// <summary>
        /// Deep-copy the SolverParameters.
        /// </summary>
        /// <returns></returns>
        public object Clone()
        {
            OverlapRemovalParameters newParams = (OverlapRemovalParameters)this.MemberwiseClone();

            newParams.SolverParameters = (Parameters)this.SolverParameters.Clone();
            return(newParams);
        }
Exemple #2
0
        /// <summary>
        /// Generates and solves the constraints.
        /// </summary>
        /// <param name="solver">The solver to generate into and solve.  May be null, in which case one
        ///                     is created by the method.</param>
        /// <param name="parameters">Parameters to OverlapRemoval and ProjectionSolver.Solver.Solve().</param>
        /// <param name="doGenerate">Generate constraints before solving; if false, solver is assumed to
        ///                     have already been populated by this.Generate().</param>
        /// <returns>The set of OverlapRemoval.Constraints that were unsatisfiable, or NULL.</returns>
        public ProjectionSolver.Solution Solve(ProjectionSolver.Solver solver,
                                               OverlapRemovalParameters parameters, bool doGenerate)
        {
            if (null == solver)
            {
                solver = new ProjectionSolver.Solver();
            }

            if (null == parameters)
            {
                parameters = new OverlapRemovalParameters();
            }

            if (doGenerate)
            {
                Generate(solver, parameters);
            }

            ProjectionSolver.Solution solverSolution = solver.Solve(parameters.SolverParameters);
            foreach (OverlapRemovalCluster cluster in this.clusterHierarchies)
            {
                cluster.UpdateFromVariable();  // "recursively" processes all child clusters
            }
            return(solverSolution);
        }
        /// <summary>
        /// Generate the necessary constraints to ensure there is no overlap (unless we're doing
        /// a horizontal pass and deferring some movement, which would be smaller, to the vertical pass).
        /// </summary>
        /// <param name="solver">The solver to generate into.</param>
        /// <param name="parameters">Parameters to OverlapRemoval and ProjectionSolver.Solver.Solve().</param>
        public void Generate(ProjectionSolver.Solver solver, OverlapRemovalParameters parameters)
        {
            ValidateArg.IsNotNull(solver, "solver");
            if (null == parameters)
            {
                parameters = new OverlapRemovalParameters();
            }
            foreach (OverlapRemovalCluster cluster in this.clusterHierarchies)
            {
                cluster.Generate(solver, parameters, this.IsHorizontal);
            }

            // For Clusters we reposition their "fake border" variables between the constraint-generation
            // and solving phases, so we need to tell the solver to do this.
            solver.UpdateVariables();   // @@PERF: Not needed if no clusters were created.
        }
        private void VerifyAxisResults(uint rep, ConstraintGenerator generator, OverlapRemovalParameters olapParameters, ref bool succeeded)
        {
            var axisName = generator.IsHorizontal ? "X" : "Y";
            var solver = generator.IsHorizontal ? this.SolverX : this.SolverY;
            var solution = generator.IsHorizontal ? this.SolutionX : this.SolutionY;
            if (TestGlobals.VerboseLevel >= 3)
            {
                this.WriteLine("  {0} Nodes after solving Constraints:", axisName);
                foreach (Variable var in solver.Variables)
                {
                    var node = (OverlapRemovalNode)var.UserData;
                    Console.Write("    {0}", node);

                    const string Format = " - L/R T/B {0:F5}/{1:F5} {2:F5}/{3:F5}";
                    if (generator.IsHorizontal)
                    {
                        // X is Perpendicular here
                        this.WriteLine(Format, node.OpenP, node.CloseP, node.Open, node.Close);
                    }
                    else
                    {
                        // Y is Perpendicular here
                        this.WriteLine(Format, node.Open, node.Close, node.OpenP, node.CloseP);
                    }
                }
            }

            // We should never see unsatisfiable constraints since we created them ourselves.
            if (0 != solution.NumberOfUnsatisfiableConstraints)
            {
                succeeded = false;
                this.WriteLine(" *** Error! {0} unsatisfiable {1} constraints found ***",
                        solution.NumberOfUnsatisfiableConstraints, axisName);
                if ((TestGlobals.VerboseLevel >= 2) && (0 == rep))
                {
                    foreach (Constraint cst in solver.Constraints.Where(cst => cst.IsUnsatisfiable))
                    {
                        this.WriteLine("      {0}", cst);
                    }
                } // endif VerboseLevel
            } // endif unsatisfiable constraints

            bool violationSeen = false;
            foreach (Constraint cst in solver.Constraints)
            {
                if (!this.VerifyConstraint(olapParameters.SolverParameters, cst, generator.IsHorizontal, ref violationSeen))
                {
                    succeeded = false;
                }
            }

            if (solution.ExecutionLimitExceeded)
            {
                this.WriteLine(GetCutoffString(solution));
            }
        }
        private void AddNodesAndSolve(ConstraintGenerator generator,
                        IEnumerable<VariableDef> iterVariableDefs,
                        OverlapRemovalParameters olapParameters,
                        IEnumerable<ConstraintDef> iterConstraintDefs,
                        ref bool succeeded)
        {
            var axisName = generator.IsHorizontal ? "X" : "Y";
            var solver = generator.IsHorizontal ? this.SolverX : this.SolverY;
            foreach (VariableDef varDef in iterVariableDefs)
            {
                // Create the variable in its initial cluster.
                OverlapRemovalCluster olapClusParent = generator.DefaultClusterHierarchy;
                if (varDef.ParentClusters.Count > 0)
                {
                    olapClusParent = varDef.ParentClusters[0].Cluster;
                }
                OverlapRemovalNode newNode;
                if (generator.IsHorizontal)
                {
                    newNode = generator.AddNode(olapClusParent, varDef.IdString,
                                           varDef.DesiredPosX, varDef.DesiredPosY,
                                           varDef.SizeX, varDef.SizeY, varDef.WeightX);
                    varDef.VariableX = new OlapTestNode(newNode);
                }
                else 
                {
                    newNode = generator.AddNode(olapClusParent, varDef.IdString,
                                           varDef.DesiredPosY, varDef.VariableX.ActualPos,
                                           varDef.SizeY, varDef.SizeX, varDef.WeightY);
                    varDef.VariableY = new OlapTestNode(newNode);
                }

                // Add it to its other clusters.
                for (int ii = 1; ii < varDef.ParentClusters.Count; ++ii)
                {
                    olapClusParent = varDef.ParentClusters[ii].Cluster;
                    generator.AddNodeToCluster(olapClusParent, newNode);
                }
            }
            generator.Generate(solver, olapParameters);
            if (TestGlobals.VerboseLevel >= 3)
            {
                this.WriteLine("  {0} Constraints:", axisName);
                foreach (Constraint cst in solver.Constraints.OrderBy(cst => cst))
                {
                    this.WriteLine("    {0} {1} g {2:F5}", cst.Left.UserData, cst.Right.UserData, cst.Gap);
                }
            }

            if (null != iterConstraintDefs)
            {
                // TODO: Compare expected to actual constraints generated in solver
            }

            var solution = generator.Solve(solver, olapParameters, false /* doGenerate */);
            if (!this.VerifySolutionMembers(solution, /*iterNeighborDefs:*/ null))
            {
                succeeded = false;
            }
            if (generator.IsHorizontal)
            {
                this.SolutionX = solution;
            }
            else
            {
                this.SolutionY = solution;
            }
        }
        private void CreateSolversAndGetSolutions(Stopwatch sw, IEnumerable<ClusterDef> iterClusterDefs, IEnumerable<VariableDef> iterVariableDefs, IEnumerable<ConstraintDef> iterConstraintDefsX, IEnumerable<ConstraintDef> iterConstraintDefsY, ref bool succeeded)
        {
            var olapParameters = new OverlapRemovalParameters(this.SolverParameters)
                {
                    AllowDeferToVertical = this.AllowDeferToVertical
                };

            for (uint rep = 0; rep < TestGlobals.TestReps; ++rep)
            {
                sw.Start();

                // Load the Horizontal ProjectionSolver and ConstraintGenerator with variables.
                // We must Solve the X coordinates before generating the Y ones, so that the
                // Y generation, whose scan line uses X coordinates, uses the updated values.
                var generatorX = new ConstraintGenerator(true /* fIsHorizontal */, this.MinPaddingX, this.MinPaddingY);
                this.SolverX = new Solver();

                // First create the X Clusters.
                if (null != iterClusterDefs)
                {
                    foreach (ClusterDef clusDef in iterClusterDefs)
                    {
                        clusDef.ComputeInitialBorders();  // called for H only; does both H and V
                        clusDef.CreateCluster(generatorX);
                    }
                }
                this.AddNodesAndSolve(generatorX, iterVariableDefs, olapParameters, iterConstraintDefsX, ref succeeded);

                sw.Stop();

                this.VerifyAxisResults(rep, generatorX, olapParameters, ref succeeded);

                sw.Start();

                // Load the Vertical ProjectionSolver and ConstraintGenerator with variables.
                // This uses the X coordinate determined by the above solution for the perpendicular.
                var generatorY = new ConstraintGenerator(false /* fIsHorizontal */, this.MinPaddingY, this.MinPaddingX);
                this.SolverY = new Solver();

                // First create the Y Clusters.
                if (null != iterClusterDefs)
                {
                    // Clear out the ConGenX Clusters first, then create them in ConGenY.
                    foreach (ClusterDef clusDef in iterClusterDefs)
                    {
                        if (!clusDef.PostX())
                        {
                            succeeded = false;
                        }
                    }
                    foreach (ClusterDef clusDef in iterClusterDefs)
                    {
                        clusDef.CreateCluster(generatorY);
                    }
                }

                this.AddNodesAndSolve(generatorY, iterVariableDefs, olapParameters, iterConstraintDefsY, ref succeeded);

                sw.Stop();

                if (null != iterClusterDefs)
                {
                    foreach (ClusterDef clusDef in iterClusterDefs)
                    {
                        if (!clusDef.PostY())
                        {
                            succeeded = false;
                        }
                    }
                }

                this.VerifyAxisResults(rep, generatorY, olapParameters, ref succeeded);
            }
        }
        } // end GetLeftNeighbours

         List<OverlapRemovalNode> GetRightNeighbours(OverlapRemovalParameters parameters, ScanLine scanLine,
                                    OverlapRemovalNode currentNode, bool isHorizontal)
        {
            var lstNeighbours = new List<OverlapRemovalNode>();
            OverlapRemovalNode nextNode = scanLine.NextRight(currentNode);
            for (; null != nextNode; nextNode = scanLine.NextRight(nextNode))
            {
                // AddNeighbor returns false if we are done adding them.
                if (!AddNeighbour(parameters, currentNode, nextNode, lstNeighbours, false /* isLeftNeighbor */
                                , isHorizontal))
                {
                    if (!nextNode.DeferredRightNeighborToV)
                    {
                        break;
                    }
                }
            } // endfor NextLeft
            return lstNeighbours;
        } // end GetRightNeighbours
        } // end GetRightNeighbours

         bool AddNeighbour(OverlapRemovalParameters parameters, OverlapRemovalNode currentNode, OverlapRemovalNode nextNode,
                        List<OverlapRemovalNode> neighbors, bool isLeftNeighbor, bool isHorizontal)
        {
            // Sanity check to be sure that the borders are past all other nodes.
            Debug.Assert(currentNode != (isLeftNeighbor ? this.LeftBorderNode : this.RightBorderNode), "currentNode must != BorderNode");

            double overlap = Overlap(currentNode, nextNode, NodePadding);
            if (overlap <= 0)
            {
                // This is the first node encountered on this neighbour-traversal that did not
                // overlap within the required padding. Add it to the list and we're done with
                // this traversal, unless this is a vertical pass and it is not an overlap on
                // the horizontal axis; in that case, pretend we never saw it and return true
                // so the next non-overlapping node will be found.  (See below for more information
                // on why this is necessary).
                if (!isHorizontal && (OverlapP(currentNode, nextNode, NodePaddingP) <= parameters.SolverParameters.GapTolerance))
                {
#if VERBOSE
                    Console.WriteLine(" V {0}nbourHTolSkipNO: {1}", isLeftNeighbor ? "L" : "R", nextNode);
#endif // VERBOSE
                    return true;
                }
                neighbors.Add(nextNode);
#if VERBOSE
                Console.WriteLine("{0}nbourAddNO: {1}", isLeftNeighbor ? "L" : "R", nextNode);
#endif // VERBOSE
                return false;
            }

            if (isHorizontal)
            {
                if (parameters.AllowDeferToVertical)
                {
                    // We are doing horizontal constraints so examine the vertical overlap and see which
                    // is the smallest (normalized by total node size in that orientation) such that the
                    // least amount of movement required.  this.padding is currently the same in both
                    // directions; if this changes, we'll have to add different padding values here for
                    // each direction.  @@DCR: consider adding weights to the defer-to-vertical calculation;
                    // this would allow two nodes to pop up/down if they're being squeezed, rather than
                    // force apart the borders (which happens regardless of their weight).
                    double overlapP = OverlapP(currentNode, nextNode, NodePaddingP);
                    bool isOverlapping =
                         parameters.ConsiderProportionalOverlap
                        ? overlap / (currentNode.Size + nextNode.Size) > overlapP / (currentNode.SizeP + nextNode.SizeP)
                        : overlap > overlapP;
                    if (isOverlapping)
                    {
                        // Don't skip if either of these is a border node.
                        if ((currentNode != this.LeftBorderNode)
                                && (currentNode != this.RightBorderNode)
                                && (nextNode != this.LeftBorderNode)
                                && (nextNode != this.RightBorderNode))
                        {
                            // Moving in the horizontal direction requires more movement than in the vertical
                            // direction to remove the overlap, so skip this node on horizontal constraint
                            // generation and we'll pick it up on vertical constraint generation.  Return true
                            // to keep looking for more overlapping nodes.
                            // Note: it is still possible that we'll pick up a constraint in both directions,
                            // due to either or both of this.padding and the "create a constraint to the first
                            // non-overlapping node" logic.  This is expected and the latter helps retain stability.
#if VERBOSE
                            Console.WriteLine("{0}nbourDeferToV: {1}", isLeftNeighbor ? "L" : "R", nextNode);
#endif // VERBOSE
                            // We need to track whether we skipped these so that we don't have a broken transition chain.
                            // See Test_OverlapRemoval.cs, Test_DeferToV_Causing_Missing_Cst() for more information.
                            if (isLeftNeighbor)
                            {
                                currentNode.DeferredLeftNeighborToV = true;
                                nextNode.DeferredRightNeighborToV = true;
                            }
                            else
                            {
                                currentNode.DeferredRightNeighborToV = true;
                                nextNode.DeferredLeftNeighborToV = true;
                            }
                            return true;
                        }
                    } // endif Overlap is greater than OverlapP
                } // endif AllowDeferToVertical
            }
            else
            {
                // We're on the vertical pass so make sure we match up with the Solver's tolerance in the
                // scanline direction, because it is possible that there was a horizontal constraint between
                // these nodes that was within the Solver's tolerance and thus was not enforced.  In that
                // case, we could spuriously add a vertical constraint here that would result in undesired
                // and possibly huge vertical movement.  There is a corresponding Assert during constraint
                // generation when the node is Closed. We have to do this here rather than at runtime because
                // doing it then may skip a Neighbour that replaced other Neighbors by transitivity.
                if (OverlapP(currentNode, nextNode, NodePaddingP) <= parameters.SolverParameters.GapTolerance)
                {
#if VERBOSE
                    Console.WriteLine(" V {0}nbourHTolSkipO: {1}", isLeftNeighbor ? "L" : "R", nextNode);
#endif // VERBOSE
                    return true;
                }
            }

            // Add this overlapping neighbour and return true to keep looking for more overlapping neighbours.
            neighbors.Add(nextNode);
#if VERBOSE
            Console.WriteLine("{0}nbourAddO: {1}", isLeftNeighbor ? "L" : "R", nextNode);
#endif // VERBOSE
            return true;
        }
         void GenerateFromEvents(Solver solver, OverlapRemovalParameters parameters,
                        List<Event> events, bool isHorizontal)
        {
            // First, sort the events on the perpendicular coordinate of the event
            // (e.g. for horizontal constraint generation, order on vertical position).
            events.Sort();

#if VERBOSE
            Console.WriteLine("Events:");
            foreach (Event evt in events)
            {
                Console.WriteLine("    {0}", evt);
            }
#endif // VERBOSE

            var scanLine = new ScanLine();
            foreach (Event evt in events)
            {
                OverlapRemovalNode currentNode = evt.Node;
                if (evt.IsForOpen)
                {
                    // Insert the current node into the scan line.
                    scanLine.Insert(currentNode);
#if VERBOSE
                    Console.WriteLine("ScanAdd: {0}", currentNode);
#endif // VERBOSE

                    // Find the nodes that are currently open to either side of it and are either overlapping
                    // nodes or the first non-overlapping node in that direction.
                    currentNode.LeftNeighbors = GetLeftNeighbours(parameters, scanLine, currentNode, isHorizontal);
                    currentNode.RightNeighbors = GetRightNeighbours(parameters, scanLine, currentNode, isHorizontal);

                    // Use counts for indexing for performance (rather than foreach, and hoist the count-control
                    // variable out of the loop so .Count isn't checked on each iteration, since we know it's
                    // not going to be changed).
                    int numLeftNeighbors = currentNode.LeftNeighbors.Count;
                    int numRightNeighbors = currentNode.RightNeighbors.Count;

                    // If there is currently a non-overlap constraint between any two nodes across the
                    // two neighbour lists we've just created, we can remove them because they will be
                    // transitively enforced by the constraints we'll create for the current node.
                    // I.e., we can remove the specification for the constraint
                    //      leftNeighborNode + gap + padding <= rightNeighborNode
                    // because it is implied by the constraints we'll create for
                    //      leftNeighborNode + gap + padding <= node
                    //      node + gap + padding <= rightNeighborNode
                    // We must also add the current node as a neighbour in the appropriate direction.
                    // @@PERF: List<T>.Remove is a sequential search so averages 1/2 the size of the
                    // lists. We currently don't expect the neighbour lists to be very large or Remove
                    // to be a frequent operation, and using HashSets would incur the GetEnumerator overhead
                    // on the outer and inner loops; but .Remove creates an inner-inner loop so do some
                    // timing runs to compare performance.
                    // @@PERF:  handles the case where we are node c and have added node b as a lnbour
                    // and node d as rnbour, where those nodes are already nbours.  But it does not handle
                    // the case where we add node b and node a as lnbours, and node b already has node a
                    // as an lnbour.  To do this I think we'd just want to skip adding the node-a lnbour,
                    // but that forms a new inner loop (iterating all lnbours before adding a new one)
                    // unless we develop different storage for nbours.
                    for (int ii = 0; ii < numLeftNeighbors; ++ii)
                    {
                        OverlapRemovalNode leftNeighborNode = currentNode.LeftNeighbors[ii];
                        for (int jj = 0; jj < numRightNeighbors; ++jj)
                        {     // TODOunit: test this
                            OverlapRemovalNode nodeToRemove = currentNode.RightNeighbors[jj];
                            if (leftNeighborNode.RightNeighbors.Remove(nodeToRemove))
                            {
#if VERBOSE
                                Console.WriteLine(" {0} RnbourRem {1} --> {2}", isHorizontal ? "H" : "V", leftNeighborNode, nodeToRemove);
#endif // VERBOSE
                            }
                        }
                        leftNeighborNode.RightNeighbors.Add(currentNode);
                    }
                    for (int ii = 0; ii < numRightNeighbors; ++ii)
                    {         // TODOunit: test this
                        OverlapRemovalNode rightNeighborNode = currentNode.RightNeighbors[ii];
                        for (int jj = 0; jj < numLeftNeighbors; ++jj)
                        {
                            OverlapRemovalNode nodeToRemove = currentNode.LeftNeighbors[jj];
                            if (rightNeighborNode.LeftNeighbors.Remove(nodeToRemove))
                            {
#if VERBOSE
                                Console.WriteLine(" {0} LnbourRem {1} --> {2}", isHorizontal ? "H" : "V", nodeToRemove, rightNeighborNode);
#endif // VERBOSE
                            }
                        }
                        rightNeighborNode.LeftNeighbors.Add(currentNode);
                    }
                } // endif evt.IsForOpen
                else
                {
                    // This is a close event, so generate the constraints and remove the closing node
                    // from its neighbours lists.  If we're closing we should have left neighbours so
                    // this is null then we've likely got some sort of internal calculation error.
                    if (null == currentNode.LeftNeighbors)
                    {
                        Debug.Assert(null != currentNode.LeftNeighbors, "LeftNeighbors should not be null for a Close event");
                        continue;
                    }

                    // currentNode is the current node; if it's a cluster, translate it to the node that
                    // should be involved in the constraint (if it's the left neighbour then use its
                    // right border as the constraint variable, and vice-versa).
                    OverlapRemovalNode currentLeftNode = GetLeftConstraintNode(currentNode);
                    OverlapRemovalNode currentRightNode = GetRightConstraintNode(currentNode);

                    // LeftNeighbors must end before the current node...
                    int cLeftNeighbours = currentNode.LeftNeighbors.Count;
                    for (int ii = 0; ii < cLeftNeighbours; ++ii)
                    {
                        // Keep track of the original Node; it may be the base of a Cluster, in which
                        // case it will have the active neighbours list, not leftNeighborNode (which will
                        // be the left border "fake Node").
                        OverlapRemovalNode origLeftNeighborNode = currentNode.LeftNeighbors[ii];
                        origLeftNeighborNode.RightNeighbors.Remove(currentNode);
                        OverlapRemovalNode leftNeighborNode = GetLeftConstraintNode(origLeftNeighborNode);
                        Debug.Assert(leftNeighborNode.OpenP == origLeftNeighborNode.OpenP, "leftNeighborNode.OpenP must == origLeftNeighborNode.OpenP");

                        // This assert verifies we match the Solver.ViolationTolerance check in AddNeighbor.
                        // We are closing the node here so use an alternative to OverlapP for additional
                        // consistency verification.  Allow a little rounding error.
                        Debug.Assert(isHorizontal
                                || ((currentNode.CloseP + NodePaddingP - leftNeighborNode.OpenP) > (parameters.SolverParameters.GapTolerance - 1e-6)),
                                "LeftNeighbors: unexpected close/open overlap");

                        double p = leftNeighborNode == LeftBorderNode || currentRightNode == RightBorderNode ? ClusterPadding : NodePadding;
                        double separation = ((leftNeighborNode.Size + currentRightNode.Size) / 2) + p;
                        if (TranslateChildren)
                        {
                            separation = Math.Max(separation, currentRightNode.Position - leftNeighborNode.Position);
                        }
                        Constraint cst = solver.AddConstraint(leftNeighborNode.Variable, currentRightNode.Variable, separation);
                        Debug.Assert(null != cst, "LeftNeighbors: unexpected null cst");
#if VERBOSE
                        Console.WriteLine(" {0} LnbourCst {1} -> {2} g {3:F5}", isHorizontal ? "H" : "V"
                                , cst.Left.Name, cst.Right.Name, cst.Gap);
#endif // VERBOSE
                    }

                    // ... and RightNeighbors must start after the current node.
                    int cRightNeighbours = currentNode.RightNeighbors.Count;
                    for (int ii = 0; ii < cRightNeighbours; ++ii)
                    {
                        // Keep original node, which may be a cluster; see comments in LeftNeighbors above.
                        OverlapRemovalNode origRightNeighborNode = currentNode.RightNeighbors[ii];
                        origRightNeighborNode.LeftNeighbors.Remove(currentNode);
                        OverlapRemovalNode rightNeighborNode = GetRightConstraintNode(origRightNeighborNode);

                        // This assert verifies we match the Solver.ViolationTolerance check in AddNeighbor.
                        // Allow a little rounding error.
                        Debug.Assert(isHorizontal
                                || ((currentNode.CloseP + NodePaddingP - rightNeighborNode.OpenP) > (parameters.SolverParameters.GapTolerance - 1e-6)),
                                "RightNeighbors: unexpected close/open overlap");

                        double p = currentLeftNode == LeftBorderNode || rightNeighborNode == RightBorderNode ? ClusterPadding : NodePadding;
                        double separation = ((currentLeftNode.Size + rightNeighborNode.Size) / 2) + p;
                        if (TranslateChildren)
                        {
                            separation = Math.Max(separation, rightNeighborNode.Position - currentLeftNode.Position);
                        }
                        Constraint cst = solver.AddConstraint(currentLeftNode.Variable, rightNeighborNode.Variable, separation);
                        Debug.Assert(null != cst, "RightNeighbors: unexpected null cst");
#if VERBOSE
                        Console.WriteLine(" {0} RnbourCst {1} -> {2} g {3:F5}", isHorizontal ? "H" : "V"
                                , cst.Left.Name, cst.Right.Name, cst.Gap);
#endif // VERBOSE
                    }

                    // Note:  although currentNode is closed, there may still be open nodes in its
                    // Neighbour lists; these will subsequently be processed (and removed from
                    // currentNode.*Neighbour) when those Neighbors are closed.
                    scanLine.Remove(currentNode);
#if VERBOSE
                    Console.WriteLine("ScanRem: {0}", currentNode);
#endif // VERBOSE
                } // endelse !evt.IsForOpen

                // @@PERF:  Set Node.Left/RightNeighbors null to let the GC know we're not using them
                // anymore, unless we can reasonably assume a short lifetime for the ConstraintGenerator.
            } // endforeach Event
        }
        // Returns false if the cluster is empty; this handles nested clusters of empty clusters.
        // TODOunit: several of the test files cover this but add a specific test for it.
         bool GenerateWorker(Solver solver, OverlapRemovalParameters parameters,
                             bool isHorizontal)
        {
            // @@DCR "Precalculate Cluster Sizes": if we are solving per-cluster to calculate best sizes before
            // generating constraints, then solver would be passed in as null and we'd create one here.

            // Variables to calculate our boundaries.  Top and Bottom refer to the perpendicular direction;
            // for vertical, read Top <-> Left and Bottom <-> Right.
            var boundaryRect = new Rectangle
                {
                    //Left =
                    //    this.OpenBorderInfo.IsFixedPosition && this.TranslateChildren
                    //        ? this.OpenBorderInfo.FixedPosition
                    //        : double.MaxValue,
                    //Right =
                    //    this.CloseBorderInfo.IsFixedPosition && this.TranslateChildren
                    //        ? this.CloseBorderInfo.FixedPosition
                    //        : double.MinValue,
                    //Bottom =
                    //    this.OpenBorderInfoP.IsFixedPosition && this.TranslateChildren
                    //        ? this.OpenBorderInfoP.FixedPosition
                    //        : double.MaxValue,
                    //Top =
                    //    this.CloseBorderInfoP.IsFixedPosition && this.TranslateChildren
                    //        ? this.CloseBorderInfoP.FixedPosition
                    //        : double.MinValue
                    Left = double.MaxValue,
                    Right = double.MinValue,
                    Bottom = double.MaxValue,
                    Top = double.MinValue
                };

            if (IsEmpty)
            {
                // Nothing to generate.
                return false;
            }

            // The list of open/close events, which will be sorted on the perpendicular coordinate of the event
            // (e.g. for horizontal constraint generation, order on vertical position).
            var events = this.CreateEvents(solver, ref boundaryRect);

            // If we added no events, we're either Fixed (so continue) or empty (so return).
            if (0 == events.Count && !TranslateChildren)
            {
                return false;
            }

            // Top/Bottom are considered the secondary (Perpendicular) axis here.
            double leftBorderWidth = DefaultBorderWidth;
            double rightBorderWidth = DefaultBorderWidth;
            if (!this.IsRootCluster)
            {
                CalculateBorderWidths(solver, events, boundaryRect, out leftBorderWidth, out rightBorderWidth);
#if VERBOSE
                Console.WriteLine(" {0} After CalculateBorderWidths: p {1:F5} s {2:F5} pP {3:F5} sP {4:F5}"
                        , this.Name, this.Size, this.Position, this.Size, this.SizeP);
#endif
            }

            GenerateFromEvents(solver, parameters, events, isHorizontal);

            if (!this.IsRootCluster)
            {
                // Non-fixed borders are moved later by SqueezeNonFixedBorderPositions().
                this.AdjustFixedBorderPositions(solver, leftBorderWidth, rightBorderWidth, isHorizontal);
            }
            return true;
        }
 internal void Generate(Solver solver, OverlapRemovalParameters parameters, bool isHorizontal)
 {
     ProcessClusterHierarchy(this,
             cluster => cluster.IsInSolver = cluster.GenerateWorker(solver, parameters, isHorizontal));
     ProcessClusterHierarchy(this, cluster => cluster.SqueezeNonFixedBorderPositions());
 }
 /// <summary>
 /// Generates and solves the constraints.
 /// </summary>
 /// <param name="solver">The solver to generate into and solve.  May be null, in which case one
 ///                     is created by the method.</param>
 /// <param name="parameters">Parameters to OverlapRemoval and ProjectionSolver.Solver.Solve().</param>
 /// <param name="doGenerate">Generate constraints before solving; if false, solver is assumed to
 ///                     have already been populated by this.Generate().</param>
 /// <returns>The set of OverlapRemoval.Constraints that were unsatisfiable, or NULL.</returns>
 public ProjectionSolver.Solution Solve(ProjectionSolver.Solver solver,
                             OverlapRemovalParameters parameters, bool doGenerate)
 {
     if (null == solver)
     {
         solver = new ProjectionSolver.Solver();
     }
     if (null == parameters)
     {
         parameters = new OverlapRemovalParameters();
     }
     if (doGenerate)
     {
         Generate(solver, parameters);
     }
     ProjectionSolver.Solution solverSolution = solver.Solve(parameters.SolverParameters);
     foreach (OverlapRemovalCluster cluster in this.clusterHierarchies)
     {
         cluster.UpdateFromVariable();   // "recursively" processes all child clusters
     }
     return solverSolution;
 }
        /// <summary>
        /// Generate the necessary constraints to ensure there is no overlap (unless we're doing
        /// a horizontal pass and deferring some movement, which would be smaller, to the vertical pass).
        /// </summary>
        /// <param name="solver">The solver to generate into.</param>
        /// <param name="parameters">Parameters to OverlapRemoval and ProjectionSolver.Solver.Solve().</param>
        public void Generate(ProjectionSolver.Solver solver, OverlapRemovalParameters parameters)
        {
            ValidateArg.IsNotNull(solver, "solver");
            if (null == parameters)
            {
                parameters = new OverlapRemovalParameters();
            }
            foreach (OverlapRemovalCluster cluster in this.clusterHierarchies)
            {
                cluster.Generate(solver, parameters, this.IsHorizontal);
            }

            // For Clusters we reposition their "fake border" variables between the constraint-generation
            // and solving phases, so we need to tell the solver to do this.
            solver.UpdateVariables();   // @@PERF: Not needed if no clusters were created.
        }