OptimalDecisionAndValue(ICmdtyStorage <T> storage, T period, double inventory, double nextStepInventorySpaceMin, double nextStepInventorySpaceMax, double cmdtyPrice, Func <double, double> continuationValueByInventory, double discountFactorFromCmdtySettlement, Func <Day, double> discountFactors, double numericalTolerance) { InjectWithdrawRange injectWithdrawRange = storage.GetInjectWithdrawRange(period, inventory); double inventoryLoss = storage.CmdtyInventoryPercentLoss(period) * inventory; double[] decisionSet = StorageHelper.CalculateBangBangDecisionSet(injectWithdrawRange, inventory, inventoryLoss, nextStepInventorySpaceMin, nextStepInventorySpaceMax, numericalTolerance); var valuesForDecision = new double[decisionSet.Length]; var cmdtyConsumedForDecision = new double[decisionSet.Length]; var periodPvForDecision = new double[decisionSet.Length]; for (var j = 0; j < decisionSet.Length; j++) { double decisionInjectWithdraw = decisionSet[j]; (valuesForDecision[j], cmdtyConsumedForDecision[j], periodPvForDecision[j]) = StorageValueForDecision(storage, period, inventory, inventoryLoss, decisionInjectWithdraw, cmdtyPrice, continuationValueByInventory, discountFactorFromCmdtySettlement, discountFactors); } (double storageNpv, int indexOfOptimalDecision) = StorageHelper.MaxValueAndIndex(valuesForDecision); return(StorageNpv : storageNpv, OptimalInjectWithdraw : decisionSet[indexOfOptimalDecision], CmdtyConsumedOnAction : cmdtyConsumedForDecision[indexOfOptimalDecision], InventoryLoss : inventoryLoss, PeriodPv : periodPvForDecision[indexOfOptimalDecision]); }
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)); }