/// <summary>
        /// Create variables, generate non-overlap constraints.
        /// </summary>
        /// <param name="hPad">horizontal node padding</param>
        /// <param name="vPad">vertical node padding</param>
        /// <param name="cHPad">horizontal cluster padding</param>
        /// <param name="cVPad">vertical cluster padding</param>
        /// <param name="nodeCenter"></param>
        internal void Initialize(double hPad, double vPad, double cHPad, double cVPad, InitialCenterDelegateType nodeCenter) {
            // For the Vertical ConstraintGenerator, Padding is vPad and PadddingP(erpendicular) is hPad.
            cg = new ConstraintGenerator(IsHorizontal
                                            , IsHorizontal ? hPad : vPad
                                            , IsHorizontal ? vPad : hPad
                                            , IsHorizontal ? cHPad : cVPad
                                            , IsHorizontal ? cVPad : cHPad);
            solver = new Solver();

            foreach (var filNode in nodes) {
                filNode.SetOlapNode(IsHorizontal,null);
            }
            // Calculate horizontal non-Overlap constraints.  
            if (avoidOverlaps && clusterHierarchies != null) {
                foreach (var c in clusterHierarchies) {
                    AddOlapClusters(cg, null /* OlapParentCluster */, c, nodeCenter);
                }
            }

            foreach (var filNode in nodes) {
                if (filNode.getOlapNode(IsHorizontal) == null) {
                    AddOlapNode(cg, cg.DefaultClusterHierarchy /* olapParentCluster */, filNode, nodeCenter);
                }
                filNode.getOlapNode(IsHorizontal).CreateVariable(solver);
            }
            if (avoidOverlaps && this.ConstraintLevel >= 2) {
                cg.Generate(solver, OverlapRemovalParameters);
            }
            AddStructuralConstraints();
        }
         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
        }
