public static IAddMinInventory <T> WithConstantInjectWithdrawRange <T>([NotNull] this IAddInjectWithdrawConstraints <T> builder,
                                                                               double minInjectWithdrawRate, double maxInjectWithdrawRate)
            where T : ITimePeriod <T>
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }
            var constantInjectWithdrawConstraint = new ConstantInjectWithdrawConstraint(minInjectWithdrawRate, maxInjectWithdrawRate);

            return(builder.WithInjectWithdrawConstraint(constantInjectWithdrawConstraint));
        }
        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);
        }