private void BuildMIPModel() { _env = new GRBEnv(); _model = new GRBModel(_env); //room used. varR = new Dictionary <Room, GRBVar>(); foreach (var room in Data.Rooms) { varR[room] = _model.AddVar(0, GRB.INFINITY, room.Cost, GRB.INTEGER, $"r_{room}"); } _model.Update(); _model.AddConstr(Data.TimeSlots.Count * varR.Values.Sumx(), GRB.GREATER_EQUAL, Data.Courses.Sum(c => c.Lectures), ""); foreach (var roomCapacity in Data.RoomCapacities) { var overCap = OverCapacity * ((double)Data.Courses.Where(c => c.NumberOfStudents > roomCapacity.Value).Sum(c => c.Lectures) / Data.TimeSlots.Count); _model.AddConstr(varR.Where(r => r.Key.Capacity > roomCapacity.Value).Sumx(r => r.Value) * Data.TimeSlots.Count, GRB.GREATER_EQUAL, Data.Courses.Where(c => c.NumberOfStudents > roomCapacity.Value).Sum(c => c.Lectures) + overCap, $"roomcapcut_{roomCapacity.Value}"); } _model.SetCallback(new OutputCallback()); }
/// <summary> /// Creates a cancellable task that runs Gurobi on the given instance. /// </summary> /// <param name="instance">Instance to run on.</param> /// <param name="cancellationToken"> /// Token that is regurlarly checked for cancellation. /// If cancellation is detected, the task will be stopped. /// </param> /// <returns> /// A task that returns the run's runtime, gap, feasibility and completion status onto return. /// </returns> public Task <GurobiResult> Run(InstanceSeedFile instance, CancellationToken cancellationToken) { // Check if the runner has already been disposed. if (this._hasAlreadyBeenDisposed) { throw new ObjectDisposedException("GurobiRunner", "Called Run on a disposed GurobiRunner."); } // Continue if it hasn't. var solveTask = Task.Run( () => { // Prepare Gurobi model: Use configured _environment and the given instance, // then add a callback for cancellation. var instanceFile = new FileInfo(instance.Path); if (!File.Exists(instanceFile.FullName)) { throw new Exception(string.Format("Instance {0} not found!", instance.Path)); } LoggingHelper.WriteLine(VerbosityLevel.Debug, $"Setting MIPGap to {this._runnerConfiguration.TerminationMipGap}"); this._environment.MIPGap = this._runnerConfiguration.TerminationMipGap; LoggingHelper.WriteLine(VerbosityLevel.Debug, $"Current Seed: {instance.Seed}"); this._environment.Seed = instance.Seed; this._environment.TimeLimit = this._runnerConfiguration.CpuTimeout.TotalSeconds; var fileName = Path.GetFileNameWithoutExtension(instance.Path); if (!Directory.Exists("GurobiLog")) { Directory.CreateDirectory("GurobiLog"); } this._environment.LogFile = $"GurobiLog/GurobiRunner_{DateTime.Now:yy-MM-dd_HH-mm-ss-ffff}_" + fileName + ".log"; var model = new GRBModel(this._environment, instance.Path) { ModelName = instanceFile.Name }; var mstfileName = instance.Path.Substring(0, instance.Path.Length - instanceFile.Extension.Length) + ".mst"; if (File.Exists(mstfileName)) { model.Read(mstfileName); } model.SetCallback(new GurobiCallback(cancellationToken)); // Optimize. This step may be aborted in the callback. model.Optimize(); var result = this.CreateGurobiResult(model); // Before returning, dispose of Gurobi model. model.Dispose(); return(result); }, cancellationToken); return(solveTask); }
public LinearModel(SolverType type, Action <string> logger, int threadCount = 0) { Type = type; _logger = logger; switch (type) { case SolverType.CPLEX: { CplexModel = new Cplex(); _cplexStatusCallback = new CplexStatusCallback(this) { }; CplexModel.SetOut(new CplexOutputHandler(logger)); CplexModel.Use(_cplexStatusCallback); if (threadCount > 0) { CplexModel.SetParam(Cplex.IntParam.Threads, threadCount); } } break; case SolverType.Gurobi: { GRBEnv gurobiEnvironment = new GRBEnv(); GurobiModel = new GRBModel(gurobiEnvironment); GurobiModel.GetEnv().Set(GRB.IntParam.UpdateMode, 1); // Enable immediate updates to better support the lazy initialization of the wrappers (at minor performance and memory costs) GurobiModel.GetEnv().Set(GRB.IntParam.OutputFlag, 0); if (threadCount > 0) { GurobiModel.GetEnv().Set(GRB.IntParam.Threads, threadCount); } _gurobiStatusCallback = new GurobiStatusCallback(this) { Logger = logger }; GurobiModel.SetCallback(_gurobiStatusCallback); } break; default: throw new ArgumentException("Unknown solver type: " + Type); } }
static void Main(string[] args) { if (args.Length < 1) { Console.Out.WriteLine("Usage: callback_cs filename"); return; } try { GRBEnv env = new GRBEnv(); GRBModel model = new GRBModel(env, args[0]); GRBVar[] vars = model.GetVars(); // Create a callback object and associate it with the model model.SetCallback(new callback_cs(vars)); model.Optimize(); double[] x = model.Get(GRB.DoubleAttr.X, vars); string[] vnames = model.Get(GRB.StringAttr.VarName, vars); for (int j = 0; j < vars.Length; j++) { if (x[j] != 0.0) { Console.WriteLine(vnames[j] + " " + x[j]); } } // Dispose of model and env model.Dispose(); env.Dispose(); } catch (GRBException e) { Console.WriteLine("Error code: " + e.ErrorCode + ". " + e.Message); Console.WriteLine(e.StackTrace); } }
private void BuildMIPModel() { _env = new GRBEnv(); _model = new GRBModel(_env); _varTimeslotUsed = new Dictionary <TimeSlot, GRBVar>(); foreach (var timeSlot in Data.TimeSlots) { _varTimeslotUsed[timeSlot] = _model.AddVar(0, 1, 1, GRB.BINARY, $"t_{timeSlot}"); } _model.Update(); _model.AddConstr(Data.Rooms.Count * _varTimeslotUsed.Values.Sumx(), GRB.GREATER_EQUAL, Data.Courses.Sum(c => c.Lectures), ""); foreach (var roomCapacity in Data.RoomCapacities) { _model.AddConstr(Data.Rooms.Count(r => r.Capacity > roomCapacity.Value) * Data.TimeSlots.Count, GRB.GREATER_EQUAL, Data.Courses.Where(c => c.NumberOfStudents > roomCapacity.Value).Sum(c => c.Lectures), $"timecapcapcut_{roomCapacity.Value}"); } //Consecutive timeslots var timeslotlist = Data.TimeSlots.OrderBy(ts => ts.Period).ThenBy(ts => ts.Day).ToList(); for (var i = 0; i < timeslotlist.Count - 1; i++) { _model.AddConstr(_varTimeslotUsed[timeslotlist[i + 1]] - _varTimeslotUsed[timeslotlist[i]], GRB.LESS_EQUAL, 0, $"timeslotconsec_{i}"); } _model.SetCallback(new OutputCallback()); }
static void Main(string[] args) { if (args.Length < 1) { Console.Out.WriteLine("Usage: callback_cs filename"); return; } StreamWriter logfile = null; try { // Create environment GRBEnv env = new GRBEnv(); // Read model from file GRBModel model = new GRBModel(env, args[0]); // Turn off display and heuristics model.Parameters.OutputFlag = 0; model.Parameters.Heuristics = 0.0; // Open log file logfile = new StreamWriter("cb.log"); // Create a callback object and associate it with the model GRBVar[] vars = model.GetVars(); callback_cs cb = new callback_cs(vars, logfile); model.SetCallback(cb); // Solve model and capture solution information model.Optimize(); Console.WriteLine(""); Console.WriteLine("Optimization complete"); if (model.SolCount == 0) { Console.WriteLine("No solution found, optimization status = " + model.Status); } else { Console.WriteLine("Solution found, objective = " + model.ObjVal); string[] vnames = model.Get(GRB.StringAttr.VarName, vars); double[] x = model.Get(GRB.DoubleAttr.X, vars); for (int j = 0; j < vars.Length; j++) { if (x[j] != 0.0) { Console.WriteLine(vnames[j] + " " + x[j]); } } } // Dispose of model and environment model.Dispose(); env.Dispose(); } catch (GRBException e) { Console.WriteLine("Error code: " + e.ErrorCode); Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); } catch (Exception e) { Console.WriteLine("Error during optimization"); Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); } finally { // Close log file if (logfile != null) { logfile.Close(); } } }
public static void Main(String[] args) { if (args.Length < 1) { Console.WriteLine("Usage: tsp_cs nnodes"); return; } int n = Convert.ToInt32(args[0]); try { GRBEnv env = new GRBEnv(); GRBModel model = new GRBModel(env); // Must set LazyConstraints parameter when using lazy constraints model.Parameters.LazyConstraints = 1; double[] x = new double[n]; double[] y = new double[n]; Random r = new Random(); for (int i = 0; i < n; i++) { x[i] = r.NextDouble(); y[i] = r.NextDouble(); } // Create variables GRBVar[,] vars = new GRBVar[n, n]; for (int i = 0; i < n; i++) { for (int j = 0; j <= i; j++) { vars[i, j] = model.AddVar(0.0, 1.0, distance(x, y, i, j), GRB.BINARY, "x" + i + "_" + j); vars[j, i] = vars[i, j]; } } // Degree-2 constraints for (int i = 0; i < n; i++) { GRBLinExpr expr = 0; for (int j = 0; j < n; j++) { expr.AddTerm(1.0, vars[i, j]); } model.AddConstr(expr == 2.0, "deg2_" + i); } // Forbid edge from node back to itself for (int i = 0; i < n; i++) { vars[i, i].UB = 0.0; } model.SetCallback(new tsp_cs(vars)); model.Optimize(); if (model.SolCount > 0) { int[] tour = findsubtour(model.Get(GRB.DoubleAttr.X, vars)); Console.Write("Tour: "); for (int i = 0; i < tour.Length; i++) { Console.Write(tour[i] + " "); } Console.WriteLine(); } // Dispose of model and environment model.Dispose(); env.Dispose(); } catch (GRBException e) { Console.WriteLine("Error code: " + e.ErrorCode + ". " + e.Message); Console.WriteLine(e.StackTrace); } }
public GurobiSolver2(Crossword crossword) { var wordLengthHistogram = new Dictionary <int, double>() { { 3, 18 }, { 4, 24 }, { 5, 20 }, { 6, 18 }, { 7, 12 }, { 8, 4 }, { 9, 4 }, }; const int maxWordLength = 9; int sizeY = crossword.Grid.GetLength(0); int sizeX = crossword.Grid.GetLength(1); var requiredAmountOfLetters = wordLengthHistogram.Sum(wl => wl.Key * wl.Value) / 1.8d; int totalFields = sizeX * sizeY; for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { totalFields -= crossword.Grid[y, x] is Blocked ? 1 : 0; } } int amountQuestions = (int)Math.Round(0.22 * totalFields); var wordLengthRatio = requiredAmountOfLetters / (totalFields - amountQuestions); GRBEnv env = new GRBEnv(); GRBModel m = new GRBModel(env); // 0 = letter, 1 = question GRBVar[,] fields = new GRBVar[sizeY, sizeX]; // 0 = right, 1 = down GRBVar[,] questionType = new GRBVar[sizeY, sizeX]; for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { // create a var for every non-blocked field if (!(crossword.Grid[y, x] is Blocked)) { fields[y, x] = m.AddVar(0, 1, 0, GRB.BINARY, "Field" + x + "_" + y); questionType[y, x] = m.AddVar(0, 1, 0, GRB.BINARY, "QType" + x + "_" + y); } } } GRBLinExpr allFieldsSum = new GRBLinExpr(); // count word lengths (to compare with histogram) var lengths = new Dictionary <int, GRBLinExpr>(); foreach (var l in wordLengthHistogram.Keys) { lengths.Add(l, new GRBLinExpr()); } var partOfAWord = new GRBLinExpr[sizeY, sizeX]; for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { partOfAWord[y, x] = new GRBLinExpr(); } } for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { allFieldsSum += fields[y, x]; foreach (var l in wordLengthHistogram.Keys) { if (x + l < sizeX) { // + 1 if the following fields are all words and the last one is not a word (question or outside grid) var wordCount = new GRBLinExpr(); for (int i = 1; i <= l; i++) { wordCount += fields[y, x + i]; } // last field is question or outside? var lastFieldIsNoWord = m.AddVar(0, 1, 0, GRB.BINARY, "lastFieldIsNoWord" + y + "_" + x + "_" + l); if (x + l + 1 < sizeX) { m.AddConstr(lastFieldIsNoWord == fields[y, x + l + 1], "lastFieldIsInGridAndIsQuestion" + y + "_" + x + "_" + l); } else { m.AddConstr(lastFieldIsNoWord == 1, "lastFieldOutsideGrid" + y + "_" + x + "_" + l); } // https://cs.stackexchange.com/questions/51025/cast-to-boolean-for-integer-linear-programming var hasCorrectWordCount = m.AddVar(0, 1, 0, GRB.BINARY, "hasCorrect" + y + "_" + x + "_" + l); var hcTemp = m.AddVar(0, 1, 0, GRB.BINARY, "hasCorrectTemp" + y + "_" + x + "_" + l); var worddiff = wordCount - l; m.AddConstr(-sizeX * hasCorrectWordCount <= worddiff, "ToBoolean" + y + "_" + x + "_" + l + "_1"); m.AddConstr(worddiff <= sizeX * hasCorrectWordCount, "ToBoolean" + y + "_" + x + "_" + l + "_2"); m.AddConstr(0.001 * hasCorrectWordCount - (sizeX + 0.001) * hcTemp <= worddiff, "ToBoolean" + y + "_" + x + "_" + l + "_3"); m.AddConstr(worddiff <= -0.001 * hasCorrectWordCount + (sizeX + 0.001) * (1 - hcTemp), "ToBoolean" + y + "_" + x + "_" + l + "_4"); // firstFieldIsQuestion AND questionIsHorizontal AND hasCorrectWordCount AND lastFieldIsNoWord var allConditionsMet = m.AddVar(0, 1, 0, GRB.BINARY, "allCondsMet" + y + "_" + x + "_" + l); var condVal = fields[y, x] + (1 - questionType[y, x]) + hasCorrectWordCount + lastFieldIsNoWord - 3; m.AddConstr(allConditionsMet >= condVal, "AllConditionsAND" + y + "_" + x + "_" + l + "_1"); m.AddConstr(allConditionsMet <= fields[y, x], "AllConditionsAND" + y + "_" + x + "_" + l + "_2"); m.AddConstr(allConditionsMet <= (1 - questionType[y, x]), "AllConditionsAND" + y + "_" + x + "_" + l + "_3"); m.AddConstr(allConditionsMet <= hasCorrectWordCount, "AllConditionsAND" + y + "_" + x + "_" + l + "_4"); m.AddConstr(allConditionsMet <= lastFieldIsNoWord, "AllConditionsAND" + y + "_" + x + "_" + l + "_5"); lengths[l] += allConditionsMet; // If all conditions are met, all letters are part of a word for (int i = 1; i <= l; i++) { partOfAWord[y, x + i] += allConditionsMet; } } if (y + l < sizeY) { // + 1 if the following fields are all words and the last one is not a word (question or outside grid) var wordCount = new GRBLinExpr(); for (int i = 1; i <= l; i++) { wordCount += fields[y + i, x]; } // last field is question or outside? var lastFieldIsNoWord = m.AddVar(0, 1, 0, GRB.BINARY, "lastFieldIsNoWord" + y + "_" + x + "_" + l); if (y + l + 1 < sizeY) { m.AddConstr(lastFieldIsNoWord == fields[y + l + 1, x], "lastFieldIsInGridAndIsQuestion" + y + "_" + x + "_" + l); } else { m.AddConstr(lastFieldIsNoWord == 1, "lastFieldOutsideGrid" + y + "_" + x + "_" + l); } // https://cs.stackexchange.com/questions/51025/cast-to-boolean-for-integer-linear-programming var hasCorrectWordCount = m.AddVar(0, 1, 0, GRB.BINARY, "hasCorrect" + y + "_" + x + "_" + l); var hcTemp = m.AddVar(0, 1, 0, GRB.BINARY, "hasCorrectTemp" + y + "_" + x + "_" + l); var worddiff = wordCount - l; m.AddConstr(-sizeY * hasCorrectWordCount <= worddiff, "ToBoolean" + y + "_" + x + "_" + l + "_1"); m.AddConstr(worddiff <= sizeY * hasCorrectWordCount, "ToBoolean" + y + "_" + x + "_" + l + "_2"); m.AddConstr(0.001 * hasCorrectWordCount - (sizeY + 0.001) * hcTemp <= worddiff, "ToBoolean" + y + "_" + x + "_" + l + "_3"); m.AddConstr(worddiff <= -0.001 * hasCorrectWordCount + (sizeY + 0.001) * (1 - hcTemp), "ToBoolean" + y + "_" + x + "_" + l + "_4"); // firstFieldIsQuestion AND questionIsHorizontal AND hasCorrectWordCount AND lastFieldIsNoWord var allConditionsMet = m.AddVar(0, 1, 0, GRB.BINARY, "allCondsMet" + y + "_" + x + "_" + l); var condVal = fields[y, x] + (questionType[y, x]) + hasCorrectWordCount + lastFieldIsNoWord - 3; m.AddConstr(allConditionsMet >= condVal, "AllConditionsAND" + y + "_" + x + "_" + l + "_1"); m.AddConstr(allConditionsMet <= fields[y, x], "AllConditionsAND" + y + "_" + x + "_" + l + "_2"); m.AddConstr(allConditionsMet <= (questionType[y, x]), "AllConditionsAND" + y + "_" + x + "_" + l + "_3"); m.AddConstr(allConditionsMet <= hasCorrectWordCount, "AllConditionsAND" + y + "_" + x + "_" + l + "_4"); m.AddConstr(allConditionsMet <= lastFieldIsNoWord, "AllConditionsAND" + y + "_" + x + "_" + l + "_5"); lengths[l] += allConditionsMet; // If all conditions are met, all letters are part of a word for (int i = 1; i <= l; i++) { partOfAWord[y + i, x] += allConditionsMet; } } } // max word count if (x + maxWordLength < sizeX) { var maxWordCount = new GRBLinExpr(); var range = maxWordLength + 1; if (x + range == sizeX) { range--; } for (int i = 0; i < maxWordLength + 1; i++) { maxWordCount += 1 - fields[y, x + i]; } m.AddConstr(maxWordCount <= maxWordLength, "maxWordsX" + y + "_" + x); } if (y + maxWordLength < sizeY) { var maxWordCount = new GRBLinExpr(); var range = maxWordLength + 1; if (y + range == sizeY) { range--; } for (int i = 0; i < maxWordLength + 1; i++) { maxWordCount += 1 - fields[y + i, x]; } m.AddConstr(maxWordCount <= maxWordLength, "maxWordsY" + y + "_" + x); } } } // All non-question fields have to belong to a word for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { m.AddConstr(1 - fields[y, x] <= partOfAWord[y, x], "NonQuestionsToWords" + y + "_" + x + "_1"); m.AddConstr(1 - fields[y, x] >= partOfAWord[y, x] * 0.5, "NonQuestionsToWords" + y + "_" + x + "_2"); } } // after a question, no word of length 1 or 2 is allowed for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { if (x < sizeX - 3) { // if there's a question AND it's pointing right THEN sum(fields[y, x+1/2/3]) = 0 var minLetters = 3 - (fields[y, x + 1] + fields[y, x + 2] + fields[y, x + 3]); m.AddConstr(fields[y, x] + (1 - questionType[y, x]) - 1 <= minLetters * (1d / 3d), "AfterRightQuestionMin2Letters" + y + "_" + x); } else { m.AddConstr(questionType[y, x] == 1, "NoRightQuestionAtRightBorder" + y + "_" + x); } if (y < sizeY - 3) { // if there's a question AND it's pointing down THEN sum(fields[y+1/2/3, x]) = 0 var minLetters = 3 - (fields[y + 1, x] + fields[y + 2, x] + fields[y + 3, x]); m.AddConstr(fields[y, x] + questionType[y, x] - 1 <= minLetters * (1d / 3d), "AfterDownQuestionMin2Letters" + y + "_" + x); } else if (x < sizeX - 3) { m.AddConstr(questionType[y, x] == 0, "NoDownQuestionAtBottomBorder" + y + "_" + x); } else { m.AddConstr(fields[y, x] == 0, "OnlyWordsInBottomrightCorner" + y + "_" + x); } } } // Objective: // questions should be around ~22% (allFieldsSum ~= amountQuestions) int tolerance = (int)(amountQuestions * 0.2); m.AddConstr(allFieldsSum - amountQuestions >= -tolerance); m.AddConstr(allFieldsSum - amountQuestions <= tolerance); // as many partOfAWord == 2 as possible var manyCrossedWords = new GRBLinExpr(); for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { manyCrossedWords += partOfAWord[y, x]; } } // ideal histogram comparison //var wordHistogramDifferences = new GRBLinExpr(); foreach (var wl in wordLengthHistogram.Keys) { /*var varDiffInput = m.AddVar(-sizeX * sizeY / 3, sizeX * sizeY / 3, 0, GRB.INTEGER, "varDiffInput" + wl); * m.AddConstr(varDiffInput == (wordLengthHistogram[wl] - lengths[wl])); * var varDiffRes = m.AddVar(0, sizeX * sizeY / 3, 0, GRB.INTEGER, "varDiff" + wl); * m.AddGenConstrAbs(varDiffRes, varDiffInput, "diffGenConstr" + wl); * wordHistogramDifferences += varDiffRes;*/ int histogramTolerance = Math.Max(1, (int)(wordLengthHistogram[wl] * 0.2 * wordLengthRatio)); //m.AddConstr(wordLengthHistogram[wl] - lengths[wl] >= -histogramTolerance); //m.AddConstr(wordLengthHistogram[wl] - lengths[wl] <= histogramTolerance); } // question field clusters // in a field of 2x2, minimize the nr of fields where there are 2-4 questions resp. maximize 0-1 questions /*var clusterPenalty = new GRBLinExpr(); * int area = 2; * for (int y = 0; y < sizeY - (area - 1); y++) * { * for (int x = 0; x < sizeX - (area - 1); x++) * { * var clusterTotal = new GRBLinExpr(); * for (int i = 0; i < area; i++) * { * for (int j = 0; j < area; j++) * { * clusterTotal += fields[y + i, x + j]; * } * } * var varClusterTotalPenalty = m.AddVar(0, 1, 0, GRB.BINARY, "varClusterTotalPenalty" + y + "_" + x); * // 0-1 = good, 2-4 = bad * m.AddConstr(varClusterTotalPenalty <= clusterTotal * 0.5); * m.AddConstr(varClusterTotalPenalty >= (clusterTotal - 1) * (1d / 3)); * clusterPenalty += varClusterTotalPenalty; * } * }*/ //amountOfQuestionsRating * (100d / sizeX / sizeY) + manyCrossedWords + + wordHistogramDifferences // clusterPenalty * 100 m.SetObjective(manyCrossedWords, GRB.MAXIMIZE); m.SetCallback(new GRBMipSolCallback(crossword, fields, questionType, null)); m.Optimize(); m.ComputeIIS(); m.Write("model.ilp"); m.Dispose(); env.Dispose(); }
static void Main(string[] args) { if (args.Length < 1) { Console.Out.WriteLine("Usage: callback_cs filename"); return; } try { GRBEnv env = new GRBEnv(); GRBModel model = new GRBModel(env, args[0]); GRBVar[] vars = model.GetVars(); model.SetCallback(new callback_cs(vars)); model.Optimize(); double[] x = model.Get(GRB.DoubleAttr.X, vars); string[] vnames = model.Get(GRB.StringAttr.VarName, vars); for (int j = 0; j < vars.Length; j++) { if (x[j] != 0.0) Console.WriteLine(vnames[j] + " " + x[j]); } for (int j = 0; j < vars.Length; j++) { if (x[j] != 0.0) Console.WriteLine(vnames[j] + " " + x[j]); } // Dispose of model and env model.Dispose(); env.Dispose(); } catch (GRBException e) { Console.WriteLine("Error code: " + e.ErrorCode + ". " + e.Message); Console.WriteLine(e.StackTrace); } }
/// <summary> /// Creates a task that runs Gurobi on the given instance. /// </summary> /// <param name="instance"> /// Instance to run on. /// </param> /// <param name="cancellationToken"> /// The cancellation token given to the task. NOTE: In this implementation, we ignore this cancellation token, since Gurobi uses its own cancellation token in the callback. /// </param> /// <returns> /// A task that returns the run's runtime, gap, feasibility and completion status onto return. /// </returns> public Task <GurobiResult> Run(InstanceSeedFile instance, CancellationToken cancellationToken) { // Check if the runner has already been disposed. if (this._hasAlreadyBeenDisposed) { throw new ObjectDisposedException("GurobiRunner", "Called Run on a disposed GurobiRunner."); } // Continue if it hasn't. // ReSharper disable once MethodSupportsCancellation var solveTask = Task.Run( () => { this._recordTimer = null; try { // Prepare Gurobi model: Use configured _environment and the given instance, // then add a cancellation token source and the Gurobi callback for cancellation. var instanceFile = new FileInfo(instance.Path); if (!File.Exists(instanceFile.FullName)) { throw new FileNotFoundException($"Instance {instanceFile.FullName} not found!"); } LoggingHelper.WriteLine(VerbosityLevel.Debug, $"Setting MIPGap to {this._runnerConfiguration.TerminationMipGap}"); this._environment.MIPGap = this._runnerConfiguration.TerminationMipGap; LoggingHelper.WriteLine(VerbosityLevel.Debug, $"Current Seed: {instance.Seed}"); this._environment.Seed = instance.Seed; this._environment.TimeLimit = this._runnerConfiguration.CpuTimeout.TotalSeconds; if (!Directory.Exists("GurobiLog")) { Directory.CreateDirectory("GurobiLog"); } var instanceFileNameWithoutExtension = GurobiUtils.GetFileNameWithoutGurobiExtension(instanceFile); this._environment.LogFile = "GurobiLog" + Path.DirectorySeparatorChar + $"GurobiRunner_{DateTime.Now:yy-MM-dd_HH-mm-ss-ffff}_" + instanceFileNameWithoutExtension + $"_{Guid.NewGuid()}.log"; var model = new GRBModel(this._environment, instanceFile.FullName) { ModelName = instanceFileNameWithoutExtension }; if (GurobiRunner.TryToGetMstFile(instanceFile.DirectoryName, instanceFileNameWithoutExtension, out var mstFileFullName)) { model.Read(mstFileFullName); } this._startTimeStamp = DateTime.Now; this._targetAlgorithmStatus = TargetAlgorithmStatus.Running; this._innerCancellationTokenSource = new CancellationTokenSource(this._runnerConfiguration.CpuTimeout); this._grbCallback = new GurobiCallback( this._innerCancellationTokenSource.Token, this._tunerConfiguration.EnableDataRecording, this._startTimeStamp); model.SetCallback(this._grbCallback); if (this._tunerConfiguration.EnableDataRecording) { this._lastRuntimeFeatures = null; this.SetGurobiInstanceFeatures(model); // Start record timer. var autoResetEvent = new AutoResetEvent(false); this._recordTimer = new Timer( (timerCallback) => { var currentTimeStamp = DateTime.Now; if (currentTimeStamp - this._startTimeStamp < this._runnerConfiguration.CpuTimeout) { // Write current line to data log. var currentAdapterData = this.CreateCurrentAdapterDataRecord(currentTimeStamp); this.OnNewDataRecord?.Invoke(this, currentAdapterData); } }, autoResetEvent, TimeSpan.FromSeconds(0), this._tunerConfiguration.DataRecordUpdateInterval); } // Optimize. This step may be aborted in the callback. model.Optimize(); if (this._targetAlgorithmStatus != TargetAlgorithmStatus.CancelledByGrayBox) { this._recordTimer?.Dispose(); this._targetAlgorithmStatus = this.GetIsRunInterrupted(model) ? TargetAlgorithmStatus.CancelledByTimeout : TargetAlgorithmStatus.Finished; } var finalTimeStamp = DateTime.Now; var result = this.CreateGurobiResult(finalTimeStamp, model); if (this._tunerConfiguration.EnableDataRecording) { // Write last line to data log. var lastAdapterData = this.CreateFinalAdapterDataRecord(finalTimeStamp, model, result); this.OnNewDataRecord?.Invoke(this, lastAdapterData); } // Before returning, dispose of Gurobi model. model.Dispose(); return(result); } finally { this._recordTimer?.Dispose(); } }); return(solveTask); }
public GurobiSolver4(Crossword crossword) { var wordLengthHistogram = new Dictionary <int, double>() { { 2, 0 }, { 3, 18 }, { 4, 24 }, { 5, 20 }, { 6, 18 }, { 7, 12 }, { 8, 4 }, { 9, 1 }, { 10, 1 }, { 11, 1 }, { 12, 1 }, { 13, 0 }, { 14, 0 }, { 15, 0 }, }; const int maxWordLength = 15; const int minWordLength = 2; int sizeY = crossword.Grid.GetLength(0); int sizeX = crossword.Grid.GetLength(1); var requiredAmountOfLetters = wordLengthHistogram.Sum(wl => wl.Key * wl.Value) / 1.8d; int totalFields = sizeX * sizeY; for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { totalFields -= crossword.Grid[y, x] is Blocked ? 1 : 0; } } int amountQuestions = (int)Math.Round(0.22 * totalFields); var wordLengthRatio = requiredAmountOfLetters / (totalFields - amountQuestions); GRBEnv env = new GRBEnv(); GRBModel m = new GRBModel(env); m.GetEnv().Method = 0; // 0 = letter, 1 = question GRBVar[,] fields = new GRBVar[sizeY, sizeX]; // 0 = right, 1 = down GRBVar[,] questionType = new GRBVar[sizeY, sizeX]; // 0 = Down, then right // 1 = Left, then down // 2 = Right, then down // 3 = Up, then right // Mostly null, except for places down and right of a blocked field or y==0 or x==0 GRBVar[,,] specialQuestionType = new GRBVar[sizeY, sizeX, 4]; var specialQuestionUsed = new GRBLinExpr[sizeY, sizeX]; for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { // create a var for every non-blocked field if (!(crossword.Grid[y, x] is Blocked)) { fields[y, x] = m.AddVar(0, 1, 0, GRB.BINARY, "Field" + y + "_" + x); questionType[y, x] = m.AddVar(0, 1, 0, GRB.BINARY, "QType" + y + "_" + x); // Is null except for places down and right of a blocked field or y==0 or x==0 if (y == 0 || x == 0 || crossword.Grid[y - 1, x] is Blocked || crossword.Grid[y, x - 1] is Blocked) { for (int t = 0; t < 4; t++) { specialQuestionType[y, x, t] = m.AddVar(0, 1, 0, GRB.BINARY, "SpecialQType" + t + "_" + y + "_" + x); } specialQuestionUsed[y, x] = specialQuestionType[y, x, 0] + specialQuestionType[y, x, 1] + specialQuestionType[y, x, 2] + specialQuestionType[y, x, 3]; // Max 1 special type, can also be no special question m.AddConstr(specialQuestionUsed[y, x] <= 1, "MaxOneSpecialQuestion" + y + "_" + x); } } } } // TEST //m.AddConstr(specialQuestionType[0, 0, 0] == 1); GRBLinExpr allFieldsSum = new GRBLinExpr(); for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { if (crossword.Grid[y, x] is Blocked) { continue; } allFieldsSum += fields[y, x]; bool noQuestionToTheRightAllowed = false; bool noQuestionTowardsDownAllowed = false; if (x + minWordLength < sizeX && !crossword.HasBlock(y, x, y, x + minWordLength)) { // for right: if [0,0] is question, [0,1..3] must not be question var totalQuestionsHorizontal = fields.SumRange(y, x + 1, y, x + minWordLength); var isQuestionAndPointsRight = fields[y, x] + (1 - questionType[y, x]) - 1; if ((object)specialQuestionUsed[y, x] != null) { isQuestionAndPointsRight += (1 - specialQuestionUsed[y, x]) - 1; } for (int len = 1; len <= minWordLength; len++) { m.AddConstr(isQuestionAndPointsRight <= 1 - fields[y, x + len], "MinWordLength3" + y + "_" + x + "_right" + len); } } else { noQuestionToTheRightAllowed = true; } // for down: if (y + minWordLength < sizeY && !crossword.HasBlock(y, x, y + minWordLength, x)) { var totalQuestionsVertical = fields.SumRange(y + 1, x, y + minWordLength, x); var isQuestionAndPointsDown = fields[y, x] + questionType[y, x] - 1; if ((object)specialQuestionUsed[y, x] != null) { isQuestionAndPointsDown += (1 - specialQuestionUsed[y, x]) - 1; } for (int len = 1; len <= minWordLength; len++) { m.AddConstr(isQuestionAndPointsDown <= 1 - fields[y + len, x], "MinWordLength3" + y + "_" + x + "_down" + len); } } else { noQuestionTowardsDownAllowed = true; } bool atLeastOneSpecialAllowed = false; if ((object)specialQuestionUsed[y, x] != null) { // down, then right if (y + 1 < sizeY && x + minWordLength - 1 < sizeX && !crossword.HasBlock(y + 1, x, y + 1, x + minWordLength - 1)) { for (int len = 1; len <= minWordLength; len++) { m.AddConstr(fields[y, x] + specialQuestionType[y, x, 0] - 1 <= 1 - fields[y + 1, x + len - 1], "MinWordLength3" + y + "_" + x + "_downRight" + len); } if (x > 0) { m.AddConstr(fields[y, x] + specialQuestionType[y, x, 0] - 1 <= fields[y + 1, x - 1], "QuestionBeforeSQ" + y + "_" + x + "_downRight"); } atLeastOneSpecialAllowed = true; } else { m.AddConstr(specialQuestionType[y, x, 0] == 0, "NoSpecialQuestionAllowed" + y + "_" + x + "_downRight"); } // left, then down if (y + minWordLength - 1 < sizeY && x - 1 >= 0 && !crossword.HasBlock(y, x - 1, y + minWordLength - 1, x - 1)) { for (int len = 1; len <= minWordLength; len++) { m.AddConstr(fields[y, x] + specialQuestionType[y, x, 1] - 1 <= 1 - fields[y + len - 1, x - 1], "MinWordLength3" + y + "_" + x + "_leftDown" + len); } if (y > 0) { m.AddConstr(fields[y, x] + specialQuestionType[y, x, 1] - 1 <= fields[y - 1, x - 1], "QuestionBeforeSQ" + y + "_" + x + "_leftDown"); } atLeastOneSpecialAllowed = true; } else { m.AddConstr(specialQuestionType[y, x, 1] == 0, "NoSpecialQuestionAllowed" + y + "_" + x + "_leftDown"); } // right, then down if (y + minWordLength - 1 < sizeY && x + 1 < sizeX && !crossword.HasBlock(y, x + 1, y + minWordLength - 1, x + 1)) { for (int len = 1; len <= minWordLength; len++) { m.AddConstr(fields[y, x] + specialQuestionType[y, x, 2] - 1 <= 1 - fields[y + len - 1, x + 1], "MinWordLength3" + y + "_" + x + "_rightDown" + len); } if (y > 0) { m.AddConstr(fields[y, x] + specialQuestionType[y, x, 2] - 1 <= fields[y - 1, x + 1], "QuestionBeforeSQ" + y + "_" + x + "_rightDown"); } atLeastOneSpecialAllowed = true; } else { m.AddConstr(specialQuestionType[y, x, 2] == 0, "NoSpecialQuestionAllowed" + y + "_" + x + "_rightDown"); } // up, then right if (y - 1 >= 0 && x + minWordLength - 1 < sizeX && !crossword.HasBlock(y - 1, x, y - 1, x + minWordLength - 1)) { for (int len = 1; len <= minWordLength; len++) { m.AddConstr(fields[y, x] + specialQuestionType[y, x, 3] - 1 <= 1 - fields[y - 1, x + len - 1], "MinWordLength3" + y + "_" + x + "_upRight" + len); } if (x > 0) { m.AddConstr(fields[y, x] + specialQuestionType[y, x, 3] - 1 <= fields[y - 1, x - 1], "QuestionBeforeSQ" + y + "_" + x + "_upRight"); } atLeastOneSpecialAllowed = true; } else { m.AddConstr(specialQuestionType[y, x, 3] == 0, "NoSpecialQuestionAllowed" + y + "_" + x + "_upRight"); } if (!atLeastOneSpecialAllowed) { m.AddConstr(specialQuestionUsed[y, x] == 0, "NoSpecialQuestionAllowedAtALl" + y + "_" + x); } } if (noQuestionToTheRightAllowed && noQuestionTowardsDownAllowed) { if (!atLeastOneSpecialAllowed) { m.AddConstr(fields[y, x] == 0, "NoQuestionAllowed" + y + "_" + x); } else { m.AddConstr(specialQuestionUsed[y, x] == 1, "OnlySpecialQuestionAllowed" + y + "_" + x); } } else { if (noQuestionToTheRightAllowed) { m.AddConstr(questionType[y, x] == 1, "QuestionCantPointRight" + y + "_" + x); } if (noQuestionTowardsDownAllowed) { m.AddConstr(questionType[y, x] == 0, "QuestionCantPointDown" + y + "_" + x); } } // max word length constraints // for right: if [0,0] is question, [0,1..maxLength+1] must have at least another question field if (x + maxWordLength + 1 < sizeX && !crossword.HasBlock(y, x, y, x + maxWordLength + 1)) { var allHorizontalFields = new GRBLinExpr(); for (int xi = 1; xi <= maxWordLength + 1; xi++) { allHorizontalFields += fields[y, x + xi]; } if ((object)specialQuestionUsed[y, x] != null) { m.AddConstr(fields[y, x] + (1 - questionType[y, x]) + (1 - specialQuestionUsed[y, x]) - 2 <= allHorizontalFields, "MaxLengthHorizontal" + y + "_" + x); } else { m.AddConstr(fields[y, x] + (1 - questionType[y, x]) - 1 <= allHorizontalFields, "MaxLengthHorizontal" + y + "_" + x); } } // for down: if (y + maxWordLength + 1 < sizeY && !crossword.HasBlock(y, x, y + maxWordLength + 1, x)) { var fieldsSum = fields.SumRange(y + 1, x, y + maxWordLength + 1, x); if ((object)specialQuestionUsed[y, x] != null) { m.AddConstr(fields[y, x] + questionType[y, x] + (1 - specialQuestionUsed[y, x]) - 2 <= fieldsSum, "MaxLengthVertical" + y + "_" + x); } else { m.AddConstr(fields[y, x] + questionType[y, x] - 1 <= fieldsSum, "MaxLengthVertical" + y + "_" + x); } } if ((object)specialQuestionUsed[y, x] != null) { // down, then right if (y + 1 < sizeY && x + maxWordLength < sizeX && !crossword.HasBlock(y + 1, x, y + 1, x + maxWordLength)) { var fieldsSum = fields.SumRange(y + 1, x, y + 1, x + maxWordLength); m.AddConstr(fields[y, x] + specialQuestionType[y, x, 0] - 1 <= fieldsSum, "MaxLengthSpecialQuestion0_" + y + "_" + x); // if there is a normal field to the left of the word, it has to be a question if (x - 1 >= 0 && !crossword.HasBlock(y + 1, x - 1)) { m.AddConstr(fields[y, x] + specialQuestionType[y, x, 0] - 1 <= fields[y + 1, x - 1], "QuestionRequiredBeforeSpecialQuestion0Word_" + y + "_" + x); } } // left, then down if (y + maxWordLength < sizeY && x - 1 >= 0 && !crossword.HasBlock(y, x - 1, y + maxWordLength, x - 1)) { var fieldsSum = fields.SumRange(y, x - 1, y + maxWordLength, x - 1); m.AddConstr(fields[y, x] + specialQuestionType[y, x, 1] - 1 <= fieldsSum, "MaxLengthSpecialQuestion1_" + y + "_" + x); if (y - 1 >= 0 && !crossword.HasBlock(y - 1, x - 1)) { m.AddConstr(fields[y, x] + specialQuestionType[y, x, 0] - 1 <= fields[y - 1, x - 1], "QuestionRequiredBeforeSpecialQuestion1Word_" + y + "_" + x); } } // right, then down if (y + maxWordLength < sizeY && x + 1 < sizeX && !crossword.HasBlock(y, x + 1, y + maxWordLength, x + 1)) { var fieldsSum = fields.SumRange(y, x + 1, y + maxWordLength, x + 1); m.AddConstr(fields[y, x] + specialQuestionType[y, x, 2] - 1 <= fieldsSum, "MaxLengthSpecialQuestion2_" + y + "_" + x); if (y - 1 >= 0 && !crossword.HasBlock(y - 1, x + 1)) { m.AddConstr(fields[y, x] + specialQuestionType[y, x, 0] - 1 <= fields[y - 1, x + 1], "QuestionRequiredBeforeSpecialQuestion2Word_" + y + "_" + x); } } // up, then right if (y - 1 >= 0 && x + maxWordLength < sizeX && !crossword.HasBlock(y - 1, x, y - 1, x + maxWordLength)) { var fieldsSum = fields.SumRange(y - 1, x, y - 1, x + maxWordLength); m.AddConstr(fields[y, x] + specialQuestionType[y, x, 3] - 1 <= fieldsSum, "MaxLengthSpecialQuestion3_" + y + "_" + x); if (x - 1 >= 0 && !crossword.HasBlock(y - 1, x - 1)) { m.AddConstr(fields[y, x] + specialQuestionType[y, x, 0] - 1 <= fields[y - 1, x - 1], "QuestionRequiredBeforeSpecialQuestion3Word_" + y + "_" + x); } } } } } // Does a field belong to zero, one or two questions? var partOfAWord = new GRBLinExpr[sizeY, sizeX, 6]; for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { // this constraint doesn't work for [0,0] if (crossword.HasBlock(y, x)) { continue; } // Is this field attached to a question on the left, pointing right? var attachedToHorizontalQuestion = m.AddVar(0, 1, 0, GRB.BINARY, "attachedToHorizontalQuestion" + y + "_" + x); for (int len = 1; len <= maxWordLength; len++) { if (x - len < 0 || crossword.HasBlock(y, x - len, y, x)) { continue; } var isQuestionAndPointsRight = fields[y, x - len] + (1 - questionType[y, x - len]); if ((object)specialQuestionUsed[y, x - len] != null) { isQuestionAndPointsRight += (1 - specialQuestionUsed[y, x - len]) - 1; } var questionsInbetween = fields.SumRange(y, x - len + 1, y, x); m.AddConstr(attachedToHorizontalQuestion >= isQuestionAndPointsRight - 1 - questionsInbetween, "attachedToHorizontalQuestionConstraint0_" + y + "_" + x); // 0 IF first question is not pointing right OR there is no question to the left // firstQuestion ==> total fields < 2 if ((object)specialQuestionUsed[y, x - len] != null) { m.AddConstr(attachedToHorizontalQuestion <= questionsInbetween + (1 - fields[y, x - len]) + 1 - (questionType[y, x - len] + specialQuestionUsed[y, x - len]) * 0.5, "attachedToHorizontalQuestionConstraint1_" + y + "_" + x); // the first question but DOESNT look right } else { m.AddConstr(attachedToHorizontalQuestion <= questionsInbetween + (1 - fields[y, x - len]) + 1 - questionType[y, x - len], "attachedToHorizontalQuestionConstraint2_" + y + "_" + x); // the first question but DOESNT look right } } var questionsToTheLeft = new GRBLinExpr(); int qlCount = 0; for (int len = 0; len <= maxWordLength; len++) { if (x - len < 0 || crossword.HasBlock(y, x - len, y, x)) { continue; } questionsToTheLeft += fields[y, x - len]; qlCount++; } if (qlCount > 0) { m.AddConstr(attachedToHorizontalQuestion <= questionsToTheLeft, "attachedToHorizontalQuestionConstraint4_" + y + "_" + x); } // Is this field attached to a question pointing towards down? var attachedToVerticalQuestion = m.AddVar(0, 1, 0, GRB.BINARY, "attachedToVerticalQuestion" + y + "_" + x); for (int len = 1; len <= maxWordLength; len++) { if (y - len < 0 || crossword.HasBlock(y - len, x, y, x)) { continue; } var isQuestionAndPointsDown = fields[y - len, x] + questionType[y - len, x]; if ((object)specialQuestionUsed[y - len, x] != null) { isQuestionAndPointsDown += (1 - specialQuestionUsed[y - len, x]) - 1; } var questionsInbetween = fields.SumRange(y - len + 1, x, y, x); m.AddConstr(attachedToVerticalQuestion >= isQuestionAndPointsDown - 1 - questionsInbetween, "attachedToVerticalQuestionConstraint0_" + y + "_" + x); if ((object)specialQuestionUsed[y - len, x] != null) { m.AddConstr(attachedToVerticalQuestion <= questionsInbetween + (1 - fields[y - len, x]) + 1 - (1 - questionType[y - len, x] + specialQuestionUsed[y - len, x]) * 0.5, "attachedToVerticalQuestionConstraint1_" + y + "_" + x); // the first question but DOESNT look down OR IS specialquestion } else { m.AddConstr(attachedToVerticalQuestion <= questionsInbetween + (1 - fields[y - len, x]) + 1 - (1 - questionType[y - len, x]), "attachedToVerticalQuestionConstraint2_" + y + "_" + x); // the first question but DOESNT look down OR IS specialquestion } } var questionsTowardsDown = new GRBLinExpr(); int qdCount = 0; for (int len = 0; len <= maxWordLength; len++) { if (y - len < 0 || crossword.HasBlock(y - len, x, y, x)) { continue; } questionsTowardsDown += fields[y - len, x]; qdCount++; } if (qdCount > 0) { m.AddConstr(attachedToVerticalQuestion <= questionsTowardsDown, "attachedToVerticalQuestionConstraint3_" + y + "_" + x); } var attachedToSpecialQuestions = new GRBLinExpr[4]; var spAll = new GRBLinExpr(); for (int type = 0; type < 4; type++) { attachedToSpecialQuestions[type] = AttachedToSpecialQuestion(y, x, type, crossword, m, sizeX, sizeY, maxWordLength, fields, specialQuestionUsed, specialQuestionType); if ((object)attachedToSpecialQuestions[type] != null) { spAll += attachedToSpecialQuestions[type]; } } // if attached to horizontal question, can't be attached to horizontal sq (0, 3) if ((object)attachedToSpecialQuestions[0] != null) { m.AddConstr((1 - attachedToHorizontalQuestion) >= attachedToSpecialQuestions[0], "noHorizontalOverlap1_" + y + "_" + x); } if ((object)attachedToSpecialQuestions[3] != null) { m.AddConstr((1 - attachedToHorizontalQuestion) >= attachedToSpecialQuestions[3], "noHorizontalOverlap2_" + y + "_" + x); } // give preference to one horizontal kind of sq if ((object)attachedToSpecialQuestions[0] != null && (object)attachedToSpecialQuestions[3] != null) { m.AddConstr((1 - attachedToSpecialQuestions[0]) >= attachedToSpecialQuestions[3], "noHorizontalOverlap3_" + y + "_" + x); } // if attached to vertical question, can't be attached to vertical sq (1, 2) if ((object)attachedToSpecialQuestions[1] != null) { m.AddConstr((1 - attachedToVerticalQuestion) >= attachedToSpecialQuestions[1], "noVerticalOverlap1_" + y + "_" + x); } if ((object)attachedToSpecialQuestions[2] != null) { m.AddConstr((1 - attachedToVerticalQuestion) >= attachedToSpecialQuestions[2], "noVerticalOverlap2_" + y + "_" + x); } // give preference to one horizontal kind of sq if ((object)attachedToSpecialQuestions[1] != null && (object)attachedToSpecialQuestions[2] != null) { m.AddConstr((1 - attachedToSpecialQuestions[1]) >= attachedToSpecialQuestions[2], "noVerticalOverlap3_" + y + "_" + x); } var c = m.AddConstr(attachedToHorizontalQuestion + attachedToVerticalQuestion + spAll >= 1 - fields[y, x], "AttachedToQuestionConstraint_" + y + "_" + x); //c.Lazy = 1; partOfAWord[y, x, 0] = attachedToHorizontalQuestion; partOfAWord[y, x, 1] = attachedToVerticalQuestion; partOfAWord[y, x, 2] = attachedToSpecialQuestions[0]; partOfAWord[y, x, 3] = attachedToSpecialQuestions[1]; partOfAWord[y, x, 4] = attachedToSpecialQuestions[2]; partOfAWord[y, x, 5] = attachedToSpecialQuestions[3]; } } // right now, [0,0] can only be a question //if (!crossword.HasBlock(0, 0)) m.AddConstr(fields[0, 0] == 1); // and similarly the bottom 3x3 can only be letters for (int y = sizeY - minWordLength; y < sizeY; y++) { for (int x = sizeX - minWordLength; x < sizeX; x++) { if (!crossword.HasBlock(y, x)) { m.AddConstr(fields[y, x] == 0, "BottomOnlyLetters_" + y + "_" + x); } } } // Objective: // questions should be around ~22% (allFieldsSum ~= amountQuestions) /*int tolerance = (int)(amountQuestions * 0.1); * m.AddConstr(allFieldsSum >= amountQuestions - tolerance, "amountOfQuestionsTolerance_1"); * m.AddConstr(allFieldsSum <= amountQuestions + tolerance, "amountOfQuestionsTolerance_2");*/ m.AddConstr(allFieldsSum == amountQuestions); // uncrossed var partOfWordTotals = new GRBLinExpr[sizeY, sizeX]; for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { if (!crossword.HasBlock(y, x)) //(x >= 1 || y >= 1) && { var partOfWordTotal = new GRBLinExpr(); for (int t = 0; t < 6; t++) { if ((object)partOfAWord[y, x, t] != null) { partOfWordTotal += partOfAWord[y, x, t]; } } partOfWordTotals[y, x] = partOfWordTotal; } } } for (int y = 0; y < sizeY - 1; y++) { for (int x = 0; x < sizeX - 1; x++) { if (!crossword.HasBlock(y, x)) //(x >= 1 || y >= 1) && { if (!crossword.HasBlock(y + 1, x)) { m.AddConstr(partOfWordTotals[y, x] + partOfWordTotals[y + 1, x] >= (1 - fields[y, x] - fields[y + 1, x]) * 3, "noUncrossedFields" + y + "_" + x); } if (!crossword.HasBlock(y, x + 1)) { m.AddConstr(partOfWordTotals[y, x] + partOfWordTotals[y, x + 1] >= (1 - fields[y, x] - fields[y, x + 1]) * 3, "noUncrossedFields" + y + "_" + x); } } } } // penalty for nearby uncrossed letters (dead fields) /*var deadFieldPenalty = new GRBLinExpr(); * for (int y = 0; y < sizeY; y++) * { * for (int x = 0; x < sizeX; x++) * { * var hby = y - 1 >= 0 && !crossword.HasBlock(y - 1, x); * var hbx = x - 1 >= 0 && !crossword.HasBlock(y, x - 1); * if (!crossword.HasBlock(y, x) && (hby || hbx)) * { * var isDeadArea = m.AddVar(0, 1, 0, GRB.BINARY, "isDeadArea" + y + "_" + x); * if (hby) m.AddConstr(isDeadArea >= uncrossedLetters[y, x] + uncrossedLetters[y - 1, x] - 1, "deadAreaConstr1" + y + "_" + x); * if (hbx) m.AddConstr(isDeadArea >= uncrossedLetters[y, x] + uncrossedLetters[y, x - 1] - 1, "deadAreaConstr2" + y + "_" + x); * m.AddConstr(isDeadArea <= uncrossedLetters[y, x]); * if (hby && hbx) * m.AddConstr(isDeadArea <= uncrossedLetters[y - 1, x] + uncrossedLetters[y, x - 1], "deadAreaConstr3" + y + "_" + x); * else if (hby) * m.AddConstr(isDeadArea <= uncrossedLetters[y - 1, x], "deadAreaConstr4" + y + "_" + x); * else if (hbx) * m.AddConstr(isDeadArea <= uncrossedLetters[y, x - 1], "deadAreaConstr5" + y + "_" + x); * deadFieldPenalty += isDeadArea; * } * } * }*/ // ideal histogram comparison //var wordHistogramDifferences = new GRBLinExpr(); var wlTotals = new Dictionary <int, GRBLinExpr>(); foreach (var wl in wordLengthHistogram.Keys) { var total = new GRBLinExpr(); for (int y = 0; y + wl - 1 < sizeY; y++) { for (int x = 0; x < sizeX; x++) { if (crossword.HasBlock(y, x, y + wl - 1, x)) { continue; } // true if field-1 is question or start AND field + wl (after word) is question or end var hasLength = m.AddVar(0, 1, 0, GRB.BINARY, "hasLenVert" + wl + "__" + y + "_" + x); var sum = fields.SumRange(y, x, y + wl - 1, x); // no questions inbetween for (int i = 0; i < wl; i++) { m.AddConstr(hasLength <= 1 - fields[y + i, x]); } // question at end if (y + wl < sizeY && !crossword.HasBlock(y + wl, x)) { sum += (1 - fields[y + wl, x]); m.AddConstr(hasLength <= fields[y + wl, x]); } // question at start if (y - 1 >= 0 && !crossword.HasBlock(y - 1, x)) { sum += (1 - fields[y - 1, x]); m.AddConstr(hasLength <= fields[y - 1, x]); } // counts if a letter is attached to a horizontal question var qsum = new GRBLinExpr(); if ((object)partOfAWord[y, x, 1] != null) { qsum += partOfAWord[y, x, 1]; } if ((object)partOfAWord[y, x, 3] != null) { qsum += partOfAWord[y, x, 3]; } if ((object)partOfAWord[y, x, 4] != null) { qsum += partOfAWord[y, x, 4]; } sum += 1 - qsum; m.AddConstr(hasLength <= qsum); m.AddConstr(hasLength >= 1 - sum); total += hasLength; } } for (int y = 0; y < sizeY; y++) { for (int x = 0; x + wl - 1 < sizeX; x++) { if (crossword.HasBlock(y, x, y, x + wl - 1)) { continue; } var hasLength = m.AddVar(0, 1, 0, GRB.BINARY, "hasLenHoriz" + wl + "__" + y + "_" + x); var sum = fields.SumRange(y, x, y, x + wl - 1); // no questions inbetween for (int i = 0; i < wl; i++) { m.AddConstr(hasLength <= 1 - fields[y, x + i]); } // question at end if (x + wl < sizeX && !crossword.HasBlock(y, x + wl)) { sum += (1 - fields[y, x + wl]); m.AddConstr(hasLength <= fields[y, x + wl]); } // question at start if (x - 1 >= 0 && !crossword.HasBlock(y, x - 1)) { sum += (1 - fields[y, x - 1]); m.AddConstr(hasLength <= fields[y, x - 1]); } // counts if a letter is attached to a horizontal question var qsum = new GRBLinExpr(); if ((object)partOfAWord[y, x, 0] != null) { qsum += partOfAWord[y, x, 0]; } if ((object)partOfAWord[y, x, 2] != null) { qsum += partOfAWord[y, x, 2]; } if ((object)partOfAWord[y, x, 5] != null) { qsum += partOfAWord[y, x, 5]; } sum += 1 - qsum; m.AddConstr(hasLength <= qsum); m.AddConstr(hasLength >= 1 - sum); total += hasLength; } } if (wl <= 9) { wlTotals.Add(wl, total); } else { wlTotals[9] += total; } } var wlPenalty = new GRBLinExpr(); var wordCounts = m.AddVars(8, 0, amountQuestions * 2, GRB.INTEGER, "amount"); foreach (var wl in wlTotals.Keys) { var input = wordCounts[wl - 2]; m.AddConstr(input == wlTotals[wl]); var absRes = m.AddVar(0, 100, 0, GRB.CONTINUOUS, "absRes"); Console.WriteLine(wl == 9 ? 4 : wordLengthHistogram[wl]); var percentageDiff = input * (100d / amountQuestions) - (wl == 9 ? 4 : wordLengthHistogram[wl]); m.AddConstr(percentageDiff <= absRes, "absPos"); m.AddConstr(-percentageDiff <= absRes, "absNeg"); wlPenalty += absRes; } wlPenalty *= (1d / 8); // question field clusters // in a field of 2x2, minimize the nr of fields where there are 2-4 questions resp. maximize 0-1 questions //var clusterPenalty = new GRBLinExpr(); int area = 3; for (int y = 0; y < sizeY - (area - 1); y++) { for (int x = 0; x < sizeX - (area - 1); x++) { var clusterTotal = new GRBLinExpr(); int ct = 0; for (int i = 0; i < area; i++) { for (int j = 0; j < area; j++) { if (crossword.HasBlock(y + i, x + j) || (i == 1 && j == 1)) { continue; } clusterTotal += fields[y + i, x + j]; ct++; } } if (ct >= 2 && !crossword.HasBlock(y + 1, x + 1)) { //var varClusterTotalPenalty = m.AddVar(0, 1, 0, GRB.BINARY, "varClusterTotalPenalty" + y + "_" + x); // 0-1 = good, 2-4 = bad m.AddConstr(clusterTotal - 1 <= (1 - fields[y + 1, x + 1]) * 8, "cluster" + y + "_" + x); /*m.AddConstr(varClusterTotalPenalty <= clusterTotal * 0.5, "clusterPenaltyConstr1_" + y + "_" + x); * m.AddConstr(varClusterTotalPenalty >= (clusterTotal - 1) * (1d / 3), "clusterPenaltyConstr2_" + y + "_" + x); * clusterPenalty += varClusterTotalPenalty;*/ } } } //m.AddConstr(deadFieldPenalty <= 30); //amountOfQuestionsRating * (100d / sizeX / sizeY) + manyCrossedWords + + wordHistogramDifferences // clusterPenalty * 100 m.SetObjective(wlPenalty, GRB.MINIMIZE); m.SetCallback(new GRBMipSolCallback(crossword, fields, questionType, specialQuestionType, true, wordCounts)); // Insert previous solution /*var cwdCheck = new Crossword(@"C:\Users\Roman Bolzern\Documents\GitHub\Crossword\docs\15x15_1_noDoubles.cwg"); * cwdCheck.Draw(); * for (int y = 0; y < cwdCheck.Grid.GetLength(0); y++) * { * for (int x = 0; x < cwdCheck.Grid.GetLength(1); x++) * { * if (cwdCheck.Grid[y, x] is Question) * { * m.AddConstr(fields[y, x] == 1); * var q = (Question)cwdCheck.Grid[y, x]; * if (q.Arrow == Question.ArrowType.Right) * { * m.AddConstr(questionType[y, x] == 0); * if ((object)specialQuestionType[y, x, 0] != null) * m.AddConstr(specialQuestionType[y, x, 0] + specialQuestionType[y, x, 1] + specialQuestionType[y, x, 2] + specialQuestionType[y, x, 3] == 0); * } * else if (q.Arrow == Question.ArrowType.Down) * { * m.AddConstr(questionType[y, x] == 1); * if ((object)specialQuestionType[y, x, 0] != null) * m.AddConstr(specialQuestionType[y, x, 0] + specialQuestionType[y, x, 1] + specialQuestionType[y, x, 2] + specialQuestionType[y, x, 3] == 0); * } * else if (q.Arrow == Question.ArrowType.DownRight) * { * m.AddConstr(specialQuestionType[y, x, 0] == 1); * } * else if (q.Arrow == Question.ArrowType.LeftDown) * { * m.AddConstr(specialQuestionType[y, x, 1] == 1); * } * else if (q.Arrow == Question.ArrowType.RightDown) * { * m.AddConstr(specialQuestionType[y, x, 2] == 1); * } * else if (q.Arrow == Question.ArrowType.UpRight) * { * m.AddConstr(specialQuestionType[y, x, 3] == 1); * } * } * else if (cwdCheck.Grid[y, x] is Letter) * { * m.AddConstr(fields[y, x] == 0); * } * } * }*/ m.Optimize(); m.ComputeIIS(); m.Write("model.ilp"); m.Dispose(); env.Dispose(); }
public static void Main(String[] args) { if (args.Length < 1) { Console.WriteLine("Usage: tsp_cs nnodes"); return; } int n = Convert.ToInt32(args[0]); try { GRBEnv env = new GRBEnv(); GRBModel model = new GRBModel(env); // Must disable dual reductions when using lazy constraints model.GetEnv().Set(GRB.IntParam.DualReductions, 0); double[] x = new double[n]; double[] y = new double[n]; Random r = new Random(); for (int i = 0; i < n; i++) { x[i] = r.NextDouble(); y[i] = r.NextDouble(); } // Create variables GRBVar[,] vars = new GRBVar[n, n]; for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) vars[i, j] = model.AddVar(0.0, 1.0, distance(x, y, i, j), GRB.BINARY, "x"+i+"_"+j); // Integrate variables model.Update(); // Degree-2 constraints for (int i = 0; i < n; i++) { GRBLinExpr expr = 0; for (int j = 0; j < n; j++) expr += vars[i, j]; model.AddConstr(expr == 2.0, "deg2_"+i); } // Forbid edge from node back to itself for (int i = 0; i < n; i++) vars[i, i].Set(GRB.DoubleAttr.UB, 0.0); // Symmetric TSP for (int i = 0; i < n; i++) for (int j = 0; j < i; j++) model.AddConstr(vars[i, j]== vars[j, i], ""); model.SetCallback(new tsp_cs(vars)); model.Optimize(); if (model.Get(GRB.IntAttr.SolCount) > 0) { int[] tour = findsubtour(model.Get(GRB.DoubleAttr.X, vars)); Console.Write("Tour: "); for (int i = 0; i < tour.Length; i++) Console.Write(tour[i] + " "); Console.WriteLine(); } // Dispose of model and environment model.Dispose(); env.Dispose(); } catch (GRBException e) { Console.WriteLine("Error code: " + e.ErrorCode + ". " + e.Message); Console.WriteLine(e.StackTrace); } }
public List <int[]> SolveVRP(int timeLimitMilliseconds) { using (var env = new GRBEnv($"{DateTime.Now:yy-MM-dd-HH-mm-ss}_vrp_gurobi.log")) using (var vrpModel = new GRBModel(env)) { #region initialize Variables var numberOfRoutes = input.Santas.Length * input.Days.Length; var v = new GRBVar[numberOfRoutes][]; // [santa] visits [visit] var w = new GRBVar[numberOfRoutes][]; // [santa] uses [way] for (int s = 0; s < numberOfRoutes; s++) { v[s] = new GRBVar[visitDurations.Length]; for (int i = 0; i < v[s].Length; i++) { v[s][i] = vrpModel.AddVar(0, 1, 0.0, GRB.BINARY, $"v[{s}][{i}]"); } w[s] = vrpModel.AddVars(distances.GetLength(0) * distances.GetLength(1), GRB.BINARY); } #endregion initialize Variables #region add constraints SelfieConstraint(vrpModel, numberOfRoutes, w); VisitVisitedOnce(vrpModel, numberOfRoutes, v); IncomingOutgoingWaysConstraints(vrpModel, numberOfRoutes, w, v); IncomingOutgoingSanta(vrpModel, numberOfRoutes, v, w); IncomingOutgoingSantaHome(vrpModel, numberOfRoutes, w, v); NumberOfWaysMatchForSanta(vrpModel, numberOfRoutes, v, w); BreakHandling(vrpModel, numberOfRoutes, v); #endregion add constraints var totalWayTime = new GRBLinExpr(0); var longestRoute = vrpModel.AddVar(0, input.Days.Max(d => d.to - d.from), 0, GRB.CONTINUOUS, "longestRoute"); for (int s = 0; s < numberOfRoutes; s++) { var routeTime = new GRBLinExpr(0); for (int i = 0; i < visitDurations.Length; i++) { routeTime += v[s][i] * visitDurations[i]; for (int j = 0; j < visitDurations.Length; j++) { routeTime += AccessW(w[s], i, j) * distances[i, j]; } } totalWayTime += routeTime; vrpModel.AddConstr(longestRoute >= routeTime, $"longesRouteConstr{s}"); } vrpModel.SetObjective( +(40d / 3600d) * totalWayTime + (30d / 3600d) * longestRoute , GRB.MINIMIZE); vrpModel.Parameters.LazyConstraints = 1; vrpModel.SetCallback(new VRPCallbackSolverCallback(w, AccessW, ConvertBack)); vrpModel.Parameters.TimeLimit = timeLimitMilliseconds / 1000; InitializeModel(w, v, numberOfRoutes, input.Visits, input.Santas.Length); vrpModel.Optimize(); if (vrpModel.SolCount == 0) { return(null); } var routes = new List <int[]>(); for (int s = 0; s < numberOfRoutes; s++) { var route = new List <int>(); var currVisit = 0; do { route.Add(currVisit); for (int i = 0; i < visitDurations.Length; i++) { if (AccessW(w[s], currVisit, i).X > 0.5) { currVisit = i; break; } } } while (currVisit != 0); routes.Add(route.ToArray()); } vrpModel.Reset(); return(routes); } }
public static Graph RunSolver(Graph graph) { GRBEnv env = new GRBEnv(); env.Set(GRB.IntParam.OutputFlag, 0); env.Set(GRB.IntParam.LogToConsole, 0); env.Set(GRB.IntParam.Presolve, 2); env.Set(GRB.DoubleParam.Heuristics, 0.0); GRBModel model = new GRBModel(env); GRBVar[] variables = new GRBVar[graph.NumberOfEdges]; model.SetCallback(new LPSolverCallback()); Dictionary<Edge, GRBVar> edgeVars = new Dictionary<Edge, GRBVar>(); // Add variables to the LP model for (int i = 0; i < graph.NumberOfEdges; i++) { variables[i] = model.AddVar(0.0, 1.0, 0.0, GRB.BINARY, "x_" + i); edgeVars.Add(graph.Edges[i], variables[i]); } model.Update(); // Add constraints to the LP model Console.Write("\rRunning LP. Creating constraints...\r"); //var nonTerminals = graph.Vertices.Except(graph.Terminals).ToList(); ulong conNr = 0; //var terminalCombinations = new List<List<Vertex>>(); // Assume, without loss of generality, that Terminals[0] is the root, and thus is always included int rootNr = 1; foreach (var rootTerminal in graph.Terminals) //var rootTerminal = graph.Terminals[0]; { Console.Write("\rRunning LP. Creating constraints... {0}/{1}\r", rootNr, graph.Terminals.Count); foreach (var combination in GetBFS(graph, rootTerminal)) { var nodes = combination.ToList(); //new HashSet<Vertex>(combination); if (nodes.Count == graph.NumberOfVertices || graph.Terminals.All(nodes.Contains)) continue; //Debug.WriteLine("Combination: {0}", string.Join(" ", nodes)); //for (int i = 1; i <= nodes.Count; i++) { var edges = nodes//.Take(i) .SelectMany(graph.GetEdgesForVertex) .Distinct() .Where(x => x.WhereOne(y => !nodes.Contains(y))); GRBLinExpr expression = 0; foreach (var edge in edges) expression.AddTerm(1, edgeVars[edge]); model.AddConstr(expression >= 1.0, "subset_" + conNr); conNr++; if (conNr % 100000 == 0) { //model = model.Presolve(); //Pre-solve the model every 1000 constraints. int constrBefore = model.GetConstrs().Length, varsBefore = model.GetVars().Length; Debug.WriteLine("Presolve called."); var presolved = model.Presolve(); Debug.WriteLine("Model has {0} constraints, {1} variables. Presolve has {2} constraints, {3} variables", constrBefore, varsBefore, presolved.GetConstrs().Length, presolved.GetVars().Length); } } } //Debug.WriteLine(" "); //Debug.WriteLine(" "); rootNr++; } //terminalCombinations.Add(new List<Vertex>(new[] { graph.Terminals[0] })); //for (int j = 1; j < graph.Terminals.Count - 1; j++) // terminalCombinations.AddRange(new Combinations<Vertex>(graph.Terminals.Skip(1), j).Select(combination => combination.Union(new[] { graph.Terminals[0] }).ToList())); //long nonTerminalSetsDone = 0; //long nonTerminalSets = 0; //for (int i = 0; i <= nonTerminals.Count; i++) // nonTerminalSets += Combinations<Vertex>.NumberOfCombinations(nonTerminals.Count, i); //for (int i = 0; i <= nonTerminals.Count; i++) //{ // foreach (var nonTerminalSet in new Combinations<Vertex>(nonTerminals, i)) // { // foreach (var nodes in (from a in terminalCombinations // select new HashSet<Vertex>(a.Union(nonTerminalSet)))) // { // var edges = nodes.SelectMany(graph.GetEdgesForVertex) // .Distinct() // .Where(x => x.WhereOne(y => !nodes.Contains(y))); // GRBLinExpr expression = 0; // foreach (var edge in edges) // expression.AddTerm(1, edgeVars[edge]); // model.AddConstr(expression >= 1.0, "subset_" + conNr); // conNr++; // } // nonTerminalSetsDone++; // if (nonTerminalSetsDone % 100 == 0) // Console.Write("\rRunning LP. Creating constraints... {0}/{1} ({2:0.000}%)\r", nonTerminalSetsDone, nonTerminalSets, nonTerminalSetsDone * 100.0 / nonTerminalSets); // } //} // Solve the LP model Console.Write("\rRunning LP. Creating objective & updating... \r"); GRBLinExpr objective = new GRBLinExpr(); for (int i = 0; i < graph.NumberOfEdges; i++) objective.AddTerm(graph.Edges[i].Cost, variables[i]); model.SetObjective(objective, GRB.MINIMIZE); Console.Write("\rRunning LP. Tuning... \r"); model.Tune(); Debug.WriteLine("Presolve called."); model.Presolve(); Console.Write("\rRunning LP. Solving... \r"); Debug.WriteLine("Optimize called."); model.Optimize(); Graph solution = graph.Clone(); HashSet<Edge> includedEdges = new HashSet<Edge>(); for (int i = 0; i < solution.NumberOfEdges; i++) { var value = variables[i].Get(GRB.DoubleAttr.X); if (value == 1) includedEdges.Add(solution.Edges[i]); } foreach (var edge in solution.Edges.ToList()) if (!includedEdges.Contains(edge)) solution.RemoveEdge(edge); Console.Write("\r \r"); return solution; }
public GurobiSolver3(Crossword crossword) { var wordLengthHistogram = new Dictionary <int, double>() { { 3, 18 }, { 4, 24 }, { 5, 20 }, { 6, 18 }, { 7, 12 }, { 8, 4 }, { 9, 4 }, }; const int maxWordLength = 9; int sizeY = crossword.Grid.GetLength(0); int sizeX = crossword.Grid.GetLength(1); var requiredAmountOfLetters = wordLengthHistogram.Sum(wl => wl.Key * wl.Value) / 1.8d; int totalFields = sizeX * sizeY; for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { totalFields -= crossword.Grid[y, x] is Blocked ? 1 : 0; } } int amountQuestions = (int)Math.Round(0.22 * totalFields); var wordLengthRatio = requiredAmountOfLetters / (totalFields - amountQuestions); GRBEnv env = new GRBEnv(); GRBModel m = new GRBModel(env); // 0 = letter, 1 = question GRBVar[,] fields = new GRBVar[sizeY, sizeX]; // 0 = right, 1 = down GRBVar[,] questionType = new GRBVar[sizeY, sizeX]; for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { // create a var for every non-blocked field if (!(crossword.Grid[y, x] is Blocked)) { fields[y, x] = m.AddVar(0, 1, 0, GRB.BINARY, "Field" + x + "_" + y); questionType[y, x] = m.AddVar(0, 1, 0, GRB.BINARY, "QType" + x + "_" + y); } } } GRBLinExpr allFieldsSum = new GRBLinExpr(); // All non-question fields have to belong to a word // E.g. if a question points right, only lengths 3 to 9 are allowed for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { if (crossword.Grid[y, x] is Blocked) { continue; } allFieldsSum += fields[y, x]; bool noQuestionToTheRightAllowed = false; bool noQuestionTowardsDownAllowed = false; if (x + 3 < sizeX && !crossword.HasBlock(y, x, y, x + 3)) { // for right: if [0,0] is question, [0,1..3] must not be question var totalQuestionsHorizontal = fields[y, x + 1] + fields[y, x + 2] + fields[y, x + 3]; m.AddConstr(fields[y, x] + (1 - questionType[y, x]) - 1 <= 1 - fields[y, x + 1], "MinWordLength3" + y + "_" + x + "_right1"); m.AddConstr(fields[y, x] + (1 - questionType[y, x]) - 1 <= 1 - fields[y, x + 2], "MinWordLength3" + y + "_" + x + "_right2"); m.AddConstr(fields[y, x] + (1 - questionType[y, x]) - 1 <= 1 - fields[y, x + 3], "MinWordLength3" + y + "_" + x + "_right3"); } else { noQuestionToTheRightAllowed = true; } // for down: if (y + 3 < sizeY && !crossword.HasBlock(y, x, y + 3, x)) { var totalQuestionsVertical = fields[y + 1, x] + fields[y + 2, x] + fields[y + 3, x]; m.AddConstr(fields[y, x] + questionType[y, x] - 1 <= 1 - fields[y + 1, x], "MinWordLength3" + y + "_" + x + "_down1"); m.AddConstr(fields[y, x] + questionType[y, x] - 1 <= 1 - fields[y + 2, x], "MinWordLength3" + y + "_" + x + "_down2"); m.AddConstr(fields[y, x] + questionType[y, x] - 1 <= 1 - fields[y + 3, x], "MinWordLength3" + y + "_" + x + "_down3"); } else { noQuestionTowardsDownAllowed = true; } if (noQuestionToTheRightAllowed && noQuestionTowardsDownAllowed) { m.AddConstr(fields[y, x] == 0, "NoQuestionAllowed" + y + "_" + x); } else { if (noQuestionToTheRightAllowed) { m.AddConstr(questionType[y, x] == 1, "QuestionCantPointRight" + y + "_" + x); } if (noQuestionTowardsDownAllowed) { m.AddConstr(questionType[y, x] == 0, "QuestionCantPointDown" + y + "_" + x); } } // max word length constraints if (x + maxWordLength + 1 < sizeX && !crossword.HasBlock(y, x, y, x + maxWordLength + 1)) { // for right: if [0,0] is question, [0,1..maxLength+1] must have at least another question field var allHorizontalFields = new GRBLinExpr(); for (int xi = 1; xi <= maxWordLength + 1; xi++) { allHorizontalFields += fields[y, x + xi]; } m.AddConstr(fields[y, x] + (1 - questionType[y, x]) - 1 <= allHorizontalFields, "MaxLengthHorizontal" + y + "_" + x); } if (y + maxWordLength + 1 < sizeY && !crossword.HasBlock(y, x, y + maxWordLength + 1, x)) { // for down: var allVerticalFields = new GRBLinExpr(); for (int yi = 1; yi <= maxWordLength + 1; yi++) { allVerticalFields += fields[y + yi, x]; } m.AddConstr(fields[y, x] + questionType[y, x] - 1 <= allVerticalFields, "MaxLengthVertical" + y + "_" + x); } } } // Does a field belong to zero, one or two questions? var partOfAWord = new GRBLinExpr[sizeY, sizeX, 2]; for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { // this constraint doesn't work for [0,0] if ((x == 0 && y == 0) || crossword.HasBlock(y, x)) { continue; } // does this field have a question to the left? /*var attachedToHorizontalQuestion = new GRBLinExpr(); * for (int l = 0; l < maxWordLength; l++) * { * if (x - l - 1 < 0) continue; * var attachedToHorizontalQuestionSpecificLength = m.AddVar(0, 1, 0, GRB.BINARY, "varAttachedToHorizontalQuestionLength" + l + "_" + y + "_" + x); * // If the first field is a question and points to the right, and no letters inbetween are questions * var isQuestionAndPointsRight = fields[y, x - l - 1] + (1 - questionType[y, x - l - 1]); * var allHorizontalFields = new GRBLinExpr(); * for (int xi = 0; xi <= l; xi++) * allHorizontalFields += fields[y, x - xi]; * m.AddConstr(attachedToHorizontalQuestionSpecificLength >= isQuestionAndPointsRight - 1 - allHorizontalFields); * for (int xi = 0; xi <= l; xi++) m.AddConstr(attachedToHorizontalQuestionSpecificLength <= 1 - fields[y, x - xi]); * m.AddConstr(attachedToHorizontalQuestionSpecificLength <= fields[y, x - l - 1]); * m.AddConstr(attachedToHorizontalQuestionSpecificLength <= 1 - questionType[y, x - l - 1]); * //m.AddConstr(attachedToHorizontalQuestionSpecificLength <= isQuestionAndPointsRight * 0.5); * //m.AddConstr(attachedToHorizontalQuestionSpecificLength <= 1 - allHorizontalFields * (1d / (l + 1))); * attachedToHorizontalQuestion += attachedToHorizontalQuestionSpecificLength; * }*/ // RETRY var attachedToHorizontalQuestion = m.AddVar(0, 1, 0, GRB.BINARY, "attachedToHorizontalQuestion" + y + "_" + x); for (int len = 1; len <= maxWordLength; len++) { if (x - len < 0 || crossword.HasBlock(y, x - len, y, x)) { continue; } var isQuestionAndPointsRight = fields[y, x - len] + (1 - questionType[y, x - len]); var questionsInbetween = new GRBLinExpr(); for (int xi = 0; xi < len; xi++) { questionsInbetween += fields[y, x - xi]; } m.AddConstr(attachedToHorizontalQuestion >= isQuestionAndPointsRight - 1 - questionsInbetween); // 0 IF first question is not pointing right OR there is no question to the left // firstQuestion ==> total fields < 2 m.AddConstr(attachedToHorizontalQuestion <= questionsInbetween + (1 - fields[y, x - len]) + 1 - questionType[y, x - len]); // the first question but DOESNT look right } var questionsToTheLeft = new GRBLinExpr(); int qlCount = 0; for (int len = 0; len <= maxWordLength; len++) { if (x - len < 0 || crossword.HasBlock(y, x - len, y, x)) { continue; } questionsToTheLeft += fields[y, x - len]; qlCount++; } if (qlCount > 0) { m.AddConstr(attachedToHorizontalQuestion <= questionsToTheLeft); } // does this field have a question towards down? /*var attachedToVerticalQuestion = new GRBLinExpr(); * for (int l = 0; l < maxWordLength; l++) * { * if (y - l - 1 < 0) continue; * var attachedToVerticalQuestionSpecificLength = m.AddVar(0, 1, 0, GRB.BINARY, "varAttachedToVerticalQuestionLength" + l + "_" + y + "_" + x); * // If the first field is a question and points to the right, and no letters inbetween are questions * var isQuestionAndPointsDown = fields[y - l - 1, x] + questionType[y - l - 1, x]; * var allVerticalFields = new GRBLinExpr(); * for (int yi = 0; yi <= l; yi++) * allVerticalFields += fields[y - yi, x]; * m.AddConstr(attachedToVerticalQuestionSpecificLength >= isQuestionAndPointsDown - 1 - allVerticalFields); * for (int yi = 0; yi <= l; yi++) m.AddConstr(attachedToVerticalQuestionSpecificLength <= 1 - fields[y - yi, x]); * m.AddConstr(attachedToVerticalQuestionSpecificLength <= fields[y - l - 1, x]); * m.AddConstr(attachedToVerticalQuestionSpecificLength <= questionType[y - l - 1, x]); * //m.AddConstr(attachedToVerticalQuestionSpecificLength <= isQuestionAndPointsDown * 0.5); * //m.AddConstr(attachedToVerticalQuestionSpecificLength <= 1 - allVerticalFields * (1d / (l + 1))); * attachedToVerticalQuestion += attachedToVerticalQuestionSpecificLength; * }*/ var attachedToVerticalQuestion = m.AddVar(0, 1, 0, GRB.BINARY, "attachedToVerticalQuestion" + y + "_" + x); for (int len = 1; len <= maxWordLength; len++) { if (y - len < 0 || crossword.HasBlock(y - len, x, y, x)) { continue; } var isQuestionAndPointsDown = fields[y - len, x] + questionType[y - len, x]; var questionsInbetween = new GRBLinExpr(); for (int yi = 0; yi < len; yi++) { questionsInbetween += fields[y - yi, x]; } m.AddConstr(attachedToVerticalQuestion >= isQuestionAndPointsDown - 1 - questionsInbetween); m.AddConstr(attachedToVerticalQuestion <= questionsInbetween + (1 - fields[y - len, x]) + 1 - (1 - questionType[y - len, x])); // the first question but DOESNT look down } var questionsTowardsDown = new GRBLinExpr(); int qdCount = 0; for (int len = 0; len <= maxWordLength; len++) { if (y - len < 0 || crossword.HasBlock(y - len, x, y, x)) { continue; } questionsTowardsDown += fields[y - len, x]; qdCount++; } if (qdCount > 0) { m.AddConstr(attachedToVerticalQuestion <= questionsTowardsDown); } var c = m.AddConstr(attachedToHorizontalQuestion + attachedToVerticalQuestion >= 1 - fields[y, x], "AttachedToQuestionConstraint_" + y + "_" + x); //c.Lazy = 1; partOfAWord[y, x, 0] = attachedToHorizontalQuestion; partOfAWord[y, x, 1] = attachedToVerticalQuestion; } } // right now, [0,0] can only be a question if (!crossword.HasBlock(0, 0)) { m.AddConstr(fields[0, 0] == 1); } // and similarly the bottom 3x3 can only be letters for (int y = sizeY - 3; y < sizeY; y++) { for (int x = sizeX - 3; x < sizeX; x++) { if (!crossword.HasBlock(y, x)) { m.AddConstr(fields[y, x] == 0); } } } // Objective: // questions should be around ~22% (allFieldsSum ~= amountQuestions) int tolerance = (int)(amountQuestions * 0.1); m.AddConstr(allFieldsSum - amountQuestions >= -tolerance, "amountOfQuestionsTolerance_1"); m.AddConstr(allFieldsSum - amountQuestions <= tolerance, "amountOfQuestionsTolerance_2"); // dead fields var uncrossedLetters = new GRBVar[sizeY, sizeX]; var uncrossedLettersPenalty = new GRBLinExpr(); for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { if ((x >= 1 || y >= 1) && !crossword.HasBlock(y, x)) { uncrossedLetters[y, x] = m.AddVar(0, 1, 0, GRB.BINARY, "isUncrossedLetter" + y + "_" + x); m.AddConstr(uncrossedLetters[y, x] <= partOfAWord[y, x, 0] + partOfAWord[y, x, 1]); // if 0 ==> 0 NECESSARY? m.AddConstr(uncrossedLetters[y, x] <= 2 - partOfAWord[y, x, 0] - partOfAWord[y, x, 1]); // if 2 ==> 0 m.AddConstr(uncrossedLetters[y, x] <= 1 - fields[y, x]); // if it's a question it can't be a dead field m.AddConstr(uncrossedLetters[y, x] >= partOfAWord[y, x, 0] - partOfAWord[y, x, 1] - fields[y, x]); // horizontal XOR vertical m.AddConstr(uncrossedLetters[y, x] >= partOfAWord[y, x, 1] - partOfAWord[y, x, 0] - fields[y, x]); uncrossedLettersPenalty += uncrossedLetters[y, x]; } } } // penalty for nearby uncrossed letters var deadFieldPenalty = new GRBLinExpr(); for (int y = 0; y < sizeY; y++) { for (int x = 0; x < sizeX; x++) { var hby = y - 1 < 0 || !crossword.HasBlock(y - 1, x); var hbx = x - 1 < 0 || !crossword.HasBlock(y, x - 1); if (x >= 1 && y >= 1 && !crossword.HasBlock(y, x) && (hby || hbx)) { var isDeadArea = m.AddVar(0, 1, 0, GRB.BINARY, "isDeadArea" + y + "_" + x); if (hby) { m.AddConstr(isDeadArea >= uncrossedLetters[y, x] + uncrossedLetters[y - 1, x] - 1); } if (hbx) { m.AddConstr(isDeadArea >= uncrossedLetters[y, x] + uncrossedLetters[y, x - 1] - 1); } m.AddConstr(isDeadArea <= uncrossedLetters[y, x]); if (hby && hbx) { m.AddConstr(isDeadArea <= uncrossedLetters[y - 1, x] + uncrossedLetters[y, x - 1]); } else if (hby) { m.AddConstr(isDeadArea <= uncrossedLetters[y - 1, x]); } else if (hbx) { m.AddConstr(isDeadArea <= uncrossedLetters[y, x - 1]); } deadFieldPenalty += isDeadArea; } } } // as many partOfAWord == 2 as possible /*var manyCrossedWords = new GRBLinExpr(); * for (int y = 0; y < sizeY; y++) * for (int x = 0; x < sizeX; x++) * manyCrossedWords += partOfAWord[y, x];*/ // ideal histogram comparison //var wordHistogramDifferences = new GRBLinExpr(); foreach (var wl in wordLengthHistogram.Keys) { /*var varDiffInput = m.AddVar(-sizeX * sizeY / 3, sizeX * sizeY / 3, 0, GRB.INTEGER, "varDiffInput" + wl); * m.AddConstr(varDiffInput == (wordLengthHistogram[wl] - lengths[wl])); * var varDiffRes = m.AddVar(0, sizeX * sizeY / 3, 0, GRB.INTEGER, "varDiff" + wl); * m.AddGenConstrAbs(varDiffRes, varDiffInput, "diffGenConstr" + wl); * wordHistogramDifferences += varDiffRes;*/ int histogramTolerance = Math.Max(1, (int)(wordLengthHistogram[wl] * 0.2 * wordLengthRatio)); //m.AddConstr(wordLengthHistogram[wl] - lengths[wl] >= -histogramTolerance); //m.AddConstr(wordLengthHistogram[wl] - lengths[wl] <= histogramTolerance); } // question field clusters // in a field of 2x2, minimize the nr of fields where there are 2-4 questions resp. maximize 0-1 questions var clusterPenalty = new GRBLinExpr(); int area = 2; for (int y = 0; y < sizeY - (area - 1); y++) { for (int x = 0; x < sizeX - (area - 1); x++) { var clusterTotal = new GRBLinExpr(); int ct = 0; for (int i = 0; i < area; i++) { for (int j = 0; j < area; j++) { if (crossword.HasBlock(y + i, x + j)) { continue; } clusterTotal += fields[y + i, x + j]; ct++; } } if (ct >= 3) { var varClusterTotalPenalty = m.AddVar(0, 1, 0, GRB.BINARY, "varClusterTotalPenalty" + y + "_" + x); // 0-1 = good, 2-4 = bad m.AddConstr(varClusterTotalPenalty <= clusterTotal * 0.5, "clusterPenaltyConstr1_" + y + "_" + x); m.AddConstr(varClusterTotalPenalty >= (clusterTotal - 1) * (1d / 3), "clusterPenaltyConstr2_" + y + "_" + x); clusterPenalty += varClusterTotalPenalty; } } } //m.AddConstr(deadFieldPenalty <= 30); //amountOfQuestionsRating * (100d / sizeX / sizeY) + manyCrossedWords + + wordHistogramDifferences // clusterPenalty * 100 m.SetObjective(deadFieldPenalty + clusterPenalty, GRB.MINIMIZE); m.SetCallback(new GRBMipSolCallback(crossword, fields, questionType, null)); m.Optimize(); m.ComputeIIS(); m.Write("model.ilp"); m.Dispose(); env.Dispose(); }