private void SortFinalResults() { // if the best list didn't fill up if (NumberValidSolutions_ < MaxResults_) { SolutionHeap_ = new HeapWithComparer <Solution>(MaxResults_, UnorderedSolutions_, Comparer_); } // run heapsort on the top solutions, copy into solution list Solutions_.AddRange(SolutionHeap_.GetSorted()); }
public SolverResult Compute(BackgroundWorker worker, DoWorkEventArgs e) { if (Timetable_ == null || !Timetable_.HasData()) { return(SolverResult.NoTimetable); } Solutions_.Clear(); StreamSets_.Clear(); long totalSolutions = 1; SolutionHeap_ = null; UnorderedSolutions_ = new Solution[MaxResults_]; #region Build lists of stream options foreach (Type type in Timetable_.TypeList) { // if the stream type is ignored, skip if (!type.Required) { continue; } // create a list of streams for each type List <Stream> streams = new List <Stream>(); // stream already enabled? only one option if (type.SelectedStream != null) { streams.Add(type.SelectedStream); } else { // for each stream in the current type foreach (Stream stream in type.UniqueStreams) { // if stream can't be selected, skip it if (!Timetable_.Fits(stream)) { continue; } streams.Add(stream); } // no streams made it - skip if (streams.Count == 0) { return(SolverResult.Clash); } } // add streams to list StreamSets_.Add(streams); // update the complexity of the solution totalSolutions *= streams.Count; } #endregion #region Optimise unique stream list order // order determined greedily on basis of maximum clashes float[,] clashChance = new float[StreamSets_.Count, StreamSets_.Count]; // build 2D clash probability table for (int i = 0; i < StreamSets_.Count; i++) { List <Stream> set = StreamSets_[i]; for (int j = i + 1; j < StreamSets_.Count; j++) { List <Stream> otherSet = StreamSets_[j]; int total = set.Count * otherSet.Count; int clash = 0; foreach (Stream stream in set) { foreach (Stream otherStream in otherSet) { if (stream.ClashesWith(otherStream)) { clash++; } } } float chance = (float)clash / (float)total; clashChance[i, j] = chance; clashChance[j, i] = chance; } } // build index lists, extract sets of only one stream List <int> ordered = new List <int>(); List <int> remaining = new List <int>(); for (int i = 0; i < StreamSets_.Count; i++) { if (StreamSets_[i].Count == 1) { ordered.Add(i); } else { remaining.Add(i); } } // get probability for single-option sets foreach (int i in ordered) { float chance = 1f; foreach (int j in ordered) { // reached same index - compared to all preceding if (j == i) { break; } chance *= 1f - clashChance[i, j]; } chance = 1f - chance; } // order by probability of clash with previously selected items while (remaining.Count > 0) { float maxChance = 0; float maxFuture = 0; int maxIndex = -1; foreach (int i in remaining) { float chance = 1f; float future = 1f; foreach (int j in ordered) { // find chance of not successively not clashing chance *= 1f - clashChance[i, j]; } foreach (int j in remaining) { if (i == j) { continue; } // chance of clashing with remaining options future *= 1f - clashChance[i, j]; } // chance of clashing chance = 1f - chance; future = 1f - future; // check if there's a pair which is more likely to clash float pairMax = 0f; foreach (int j in remaining) { if (i == j) { continue; } if (clashChance[i, j] > pairMax) { pairMax = clashChance[i, j]; } } if (pairMax > chance) { chance = pairMax; // give preference to any instant solution future = -1; } if (maxIndex < 0 || chance > maxChance || (chance == maxChance && future > maxFuture)) { maxIndex = i; maxChance = chance; maxFuture = future; } } remaining.Remove(maxIndex); ordered.Add(maxIndex); } /* * // do specialised selection sort to bring most incompatible streams to the front * * // first find single worst instance of incompatibility * float maxValue = -1; * int maxIndex = -1; * // look for worst incompatibility between a set of streams * foreach (int i in remaining) * { * if (StreamSets_[i].Count == 1) * { * maxIndex = i; * break; * } * // and all sets of streams which follow * foreach (int j in remaining) * { * if (i == j) * continue; * if (clashChance[i, j] > maxValue) * { * maxValue = clashChance[i, j]; * maxIndex = i; * } * } * } * * remaining.Remove(maxIndex); * ordered.Add(maxIndex); * * // selection sort the rest of the list * while (remaining.Count > 0) * { * // TODO: initialisation required? * maxValue = -1; * maxIndex = -1; * // find maximum from all remaining sets of streams * foreach (int j in remaining) * { * if (StreamSets_[j].Count == 1) * { * maxIndex = j; * break; * } * * float currentMaxValue = -1; * // to find the maximum for a set, find its worst match from the already selected streams * foreach (int k in ordered) * { * // if found new max for current stream list * if (clashChance[j, k] > currentMaxValue) * { * currentMaxValue = clashChance[j, k]; * } * } * // if found new max for all stream lists * if (currentMaxValue > maxValue) * { * maxValue = currentMaxValue; * maxIndex = j; * } * } * * remaining.Remove(maxIndex); * ordered.Add(maxIndex); * }*/ List <List <Stream> > final = new List <List <Stream> >(); foreach (int index in ordered) { final.Add(StreamSets_[index]); } StreamSets_ = final; #endregion #region Stack-based algorithm Stack <Solution> solutionStack = new Stack <Solution>(StreamSets_.Count); solutionStack.Push(new Solution()); Solution solution = new Solution(); NumberValidSolutions_ = 0; long progress = 0; int progressPercent = 0; long numLoops = 0; // create an array such that the value at an index gives the number // of complete solutions "down that alley" when processing is cut // short at that index into the unique streams list long[] cumulativeProduct = new long[StreamSets_.Count]; cumulativeProduct[cumulativeProduct.Length - 1] = 1; for (int i = cumulativeProduct.Length - 2; i >= 0; i--) { cumulativeProduct[i] = cumulativeProduct[i + 1] * StreamSets_[i + 1].Count; } // setIndex represents from which set of streams the next stream is being selected int setIndex = 0; int[] streamIndices = new int[StreamSets_.Count]; for (int i = 0; i < streamIndices.Length; i++) { streamIndices[i] = 0; } //while (true) while (true) { if (worker.CancellationPending) { SortFinalResults(); e.Cancel = true; return(SolverResult.UserCancel); } numLoops++; // check if the stream index is out of range // (done all options for current set of streams) if (streamIndices[setIndex] == StreamSets_[setIndex].Count) { // if we're back at the start if (setIndex == 0) { // then we're spent! exit break; } // tried all possibilities at the current level, backtrack a level setIndex--; // and go to the next possibility there streamIndices[setIndex]++; // pop off the saved solution from the previous level //solution.Streams.RemoveAt(solution.Streams.Count - 1); solutionStack.Pop(); solution = new Solution(solutionStack.Peek()); // try with the new indices! continue; } // attempt to add the current stream to the solution if (!solution.AddStream(StreamSets_[setIndex][streamIndices[setIndex]])) { // add to progress the number of solutions just skipped progress += cumulativeProduct[setIndex]; int percent = (int)((float)progress / totalSolutions * 100); if (percent > progressPercent) { worker.ReportProgress(percent); progressPercent = percent; } // stream is incompatible, try adjacent possibility streamIndices[setIndex]++; // try now continue; } // found stream that fits! // if we have a complete solution if (setIndex == StreamSets_.Count - 1) { // increment progress progress++; int percent = (int)((float)progress / totalSolutions * 100); if (percent > progressPercent) { worker.ReportProgress(percent); progressPercent = percent; } bool filteredOut = false; foreach (Filter filter in Filters_) { // failed on a filter? if (!filter.Pass(solution)) { filteredOut = true; break; } } if (!filteredOut) { NumberValidSolutions_++; // if the solution list is full to capacity if (NumberValidSolutions_ > MaxResults_) { // if the solution is in the top so far, take the place of the worst solution SolutionHeap_.CompareReplaceMaximum(solution); } else { // append the solution to the array UnorderedSolutions_[NumberValidSolutions_ - 1] = solution; // if the unordered list just filled up if (NumberValidSolutions_ == MaxResults_) { // build the heap from the array SolutionHeap_ = new HeapWithComparer <Solution>(MaxResults_, UnorderedSolutions_, Comparer_); } } } // now revert current solution solution = new Solution(solutionStack.Peek()); //solution.Streams.RemoveAt(solution.Streams.Count - 1); // and move to next along streamIndices[setIndex]++; // try next continue; } // no real gain observed // check if path is worth pursuing /*else if (NumberValidSolutions_ > MaxResults_) * { * bool abort = false; * Solution worst = SolutionHeap_.FindMaximum(); * foreach (Criteria critieria in Comparer_.Criteria) * { * // grows randomly * if (critieria.Field.Progression == FieldProgression.Unknown) * { * break; * } * // grows and trying to minimise * else if (critieria.Field.Progression == FieldProgression.Grow && critieria.Preference == Preference.Minimise) * { * if (solution.FieldValueToInt(critieria.Field.Index) > worst.FieldValueToInt(critieria.Field.Index)) * { * abort = true; * } * break; * } * // shrinks and trying to maximise * else if (critieria.Field.Progression == FieldProgression.Shrink && critieria.Preference == Preference.Maximise) * { * if (solution.FieldValueToInt(critieria.Field.Index) < worst.FieldValueToInt(critieria.Field.Index)) * { * abort = true; * } * break; * } * } * if (abort) * { * // stream won't work, restore and try adjacent * solution = new Solution(solutionStack.Peek()); * streamIndices[setIndex]++; * continue; * } * }*/ // move to next level setIndex++; // and try first option there streamIndices[setIndex] = 0; // push the solution with the new stream added on to the stack solutionStack.Push(solution); // set the working solution to a copy of the previous solution solution = new Solution(solution); } //MessageBox.Show(numLoops.ToString() + " loops\n" + totalSolutions.ToString() + " combinations"); //MessageBox.Show("Number of valid solutions found: " + numValid.ToString() + // "\nExpected number of solutions: " + totalSolutions.ToString() + // "\nNumber of solutions \"checked\": " + progress.ToString()); SortFinalResults(); #endregion Timetable_.RecomputeSolutions = false; return(SolverResult.Complete); }