static void Main(string[] args) { if (args.Length < 1) { Console.Out.WriteLine("Usage: lp_cs filename"); return; } try { GRBEnv env = new GRBEnv(); GRBModel model = new GRBModel(env, args[0]); model.Optimize(); int optimstatus = model.Status; if (optimstatus == GRB.Status.INF_OR_UNBD) { model.Parameters.Presolve = 0; model.Optimize(); optimstatus = model.Status; } if (optimstatus == GRB.Status.OPTIMAL) { double objval = model.ObjVal; Console.WriteLine("Optimal objective: " + objval); } else if (optimstatus == GRB.Status.INFEASIBLE) { Console.WriteLine("Model is infeasible"); // compute and write out IIS model.ComputeIIS(); model.Write("model.ilp"); } else if (optimstatus == GRB.Status.UNBOUNDED) { Console.WriteLine("Model is unbounded"); } else { Console.WriteLine("Optimization was stopped with status = " + optimstatus); } // Dispose of model and env model.Dispose(); env.Dispose(); } catch (GRBException e) { Console.WriteLine("Error code: " + e.ErrorCode + ". " + e.Message); } }
public void Optimize(double timelimit = GRB.INFINITY, double mipGap = 0.00) { _model.GetEnv().Set(GRB.DoubleParam.TimeLimit, timelimit); _model.GetEnv().Set(GRB.DoubleParam.MIPGap, mipGap); _model.Optimize(); if (_model.Get(GRB.IntAttr.Status) == GRB.Status.INFEASIBLE) { _model.ComputeIIS(); Console.WriteLine("\nThe following constraints cannot be satisfied:"); foreach (GRBConstr c in _model.GetConstrs()) { if (c.Get(GRB.IntAttr.IISConstr) == 1) { Console.WriteLine(c.Get(GRB.StringAttr.ConstrName)); // Remove a single constraint from the model } } throw new Exception("Infeasible"); } }
static void Main() { try { // Sample data // Sets of days and workers string[] Shifts = new string[] { "Mon1", "Tue2", "Wed3", "Thu4", "Fri5", "Sat6", "Sun7", "Mon8", "Tue9", "Wed10", "Thu11", "Fri12", "Sat13", "Sun14" }; string[] Workers = new string[] { "Amy", "Bob", "Cathy", "Dan", "Ed", "Fred", "Gu" }; int nShifts = Shifts.Length; int nWorkers = Workers.Length; // Number of workers required for each shift double[] shiftRequirements = new double[] { 3, 2, 4, 4, 5, 6, 5, 2, 2, 3, 4, 6, 7, 5 }; // Amount each worker is paid to work one shift double[] pay = new double[] { 10, 12, 10, 8, 8, 9, 11 }; // Worker availability: 0 if the worker is unavailable for a shift double[,] availability = new double[, ] { { 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1 }, { 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0 }, { 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1 }, { 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1 }, { 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1 }, { 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } }; // Model GRBEnv env = new GRBEnv(); GRBModel model = new GRBModel(env); model.ModelName = "assignment"; // Assignment variables: x[w][s] == 1 if worker w is assigned // to shift s. Since an assignment model always produces integer // solutions, we use continuous variables and solve as an LP. GRBVar[,] x = new GRBVar[nWorkers, nShifts]; for (int w = 0; w < nWorkers; ++w) { for (int s = 0; s < nShifts; ++s) { x[w, s] = model.AddVar(0, availability[w, s], pay[w], GRB.CONTINUOUS, Workers[w] + "." + Shifts[s]); } } // The objective is to minimize the total pay costs model.ModelSense = GRB.MINIMIZE; // Constraint: assign exactly shiftRequirements[s] workers // to each shift s for (int s = 0; s < nShifts; ++s) { GRBLinExpr lhs = 0.0; for (int w = 0; w < nWorkers; ++w) { lhs.AddTerm(1.0, x[w, s]); } model.AddConstr(lhs == shiftRequirements[s], Shifts[s]); } // Optimize model.Optimize(); int status = model.Status; if (status == GRB.Status.UNBOUNDED) { Console.WriteLine("The model cannot be solved " + "because it is unbounded"); return; } if (status == GRB.Status.OPTIMAL) { Console.WriteLine("The optimal objective is " + model.ObjVal); return; } if ((status != GRB.Status.INF_OR_UNBD) && (status != GRB.Status.INFEASIBLE)) { Console.WriteLine("Optimization was stopped with status " + status); return; } // Do IIS Console.WriteLine("The model is infeasible; computing IIS"); model.ComputeIIS(); Console.WriteLine("\nThe following constraint(s) " + "cannot be satisfied:"); foreach (GRBConstr c in model.GetConstrs()) { if (c.IISConstr == 1) { Console.WriteLine(c.ConstrName); } } // Dispose of model and env model.Dispose(); env.Dispose(); } catch (GRBException e) { Console.WriteLine("Error code: " + e.ErrorCode + ". " + e.Message); } }
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(); }
public override Schedule Generate(Scenario scenario, FilledBaseline baseline = null) { var opportunities = scenario.Projects.Where(p => p is Opportunity).ToArray(); var batches = scenario.Projects.SelectMany(p => p.Batches).ToArray(); var batchesBothLines = batches.Where(b => b.Compatibility == Batch.LineCompatibility.Both).ToArray(); var batchesLine1 = batches.Where(b => b.Compatibility == Batch.LineCompatibility.Line1).ToArray(); var batchesLine2 = batches.Where(b => b.Compatibility == Batch.LineCompatibility.Line2).ToArray(); int bBc = batchesBothLines.Count(); var earliestDate = scenario.Projects.Min(p => p.DeliveryDate); var lastDate = scenario.Projects.Max(p => p.DeliveryDate.AddDays(7 * p.Batches.Count())).AddDays(7 * 3); // add an additional buffer of 3 weeks. double maxDays = (lastDate - earliestDate).Days; var maxWeek = maxDays / 7 + 2; const int weeksBuffer = 3; const double numericScale = 100d; const double delayPenaltyMultiplier = 0.01; // 1% of revenue per week const double interestPenaltyMultiplier = (0.045 / 52.14); // 4.5% / 52.14 * (revenue – margin) var env = new GRBEnv(); env.Set(GRB.IntParam.UpdateMode, 1); env.Set(GRB.IntParam.LogToConsole, 1); //env.Set(GRB.IntParam.ScaleFlag, 2); //env.Set(GRB.IntParam.Presolve, 0); //env.Set(GRB.DoubleParam.TimeLimit, _config.TimeLimit.Value); //env.Set(GRB.DoubleParam.MIPGap, 0.02); var m = new GRBModel(env); // decide: which batch is when, on which line // [batch, time] var varBatchTimeLine1 = m.AddVars(bBc + batchesLine1.Count(), bBc + batchesLine1.Count(), 0, 1, GRB.BINARY, "varBatchTimeLine1"); var varBatchTimeLine2 = m.AddVars(bBc + batchesLine2.Count(), bBc + batchesLine2.Count(), 0, 1, GRB.BINARY, "varBatchTimeLine2"); // what are the time differences between times var varBatchTimeDiffsLine1 = m.AddVars(bBc + batchesLine1.Count() - 1, 0, maxDays, GRB.CONTINUOUS, "varBatchTimeDiffsLine1"); var varBatchTimeDiffsLine2 = m.AddVars(bBc + batchesLine2.Count() - 1, 0, maxDays, GRB.CONTINUOUS, "varBatchTimeDiffsLine2"); var varLineDecision = m.AddVars(bBc, 0, 1, GRB.BINARY, "varLineDecision"); // 0 = Line1, 1 = Line2 // make stupid solution: // assign fixed projects, set the rest as unused m.Update(); for (int l = 0; l < 2; l++) { var line = l == 0 ? varBatchTimeLine1 : varBatchTimeLine2; var diffs = l == 0 ? varBatchTimeDiffsLine1 : varBatchTimeDiffsLine2; for (int i = 0; i < diffs.GetLength(0); i++) { diffs[i].Set(GRB.DoubleAttr.Start, 0); } //m.AddConstr(diffs[i] == 0, "initial solution diffs"); int lineT = 0; for (int bi = bBc; bi < line.GetLength(0); bi++) { var batch = (l == 0 ? batchesLine1 : batchesLine2)[bi - bBc]; bool isFixedProject = scenario.Projects.First(p => p.Batches.Contains(batch)) is FixedProject; if (isFixedProject) { line[bi, lineT].Set(GRB.DoubleAttr.Start, 1); //m.AddConstr(line[bi, lineT] == 1, "assign batches of project in succession"); for (int j = 0; j < line.GetLength(1); j++) { if (j != lineT) { line[bi, j].Set(GRB.DoubleAttr.Start, 0); } } //m.AddConstr(line[bi, j] == 0, "initial solution"); lineT++; } else { for (int j = 0; j < line.GetLength(1); j++) { line[bi, j].Set(GRB.DoubleAttr.Start, 0); } //m.AddConstr(line[bi, j] == 0, "initial solution"); } } // make zeros also for both line batches for (int i = 0; i < bBc; i++) { for (int j = 0; j < line.GetLength(1); j++) { line[i, j].Set(GRB.DoubleAttr.Start, 0); } } //m.AddConstr(line[i, j] == 0, "initial solution"); } // line constraints: // assign batch only once (constraint for single lines then both lines) // if it's a fixed project though, then it must be allocated for (int l = 0; l < 2; l++) { var line = l == 0 ? varBatchTimeLine1 : varBatchTimeLine2; for (int bi = bBc; bi < line.GetLength(0); bi++) { var batchTotal = new GRBLinExpr(); for (int t = 0; t < line.GetLength(1); t++) { batchTotal += line[bi, t]; } var batch = (l == 0 ? batchesLine1 : batchesLine2)[bi - bBc]; bool isFixedProject = scenario.Projects.First(p => p.Batches.Contains(batch)) is FixedProject; if (!isFixedProject) { m.AddConstr(batchTotal <= 1, "assign batch only once"); } else { m.AddConstr(batchTotal == 1, "assign batch exactly once"); } } } for (int bi = 0; bi < bBc; bi++) { var batchTotal = new GRBLinExpr(); for (int t = 0; t < varBatchTimeLine1.GetLength(1); t++) { batchTotal += varBatchTimeLine1[bi, t]; } for (int t = 0; t < varBatchTimeLine2.GetLength(1); t++) { batchTotal += varBatchTimeLine2[bi, t]; } var batch = batchesBothLines[bi]; bool isFixedProject = scenario.Projects.First(p => p.Batches.Contains(batch)) is FixedProject; if (!isFixedProject) { m.AddConstr(batchTotal <= 1, "assign batch only once"); } else { m.AddConstr(batchTotal == 1, "assign batch exactly once"); } } // a time slot can only be used once on each line for (int l = 0; l < 2; l++) { var line = l == 0 ? varBatchTimeLine1 : varBatchTimeLine2; for (int t = 0; t < line.GetLength(1); t++) { var timeTotal = new GRBLinExpr(); for (int bi = 0; bi < line.GetLength(0); bi++) { timeTotal += line[bi, t]; } m.AddConstr(timeTotal <= 1, "assign time slot only once on line " + (l + 1).ToString()); } } // for all batches which aren't assigned to a line yet, limit the allocation of yet unassigned batches of a project to one line // TODO: If project has e.g. line1 and both lines, limit both lines to line 1? for (int pi = 0; pi < scenario.Projects.Count(); pi++) { var p = scenario.Projects[pi]; if (!p.Batches.Any(b => b.Compatibility == Batch.LineCompatibility.Both)) { continue; } int lineRestriction = -1; if (p.Batches.Any(b => b.Compatibility != Batch.LineCompatibility.Both)) { lineRestriction = p.Batches.First(b => b.Compatibility != Batch.LineCompatibility.Both).Compatibility == Batch.LineCompatibility.Line1 ? 0 : 1; } int i_prev = -1; for (int j = 0; j < p.Batches.Count(); j++) { if (p.Batches[j].Compatibility != Batch.LineCompatibility.Both) { continue; } var i = batchesBothLines.IndexOf(p.Batches[j]); if (lineRestriction == -1) { if (i_prev != -1) { m.AddConstr(varLineDecision[i] == varLineDecision[i_prev], "lineDecisionRestrictionAllSame"); } } else { // if there are other batches on this project which are already on a specific line, limit to the same line m.AddConstr(varLineDecision[i] == lineRestriction, "lineDecisionRestrictionSpecific"); } i_prev = i; } } // for each project, either all or no batches must be assigned var totalBatchesOfProject = new Dictionary <Project, List <GRBLinExpr> >(); foreach (var p in scenario.Projects) { var allBatches = new List <GRBLinExpr>(); // gather the total of all batches GRBLinExpr previousBatchTotal = null; for (int bi = 0; bi < p.Batches.Count(); bi++) { var b = p.Batches[bi]; var batchTotal = new GRBLinExpr(); if (b.Compatibility == Batch.LineCompatibility.Line1) { var bIndex = bBc + batchesLine1.IndexOf(b); for (int ti = 0; ti < varBatchTimeLine1.GetLength(1); ti++) { batchTotal += varBatchTimeLine1[bIndex, ti]; } } else if (b.Compatibility == Batch.LineCompatibility.Line2) { var bIndex = bBc + batchesLine2.IndexOf(b); for (int ti = 0; ti < varBatchTimeLine2.GetLength(1); ti++) { batchTotal += varBatchTimeLine2[bIndex, ti]; } } else { var bIndex = batchesBothLines.IndexOf(b); for (int t = 0; t < varBatchTimeLine1.GetLength(1); t++) { batchTotal += varBatchTimeLine1[bIndex, t]; } for (int t = 0; t < varBatchTimeLine2.GetLength(1); t++) { batchTotal += varBatchTimeLine2[bIndex, t]; } } // the sum of this batch over all times (0 or 1) has to be the same as the sum of the previous one if (bi > 0) { //m.AddConstr(previousBatchTotal == batchTotal, "allBatchesAllocatedOrNot"); } previousBatchTotal = batchTotal; allBatches.Add(batchTotal); } totalBatchesOfProject.Add(p, allBatches); } // Tbd: Only half of the batch slots of line 1 may be occupied. Sometimes existst as internal projects. // fill gap between 50% and internal projects, monthly resolution // gather the time of time slots GRBLinExpr[] startTimesLine1 = new GRBLinExpr[varBatchTimeLine1.GetLength(1)]; GRBLinExpr[] startTimesLine2 = new GRBLinExpr[varBatchTimeLine2.GetLength(1)]; var varStartTimesLine1 = m.AddVars(startTimesLine1.Count(), 0, maxDays, GRB.CONTINUOUS, "varStartTimesLine1"); var varStartTimesLine2 = m.AddVars(startTimesLine2.Count(), 0, maxDays, GRB.CONTINUOUS, "varStartTimesLine2"); for (int l = 0; l < 2; l++) { var batchLine = l == 0 ? varBatchTimeLine1 : varBatchTimeLine2; var startTimes = l == 0 ? startTimesLine1 : startTimesLine2; var batchesLine = l == 0 ? batchesLine1 : batchesLine2; var batchTimeDiffs = l == 0 ? varBatchTimeDiffsLine1 : varBatchTimeDiffsLine2; for (int t = 0; t < batchLine.GetLength(1); t++) { startTimes[t] = new GRBLinExpr(); // sum up all durations and buffer times before this time instance for (int previous_t = 0; previous_t < t; previous_t++) { var duration_prev_t = new GRBLinExpr(); for (int bi = 0; bi < batchLine.GetLength(0); bi++) { var batch = bi < bBc ? batchesBothLines[bi] : batchesLine[bi]; var duration = batch.UsedWorkHours / 24d; duration_prev_t += batchLine[bi, previous_t] * duration; } duration_prev_t += batchTimeDiffs[previous_t]; startTimes[t] += duration_prev_t; } } var varStartTime = l == 0 ? varStartTimesLine1 : varStartTimesLine2; for (int i = 0; i < varStartTime.Count(); i++) { m.AddConstr(varStartTime[i] == startTimes[i], "varStartTimeAssignment"); } } // gather the start time of the batches var varStartTimesBatches1 = m.AddVars(varBatchTimeLine1.GetLength(0), 0, maxDays, GRB.CONTINUOUS, "varBatchStartTimesLine1"); var varStartTimesBatches2 = m.AddVars(varBatchTimeLine2.GetLength(0), 0, maxDays, GRB.CONTINUOUS, "varBatchStartTimesLine2"); for (int l = 0; l < 2; l++) { var batchLine = l == 0 ? varBatchTimeLine1 : varBatchTimeLine2; var batchStartTimes = l == 0 ? varStartTimesBatches1 : varStartTimesBatches2; var batchesLine = l == 0 ? batchesLine1 : batchesLine2; var varStartTimes = l == 0 ? varStartTimesLine1 : varStartTimesLine2; for (int bi = 0; bi < batchLine.GetLength(0); bi++) { // make the batch take the value of its time slot for (int ti = 0; ti < batchLine.GetLength(1); ti++) { m.AddConstr(batchStartTimes[bi] <= varStartTimes[ti] + maxDays * (1 - batchLine[bi, ti]), "batchTimeAssignment"); m.AddConstr(batchStartTimes[bi] >= varStartTimes[ti] - maxDays * (1 - batchLine[bi, ti]), "batchTimeAssignment"); } } } // scheduling formulation for (int l = 0; l < 2; l++) { var varBatchStartTimes = l == 0 ? varStartTimesBatches1 : varStartTimesBatches2; var line = batchesBothLines.ToList(); line.AddRange(l == 0 ? batchesLine1 : batchesLine2); for (int bi1 = 0; bi1 < line.Count() - 1; bi1++) { var batch1 = line[bi1]; var o1_exists = opportunities.FirstOrDefault(o => o.Batches.Contains(batch1)); var oi1 = o1_exists != null ? (1 - totalBatchesOfProject[o1_exists][0]) : new GRBLinExpr(); var s1 = varBatchStartTimes[bi1]; var bothLineSlack1 = (bi1 < bBc) ? (l == 0 ? varLineDecision[bi1] - 0 : 1 - varLineDecision[bi1]) : new GRBLinExpr(); for (int bi2 = bi1 + 1; bi2 < line.Count(); bi2++) { var batch2 = line[bi2]; var o2_exists = opportunities.FirstOrDefault(o => o.Batches.Contains(batch2)); var oi2 = o2_exists != null ? (1 - totalBatchesOfProject[o1_exists][0]) : new GRBLinExpr(); var s2 = varBatchStartTimes[bi2]; var bothLineSlack2 = (bi2 < bBc) ? (l == 0 ? varLineDecision[bi2] - l : l - varLineDecision[bi2]) : new GRBLinExpr(); // S1 - E2 >= 0 OR S2 - E1 >= 0 // IF both batches are used var decisionVar = m.AddVar(0, 1, GRB.BINARY, "schedulingORvar"); var opportunityNotUsedSlack = oi1 + oi2; var varBothLinesLineDecisionSlack = bothLineSlack1 + bothLineSlack2; // LineDecisionSlack for both lines batches, i.e. if a bothline batch is used, don't restrict unused line. m.AddConstr(s1 - (s2 + batch2.UsedWorkHours / 24d) >= -maxDays * (decisionVar + opportunityNotUsedSlack + varBothLinesLineDecisionSlack), "batchesScheduling"); m.AddConstr(s2 - (s1 + batch1.UsedWorkHours / 24d) >= -maxDays * (1 - decisionVar + opportunityNotUsedSlack + varBothLinesLineDecisionSlack), "batchesScheduling"); } } } // Maximize the margin (including delay and interest penalties) and the workload // Only the delivery of the first batch is really important. The rest is less important. var margin = new GRBLinExpr(); var delays = new List <GRBLinExpr>(); var interests = new List <GRBLinExpr>(); var weekValues = new List <GRBLinExpr>(); var weekDiffs = new List <GRBLinExpr>(); foreach (var p in scenario.Projects) { // if the project is used add the margin if (p is Opportunity) { margin += totalBatchesOfProject[p][0] * p.Margin * (1d / numericScale); } else { margin += p.Margin / numericScale; } // deduct the delay penalty for each batch. var startDayOfProject = (p.DeliveryDate - earliestDate).TotalDays; var varMaxedValue = m.AddVars(p.Batches.Count(), 0, maxWeek, GRB.CONTINUOUS, "penaltyIndicator" + p.Description); //var varDecisionVar = m.AddVars(p.Batches.Count(), 0, 1, GRB.BINARY, "penaltyIndicator" + p.Description); //m.Update(); GRBLinExpr previousWeekValue = new GRBLinExpr(); for (int pbi = 0; pbi < p.Batches.Count(); pbi++) { var b = p.Batches[pbi]; // compare the minimal batch time (3 + 1 weeks) to the actual delivery time GRBLinExpr weekValue; if (batchesLine1.Contains(b)) { var bi = batchesLine1.IndexOf(b) + bBc; weekValue = varStartTimesBatches1[bi] / 7d; } else if (batchesLine2.Contains(b)) { var bi = batchesLine2.IndexOf(b) + bBc; weekValue = varStartTimesBatches2[bi] / 7d; } else { var bi = batchesBothLines.IndexOf(b); // create a new var var bothLineWeekVar = m.AddVar(0, maxWeek, GRB.CONTINUOUS, "weekValueBothLines"); //m.Update(); m.AddConstr(bothLineWeekVar >= varStartTimesBatches1[bi] / 7d - (varLineDecision[bi]) * maxWeek, "weekValueBothLinesDefinitionByConstraints"); m.AddConstr(bothLineWeekVar <= varStartTimesBatches1[bi] / 7d + (varLineDecision[bi]) * maxWeek, "weekValueBothLinesDefinitionByConstraints"); m.AddConstr(bothLineWeekVar >= varStartTimesBatches2[bi] / 7d - (1 - varLineDecision[bi]) * maxWeek, "weekValueBothLinesDefinitionByConstraints"); m.AddConstr(bothLineWeekVar <= varStartTimesBatches2[bi] / 7d + (1 - varLineDecision[bi]) * maxWeek, "weekValueBothLinesDefinitionByConstraints"); weekValue = bothLineWeekVar; } if (true || pbi < p.Batches.Count() - 1) { // for positive difference add delay penalty, for negative difference add interest penalty // x = opportunity used ? x : 0 // var = max(0, x) // margin += var * delay // margin += (x - var) * interest var plannedWeek = startDayOfProject / 7d + pbi * (weeksBuffer + 1); GRBLinExpr weekDiff; if (p is Opportunity) { var weekDiffIfUsed = m.AddVar(-maxWeek, maxWeek, GRB.CONTINUOUS, "weekDiffIfUsed"); //m.Update(); var wD = weekValue - plannedWeek; m.AddConstr(weekDiffIfUsed >= wD - (1 - totalBatchesOfProject[p][0]) * maxWeek, "weekDiffConstraintDefinition1"); m.AddConstr(weekDiffIfUsed <= wD + (1 - totalBatchesOfProject[p][0]) * maxWeek, "weekDiffConstraintDefinition2"); m.AddConstr(weekDiffIfUsed <= (totalBatchesOfProject[p][0]) * maxWeek, "weekDiffConstraintDefinition3"); m.AddConstr(weekDiffIfUsed >= -(totalBatchesOfProject[p][0]) * maxWeek, "weekDiffConstraintDefinition4"); weekDiff = weekDiffIfUsed; } else { weekDiff = weekValue - plannedWeek; } m.AddConstr(varMaxedValue[pbi] >= weekDiff, "maxedWeekDiffValueConstraintDefinition"); //m.AddConstr(varMaxedValue[pbi] >= 0, "maxedWeekDiffValueConstraintDefinition"); //m.AddConstr(varMaxedValue[pbi] <= weekDiff + maxWeek * (varDecisionVar[pbi]), "maxedWeekDiffValueConstraintDefinition"); //m.AddConstr(varMaxedValue[pbi] <= 0 + maxWeek * (1 - varDecisionVar[pbi]), "maxedWeekDiffValueConstraintDefinition"); double firstBatchImportance = pbi == 0 ? 1 : 0.2; // mainly the first batch is important, the rest is meh weekValues.Add(weekValue); weekDiffs.Add(weekDiff); double revenueBase = p.Revenue == 0 ? 1E+8 : p.Revenue; // if it's an internal project it must not be shifted, therefore we make the penalty high revenueBase /= numericScale; delays.Add(varMaxedValue[pbi] * delayPenaltyMultiplier * revenueBase * firstBatchImportance); interests.Add(-(weekDiff - varMaxedValue[pbi]) * interestPenaltyMultiplier * revenueBase * firstBatchImportance); margin -= delays.Last() + interests.Last(); } // constraint: batches of a project have to be in succession, i.e. batch2 can't come after batch3 chronologically if (pbi > 0) { m.AddConstr(weekValue - previousWeekValue >= 0, "batchesOfProjectHaveToBeInSuccession"); } previousWeekValue = weekValue; } } //m.SetObjective(margin, GRB.MAXIMIZE); m.Update(); m.Write("ilproman3.mps"); m.Write("ilproman3.mst"); //m.Tune(); //m.GetTuneResult(0); m.Optimize(); //env.Set(GRB.IntParam.IISMethod, 0); // makes IIS computation fast but potentially inaccurate m.ComputeIIS(); m.Write("ilp.ilp"); // TODO: Max 5 weeks delay per project // build the solution from the optimization var sol = new Schedule(); int batchCount = 0; double cumulatedDelays = 0; double cumulatedInterests = 0; foreach (var p in scenario.Projects) { List <Schedule.BatchAllocation> allocatedBatches = new List <Schedule.BatchAllocation>(); var data = new List <double[]>(); for (int bi = 0; bi < p.Batches.Count(); bi++) { var b = p.Batches[bi]; var delay = delays[batchCount].Value; var interest = interests[batchCount].Value; cumulatedDelays += delay; cumulatedInterests += interest; var weekValue = weekValues[batchCount].Value; var weekDiff = weekDiffs[batchCount].Value; // figure out on which week and which line the batch is allocated double dayLine1 = 0; double dayLine2 = 0; if (b.Compatibility == Batch.LineCompatibility.Line1) { dayLine1 = varStartTimesBatches1[bBc + batchesLine1.IndexOf(b)].Get(GRB.DoubleAttr.X); } else if (b.Compatibility == Batch.LineCompatibility.Line2) { dayLine2 = varStartTimesBatches2[bBc + batchesLine2.IndexOf(b)].Get(GRB.DoubleAttr.X); } else { var bbi = batchesBothLines.IndexOf(b); var lineDecision = varLineDecision[bbi].Get(GRB.DoubleAttr.X); dayLine1 = varStartTimesBatches1[bbi].Get(GRB.DoubleAttr.X) * (1 - lineDecision); dayLine2 = varStartTimesBatches2[bbi].Get(GRB.DoubleAttr.X) * lineDecision; } data.Add(new double[] { delay, interest, weekValue, weekDiff, dayLine1 + dayLine2 }); if (dayLine1 > 0 && dayLine2 > 0) { throw new InvalidOperationException(); } var alloc = Schedule.LineAllocation.None; if (p is FixedProject || (p is Opportunity && totalBatchesOfProject[p][0].Value > 0.5)) { if (b.Compatibility == Batch.LineCompatibility.Both) { alloc = varLineDecision[batchesBothLines.IndexOf(b)].Get(GRB.DoubleAttr.X) > 0.5 ? Schedule.LineAllocation.Line2 : Schedule.LineAllocation.Line1; } else { alloc = b.Compatibility == Batch.LineCompatibility.Line1 ? Schedule.LineAllocation.Line1 : Schedule.LineAllocation.Line2; } } //if (dayLine1 > 0) alloc = Solution.LineAllocation.Line1; //if (dayLine2 > 0) alloc = Solution.LineAllocation.Line2; allocatedBatches.Add(new Schedule.BatchAllocation(b, alloc, earliestDate.AddDays(dayLine1 + dayLine2))); batchCount++; } sol.Add(new Schedule.ProjectAllocation(p, allocatedBatches)); } /*var maxMustWinDate = mustWinOpportunitites.Max(p => p.DeliveryDate.AddDays(7 * p.Batches.Count())).AddDays(7 * 3); * var minMustWinDate = mustWinOpportunitites.Min(p2 => p2.DeliveryDate); * var sevenmonthsrange = sol.Where(p => p.Project.DeliveryDate >= minMustWinDate && p.Project.DeliveryDate <= maxMustWinDate).ToArray(); * var ops = sevenmonthsrange.Where(p => mustWinOpportunitites.Contains(p.Project) || p.Project is FixedProject); * var s = ops.Sum(p => p.Project.Revenue);*/ return(sol); }
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(); }
static void Main() { try { // Sample data // Sets of days and workers string[] Shifts = new string[] { "Mon1", "Tue2", "Wed3", "Thu4", "Fri5", "Sat6", "Sun7", "Mon8", "Tue9", "Wed10", "Thu11", "Fri12", "Sat13", "Sun14" }; string[] Workers = new string[] { "Amy", "Bob", "Cathy", "Dan", "Ed", "Fred", "Gu" }; int nShifts = Shifts.Length; int nWorkers = Workers.Length; // Number of workers required for each shift double[] shiftRequirements = new double[] { 3, 2, 4, 4, 5, 6, 5, 2, 2, 3, 4, 6, 7, 5 }; // Amount each worker is paid to work one shift double[] pay = new double[] { 10, 12, 10, 8, 8, 9, 11 }; // Worker availability: 0 if the worker is unavailable for a shift double[,] availability = new double[,] { { 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1 }, { 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0 }, { 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1 }, { 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1 }, { 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1 }, { 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1 }, { 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 } }; // Model GRBEnv env = new GRBEnv(); GRBModel model = new GRBModel(env); model.Set(GRB.StringAttr.ModelName, "assignment"); // Assignment variables: x[w][s] == 1 if worker w is assigned // to shift s. Since an assignment model always produces integer // solutions, we use continuous variables and solve as an LP. GRBVar[,] x = new GRBVar[nWorkers,nShifts]; for (int w = 0; w < nWorkers; ++w) { for (int s = 0; s < nShifts; ++s) { x[w,s] = model.AddVar(0, availability[w,s], pay[w], GRB.CONTINUOUS, Workers[w] + "." + Shifts[s]); } } // The objective is to minimize the total pay costs model.Set(GRB.IntAttr.ModelSense, 1); // Update model to integrate new variables model.Update(); // Constraint: assign exactly shiftRequirements[s] workers // to each shift s for (int s = 0; s < nShifts; ++s) { GRBLinExpr lhs = 0.0; for (int w = 0; w < nWorkers; ++w) lhs += x[w, s]; model.AddConstr(lhs == shiftRequirements[s], Shifts[s]); } // Optimize model.Optimize(); int status = model.Get(GRB.IntAttr.Status); if (status == GRB.Status.UNBOUNDED) { Console.WriteLine("The model cannot be solved " + "because it is unbounded"); return; } if (status == GRB.Status.OPTIMAL) { Console.WriteLine("The optimal objective is " + model.Get(GRB.DoubleAttr.ObjVal)); return; } if ((status != GRB.Status.INF_OR_UNBD) && (status != GRB.Status.INFEASIBLE)) { Console.WriteLine("Optimization was stopped with status " + status); return; } // Do IIS Console.WriteLine("The model is infeasible; computing IIS"); LinkedList<string> removed = new LinkedList<string>(); // Loop until we reduce to a model that can be solved while (true) { model.ComputeIIS(); Console.WriteLine("\nThe following constraint cannot be satisfied:"); foreach (GRBConstr c in model.GetConstrs()) { if (c.Get(GRB.IntAttr.IISConstr) == 1) { Console.WriteLine(c.Get(GRB.StringAttr.ConstrName)); // Remove a single constraint from the model removed.AddFirst(c.Get(GRB.StringAttr.ConstrName)); model.Remove(c); break; } } Console.WriteLine(); model.Optimize(); status = model.Get(GRB.IntAttr.Status); if (status == GRB.Status.UNBOUNDED) { Console.WriteLine("The model cannot be solved " + "because it is unbounded"); return; } if (status == GRB.Status.OPTIMAL) { break; } if ((status != GRB.Status.INF_OR_UNBD) && (status != GRB.Status.INFEASIBLE)) { Console.WriteLine("Optimization was stopped with status " + status); return; } } Console.WriteLine("\nThe following constraints were removed " + "to get a feasible LP:"); foreach (string s in removed) { Console.Write(s + " "); } Console.WriteLine(); // Dispose of model and env model.Dispose(); env.Dispose(); } catch (GRBException e) { Console.WriteLine("Error code: " + e.ErrorCode + ". " + e.Message); } }
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(); }
public void ModeloDARP() { string Agora = Convert.ToString(DateTime.Now); string[] a = Agora.Split(' '); string[] a0 = a[0].Split('/'); string[] a1 = a[1].Split(':'); Agora = a0[0] + "_" + a0[1] + "_" + a0[2] + "_" + a1[0] + "_" + a1[1] + "_" + a1[2]; /// Calcundo algumas constantes que usamos na linearização da carga e janela de tempo double[,,] M = new double[problema.NumeroDeCarros, problema.TamanhoMaximo, problema.TamanhoMaximo]; double[,,] U = new double[problema.NumeroDeCarros, problema.TamanhoMaximo, problema.TamanhoMaximo]; for (int k = 0; k < problema.NumeroDeCarros; k++) { for (int i = 0; i < problema.TamanhoMaximo; i++) { for (int j = 0; j < problema.TamanhoMaximo; j++) { M[k, i, j] = Math.Max(0, problema.usuario[i].InstanteSaida + problema.usuario[i].Duracao + +problema.Distancias[i, j] - problema.usuario[j].InstanteChegada); U[k, i, j] = Math.Min(frota.Capacidade, frota.Capacidade + problema.usuario[i].Carga); } } } /////////////// /////////////// string nome = Convert.ToString(problema.NumeroDeClientes) + '_' + Convert.ToString(problema.NumeroDeCarros) + "_" + Convert.ToString(frota.Capacidade); string caminho = @"C:\Users\hcspe\Documents\Doutorado\TeseUberPool\Classe_Rotas_SARP\MSCordeau\saidas\"; // Model GRBEnv env = new GRBEnv(caminho + "outputSARPTeste(" + Agora + "_" + nome + ").log"); GRBModel model = new GRBModel(env); model.Set(GRB.StringAttr.ModelName, "SARPTeste"); model.Set(GRB.DoubleParam.TimeLimit, 3600); ///// Declaração de variáveis // Variável x GRBVar[,,] x = new GRBVar[problema.NumeroDeCarros, problema.TamanhoMaximo, problema.TamanhoMaximo]; for (int i = 0; i < problema.TamanhoMaximo; i++) { for (int j = 0; j < problema.TamanhoMaximo; j++) { for (int k = 0; k < problema.NumeroDeCarros; k++) { x[k, i, j] = model.AddVar(0, 1, problema.Distancias[i, j], GRB.BINARY, "x" + k + "_" + i + "_" + j); if (i == problema.TamanhoMaximo) { x[k, i, j].UB = 0.0; } } } } // Variável B do tempo no nó i GRBVar[,] B = new GRBVar[problema.NumeroDeCarros, problema.TamanhoMaximo]; for (int i = 0; i < problema.TamanhoMaximo; i++) { for (int k = 0; k < problema.NumeroDeCarros; k++) { B[k, i] = model.AddVar(0, GRB.INFINITY, 0, GRB.CONTINUOUS, "B" + i + "_" + k); } } // Variavel Q da carga no nó i GRBVar[,] Q = new GRBVar[problema.NumeroDeCarros, problema.TamanhoMaximo]; for (int i = 0; i < problema.TamanhoMaximo; i++) { for (int k = 0; k < problema.NumeroDeCarros; k++) { Q[k, i] = model.AddVar(0, GRB.INFINITY, 0, GRB.CONTINUOUS, "Q" + i + "_" + k); } } // Varivavel L tempo de viagem do usuario quando passa por i GRBVar[,] L = new GRBVar[problema.NumeroDeCarros, problema.TamanhoMaximo]; for (int i = 0; i < problema.TamanhoMaximo; i++) { for (int k = 0; k < problema.NumeroDeCarros; k++) { L[k, i] = model.AddVar(0, GRB.INFINITY, 0, GRB.CONTINUOUS, "L" + i + "_" + k); } } ///////////////////////// Senso do modelo model.Set(GRB.IntAttr.ModelSense, GRB.MINIMIZE); ///////////////////////// Restrições // Add constraints // Restrição 2 GRBLinExpr soma2; GRBLinExpr soma3; for (int k = 0; k < problema.NumeroDeCarros; k++) { for (int i = 1; i <= problema.NumeroDeClientes; i++) { soma2 = 0; soma3 = 0; for (int j = 0; j < problema.TamanhoMaximo; j++) { soma2.AddTerm(1, x[k, i, j]); soma3.AddTerm(1, x[k, problema.NumeroDeClientes + i, j]); } string st = "Rt2" + i + k; model.AddConstr(soma2 - soma3 == 0, st); soma2.Clear(); soma3.Clear(); } } // Restrição 1 GRBLinExpr soma = 0.0; for (int i = 1; i <= problema.NumeroDeClientes; i++) { for (int j = 0; j < problema.TamanhoMaximo; j++) { for (int k = 0; k < problema.NumeroDeCarros; k++) { soma.AddTerm(1, x[k, i, j]); } } model.AddConstr(soma == 1, "Rt1" + "_" + i); soma.Clear(); } // Restrição 3; GRBLinExpr soma4 = 0; for (int k = 0; k < problema.NumeroDeCarros; k++) { for (int j = 0; j < problema.TamanhoMaximo; j++) { soma4.AddTerm(1, x[k, 0, j]); } model.AddConstr(soma4 == 1, "Rt3" + "_" + k); soma4.Clear(); } // Restrição 4; GRBLinExpr soma5 = 0; GRBLinExpr soma6 = 0; for (int k = 0; k < problema.NumeroDeCarros; k++) { for (int i = 0; i < problema.TamanhoMaximo - 1; i++) { for (int j = 0; j < problema.TamanhoMaximo; j++) { soma5.AddTerm(1, x[k, j, i]); soma6.AddTerm(1, x[k, i, j]); } model.AddConstr(soma5 - soma6 == 0, "Rt4" + i + k); soma5.Clear(); soma6.Clear(); } } // Restrição 5 GRBLinExpr soma7 = 0; for (int k = 0; k < problema.NumeroDeCarros; k++) { for (int i = 0; i < problema.TamanhoMaximo; i++) { soma7.AddTerm(1, x[k, i, problema.TamanhoMaximo - 1]); } model.AddConstr(soma7 == 1, "Rt5" + k); soma7.Clear(); } // Restrição 7 Atualizar Q for (int k = 0; k < problema.NumeroDeCarros; k++) { for (int i = 0; i < problema.TamanhoMaximo; i++) { for (int j = 0; j < problema.TamanhoMaximo; j++) { model.AddConstr(Q[k, j] >= Q[k, i] + problema.usuario[j].Carga - U[k, i, j] * (1 - x[k, i, j]), "Rt7_" + k + "_" + i + "_" + j); } } } // Restrição 8 Atualizar B for (int k = 0; k < problema.NumeroDeCarros; k++) { for (int i = 0; i < problema.TamanhoMaximo; i++) { for (int j = 0; j < problema.TamanhoMaximo; j++) { model.AddConstr(B[k, j] >= B[k, i] + problema.usuario[i].Duracao + problema.Distancias[i, j] - M[k, i, j] * (1 - x[k, i, j]), "Rt6_" + k + "_" + i + "_" + j); } } } // Restrição 9 atualizar L for (int k = 0; k < problema.NumeroDeCarros; k++) { for (int i = 1; i <= problema.NumeroDeClientes; i++) { model.AddConstr(L[k, i] == B[k, problema.NumeroDeClientes + i] - B[k, i] - problema.usuario[i].Duracao, "Rt8_" + k + "_" + i); } } // Restrição 10 Intervalo do B limitado por T for (int k = 0; k < problema.NumeroDeCarros; k++) { model.AddConstr(B[k, problema.TamanhoMaximo - 1] - B[k, 0] <= problema.LarguraHorizonte, "Rt9_" + k); } // Restrição 11 janela de B for (int k = 0; k < problema.NumeroDeCarros; k++) { for (int i = 0; i < problema.TamanhoMaximo; i++) { model.AddConstr(problema.usuario[i].InstanteChegada <= B[k, i], "RtjanelaBe_" + k + "_" + i); model.AddConstr(B[k, i] <= problema.usuario[i].InstanteSaida, "RtjanelaBl_" + k + "_" + i); } } // Restrição 12 janela de L for (int i = 1; i <= problema.NumeroDeClientes; i++) { for (int k = 0; k < problema.NumeroDeCarros; k++) { model.AddConstr(problema.Distancias[i, problema.NumeroDeClientes + i] <= L[k, i], "Rt10e_" + i + "_" + k); model.AddConstr(L[k, i] <= problema.TempoMaximoViagemUsuario, "Rt10l_" + i + "_" + k); } } // Restrição 12 janela Q for (int i = 1; i < problema.TamanhoMaximo; i++) { for (int k = 0; k < problema.NumeroDeCarros; k++) { model.AddConstr(Math.Max(0, problema.usuario[i].Carga) <= Q[k, i], "Rt11e_" + i + "_" + k); model.AddConstr(Q[k, i] <= Math.Min(problema.frota[0].Capacidade, problema.frota[0].Capacidade + problema.usuario[i].Carga), "Rt11l_" + i + "_" + k); } } string Str = Convert.ToString(problema.NumeroDeClientes) + "_" + Convert.ToString(problema.NumeroDeCarros) + Convert.ToString(problema.frota[0].Capacidade); // Escrever o modelo lp string caminho1 = @"C:\Users\hcspe\Documents\Doutorado\TeseUberPool\Classe_Rotas_SARP\MSCordeau\modelos\Modelo"; model.Write(Str + "_" + Agora + ".lp"); // Otimizar o modelo model.Optimize(); // Arquivos // modelo.Write("C:\Users\hellen\Dropbox\DNA sequencing\codes\DNA_Sequencing_2\Modelos\modelo.lp") // modelo.Write("C:\Users\hellen\Dropbox\DNA sequencing\codes\DNA_Sequencing_2\Modelos\modelo.mps") string caminho2 = @"C:\Users\hcspe\Documents\Doutorado\TeseUberPool\Classe_Rotas_SARP\MSCordeau\solucoes\modelo.ilp"; if (model.Get(GRB.IntAttr.Status) == GRB.Status.INFEASIBLE) { model.ComputeIIS(); //computa o modelo ilp, infactível linear problem; model.Write(caminho2); } string caminho3 = @"C:\Users\hcspe\Documents\Doutorado\TeseUberPool\Classe_Rotas_SARP\MSCordeau\solucoes\solucaoModelo"; // procurar infactibilidade if (model.Get(GRB.IntAttr.Status) != GRB.Status.INFEASIBLE) { if (model.SolCount != 0) { model.Write(caminho3 + Str + "_" + Agora + ".sol"); // modelo.Write("C:\Users\hellen\Dropbox\DNA sequencing\codes\DNA_Sequencing_2\Solucoes\solucao.mst") problema.SolucaoExata = new int[problema.NumeroDeCarros - 1, problema.TamanhoMaximo, problema.TamanhoMaximo]; int qtdnNula = 0; for (int k = 0; k < problema.NumeroDeCarros; k++) { for (int i = 0; i < problema.TamanhoMaximo; i++) { for (int j = 0; j < problema.TamanhoMaximo; j++) { problema.SolucaoExata[k, i, j] = Convert.ToInt32(x[k, i, j].X); if (x[k, i, j].X != 0) { qtdnNula += 1; } } } } problema.SolucaoExataNaoNula = new int[qtdnNula, 3]; int cont = 0; int[] QtdAtendimentosCarro = new int[problema.NumeroDeCarros - 1]; for (int k = 0; k < problema.NumeroDeCarros; k++) { for (int i = 0; i < problema.TamanhoMaximo; i++) { for (int j = 0; j < problema.TamanhoMaximo; j++) { if (x[k, i, j].X != 0) { problema.SolucaoExataNaoNula[cont, 0] = k; problema.SolucaoExataNaoNula[cont, 1] = i; problema.SolucaoExataNaoNula[cont, 2] = j; problema.SolucaoExataNaoNula[cont, 3] = Convert.ToInt32(x[k, i, j].X); cont += 1; if (k == 0) { QtdAtendimentosCarro[k] += 1; } if (k == 1) { QtdAtendimentosCarro[k] += 1; } } } } } frota.Ocupacao = new double[problema.NumeroDeCarros, qtdnNula]; for (int i = 0; i < qtdnNula; i++) { if (problema.usuario[problema.SolucaoExataNaoNula[i, 1]].Carga > 0) { problema.usuario[i].TempoDeViagem = L[problema.SolucaoExataNaoNula[i, 0], problema.SolucaoExataNaoNula[i, 1]].X; } } ///////// Escrever os extras, no caso tempo usuario viagem string caminho4 = @"C:\Users\hcspe\Documents\Doutorado\TeseUberPool\Classe_Rotas_SARP\MSCordeau\Extras\"; caminho4 = caminho4 + Str + "_" + Agora + ".txt"; Stream entrada = File.Create(caminho4); StreamWriter escritor = new StreamWriter(entrada); escritor.WriteLine("Tempo Usuario Viagem"); for (int i = 0; i < qtdnNula; i++) { escritor.Write(Convert.ToString(problema.usuario[i].TempoDeViagem) + " "); } escritor.WriteLine(" "); entrada.Close(); escritor.Close(); } } }