Exemple #1
0
        /// <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);
        }