public int[,] UCPSearch( MGInput m, float[] p_target, Action <float> progressCallback) { // Exhaustive Search is very expensive so we use a coarser step. // But we'll need to translate from the coarse step to the // orginal step so we'll store the translation coefficient // in stepSize. int stepCount = options.stepCount; int stepSize = m.tCount / stepCount; int taskCount = options.taskCount; // Get the total number of possible combinations. long nCombinations = (long)1 << (m.genCount * stepCount); // Out fitness variable. float lowestCost = Mathf.Infinity; // Pre-calculate fuel costs at max power because we'll use them // later to calculate a presumptive total cost. float[] cost_thr_at_max = ThermalGenerator.GetCostAtMaxPower( m.genCount, m.p_thr_max, m.thr_c_a, m.thr_c_b, m.thr_c_c, 60.0f); // We only need to verify min uptime/downtime constraints if our // step size is smaller than the the biggest min u/dtime. Otherwise // our step is large enough that the algorithm will never be in a // situation where it's trying to switch a generator that can't be // switched due to min downtime/uptime constraints. bool[] isValidationNeeded = IsMinTimeVerificationNeeded( m.genCount, stepSize, m.thr_min_dtime, m.thr_min_utime); // t1 and t2 define the interval of time this step occupies. var stepInterval = new Tuple <int, int> [stepCount]; for (int iStep = 0; iStep < stepCount; iStep++) { stepInterval[iStep] = Tuple.Create( iStep * stepSize, (iStep + 1) * stepSize - 1); } // It's expensive to compute the cost of the amount of power the // microgrid needs to purchase inside the main loop. There are only // 2^n * stepCount combinations possible which end up being // redundantly recalculated a lot if done in the main loop. // So we compute all of them ahead of time and store them in a // dictionary. Dictionary <StateStep, float> purchaseCostDict = ConstructCostDictionary(m, stepCount, stepSize, 400, p_target); // Running the search in threads makes pruning high cost // combinations happen earlier and more often because a low cost // variant is discovered earlier on. long bestComb = -1; var tasks = new Task[taskCount]; long combSegmentSize = nCombinations / taskCount; var taskParams = new TaskParams( p_target, stepCount, stepSize, stepInterval, isValidationNeeded, cost_thr_at_max, purchaseCostDict); var taskProgress = new float[taskCount]; void progressCallbackInner(int id, float completion) { taskProgress[id] = completion; float totalProgress = 0; for (int i = 0; i < taskProgress.Length; i++) { totalProgress += taskProgress[i]; } progressCallback(totalProgress / taskCount); } for (int iTask = 0; iTask < taskCount; iTask++) { taskProgress[iTask] = 0.0f; long searchStart = iTask * combSegmentSize; long searchEnd = (iTask + 1) * combSegmentSize; int taskID = iTask; var task = Task.Run(() => SearchTask( m, searchStart, searchEnd, ref bestComb, ref lowestCost, taskParams, progressCallbackInner, taskID)); tasks[iTask] = task; } var waiter = Task.WhenAll(tasks.ToArray()); waiter.Wait(); // Write out the step based state array int[,] u_thr_best_step = new int[m.genCount, m.tCount]; for (int iStep = 0; iStep < stepCount; iStep++) { for (int iGen = m.genCount - 1; iGen >= 0; iGen--) { u_thr_best_step[iGen, iStep] = (int)(bestComb % 2); bestComb /= 2; } } // Translate step-based state array to original time based // state array. int[,] u_thr_best = new int[m.genCount, m.tCount]; for (int t = 0; t < m.tCount; t++) { int currStep = t / stepSize; for (int iGen = 0; iGen < m.genCount; iGen++) { u_thr_best[iGen, t] = u_thr_best_step[iGen, currStep]; } } return(u_thr_best); }