protected static string GetCutoffString(Solution solution) { Validate.IsNotNull(solution, "Solution must not be null"); string strCutoff = string.Empty; if (solution.ExecutionLimitExceeded) { strCutoff = " [Cutoff:"; if (solution.TimeLimitExceeded) { strCutoff += " TimeLimit"; } if (solution.OuterProjectIterationsLimitExceeded) { strCutoff += " OuterIterLimit"; } if (solution.InnerProjectIterationsLimitExceeded) { strCutoff += " InnerIterLimit"; } strCutoff += "]"; } return strCutoff; }
/// <summary> /// Sets Variable.ActualPos to the positions of the Variables that minimally satisfy the constraints /// along this axis. This overload takes a parameter specification. /// </summary> /// <param name="solverParameters">Solution-generation options.</param> /// <returns>The only failure condition is if there are one or more unsatisfiable constraints, such as cycles /// or mutually exclusive equality constraints; if these are encountered, a list of lists of these /// constraints is returned, where each list contains a single cycle, which may be of length one for /// unsatisfiable equality constraints. Otherwise, the return value is null.</returns> public Solution Solve(Parameters solverParameters) { if (null != solverParameters) { this.solverParams = (Parameters)solverParameters.Clone(); } // Reset some parameter defaults to per-solver-instance values. if (this.solverParams.OuterProjectIterationsLimit < 0) { // If this came in 0, it stays that way, and there is no limit. Otherwise, set it to a value // reflecting the expectation of convergence roughly log-linearly in the number of variables. #if SHARPKIT //https://github.com/SharpKit/SharpKit/issues/4 integer rounding issue this.solverParams.OuterProjectIterationsLimit = 100 * (((int)Math.Log(this.numberOfVariables, 2.0)) + 1); #else this.solverParams.OuterProjectIterationsLimit = 100 * ((int)Math.Log(this.numberOfVariables, 2.0) + 1); #endif } if (this.solverParams.InnerProjectIterationsLimit < 0) { // If this came in 0, it stays that way, and there is no limit. Otherwise, assume that for // any pass, each constraint may be violated (most likely this happens only on the first pass), // and add some extra based upon constraint count. Now that we split and retry on unsatisfied // constraints, assume that any constraint may be seen twice on a pass. #if SHARPKIT //https://github.com/SharpKit/SharpKit/issues/4 integer rounding issue this.solverParams.InnerProjectIterationsLimit = (this.numberOfConstraints * 2) + (100 * (((int)Math.Log(this.numberOfConstraints, 2.0)) + 1)); #else this.solverParams.InnerProjectIterationsLimit = (this.numberOfConstraints * 2) + (100 * ((int)Math.Log(this.numberOfConstraints, 2.0) + 1)); #endif } // ReSolving can be done for updated constraints. bool isReSolve = !this.allConstraints.IsEmpty; CheckForUpdatedConstraints(); this.solverSolution = new Solution { MinInnerProjectIterations = int.MaxValue }; this.allConstraints.MaxConstraintTreeDepth = 0; this.allConstraints.SolverParameters = this.solverParams; // // First set up all the internal stuff we'll use for solutions. // #if CACHE_STATS cacheStats.Clear(); #endif // CACHE_STATS // If no constraints have been loaded, there's nothing to do. Two distinct variables // are required to create a constraint, so this also ensures a minimum number of variables. if (0 == this.numberOfConstraints) { // For Qpsc, we may have neighbours but no constraints. if (!this.IsQpsc) { return (Solution)this.solverSolution.Clone(); } } else if (!isReSolve) { SetupConstraints(); } // This is the number of unsatisfiable constraints encountered. this.allConstraints.NumberOfUnsatisfiableConstraints = 0; // Merge Equality constraints first. These do not do any constraint-splitting, and thus // remain in the same blocks, always satisfied, regardless of whether we're solving the full // Qpsc or the simpler loop. MergeEqualityConstraints(); // Prepare for timeout checking. if (this.solverParams.TimeLimit > 0) { this.timeoutStopwatch = new Stopwatch(); this.timeoutStopwatch.Start(); } // // Done with initial setup. Now if we have neighbour pairs, we do the full SolveQpsc logic // complete with Gradient projection. Otherwise, we have a much simpler Project/Split loop. // if (this.IsQpsc) { this.SolveQpsc(); } else { this.SolveByStandaloneProject(); this.CalculateStandaloneProjectGoalFunctionValue(); } // We initialized this to int.MaxValue so make sure it's sane if we didn't complete a Project iteration. if (this.solverSolution.MinInnerProjectIterations > this.solverSolution.MaxInnerProjectIterations) { // Probably this is 0. this.solverSolution.MinInnerProjectIterations = this.solverSolution.MaxInnerProjectIterations; } #if CACHE_STATS cacheStats.Print(); Console.WriteLine(" NumFinalBlocks = {0}, MinCacheBlocks = {1}, MaxCacheSize = {2}", allBlocks.Count, violationCacheMinBlockCutoff, ViolationCache.MaxConstraints); #endif // CACHE_STATS // Done. Caller will copy each var.ActualPos back to the Nodes. If we had any unsatisfiable // constraints, copy them back out to the caller. this.solverSolution.NumberOfUnsatisfiableConstraints = this.allConstraints.NumberOfUnsatisfiableConstraints; #if BLOCK_STATS int minBlockVars = this.numberOfVariables; int maxBlockVars = 0; foreach (Block block in allBlocks.Vector) { if (minBlockVars > block.Variables.Count) { minBlockVars = block.Variables.Count; } if (maxBlockVars < block.Variables.Count) { maxBlockVars = block.Variables.Count; } } // endforeach block Console.WriteLine("Num final Blocks: {0}, Min Block Vars: {1}, Max Block Vars: {2}", allBlocks.Count, minBlockVars, maxBlockVars); #endif // BLOCK_STATS this.solverSolution.MaxConstraintTreeDepth = this.allConstraints.MaxConstraintTreeDepth; return (Solution)this.solverSolution.Clone(); } // end Solve()
private void DebugVerifyClusterHierarchy(Solution solution) { if (avoidOverlaps && (null != clusterHierarchies) && (0 != solution.NumberOfUnsatisfiableConstraints )) { foreach (var c in clusterHierarchies) DebugVerifyClusters(cg, c, c); } }
internal bool VerifySolutionMembers(Solution solution, IEnumerable<NeighborDef> iterNeighbourDefs) { bool usedQpsc = (solution.AlgorithmUsed == SolverAlgorithm.QpscWithScaling) || (solution.AlgorithmUsed == SolverAlgorithm.QpscWithoutScaling); if (usedQpsc != (ForceQpsc || ((null != iterNeighbourDefs) && iterNeighbourDefs.Any()))) { WriteLine("UsedQPSC is not as expected"); return false; } return true; }
protected static string GetIterationsString(Solution solution) { Validate.IsNotNull(solution, "Solution must not be null"); string strCutoff = GetCutoffString(solution); return string.Format( "outer {0}; inner min={1} max={2} total={3} average={4:F2}; algo = {5}{6}", solution.OuterProjectIterations, solution.MinInnerProjectIterations, solution.MaxInnerProjectIterations, solution.InnerProjectIterationsTotal, (0.0 == solution.OuterProjectIterations) ? 0.0 : ((double)solution.InnerProjectIterationsTotal / solution.OuterProjectIterations), solution.AlgorithmUsed, strCutoff); }
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(); }