/// <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 }
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"); } }