Ejemplo n.º 3
0
 static void TestSolver() {
     var solver = new Solver();
     Variable a = solver.AddVariable("a", 0, 2);
     Variable b = solver.AddVariable("b", 1, 2);
     solver.AddConstraint(a, b, 2);
     solver.Solve();
 }
         void CalculateBorderWidths(Solver solver, List<Event> events, Rectangle boundaryRect,
                                            out double leftBorderWidth, out double rightBorderWidth)
        {
            // Cluster-level padding (the space around the borders) complicates this.  Margin
            // is added only at the inside edge of the cluster; for example, as space for a
            // title of the cluster to be printed.  We just use the margin as the boundary node
            // sizes.  Margin is separate from padding; padding is always added.
            leftBorderWidth = CalcBorderWidth(this.OpenBorderInfo.InnerMargin);
            rightBorderWidth = CalcBorderWidth(this.CloseBorderInfo.InnerMargin);

            // @@DCR "Precalculate Cluster Sizes": at this point we could solve them to get the "real" cluster
            // size (as above, this may be requested by solver being null on input so we create our own above).

            // Now calculate our position (as midpoints) and size.  This will be used in the parentCluster's
            // Generate() operation.  We want to get to the outside border of the border, so subtract the
            // border width.  We've added pre-border padding above.  Note: This is done before checking
            // for fixed positions, because we want the constraint generation to see them in the correct
            // relative positions - border midpoints are always outside the outermost node midpoints, so that
            // constraints will be generated in the correct direction (it would be bad if, for example, a Left
            // border was the rhs of a constraint with a node inside the cluster; it should always be an lhs
            // to any node in the cluster, and having it as an rhs will probably generate a cycle).  We adjust
            // to fixed positions below after GenerateFromEvents.
            this.Size = boundaryRect.Width;
            this.Position = boundaryRect.Center.X;

            // The final perpendicular positions may be modified below, after GenerateFromEvents; they
            // will be used by a parent cluster's Generate after we return if this is a recursive call.
            // We don't do it here because we are doing the variables internal to this cluster, based
            // upon their current positions, so this would get confused if we moved the P coordinates here.
            this.SizeP = boundaryRect.Height;
            this.PositionP = boundaryRect.Center.Y;

            // Now create the two "fake nodes" for the borders and add them to the event list line and solver.
            // This constraint will never be deferred, since there is no overlap in the secondary axis but is
            // in the primary axis.  In the perpendicular direction, we want them to be the size of the
            // outer borders of the outer nodes, regardless of whether the perpendicular borders are
            // fixed-position; this ensures that the scan line will correctly see their open and close.
            // Left/Open...
            this.LeftBorderNode.Position = boundaryRect.Left + (leftBorderWidth / 2);
            this.LeftBorderNode.Size = leftBorderWidth;
            this.LeftBorderNode.Weight = this.OpenBorderInfo.Weight;
            this.LeftBorderNode.PositionP = this.PositionP;
            this.LeftBorderNode.SizeP = this.SizeP;
            this.LeftBorderNode.CreateVariable(solver);
            AddEvents(this.LeftBorderNode, events);

            // Note:  The Left/Right, Open/Close terminology here is inconsistent with GenerateFromEvents
            //  since here Open is in the primary axis and in GenerateFromEvents it's in the secondary/P axis.

            // Right/Close...
            this.RightBorderNode.Position = boundaryRect.Right - (rightBorderWidth / 2);
            this.RightBorderNode.Size = rightBorderWidth;
            this.RightBorderNode.Weight = this.CloseBorderInfo.Weight;
            this.RightBorderNode.PositionP = this.PositionP;
            this.RightBorderNode.SizeP = this.SizeP;
            this.RightBorderNode.CreateVariable(solver);
            AddEvents(this.RightBorderNode, events);
        }
         void AdjustFixedBorderPositions(Solver solver, double leftBorderWidth, double rightBorderWidth, bool isHorizontal)
        {
            // Note:  Open == Left, Close == Right.
            if (this.OpenBorderInfo.IsFixedPosition && this.CloseBorderInfo.IsFixedPosition)
            {
                // Both are fixed, so just move them to their specified positions.  For FixedPosition
                // the API is that it's the outer border edge, so add or subtract half the (left|right)BorderWidth
                // to set the position to the midpoint.  Since both borders are fixed, this provides a
                // limit to the size of the overall node.
                this.LeftBorderNode.UpdateDesiredPosition(
                    this.OpenBorderInfo.FixedPosition + (leftBorderWidth / 2));
                this.RightBorderNode.UpdateDesiredPosition(
                    this.CloseBorderInfo.FixedPosition - (rightBorderWidth / 2));
                this.Size = this.CloseBorderInfo.FixedPosition - this.OpenBorderInfo.FixedPosition;
                this.Position = this.OpenBorderInfo.FixedPosition + (this.Size / 2);
            }
            else if (this.OpenBorderInfo.IsFixedPosition || this.CloseBorderInfo.IsFixedPosition)
            {
                // One border is fixed and the other isn't.  We'll keep the same cluster size,
                // move the fixed border to its specified position, adjust our midpoint to reflect that,
                // and then move the unfixed border to be immediately adjacent to the fixed border; the
                // solver will cause it to be moved to the minimal position satisfying the constraints.
                if (this.OpenBorderInfo.IsFixedPosition)
                {
                    // FixedPosition is the outer border edge so add BorderWidth/2 to set it to the Left midpoint.
                    this.LeftBorderNode.UpdateDesiredPosition(
                        this.OpenBorderInfo.FixedPosition + (leftBorderWidth / 2));
                    this.Position = this.OpenBorderInfo.FixedPosition + (this.Size / 2);
                }
                else /* this.CloseBorderInfo.IsFixedPosition */
                {
                    // FixedPosition is the outer border edge so subtract BorderWidth/2 to set it to the Right midpoint.
                    this.RightBorderNode.UpdateDesiredPosition(
                        this.CloseBorderInfo.FixedPosition - (rightBorderWidth / 2));
                    this.Position = this.CloseBorderInfo.FixedPosition - (this.Size / 2);
                }
            }

            // If we have a minimum size, generate constraints for it.  Although this may change the size
            // considerably, so may the movement of variables in the cluster, so we need no precalculation
            // of sizes or positions; but after the Horizontal pass, the caller must pass in the resultant
            // positions in the Horizontal (perpendicular) BorderInfos parameter to Vertical generation;
            // otherwise, because the Horizontal cluster span may be larger than is calculated simply from
            // variable positions, some variables may not have appropriate constraints generated.
            if (this.MinimumSize > 0.0)
            {
                Constraint cst = solver.AddConstraint(
                    this.LeftBorderNode.Variable, this.RightBorderNode.Variable, this.MinimumSize - leftBorderWidth/2 - rightBorderWidth/2);
                Debug.Assert(null != cst, "Minimum Cluster size: unexpected null cst");
#if VERBOSE
                    Console.WriteLine(" {0} MinClusterSizeCst {1} -> {2} g {3:F5}", isHorizontal ? "H" : "V"
                            , cst.Left.Name, cst.Right.Name, cst.Gap);
#endif
                // VERBOSE
            }

            // Now recalculate our perpendicular PositionP/SizeP if either perpendicular border is fixed,
            // since we know we're going to move things there.  We don't actually create variables for the
            // perpendicular axis on this pass, but we set the primary axis border nodes' perpendicular size
            // and position, thus creating "virtual" perpendicular borders used by the parent cluster's
            // Generate() and for its events in its GenerateFromEvents().  This must be done on both H and V
            // passes, because multiple heavyweight Fixed borders can push each other around on the horizontal
            // pass and leave excessive space between the fixed border and the outer nodes.  In that case the
            // Vertical pass can't get the true X border positions by evaluating our nodes' X positions; the
            // caller must pass this updated position in (the same thing it must do for nodes' X coordinates).
            if (this.OpenBorderInfoP.IsFixedPosition || this.CloseBorderInfoP.IsFixedPosition)
            {
                // If both are fixed, we'll set to those positions and recalculate size.
                // Remember that FixedPosition API is the outer border edge so we don't need to adjust for border width.
                if (this.OpenBorderInfoP.IsFixedPosition && this.CloseBorderInfoP.IsFixedPosition)
                {
                    this.SizeP = this.CloseBorderInfoP.FixedPosition - this.OpenBorderInfoP.FixedPosition;
                    this.PositionP = this.OpenBorderInfoP.FixedPosition + (this.SizeP / 2);
                    if (this.SizeP < 0)
                    {
                        // Open border is to the right of close border; they'll move later, but we have to
                        // make the size non-negative.  TODOunit: create a specific test for this (fixed LRTB)
                        this.SizeP = -this.SizeP;
                    }
                }
                else
                {
                    // Only one is fixed, so we'll adjust in the appropriate direction as needed.
                    // - If we're on the horizontal pass we'll preserve the above calculation of this.SizeP
                    //   and only shift things around to preserve the relative vertical starting positions;
                    //   running the Solver will change these positions.
                    // - If we're on the vertical pass, we know the horizontal nodes are in their final positions,
                    //   so we need to accommodate the case described above, where the Solver opened up space
                    //   between the fixed border and the outermost nodes (it will never *reduce* this distance
                    //   of course).  This means we adjust both border position and our overall node size.
                    double curTopOuterBorder = this.PositionP - (this.SizeP / 2);
                    double curBottomOuterBorder = this.PositionP + (this.SizeP / 2);
                    if (this.OpenBorderInfoP.IsFixedPosition)
                    {
                        if (isHorizontal)
                        {
                            // Don't change SizeP.
                            this.PositionP += this.OpenBorderInfoP.FixedPosition - curTopOuterBorder;
                        }
                        else
                        {
                            this.SizeP = curBottomOuterBorder - this.OpenBorderInfoP.FixedPosition;
                            this.PositionP = this.OpenBorderInfoP.FixedPosition + (this.SizeP / 2);
                        }
                    }
                    else
                    {
                        if (isHorizontal)
                        {
                            // Don't change SizeP.
                            this.PositionP += this.CloseBorderInfoP.FixedPosition - curBottomOuterBorder;
                        }
                        else
                        {
                            this.SizeP = this.CloseBorderInfoP.FixedPosition - curTopOuterBorder;
                            this.PositionP = curTopOuterBorder + (this.SizeP / 2);
                        }
                    }
                } // endifelse both borders fixed or only one border is

                // Now update our fake border nodes' PositionP/SizeP to be consistent.
                this.LeftBorderNode.PositionP = this.PositionP;
                this.LeftBorderNode.SizeP = this.SizeP;
                this.RightBorderNode.PositionP = this.PositionP;
                this.RightBorderNode.SizeP = this.SizeP;
            }
        }
         List<Event> CreateEvents(Solver solver, ref Rectangle boundaryRect)
        {
            var events = new List<Event>();
            int cNodes = this.nodeList.Count; // cache for perf
            double leftBorderWidth = CalcBorderWidth(this.OpenBorderInfo.InnerMargin);
            double rightBorderWidth = CalcBorderWidth(this.CloseBorderInfo.InnerMargin);
            double openBorderWidth = CalcBorderWidth(this.OpenBorderInfoP.InnerMargin);
            double closeBorderWidth = CalcBorderWidth(this.CloseBorderInfoP.InnerMargin);
            for (int nodeIndex = 0; nodeIndex < cNodes; ++nodeIndex)
            {
                OverlapRemovalNode node = this.nodeList[nodeIndex];
                var cluster = node as OverlapRemovalCluster;
                if (null != cluster)
                {
                    // Child Clusters have already "recursively" been processed before the current cluster,
                    // so we just need to check to see if it had any events.  If so, then it has created its
                    // two fake nodes (and their variables) along the primary axis, but these are only put
                    // into the event list at the nested level; at this level, we put Node underlying the 
                    // entire Cluster span (in both directions) into the event list.

                    // If a child cluster is empty, it will have zero size and no way to set its position.
                    // That includes clusters containing nothing but empty clusters.  We skip those here.
                    if (!cluster.IsInSolver)
                    {
                        continue;
                    }
                }
                else
                {
                    // Not a cluster; just have it add its variable to the solver.
                    node.CreateVariable(solver);
                }

                // Now add the Node to the ScanLine event list.  Use paddingP because the scan line moves
                // perpendicularly to the direction we're generating the constraints in.
                AddEvents(node, events);

                // Update our boundaries if this node goes past any of them.
                if (!this.IsRootCluster)
                {
                    double pad = node.Size / 2 + ClusterPadding;
                    double padP = node.SizeP / 2 + ClusterPaddingP;
                    double newLeft = node.Position - pad - leftBorderWidth;
                    double newRight = node.Position + pad + rightBorderWidth;
                    double newBottom = node.PositionP - padP - openBorderWidth;
                    double newTop = node.PositionP + padP + closeBorderWidth;

                    boundaryRect.Left = Math.Min(boundaryRect.Left, newLeft);
                    boundaryRect.Right = Math.Max(boundaryRect.Right, newRight);
                    boundaryRect.Bottom = Math.Min(boundaryRect.Bottom, newBottom);
                    boundaryRect.Top = Math.Max(boundaryRect.Top, newTop);

#if VERBOSE
                    Console.WriteLine(" {0} BoundaryRect after AddEvents: L/R T/B {1:F5}/{2:F5} {3:F5}/{4:F5}"
                            , this.Name, boundaryRect.Left, boundaryRect.Right, boundaryRect.Top, boundaryRect.Bottom);
#endif
                }
            }
            if (!this.IsRootCluster)
            {
                // Force the cluster borders to the full minimum sizes if any were specified.
                // Without the full cluster boundaries being available at constraint generation time, Tuvalu was
                // getting unresolved overlaps when dragging an external node over the corner of a cluster boundary.
                double padMinSize = this.MinimumSize - boundaryRect.Width;
                if (padMinSize > 0)
                {
                    boundaryRect.PadWidth(padMinSize / 2);
                }
                double padMinSizeP = this.MinimumSizeP - boundaryRect.Height;
                if (padMinSizeP > 0)
                {
                    boundaryRect.PadHeight(padMinSizeP / 2);
                }

#if VERBOSE
                Console.WriteLine(" {0} BoundaryRect after CreateEvents: L/R T/B {1:F5}/{2:F5} {3:F5}/{4:F5}"
                        , this.Name, boundaryRect.Left, boundaryRect.Right, boundaryRect.Top, boundaryRect.Bottom);
#endif

            }

            return events;
        }
        // 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());
 }
        internal static void WriteFile(int seed,
                        double maxWeightToGenerate,
                        Solver solverX,
                        Solver solverY,
                        Solution solutionX,
                        Solution solutionY,
                        double minPaddingX,
                        double minPaddingY,
                        double minClusterSizeX,
                        double minClusterSizeY,
                        double maxMargin,
                        List<VariableDef> lstVarDefs,
                        List<ClusterDef> lstClusDefs,
                        StreamWriter outputFileWriter)
        {
            // TODO_cleanup: make shared strings; regenerate test files to verify

            // Add the summary information as comments. @@ (high-level) and @# (low-level) allow
            // findstr etc. scans of the file metadata; @[@#] gets both.
            outputFileWriter.WriteLine("// @@Variables: {0}", lstVarDefs.Count);
            outputFileWriter.WriteLine("// @@Clusters: {0}", (null == lstClusDefs) ? 0 : lstClusDefs.Count);
            outputFileWriter.WriteLine("// @@Constraints_X: {0}", solverX.Constraints.Count());
            outputFileWriter.WriteLine("// @@Constraints_Y: {0}", solverY.Constraints.Count());
            outputFileWriter.WriteLine();

            // Values we want to read back in.
            outputFileWriter.WriteLine("Seed 0x{0}", seed.ToString("X"));
            outputFileWriter.WriteLine("Weight {0:F5}", maxWeightToGenerate);
            outputFileWriter.WriteLine("Padding {0:F5} {1:F5}", minPaddingX, minPaddingY);
            outputFileWriter.WriteLine("MinClusterSize {0:F5} {1:F5}", minClusterSizeX, minClusterSizeY);
            outputFileWriter.WriteLine("Margin {0}", maxMargin);
            outputFileWriter.WriteLine("Goal {0} {1}", solutionX.GoalFunctionValue, solutionY.GoalFunctionValue);
            outputFileWriter.WriteLine();

            outputFileWriter.WriteLine(TestFileStrings.BeginVariables);
            for (int idxVar = 0; idxVar < lstVarDefs.Count; ++idxVar)
            {
                VariableDef varDef = lstVarDefs[idxVar];
                outputFileWriter.WriteLine(
                    TestFileStrings.WriteVariable2D,
                    idxVar,
                    varDef.DesiredPosX,
                    varDef.DesiredPosY,
                    varDef.SizeX,
                    varDef.SizeY,
                    varDef.WeightX,
                    varDef.WeightY);
            }
            outputFileWriter.WriteLine(TestFileStrings.EndVariables);
            outputFileWriter.WriteLine();
            outputFileWriter.Flush();

            if (null != lstClusDefs)
            {
                // Write out the clusters, starting at 1 to skip the root.  Since we populate the
                // clusterdefs left-to-right we'll always print out the parents before the children.
                foreach (ClusterDef clusDef in lstClusDefs)
                {
                    outputFileWriter.WriteLine(TestFileStrings.BeginCluster);

                    // Write the current cluster definition.
                    outputFileWriter.WriteLine(TestFileStrings.WriteClusterId, clusDef.ClusterId);
                    outputFileWriter.WriteLine(
                        TestFileStrings.WriteClusterParent,
                        null == clusDef.ParentClusterDef ? 0 : clusDef.ParentClusterDef.ClusterId);
                    outputFileWriter.WriteLine(
                        TestFileStrings.WriteClusterMinSize, clusDef.MinimumSizeX, clusDef.MinimumSizeY);
                    if (clusDef.IsNewHierarchy)
                    {
                        outputFileWriter.WriteLine("NewHierarchy");
                    }
                    outputFileWriter.WriteLine(
                        TestFileStrings.WriteClusterBorder,
                        "Left",
                        clusDef.LeftBorderInfo.InnerMargin,
                        ClusterDef.IsFixedString(clusDef.LeftBorderInfo),
                        clusDef.LeftBorderInfo.Weight);
                    outputFileWriter.WriteLine(
                        TestFileStrings.WriteClusterBorder,
                        "Right",
                        clusDef.RightBorderInfo.InnerMargin,
                        ClusterDef.IsFixedString(clusDef.RightBorderInfo),
                        clusDef.RightBorderInfo.Weight);
                    outputFileWriter.WriteLine(
                        TestFileStrings.WriteClusterBorder,
                        "Top",
                        clusDef.TopBorderInfo.InnerMargin,
                        ClusterDef.IsFixedString(clusDef.TopBorderInfo),
                        clusDef.TopBorderInfo.Weight);
                    outputFileWriter.WriteLine(
                        TestFileStrings.WriteClusterBorder,
                        "Bottom",
                        clusDef.BottomBorderInfo.InnerMargin,
                        ClusterDef.IsFixedString(clusDef.BottomBorderInfo),
                        clusDef.BottomBorderInfo.Weight);
                    outputFileWriter.WriteLine("// @#ClusterVars: {0}", clusDef.Variables.Count);
                    foreach (VariableDef varDef in clusDef.Variables)
                    {
                        outputFileWriter.WriteLine(TestFileStrings.WriteClusterVariable, varDef.IdString);
                    }
                    outputFileWriter.WriteLine(TestFileStrings.EndCluster);
                    outputFileWriter.WriteLine();
                }
                outputFileWriter.Flush();
            } // endif clusters exist

            // Write the constraints.
            // TODOclus: This is outputting vars Lnn Rnn in DEBUG and an empty string in RELEASE; consider making the file
            //           output (with clusters, anyway) run in DEBUG-only and have TestFileReader.cs know how to decode them.
            outputFileWriter.WriteLine(TestFileStrings.BeginConstraintsX);
            foreach (Constraint cst in solverX.Constraints.OrderBy(cst => cst))
            {
                // There are no equality constraints in OverlapRemoval so pass an empty string.
                outputFileWriter.WriteLine(
                    TestFileStrings.WriteConstraint,
                    ((OverlapRemovalNode)cst.Left.UserData).UserData,
                    ((OverlapRemovalNode)cst.Right.UserData).UserData,
                    string.Empty,
                    cst.Gap);
            }
            outputFileWriter.WriteLine(TestFileStrings.EndConstraints);
            outputFileWriter.WriteLine();
            outputFileWriter.WriteLine(TestFileStrings.BeginConstraintsY);
            foreach (Constraint cst in solverY.Constraints.OrderBy(cst => cst))
            {
                // There are no equality constraints in OverlapRemoval so pass an empty string.
                outputFileWriter.WriteLine(
                    TestFileStrings.WriteConstraint,
                    ((OverlapRemovalNode)cst.Left.UserData).UserData,
                    ((OverlapRemovalNode)cst.Right.UserData).UserData,
                    string.Empty,
                    cst.Gap);
            }
            outputFileWriter.WriteLine(TestFileStrings.EndConstraints);
            outputFileWriter.WriteLine();

            // Now write the results.
            outputFileWriter.WriteLine(TestFileStrings.BeginResults);
            foreach (VariableDef varDef in lstVarDefs)
            {
                outputFileWriter.WriteLine(TestFileStrings.WriteResults2D, varDef.IdString, varDef.VariableX.ActualPos, varDef.VariableY.ActualPos);
            } // endforeach varDef
            outputFileWriter.WriteLine(TestFileStrings.EndResults);

            if (null != lstClusDefs)
            {
                outputFileWriter.WriteLine();
                outputFileWriter.WriteLine(TestFileStrings.BeginClusterResults);
                outputFileWriter.WriteLine("// (includes only clusters that are not IsNewHierarchy)");
                foreach (ClusterDef clusDef in lstClusDefs)
                {
                    // Clusters at the root of a hierarchy have no borders.
                    if (!clusDef.IsNewHierarchy)
                    {
                        outputFileWriter.WriteLine(
                            TestFileStrings.WriteClusterResults,
                            clusDef.ClusterId,
                            clusDef.Left,
                            clusDef.Right,
                            clusDef.Top,
                            clusDef.Bottom);
                    }
                }
                outputFileWriter.WriteLine(TestFileStrings.EndClusterResults);
            }

            // Done.
            outputFileWriter.Flush();
            outputFileWriter.Close();
        }
        public void Test_MoveMidVarsToSegmentEnds_PushIn()
        {
            var solver = new Solver();
            const double FixedVarWeight = 1e8;
            var heavy0 = solver.AddVariable(null, 0, FixedVarWeight);
            var heavy1 = solver.AddVariable(null, 2, FixedVarWeight);
            var light0 = solver.AddVariable(null, 1);
            var light1 = solver.AddVariable(null, 1);
            solver.AddConstraint(light0, heavy0, 0);
            solver.AddConstraint(heavy1, light1, 0);
            solver.AddNeighborPair(light0, light1, 1 / FixedVarWeight);
            solver.Solve();

            // expected values
            const double Heavy0Expected = 1.00000001E-08;
            const double Light0Expected = 1.00000001E-08;
            const double Light1Expected = 1.99999999;
            const double Heavy1Expected = 1.99999999;
            if (!ApproxEquals(heavy0.ActualPos, Heavy0Expected) || !ApproxEquals(light0.ActualPos, Light0Expected)
                || !ApproxEquals(light1.ActualPos, Light1Expected) || !ApproxEquals(heavy1.ActualPos, Heavy1Expected))
            {
                if (TestGlobals.VerboseLevel > 0)
                {
                    WriteLine("Failed - actual/expected: h0={0}/{1} l0={2}/{3} l1={4}/{5} h1={6}/{7}",
                            heavy0.ActualPos,
                            Heavy0Expected,
                            light0.ActualPos,
                            Light0Expected,
                            light1.ActualPos,
                            Light1Expected,
                            heavy1.ActualPos,
                            Heavy1Expected);
                }
                Validate.Fail("Results were not as expected");
            }
        }