private static IAddInjectionCost <T> AddInjectWithdrawRanges <T>(
            IAddInjectWithdrawConstraints <T> builder,
            IEnumerable <InjectWithdrawRangeByInventoryAndPeriod <T> > injectWithdrawRanges,
            Func <InjectWithdrawRangeByInventory[], IInjectWithdrawConstraint> constraintFactory) where T : ITimePeriod <T>
        {
            var injectWithdrawSortedList = new SortedList <T, IInjectWithdrawConstraint>();
            var inventoryRangeList       = new List <InventoryRange>();

            foreach ((T period, IEnumerable <InjectWithdrawRangeByInventory> injectWithdrawRange) in injectWithdrawRanges)
            {
                if (period == null)
                {
                    throw new ArgumentException("Null Period in collection.", nameof(injectWithdrawRanges));
                }
                if (injectWithdrawRange == null)
                {
                    throw new ArgumentException("Null InjectWithdrawRanges in collection.", nameof(injectWithdrawRange));
                }

                InjectWithdrawRangeByInventory[] injectWithdrawRangeArray = injectWithdrawRange.ToArray();
                if (injectWithdrawRangeArray.Length < 2)
                {
                    throw new ArgumentException($"Period {period} contains less than 2 inject/withdraw/inventory constraints.",
                                                nameof(injectWithdrawRanges));
                }

                IInjectWithdrawConstraint constraint;
                if (injectWithdrawRangeArray.Length == 2 &&
                    injectWithdrawRangeArray[0].InjectWithdrawRange.MinInjectWithdrawRate.AlmostEqual(
                        injectWithdrawRangeArray[1].InjectWithdrawRange.MinInjectWithdrawRate, double.Epsilon) &&
                    injectWithdrawRangeArray[0].InjectWithdrawRange.MaxInjectWithdrawRate.AlmostEqual(
                        injectWithdrawRangeArray[1].InjectWithdrawRange.MaxInjectWithdrawRate, double.Epsilon))
                {
                    // Two rows which represent constant inject/withdraw constraints over all inventories
                    constraint = new ConstantInjectWithdrawConstraint(injectWithdrawRangeArray[0].InjectWithdrawRange);
                }
                else
                {
                    constraint = constraintFactory(injectWithdrawRangeArray);
                }

                double minInventory = injectWithdrawRangeArray.Min(inventoryRange => inventoryRange.Inventory);
                double maxInventory = injectWithdrawRangeArray.Max(inventoryRange => inventoryRange.Inventory);

                try
                {
                    injectWithdrawSortedList.Add(period, constraint);
                    inventoryRangeList.Add(new InventoryRange(minInventory, maxInventory));
                }
                catch (ArgumentException) // TODO unit test
                {
                    throw new ArgumentException("Repeated periods found in inject/withdraw ranges.",
                                                nameof(injectWithdrawRanges));
                }
            }

            if (injectWithdrawSortedList.Count == 0)
            {
                throw new ArgumentException("No inject/withdraw constrains provided.", nameof(injectWithdrawRanges));
            }

            // TODO create helper method (in Cmdty.TimeSeries) to create TimeSeries from piecewise data?
            T   firstPeriod = injectWithdrawSortedList.Keys[0];
            T   lastPeriod  = injectWithdrawSortedList.Keys[injectWithdrawSortedList.Count - 1];
            int numPeriods  = lastPeriod.OffsetFrom(firstPeriod) + 1;

            var timeSeriesInjectWithdrawValues = new IInjectWithdrawConstraint[numPeriods];
            var timeSeriesInventoryRangeValues = new InventoryRange[numPeriods];

            T periodLoop = firstPeriod;
            IInjectWithdrawConstraint constraintLoop     = injectWithdrawSortedList.Values[0];
            InventoryRange            inventoryRangeLoop = inventoryRangeList[0];

            int arrayCounter      = 0;
            int sortedListCounter = 0;

            do
            {
                if (periodLoop.Equals(injectWithdrawSortedList.Keys[sortedListCounter]))
                {
                    constraintLoop     = injectWithdrawSortedList.Values[sortedListCounter];
                    inventoryRangeLoop = inventoryRangeList[sortedListCounter];
                    sortedListCounter++;
                }

                timeSeriesInjectWithdrawValues[arrayCounter] = constraintLoop;
                timeSeriesInventoryRangeValues[arrayCounter] = inventoryRangeLoop;

                periodLoop = periodLoop.Offset(1);
                arrayCounter++;
            } while (periodLoop.CompareTo(lastPeriod) <= 0);

            var injectWithdrawTimeSeries =
                new TimeSeries <T, IInjectWithdrawConstraint>(firstPeriod, timeSeriesInjectWithdrawValues);
            var inventoryRangeTimeSeries = new TimeSeries <T, InventoryRange>(firstPeriod, timeSeriesInventoryRangeValues);

            IInjectWithdrawConstraint GetInjectWithdrawConstraint(T period)
            {
                if (period.CompareTo(injectWithdrawTimeSeries.End) > 0)
                {
                    return(injectWithdrawTimeSeries[injectWithdrawTimeSeries.End]);
                }
                return(injectWithdrawTimeSeries[period]);
            }

            IAddMinInventory <T>
            addMinInventory = builder.WithInjectWithdrawConstraint(GetInjectWithdrawConstraint);

            double GetMinInventory(T period)
            {
                if (period.CompareTo(inventoryRangeTimeSeries.End) > 0)
                {
                    return(inventoryRangeTimeSeries[inventoryRangeTimeSeries.End].MinInventory);
                }
                return(inventoryRangeTimeSeries[period].MinInventory);
            }

            IAddMaxInventory <T> addMaxInventory = addMinInventory.WithMinInventory(GetMinInventory);

            double GetMaxInventory(T period)
            {
                if (period.CompareTo(inventoryRangeTimeSeries.End) > 0)
                {
                    return(inventoryRangeTimeSeries[inventoryRangeTimeSeries.End].MaxInventory);
                }
                return(inventoryRangeTimeSeries[period].MaxInventory);
            }

            IAddInjectionCost <T> addInjectionCost = addMaxInventory.WithMaxInventory(GetMaxInventory);

            return(addInjectionCost);
        }
