public static float[,] SingleCriterionED( MGInput m, float[] p_target, int[,] u_thr) { var p_thr = new float[m.tCount, m.genCount]; var indexToCost = new Dictionary <int, float>(m.genCount); for (int i = 0; i < m.genCount; i++) { // EUR for 1 hour of functioning float cost = MGHelper.ThermalGenerator.GetCost( m.p_thr_max[i], m.thr_c_a[i], m.thr_c_b[i], m.thr_c_c[i]); // EUR/MWh float alpha = cost / m.p_thr_max[i]; indexToCost.Add(i, alpha); } var orderedByCost = indexToCost.OrderBy(x => x.Value).ToDictionary(x => x.Key, y => y.Value); return(SingleCriterionED(m, p_target, u_thr, p_thr, orderedByCost, 0, m.tCount)); }
private async Task <int[, ]> UCPSearch(MGInput input, float[] p_target) { #if !UNITY_WEBGL || (UNITY_WEBGL && UNITY_SSM_WEBGL_THREADING_CAPABLE) var r = await PSOSearch.UCPSearch(input, optionsPSO, p_target, OnProgress); #else var r = PSOSearch.UCPSearch(input, optionsPSO, p_target, OnProgress).Result; #endif executionID++; return(r); }
public async Task <MGOutput> Calculate(MGInput input) { #if !UNITY_WEBGL || (UNITY_WEBGL && UNITY_SSM_WEBGL_THREADING_CAPABLE) var r = await Calculate(input, ucEnumToMethod[ucAlgorithm], edEnumToMethod[edAlgorithm]); #else var r = Calculate(input, ucEnumToMethod[ucAlgorithm], edEnumToMethod[edAlgorithm]).Result; #endif return(r); }
public static float[,] SingleCriterionED( MGInput m, float[] p_target, int[,] u_thr, float[,] p_thr, Dictionary <int, float> orderedByCost, int t1, int t2) { for (int t = t1; t < t2; t++) { float p_thr_remaining = p_target[t]; foreach (KeyValuePair <int, float> kvp in orderedByCost) { int i = kvp.Key; float alpha = kvp.Value; if (u_thr[t, i] == 1) { if (m.canSell) { if (alpha < m.price[t]) { p_thr[t, i] = m.p_thr_max[i]; p_thr_remaining -= p_thr[t, i]; continue; } } if (m.canBuy) { if (alpha > m.price[t]) { p_thr[t, i] = 0.0f; continue; } } p_thr[t, i] = Mathf.Clamp(m.p_thr_max[i], 0.0f, p_thr_remaining); p_thr_remaining -= p_thr[t, i]; } else { p_thr[t, i] = 0.0f; } } } return(p_thr); }
private async Task <MGOutput> Calculate(MGInput m, UCMethod uc, EDMethod ed) { var p_res = new float[m.tCount]; var p_res_excess = new float[m.tCount]; var p_excess = new float[m.tCount]; for (int t = 0; t < m.tCount; t++) { p_res[t] = m.p_w[t] + m.p_pv[t]; } float[] c_bat = new float[m.tCount]; float[] p_target = new float[m.tCount]; float[] p_bat = new float[m.tCount]; float[] e_bat = new float[m.tCount]; float[] soc = new float[m.tCount]; soc[0] = m.soc_ini; e_bat[0] = m.e_bat_max * soc[0]; for (int t = 0; t < m.tCount; t++) { // Renewables aren't enough to meet demand. if (m.p_load[t] > p_res[t]) { p_res_excess[t] = 0.0f; // Battery is usable. if (soc[t] > m.soc_min) { c_bat[t] = -1.0f; p_target[t] = Mathf.Max(0.0f, m.p_load[t] - p_res[t] - m.p_bat_max); } // Battery is not usable, demand isn't met. else { c_bat[t] = 0.0f; p_target[t] = Mathf.Max(0.0f, m.p_load[t] - p_res[t]); } } // Renewables are enough to meet demand. else { p_target[t] = 0.0f; p_res_excess[t] = p_res[t] - m.p_load[t]; // Battery is chargeable. if (soc[t] < m.soc_max) { c_bat[t] = 1.0f; } else { c_bat[t] = 0.0f; } } if (c_bat[t] == 1.0f) { // Charge the battery wiwth whichever is smallest between // the battery's maximum power and the available surplus. p_bat[t] = Mathf.Max(-m.p_bat_max, m.p_load[t] - p_res[t]); } else if (c_bat[t] == -1.0f) { // Draw whichever is smallest between the battery's // maximum power and the unmet demand. p_bat[t] = Mathf.Min(m.p_bat_max, m.p_load[t] - p_res[t]); } else { p_bat[t] = 0.0f; } if (t < m.tCount - 1) { e_bat[t + 1] = e_bat[t] - (p_bat[t] / 60.0f); soc[t + 1] = e_bat[t + 1] / m.e_bat_max; } } // Unit Commitment var stopWatch = new Stopwatch(); QueueForMainThread(UCStatusChanged, this, new RunStatusEventArgs(RunStatus.Started)); stopWatch.Start(); #if !UNITY_WEBGL || (UNITY_WEBGL && UNITY_SSM_WEBGL_THREADING_CAPABLE) int[,] u_thr = await uc(m, p_target); #else int[,] u_thr = uc(m, p_target).Result; #endif stopWatch.Stop(); QueueForMainThread(UCStatusChanged, this, new RunStatusEventArgs(RunStatus.FinishedRunning)); UCStopwatchStopped?.Invoke(this, new StopwatchStoppedEventArgs(stopWatch.ElapsedMilliseconds)); // Economic Dispatch float[,] p_thr = ed(m, p_target, u_thr); // Derive misc. variables float[] p_sum = new float[m.tCount]; float[] e_sys = new float[m.tCount]; float[] e_thr_sum = new float[m.tCount]; float[] p_thr_sum = new float[m.tCount]; float[] p_sys = new float[m.tCount]; float[] c_sys = new float[m.tCount]; float[,] c_thr = new float[m.tCount, m.genCount]; float[] c_thr_sum = new float[m.tCount]; float[] p_thr_max_sum = new float[m.tCount]; float c_thr_total = 0.0f; float c_sys_total = 0.0f; float e_thr_total = 0.0f; float e_sys_total = 0.0f; for (int t = 0; t < m.tCount; t++) { for (int i = 0; i < m.genCount; i++) { p_thr_max_sum[t] += m.p_thr_max[i] * u_thr[t, i]; p_thr_sum[t] += p_thr[t, i]; e_thr_sum[t] += p_thr[t, i] / 60.0f; //EUR for 1 hour of functioning c_thr[t, i] = u_thr[t, i] * MGHelper.ThermalGenerator.GetCost( p_thr[t, i], m.thr_c_a[i], m.thr_c_b[i], m.thr_c_c[i]) / 60.0f; c_thr_sum[t] += c_thr[t, i]; } p_sum[t] = p_res[t] + p_bat[t] + p_thr_sum[t]; if (m.p_load[t] - p_sum[t] < 0) { if (m.canSell) { p_sys[t] = m.p_load[t] - p_sum[t]; } else { p_sys[t] = 0.0f; p_excess[t] = p_sum[t] - m.p_load[t]; } } else { if (m.canBuy) { p_sys[t] = m.p_load[t] - p_sum[t]; } else { p_sys[t] = 0.0f; p_excess[t] = p_sum[t] - m.p_load[t]; } } e_sys[t] = p_sys[t] / 60.0f; c_sys[t] = e_sys[t] * m.price[t]; e_sys_total += e_sys[t]; c_thr_total += c_thr_sum[t]; c_sys_total += c_sys[t]; e_thr_total += e_thr_sum[t]; } return(new MGOutput { u_thr = u_thr, p_sum = p_sum, p_res = p_res, p_thr = p_thr, e_thr_sum = e_thr_sum, p_thr_sum = p_thr_sum, p_thr_max_sum = p_thr_max_sum, p_bat = p_bat, p_sys = p_sys, p_target = p_target, soc = soc, c_bat = c_bat, e_sys_total = e_sys_total, e_thr_total = e_thr_total, c_sys = c_sys, c_thr = c_thr, c_thr_sum = c_thr_sum, c_sys_total = c_sys_total, c_thr_total = c_thr_total }); }
/// <summary> /// Precomputes the cost of each possible set of generator states at /// every time step. /// </summary> /// <param name="m">Microgrid input data.</param> /// <param name="genCount">Number of generators.</param> /// <param name="stepCount">The number of time steps.</param> /// <param name="stepSize">The size of each time step.</param> /// <param name="p_target">The amount of power the system needs to supply at a given time step.</param> /// <returns>A dictionary mapping a generator state and time step pair /// to the cost of purchasing the amount of power necessary to fulfill /// remaining demand for that specific pair.</returns> public static Dictionary <StateStep, float> ConstructCostDictionary( MGInput m, int stepCount, int stepSize, float penalty, float[] p_target) { // 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. // 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] = new Tuple <int, int>( iStep * stepSize, (iStep + 1) * stepSize - 1); } int nSqr = 1 << m.genCount; //Bitwise squaring. int nPurchaseCombs = nSqr * stepCount; var purchaseCostDict = new Dictionary <StateStep, float>(nPurchaseCombs); var indexToCost = new Dictionary <int, float>(m.genCount); float[] p_thr = new float[m.genCount]; for (int i = 0; i < m.genCount; i++) { // EUR for 1 hour of functioning float cost = ThermalGenerator.GetCost( m.p_thr_max[i], m.thr_c_a[i], m.thr_c_b[i], m.thr_c_c[i]); // EUR/MWh float alpha = cost / m.p_thr_max[i]; indexToCost.Add(i, alpha); } float[] e_thr_remaining = new float[stepSize]; float[] p_remaining = new float[stepSize]; var orderedByCost = indexToCost.OrderBy(x => x.Value).ToDictionary(x => x.Key, y => y.Value); for (int iState = 0; iState < nSqr; iState++) { for (int iStep = 0; iStep < stepCount; iStep++) { float c_total = 0.0f; int t1 = stepInterval[iStep].Item1; int t2 = stepInterval[iStep].Item2; for (int t = t1; t < t2; t++) { e_thr_remaining[t - t1] = p_target[t] / 60.0f; p_remaining[t - t1] = p_target[t]; foreach (KeyValuePair <int, float> kvp in orderedByCost) { int iGen = kvp.Key; float alpha = kvp.Value; var bitValue = iState & (1 << m.genCount - iGen - 1); var state = ((bitValue | (~bitValue + 1)) >> 31) & 1; if (state == 1) { if (alpha < m.price[t]) { if (m.canSell) { // Since we can sell and our generator // makes cheaper energy than the market price, // just produce at max power. p_thr[iGen] = m.p_thr_max[iGen]; } else { // Since we can't sell but our generator // makes cheaper energy than market price, // we'll produce enough to cover local demand. p_thr[iGen] = Mathf.Clamp(m.p_thr_max[iGen], 0.0f, p_remaining[t - t1]); } } else { if (m.canBuy) { // Since we can buy and our generator // makes more expensive energy than // the market price, produce no power // and buy. p_thr[iGen] = 0.0f; } else { // Since we can't buy, produce enough // power to cover local demand, regardless // of market price. p_thr[iGen] = Mathf.Clamp(m.p_thr_max[iGen], 0.0f, p_remaining[t - t1]); } } c_total += ThermalGenerator.GetCost( p_thr[iGen], m.thr_c_a[iGen], m.thr_c_b[iGen], m.thr_c_c[iGen]) / 60.0f; p_remaining[t - t1] -= p_thr[iGen]; e_thr_remaining[t - t1] -= p_thr[iGen] / 60.0f; } else { p_thr[iGen] = 0.0f; } } if (e_thr_remaining[t - t1] < 0.0f) { if (m.canSell) { c_total += e_thr_remaining[t - t1] * m.price[t]; } } else { if (m.canBuy) { c_total += e_thr_remaining[t - t1] * m.price[t]; } else { c_total += e_thr_remaining[t - t1] * penalty; } } } var id = new StateStep(iState, iStep); #if UNITY_EDITOR || DEVELOPMENT_BUILD if (purchaseCostDict.ContainsKey(id) && purchaseCostDict[id] != c_total) { throw new InvalidProgramException( $@"IDs and purchase costs need to have a 1 to 1 mapping. There can't be an ID with a different purchase cost. If that happens, the code is wrong somewhere. UID: {id}. Existing Value: {purchaseCostDict[id]}. New Value: {c_total}."); } #endif purchaseCostDict[id] = c_total; } } return(purchaseCostDict); }