public new void Show() { mgInput = microgrid.Input; batteryRatedPowerIF.SetTextWithoutNotify(mgInput.p_bat_max.ToString("F3")); batteryRatedEnergyIF.SetTextWithoutNotify(mgInput.e_bat_max.ToString("F3")); batteryRatedPower = mgInput.p_bat_max; batteryRatedEnergy = mgInput.e_bat_max; base.Show(); }
/// <summary> /// Executes a search on the provided microgrid data to generate /// optimal unit states for the formulated Unit Commitment Problem /// (UCP). /// </summary> /// <param name="input">Microgrid input data.</param> /// <param name="options">Defines the parameters for the PSO algorithm.</param> /// <param name="p_target">The local power demand at every moment of time.</param> /// <param name="progressCallback">Callback invoked every time the algorithm finishes a new iteration.</param> /// <returns>Binary integer array of unit states.</returns> public static async Task <int[, ]> UCPSearch( MGInput input, Options options, float[] p_target, OnNewSnapshotCallback progressCallback) { // Execute the PSO search. #if !UNITY_WEBGL || (UNITY_WEBGL && UNITY_SSM_WEBGL_THREADING_CAPABLE) var t = new Task <int[]>(() => { return(UCPExecutePSORuns( input, options, p_target, progressCallback)); }); t.Start(); int[] u_thr_step_1d = await t; #else int[] u_thr_step_1d = UCPExecutePSORuns( input, options, p_target, progressCallback); #endif // Map the results from a 1D array to a 2D array where one // dimension is the generator states and the other dimension is // time, measured in time steps. int[,] u_thr_step_2d = MGHelper.Int1DToBin2D( u_thr_step_1d, options.stepCount, input.genCount); // Translate the time dimension from time steps to real time. float stepSize = input.tCount / options.stepCount; int[,] u_thr = MGHelper.TranslateTimeStep( u_thr_step_2d, stepSize, input.tCount, input.genCount); return(u_thr); }
public void Show(MGInput defaultMGInput) { mgInput = defaultMGInput; mgInput.thr_c_a = new float[0]; mgInput.thr_c_b = new float[0]; mgInput.thr_c_c = new float[0]; mgInput.thr_min_dtime = new float[0]; mgInput.thr_min_utime = new float[0]; mgInput.p_thr_max = new float[0]; mgInput.genCount = 0; batteryRatedPowerIF.SetTextWithoutNotify(mgInput.p_bat_max.ToString("F3")); batteryRatedEnergyIF.SetTextWithoutNotify(mgInput.e_bat_max.ToString("F3")); batteryRatedPower = mgInput.p_bat_max; batteryRatedEnergy = mgInput.e_bat_max; base.Show(); }
private static int[] UCPExecutePSORuns( MGInput m, Options o, float[] p_target, OnNewSnapshotCallback progressCallback) { Random random = o.randomSeed.HasValue ? new Random(o.randomSeed.Value) : new Random(); int runCount = o.runCount; int iterCount = o.iterCount; int pCount = o.particleCount; int stepCount = o.stepCount; int stepSize = m.tCount / stepCount; int[] rBest = new int[stepCount]; float rBestFitness = float.MaxValue; float totalIter = runCount * iterCount; var threads = new Thread[runCount]; var results = new PSOResult[runCount]; Dictionary <StateStep, float> cDict; cDict = MGHelper.ConstructCostDictionary(m, stepCount, stepSize, 400.0f, p_target); // Precompute an array that maps generator indices to the operating // costs for that generator if it ran at maximum power for a // timestep. float[] c_thr_max_1m = MGHelper.ThermalGenerator.GetCostAtMaxPower( m.genCount, m.p_thr_max, m.thr_c_a, m.thr_c_b, m.thr_c_c, stepSize); var lockObject = new object(); var iterLatest = new int[runCount]; var progressPerRun = new IterationSnapshot[runCount, iterCount]; for (int iRun = 0; iRun < runCount; iRun++) { iterLatest[iRun] = -1; } void progressInner( int runIndex, int iterIndex, IterationSnapshot progressStruct) { lock (lockObject) { iterLatest[runIndex] = iterLatest[runIndex] < iterIndex ? iterIndex : iterLatest[runIndex]; progressPerRun[runIndex, iterIndex] = progressStruct; progressCallback?.Invoke( runIndex, iterIndex, (int[])iterLatest.Clone(), (IterationSnapshot[, ])progressPerRun.Clone()); } } #if !UNITY_WEBGL || (UNITY_WEBGL && UNITY_SSM_WEBGL_THREADING_CAPABLE) // Run the PSO multiple times in parallel. for (int iRun = 0; iRun < runCount; iRun++) { int _iRun = iRun; threads[_iRun] = new Thread(() => { var r = random.Next(); results[_iRun] = UCPExecutePSORun( _iRun, m, o, r, p_target, c_thr_max_1m, cDict, progressInner); }); threads[_iRun].IsBackground = true; threads[_iRun].Start(); } for (int iRun = 0; iRun < threads.Length; iRun++) { threads[iRun].Join(); } #else for (int iRun = 0; iRun < runCount; iRun++) { var r = random.Next(); results[iRun] = UCPExecutePSORun( iRun, m, o, r, p_target, c_thr_max_1m, cDict, progressInner); } #endif // Pick the best run. for (int iRun = 0; iRun < runCount; iRun++) { PSOResult result = results[iRun]; if (result.bestFitness < rBestFitness) { rBestFitness = result.bestFitness; Array.Copy(result.bestPos, rBest, stepCount); } } return(rBest); }
private static PSOResult UCPExecutePSORun( int runIndex, MGInput mg, Options OptsPSO, int randomSeed, float[] p_target, float[] c_thr_max_1m, Dictionary <StateStep, float> cDict, OnNewSnapshotInnerCallback progressCallback = null) { var random = new Random(randomSeed); int iterCount = OptsPSO.iterCount; int pCount = OptsPSO.particleCount; int stepCount = OptsPSO.stepCount; int stepSize = mg.tCount / stepCount; // The power demand that needs to be fulfilled at each time step. float[] p_target_step_max = new float[stepCount]; // The min. downtime of each generator normalized to our time step. float[] dtime_norm = new float[mg.genCount]; // The min. uptime of each generator normalized to our time step. float[] utime_norm = new float[mg.genCount]; // Initialize min. downtime and uptime arrays. for (int iGen = 0; iGen < mg.genCount; iGen++) { dtime_norm[iGen] = mg.thr_min_dtime[iGen] / stepSize; utime_norm[iGen] = mg.thr_min_utime[iGen] / stepSize; } // Iterate through all time steps and initialize p_target_step_max. for (int iStep = 0; iStep < stepCount; iStep++) { // Start time of the current step. int t1 = iStep * stepSize; // End time of the current step. int t2 = (iStep + 1) * stepSize - 1; // Sum up the power demand for every real time unit across // our time step. for (int t = t1; t < t2; t++) { p_target_step_max[iStep] += UnityEngine.Mathf.Max( p_target_step_max[iStep], p_target[t]); } } // Alpha - a criterion variable for how cost efficient a generator is. // Initialize an array to keep track of the alpha of every generator. float[] thr_alpha = MGHelper.ThermalGenerator.GetAlpha( mg.genCount, mg.p_thr_max, mg.thr_c_a, mg.thr_c_b, mg.thr_c_c); // Get an array of the indices of generators, sorted by their alpha. // In other words, if the 5th generator has the 2nd lowest alpha, // then thr_sorted_by_alpha[2] == 5. var thr_sorted_by_alpha = thr_alpha .Select(x => thr_alpha.OrderBy(alpha => alpha).ToList() .IndexOf(x)).ToArray(); // Initialize an array for our particles. var p = InitParticles(pCount, mg.genCount, stepCount, random); // Initialize an array for keeping track of the global best at each time step. var gBest = new int[stepCount]; // Variable for keeping track of the global best. float gBestFitness = float.MaxValue; // How many iterations have past since we've improved our global best. int iterSinceLastGBest = -1; // Go through all of our iterations, updating the particles each time. for (int iIter = 0; iIter < iterCount; iIter++) { iterSinceLastGBest++; // Percent of iterations completed. float completion = iIter / (float)iterCount; // Get the probability of inertia being applied to the // particle's velocity update. // // This probability decreases with each iteration, effectively // slowing down particles. float prob_inertia = UnityEngine.Mathf.Lerp( 1.0f, 0.35f, completion); // Get the probability of the particle's velocity being updated // to a random value. // // This probability increases with each iteration the swarm // goes through without finding a better global best. float prob_craziness = UnityEngine.Mathf.Lerp( 0.005f, 0.50f, iterSinceLastGBest / 100.0f); // Determines if the current iteration is an iteration where // it's possible to apply a heuristic adjustment to the swarm. // // This allows us to apply the adjustment only every n iterations. bool isHeuristicIter = iIter % 10 == 0; // Probability of a particle undergoing heuristic adjustment // when the iteration is a heuristic adjustment enabled // iteration. // // This allows us to control, on average, what percentage of // particles get affected by the heuristic adjustment. float prob_heuristic = 0.25f; // Step 1. // Iterate through all particles, evaluating their fitness // and establishing their personal best. for (int iP = 0; iP < pCount; iP++) { float fitness = GetFitness(p[iP].pos, stepCount, cDict); if (fitness < p[iP].fitnessBest) { p[iP].fitnessBest = fitness; Array.Copy(p[iP].pos, p[iP].pBest, stepCount); } } // Step 2. // Iterate through all particles, establishing the global best // in the swarm. for (int iP = 0; iP < pCount; iP++) { if (p[iP].fitnessBest < gBestFitness) { gBestFitness = p[iP].fitnessBest; Array.Copy(p[iP].pBest, gBest, stepCount); iterSinceLastGBest = 0; } } // Step 3. // Update the velocity and position of every particle. // // Optionally apply a heuristic adjustment aimed at speeding // the convergence of the swarm. // // Revert state switches of generators that would violate the // min/max uptime constraints of that generator. This is // necessary because the particles have no knowledge of those // constraionts. So we deal with that simply by undoing // illegal state switches. for (int iP = 0; iP < pCount; iP++) { Binary.UpdateVel( p[iP].pos, p[iP].vel, p[iP].pBest, gBest, prob_inertia, prob_craziness, mg.genCount, random); Binary.UpdatePos(p[iP].pos, p[iP].vel); /* * if (isHeuristicIter) * { * if (random.NextDouble() < prob_heuristic) * { * HeuristicAdjustment( * mg.genCount, * stepCount, * stepSize, * p[iP].pos, * thr_sorted_by_alpha, * mg.p_thr_max, * p_target_step_max); * } * } */ MGHelper.FixMinTimeConstraints( p[iP].pos, stepCount, mg.genCount, dtime_norm, utime_norm); } var snapshot = new IterationSnapshot( iIter, iterCount, stepCount, mg.genCount, gBestFitness, gBest, p); progressCallback?.Invoke(runIndex, iIter, snapshot); } return(new PSOResult { bestPos = gBest, bestFitness = gBestFitness }); }
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); }
private static void SearchTask(MGInput m, long CombStart, long CombEnd, ref long globalBestComb, ref float globalLowestCost, TaskParams p, Action <int, float> progressCallback, int taskID) { // An array we use to store a candidate solution. int[,] u_thr_candidate = new int[m.genCount, p.stepCount]; int[] states = new int[m.genCount]; long localBestComb; float localLowestCost = Mathf.Infinity; float[] dtime_norm = new float[m.genCount]; float[] utime_norm = new float[m.genCount]; for (int iGen = 0; iGen < m.genCount; iGen++) { dtime_norm[iGen] = m.thr_min_dtime[iGen] / p.stepSize; utime_norm[iGen] = m.thr_min_utime[iGen] / p.stepSize; } for (long iComb = CombStart; iComb < CombEnd; iComb++) { long currComb = iComb; float c_total = 0.0f; // 1 1 1 1 0 1 1 1 0 0 0 1 1 0 0 1 // We assume the state is valid until proven otherwise. bool isStateValid = true; for (int iStep = 0; iStep < p.stepCount; iStep++) { float c_thr_sum_step = 0.0f; currComb = IntToBinary(currComb, m.genCount, states); for (int ithr = 0; ithr < m.genCount; ithr++) { // Get the binary digit representing the on/off sate // for our current thr and step. u_thr_candidate[ithr, iStep] = states[ithr]; if (!isStateValid) { break; } // The total cost incurred by the thrs over the time // interval of the step. c_thr_sum_step += states[ithr] * p.cost_thr_at_max[ithr] * p.stepSize; // The total power the thrs can give at any t moment // during the step. It's constant throughout the step // because the state of units is constant throughout // the time interval of the step. } // END thr int ii = BinaryToInt(states); var id = new StateStep(ii, iStep); c_total += c_thr_sum_step + p.purchaseDict[id]; // Pruning. If the total cost is already higher than the // lowest cost then there's no way this combination can // be the optimal one, so we abandon verifying it and break. if (c_total > globalLowestCost) { break; } } // END Step for (int ithr = 0; ithr < m.genCount; ithr++) { if (!MGHelper.IsStateValid(GetRow(u_thr_candidate, ithr), dtime_norm[ithr], utime_norm[ithr], p.stepCount)) { isStateValid = false; break; } } if (isStateValid) { if (c_total < localLowestCost) { progressCallback(taskID, (float)((double)(iComb - CombStart) / (double)(CombEnd - CombStart))); localLowestCost = c_total; localBestComb = iComb; if (localLowestCost < Volatile.Read(ref globalLowestCost)) { Interlocked.Exchange(ref globalLowestCost, localLowestCost); Interlocked.Exchange(ref globalBestComb, localBestComb); } } } } // END Combination }