Example #2
0
        public static TimeSeries <T, InventoryRange> CalculateInventorySpace <T>(ICmdtyStorage <T> storage, double startingInventory, T currentPeriod)
            where T : ITimePeriod <T>
        {
            if (currentPeriod.CompareTo(storage.EndPeriod) > 0)     // TODO should condition be >= 0?
            {
                throw new ArgumentException("Storage has expired"); // TODO change to return empty TimeSeries?
            }
            T startActiveStorage = storage.StartPeriod.CompareTo(currentPeriod) > 0 ? storage.StartPeriod : currentPeriod;

            int numPeriods = storage.EndPeriod.OffsetFrom(startActiveStorage);

            // Calculate the inventory space range going forward

            var forwardCalcMaxInventory = new double[numPeriods];
            var forwardCalcMinInventory = new double[numPeriods];

            double minInventoryForwardCalc = startingInventory;
            double maxInventoryForwardCalc = startingInventory;

            for (int i = 0; i < numPeriods; i++)
            {
                T      periodLoop           = startActiveStorage.Offset(i);
                T      nextPeriod           = periodLoop.Offset(1);
                double inventoryPercentLoss = storage.CmdtyInventoryPercentLoss(periodLoop);

                double injectWithdrawMin  = storage.GetInjectWithdrawRange(periodLoop, minInventoryForwardCalc).MinInjectWithdrawRate;
                double inventoryLossAtMin = inventoryPercentLoss * minInventoryForwardCalc;
                double storageMin         = storage.MinInventory(nextPeriod);
                minInventoryForwardCalc    = Math.Max(minInventoryForwardCalc - inventoryLossAtMin + injectWithdrawMin, storageMin);
                forwardCalcMinInventory[i] = minInventoryForwardCalc;

                double injectWithdrawMax  = storage.GetInjectWithdrawRange(periodLoop, maxInventoryForwardCalc).MaxInjectWithdrawRate;
                double inventoryLossAtMax = inventoryPercentLoss * maxInventoryForwardCalc;
                double storageMax         = storage.MaxInventory(nextPeriod);
                maxInventoryForwardCalc    = Math.Min(maxInventoryForwardCalc - inventoryLossAtMax + injectWithdrawMax, storageMax);
                forwardCalcMaxInventory[i] = maxInventoryForwardCalc;
            }

            // Calculate the inventory space range going backwards
            var backwardCalcMaxInventory = new double[numPeriods];

            var backwardCalcMinInventory = new double[numPeriods];

            T periodBackLoop = storage.EndPeriod;

            backwardCalcMaxInventory[numPeriods - 1] = storage.MustBeEmptyAtEnd ? 0 : storage.MaxInventory(storage.EndPeriod);
            backwardCalcMinInventory[numPeriods - 1] = storage.MustBeEmptyAtEnd ? 0 : storage.MinInventory(storage.EndPeriod);

            for (int i = numPeriods - 2; i >= 0; i--)
            {
                periodBackLoop = periodBackLoop.Offset(-1);
                backwardCalcMaxInventory[i] = storage.InventorySpaceUpperBound(periodBackLoop, backwardCalcMinInventory[i + 1], backwardCalcMaxInventory[i + 1]);
                backwardCalcMinInventory[i] = storage.InventorySpaceLowerBound(periodBackLoop, backwardCalcMinInventory[i + 1],
                                                                               backwardCalcMaxInventory[i + 1]);
            }

            // Calculate overall inventory space and check for consistency

            var inventoryRanges = new InventoryRange[numPeriods];

            for (int i = 0; i < numPeriods; i++)
            {
                double inventorySpaceMax = Math.Min(forwardCalcMaxInventory[i], backwardCalcMaxInventory[i]);
                double inventorySpaceMin = Math.Max(forwardCalcMinInventory[i], backwardCalcMinInventory[i]);
                if (inventorySpaceMin > inventorySpaceMax)
                {
                    throw new InventoryConstraintsCannotBeFulfilledException();
                }
                inventoryRanges[i] = new InventoryRange(inventorySpaceMin, inventorySpaceMax);
            }

            return(new TimeSeries <T, InventoryRange>(startActiveStorage.Offset(1), inventoryRanges));
        }