public double InventorySpaceUpperBound(double nextPeriodInventorySpaceLowerBound, double nextPeriodInventorySpaceUpperBound, double currentPeriodMinInventory, double currentPeriodMaxInventory, double inventoryPercentLoss) { InjectWithdrawRange currentPeriodInjectWithdrawRangeAtMaxInventory = GetInjectWithdrawRange(currentPeriodMaxInventory); double nextPeriodMaxInventoryFromThisPeriodMaxInventory = currentPeriodMaxInventory * (1 - inventoryPercentLoss) + currentPeriodInjectWithdrawRangeAtMaxInventory.MaxInjectWithdrawRate; double nextPeriodMinInventoryFromThisPeriodMaxInventory = currentPeriodMaxInventory * (1 - inventoryPercentLoss) + currentPeriodInjectWithdrawRangeAtMaxInventory.MinInjectWithdrawRate; if (nextPeriodMinInventoryFromThisPeriodMaxInventory <= nextPeriodInventorySpaceUpperBound && nextPeriodInventorySpaceLowerBound <= nextPeriodMaxInventoryFromThisPeriodMaxInventory) { // No need to solve root as next period inventory space can be reached from the current period max inventory return(currentPeriodMaxInventory); } // TODO share code in method up to here with PiecewiseLinearInjectWithdrawConstraint double?inventorySpaceUpper = null; for (int i = 0; i < _injectWithdrawRanges.Length - 1; i++) { // TODO reuse values between loop iterations like in PiecewiseLinearInjectWithdrawConstraint, or not bother because will make code less clear? double maxWithdrawRate = _injectWithdrawRanges[i].InjectWithdrawRange.MinInjectWithdrawRate; double bracketLowerInventory = _injectWithdrawRanges[i].Inventory; double bracketLowerInventoryAfterWithdraw = bracketLowerInventory * (1 - inventoryPercentLoss) + maxWithdrawRate; double bracketUpperInventory = _injectWithdrawRanges[i + 1].Inventory; double bracketUpperInventoryAfterWithdraw = bracketUpperInventory * (1 - inventoryPercentLoss) + maxWithdrawRate; if (bracketLowerInventoryAfterWithdraw <= nextPeriodInventorySpaceUpperBound && nextPeriodInventorySpaceUpperBound <= bracketUpperInventoryAfterWithdraw) { // If there are multiple solutions we want to take the maximum one, so we keep overwriting the solution inventorySpaceUpper = StorageHelper.InterpolateLinearAndSolve(bracketLowerInventory, bracketLowerInventoryAfterWithdraw, bracketUpperInventory, bracketUpperInventoryAfterWithdraw, nextPeriodInventorySpaceUpperBound); } } if (inventorySpaceUpper == null) { throw new ApplicationException("Storage inventory constraints cannot be satisfied."); } return(inventorySpaceUpper.Value); }
public StepInjectWithdrawConstraint([NotNull] IEnumerable <InjectWithdrawRangeByInventory> injectWithdrawRanges) { if (injectWithdrawRanges == null) { throw new ArgumentNullException(nameof(injectWithdrawRanges)); } _injectWithdrawRanges = injectWithdrawRanges.OrderBy(injectWithdrawRange => injectWithdrawRange.Inventory) .ToArray(); if (_injectWithdrawRanges.Length < 2) { throw new ArgumentException("Inject/withdraw ranges collection must contain at least two elements.", nameof(injectWithdrawRanges)); } InjectWithdrawRange secondHighestInventoryRange = _injectWithdrawRanges[_injectWithdrawRanges.Length - 2].InjectWithdrawRange; InjectWithdrawRange highestInventoryRange = _injectWithdrawRanges[_injectWithdrawRanges.Length - 1].InjectWithdrawRange; if (!StorageHelper.EqualsWithinTol(secondHighestInventoryRange.MaxInjectWithdrawRate, highestInventoryRange.MaxInjectWithdrawRate, 1E-12)) { throw new ArgumentException("Top two ratchets do not have he same max injection rate.", nameof(injectWithdrawRanges)); } if (!StorageHelper.EqualsWithinTol(secondHighestInventoryRange.MinInjectWithdrawRate, highestInventoryRange.MinInjectWithdrawRate, 1E-12)) { throw new ArgumentException("Top two ratchets do not have he same max withdrawal rate.", nameof(injectWithdrawRanges)); } _inventories = _injectWithdrawRanges.Select(injectWithdrawRange => injectWithdrawRange.Inventory) .ToArray(); if (_inventories.Length > 2) { for (int i = 1; i < _inventories.Length - 1; i++) // Don't check the last pair as these should have same inject/withdraw rates, as checked for above { if (_injectWithdrawRanges[i].InjectWithdrawRange.MaxInjectWithdrawRate > _injectWithdrawRanges[i - 1].InjectWithdrawRange.MaxInjectWithdrawRate) { throw new ArgumentException("Ratchet injection rates cannot increase with inventory."); } if (_injectWithdrawRanges[i].InjectWithdrawRange.MinInjectWithdrawRate > _injectWithdrawRanges[i - 1].InjectWithdrawRange.MinInjectWithdrawRate) { throw new ArgumentException("Ratchet withdrawal rates cannot decrease with inventory."); } } } }
public double InventorySpaceLowerBound(double nextPeriodInventorySpaceLowerBound, double nextPeriodInventorySpaceUpperBound, double currentPeriodMinInventory, double currentPeriodMaxInventory, double inventoryPercentLoss) { InjectWithdrawRange currentPeriodInjectWithdrawRangeAtMinInventory = GetInjectWithdrawRange(currentPeriodMinInventory); double nextPeriodMaxInventoryFromThisPeriodMinInventory = currentPeriodMinInventory * (1 - inventoryPercentLoss) + currentPeriodInjectWithdrawRangeAtMinInventory.MaxInjectWithdrawRate; double nextPeriodMinInventoryFromThisPeriodMinInventory = currentPeriodMinInventory * (1 - inventoryPercentLoss) + currentPeriodInjectWithdrawRangeAtMinInventory.MinInjectWithdrawRate; if (nextPeriodMinInventoryFromThisPeriodMinInventory <= nextPeriodInventorySpaceUpperBound && nextPeriodInventorySpaceLowerBound <= nextPeriodMaxInventoryFromThisPeriodMinInventory) { // No need to solve root as next period inventory space can be reached from the current period min inventory return(currentPeriodMinInventory); } // Search for inventory bracket double bracketLowerInventory = _injectWithdrawRanges[0].Inventory; double bracketLowerInventoryAfterInject = nextPeriodMaxInventoryFromThisPeriodMinInventory; for (int i = 1; i < _injectWithdrawRanges.Length; i++) { InjectWithdrawRangeByInventory bracketUpperDecisionRange = _injectWithdrawRanges[i]; double bracketUpperInventory = bracketUpperDecisionRange.Inventory; double bracketUpperInventoryAfterInject = bracketUpperInventory * (1 - inventoryPercentLoss) + bracketUpperDecisionRange.InjectWithdrawRange.MaxInjectWithdrawRate; if (bracketLowerInventoryAfterInject <= nextPeriodInventorySpaceLowerBound && nextPeriodInventorySpaceLowerBound <= bracketUpperInventoryAfterInject) { double inventorySpaceLower = StorageHelper.InterpolateLinearAndSolve(bracketLowerInventory, bracketLowerInventoryAfterInject, bracketUpperInventory, bracketUpperInventoryAfterInject, nextPeriodInventorySpaceLowerBound); return(inventorySpaceLower); } bracketLowerInventoryAfterInject = bracketUpperInventoryAfterInject; bracketLowerInventory = bracketUpperInventory; } throw new ApplicationException("Storage inventory constraints cannot be satisfied."); }
public void Deconstruct(out double inventory, out InjectWithdrawRange injectWithdrawRange) { inventory = Inventory; injectWithdrawRange = InjectWithdrawRange; }
public InjectWithdrawRangeByInventory(double inventory, [NotNull] InjectWithdrawRange injectWithdrawRange) { Inventory = inventory; InjectWithdrawRange = injectWithdrawRange ?? throw new ArgumentNullException(nameof(injectWithdrawRange)); }
public static double[] CalculateBangBangDecisionSet(InjectWithdrawRange injectWithdrawRange, double currentInventory, double inventoryLoss, double nextStepMinInventory, double nextStepMaxInventory, double numericalTolerance, int numExtraDecisions = 0) // TODO remove default value of zero { if (nextStepMinInventory > nextStepMaxInventory) { throw new ArgumentException($"Parameter {nameof(nextStepMinInventory)} value cannot be higher than parameter {nameof(nextStepMaxInventory)} value."); } if (numExtraDecisions < 0) { throw new ArgumentException($"Parameter {nameof(numExtraDecisions)} must be non-negative.", nameof(numExtraDecisions)); } double inventoryAfterLoss = currentInventory - inventoryLoss; double inventoryAfterMaxWithdrawal = injectWithdrawRange.MinInjectWithdrawRate + inventoryAfterLoss; double yieldedWithdrawalRate; if (inventoryAfterMaxWithdrawal > nextStepMaxInventory) // Max withdrawal still above next step max inventory { if (inventoryAfterMaxWithdrawal - nextStepMaxInventory < numericalTolerance) { // Next period inventory is breached, but only by a small amount, probably due to root finding in PolynomialInjectWithdrawConstraint during inventory space reduction yieldedWithdrawalRate = nextStepMaxInventory - inventoryAfterLoss; // TODO unit test code reaching here } else { throw new ArgumentException("Inventory constraints cannot be fulfilled. This could potentially be fixed by increasing the numerical tolerance."); } } else if (inventoryAfterMaxWithdrawal > nextStepMinInventory) { yieldedWithdrawalRate = injectWithdrawRange.MinInjectWithdrawRate; // Unconstrained withdrawal } else { yieldedWithdrawalRate = nextStepMinInventory - inventoryAfterLoss; //constrained withdrawal (could be made positive to injection) } double inventoryAfterMaxInjection = injectWithdrawRange.MaxInjectWithdrawRate + inventoryAfterLoss; double yieldedInjectionRate; if (inventoryAfterMaxInjection < nextStepMinInventory) // Max injection still below next step min inventory constraint { if (nextStepMinInventory - inventoryAfterMaxInjection < numericalTolerance) { // Next period inventory is breached, but only by a small amount, probably due to root finding in PolynomialInjectWithdrawConstraint during inventory space reduction yieldedInjectionRate = nextStepMinInventory - inventoryAfterLoss; // TODO unit test code reaching here } else { throw new ArgumentException("Inventory constraints cannot be fulfilled. This could potentially be fixed by increasing the numerical tolerance."); } } else if (inventoryAfterMaxInjection < nextStepMaxInventory) { yieldedInjectionRate = injectWithdrawRange.MaxInjectWithdrawRate; // Unconstrained injection } else { yieldedInjectionRate = nextStepMaxInventory - inventoryAfterLoss; // Constrained injection (could be made negative to withdrawal) } double[] decisionSet; if (yieldedWithdrawalRate >= 0.0 || yieldedInjectionRate <= 0.0) // No zero decision { if (numExtraDecisions > 0) { decisionSet = new double[numExtraDecisions + 2]; decisionSet[0] = yieldedWithdrawalRate; decisionSet[decisionSet.Length - 1] = yieldedInjectionRate; PopulateExtraDecisions(yieldedWithdrawalRate, yieldedInjectionRate, numExtraDecisions, new Span <double>(decisionSet, 1, numExtraDecisions)); } else { decisionSet = new double[] { yieldedWithdrawalRate, yieldedInjectionRate } }; } else { if (numExtraDecisions > 0) { decisionSet = new double[numExtraDecisions * 2 + 3]; decisionSet[0] = yieldedWithdrawalRate; decisionSet[decisionSet.Length - 1] = yieldedInjectionRate; PopulateExtraDecisions(yieldedWithdrawalRate, 0, numExtraDecisions, new Span <double>(decisionSet, 1, numExtraDecisions)); PopulateExtraDecisions(0, yieldedInjectionRate, numExtraDecisions, new Span <double>(decisionSet, numExtraDecisions + 2, numExtraDecisions)); } else { decisionSet = new double[] { yieldedWithdrawalRate, 0.0, yieldedInjectionRate } }; } return(decisionSet); // TODO case of yieldedWithdrawalRate equals to yieldedInjectionRate? }
public ConstantInjectWithdrawConstraint([NotNull] InjectWithdrawRange injectWithdrawRange) { _injectWithdrawRange = injectWithdrawRange ?? throw new ArgumentNullException(nameof(injectWithdrawRange)); }
public ConstantInjectWithdrawConstraint(double minInjectWithdrawRate, double maxInjectWithdrawRate) { _injectWithdrawRange = new InjectWithdrawRange(minInjectWithdrawRate, maxInjectWithdrawRate); }