Rate planner (deprecated use Piecewise functions instead)

1. What is a rate planner

RatePlanner is a class representing a piecewise linear function with steps. Below is an example of a function that can be represented by a rate planner:

Example of a function that can be represented by a rate planner

Typically, this class represents the change of some quantity over time. Thus, in the API of this class, x-axis value is called time and y-axis value is called quantity. See for instance, quantityAtTime method. However this class can represent arguments and values of any nature.

API of RatePlanner allows developers to create piecewise linear functions and answer different questions about them.

2. Creating a rate planner

An instance of RatePlanner representing the function in the figure above can be created with this code:

RatePlanner rp= new RatePlanner();
rp.addRate(0, 1, 1);	// Increase the rate between time 0 and 1 by 1
rp.addQuantityChange(1, 2);	// Add a quantity step change of +2 at time 1
rp.addRate(2, 3, -2);	// etc...
rp.addQuantityChange(4, 1);
rp.addRate(4, 5, 2);
rp.addRate(7, 9, -1);
rp.addQuantityChange(8, -2);

On the listing above, addRate method increases or decreases the rate at which the quantity is changing in the specified period. addQuantityChange method specified the step change of quantity at some point in time.

These methods return the reference to their instance of RatePlanner class, so they can be chained in a spirit of fluent API. The following code is an equivalent of the code above:

var rp= new RatePlanner()
	.addRate(0, 1, 1)
	.addQuantityChange(1, 2)
	.addRate(2, 3, -2)
	.addQuantityChange(4, 1)
	.addRate(4, 5, 2)
	.addRate(7, 9, -1)
	.addQuantityChange(8, -2);

3. Using a rate planner

Let us assume that we are modeling inventory dynamics of some product at a store. Suppose our current stock on hand is 7 units. It maps to a step change of 7 at time now (zero relative time). Suppose we have weekly sales forecast of 1 unit per day for the next week and 1.5 units per day for the week after. Also, we expect a delivery of 4 units in 4 days from now. A RatePlanner then will model a function of expected stock on hand over time:

// There is no upper bound to stock, but stock on hand cannot be negative, so lower bound is zero
RatePlanner rp= new RatePlanner(Double.POSITIVE_INFINITY, 0);
// Initial stock on hand is 7 units
rp.addQuantityChange(0, 7);
// For the first week (days 0 to 7 from now), the sales forecast is 1 unit per day,
// so our stock will be decreasing at this rate
rp.addRate(0, 7, -1);
// In the week after, we will be selling 1.5 units per day
rp.addRate(7, 14, -1.5);
// We expect a delivery of 4 units in 4 days from now
rp.addQuantityChange(4, 4);

The code above creates a RatePlanner with the following function:

Example of a function that can be represented by a rate planner

Now that we have created a rate planner, we can use its API to answer questions about our projected inventory level. For example, we can use quantityAtTime method to get the projected inventory in specific time from now:

System.out.println("Projected inventory in 8 days from now is " + rp.quantityAtTime(8));

We can also ask when will the first deficit occur by calling firstDeficitTime method:

System.out.println("The first deficit will occur at time " + rp.firstDeficitTime(0));

The quantity that we need to supply to avoid deficit during the specified period is calculated by requiredQuantity method. This method will consider our current stock on hand and the expected delivery:

System.out.println("The deficit quantity till day 14 is " + rp.deficitQuantity(0, 14));

We also might be interested to know what the maximum inventory level for some specific period will be. We can find this out by calling maxQuantity method:

System.out.println("The maximum inventory level for period [2, 8] is " + rp.maxQuantity(2, 8));

Having asked the four questions above we get the following output to the console:

Projected inventory in 8 days from now is 2.5
The first deficit will occur at time 9.666666666666666
The required quantity till day 14 is 6.500000000000001
The maximum inventory level for period [2, 8] is 7.0

Note how these results correspond to the inventory plot above.

4. Upper and lower bounds

4.1. What are bounds

The growth and decrease of the function can be limited by upper and lower bounds, respectively. These bounds affect only the limits to which the quantity can change because of a rate, specifically:

  • upper bound determines the maximum value to which quantity can grow at a positive rate,

  • lower bound determines the minimum value to which quantity can decrease at a negative rate.

Obviously, the upper bound of a rate planner must be greater than its lower bound. The bounds do not impact the quantity step changes.

The bounds can be defined in constructors of the RatePlanner class and cannot be changed after construction. Note that the first argument of a constructor defines the upper bound, and the second one defines the lower bound:

// This will create an unbounded rate planner
var unboundedRP = new RatePlanner();

// This will create a rate planner with the upper bound of 3
var rpWithUpperBound = new RatePlanner(3, Double.NEGATIVE_INFINITY);

// This will create a rate planner with the lower bound of 0
var rpWithLowerBound = new RatePlanner(Double.POSITIVE_INFINITY, 0);

// This will create a with both upper and lower bounds
var rpWithBothBounds = new RatePlanner(3, 0); 

4.2. Inventory plot example with lower bound

In the previous section we already used the lower bound to specify that there cannot be negative stock on hand. Let us take a simpler example to illustrate the details of how the bounds work. Consider we have the function representing some inventory plot:

Example of a simple rate planner with lower bound

This function can be created with the following code:

RatePlanner rp= new RatePlanner(Double.POSITIVE_INFINITY, 0)
	.setRate(0, -1)
	.addQuantityChange(0, 3)
	.addQuantityChange(4, 2);

Note how the quantity does not go below zero in the intervals of [3, 4] and [6, +∞). These intervals are highlighted with red on the plot above. Indeed, the during in both these periods the quantity is zero:

// Both these results will be zeros
System.out.println(rp.quantityAtTime(3.5));
System.out.println(rp.quantityAtTime(6.5));

4.3. Deficit

In the example above, the decrease of quantity was limited by the lower bound of zero during the periods of [3, 4] and [6, +∞). Such periods are called deficit periods.

More precisely, the there is a deficit at time t if both of these conditions hold:

  • quantity at time t is less than or equal to the lower bound of a rate planner, and

  • rate at time t is less than 0.

List of all deficit periods can be calculated using the deficitPeriods method:

// This will print [3.0 4.0, 6.0 Infinity]
System.out.println(rp.deficitPeriods());

In logic of simulation models it is quite convenient to use firstDeficitTime method to figure out when the next deficit is going to occur:

// This will print 6.0
System.out.println(rp.firstDeficitTime(4));

Note that there is no deficit at time 4, because our positive quantity change (corresponding to an expected delivery) occurs exactly at this time. Thus, the next deficit after time 4 will occur at time 6.

4.4. Bounds and step changes

The bounds do not impact the quantity step changes. This means that quantity step changes can take the quantity above the upper bound or below the lower bound. Let us look at the following example:

Example of a simple rate planner with lower bound

Similarly to the previous example, this rate planner has a constant rate of -1 starting from time zero. However, in this case there are two negative quantity changes that take the quantity below the lower bound of zero.

Let us create a rate planner for a plot from the figure above:

RatePlanner rp= new RatePlanner(Double.POSITIVE_INFINITY, 0)
	.setRate(0, -1)
	.addQuantityChange(0, -2)
	.addQuantityChange(1, 4)
	.addQuantityChange(2, 3)
	.addQuantityChange(3, -4)
	.addQuantityChange(4, 3);

Indeed, the quantities at times 0, 3, and 3.5 will be negative:

// These will print -2, -1, and -1, respectively
System.out.println(rp.quantityAtTime(0));
System.out.println(rp.quantityAtTime(3));
System.out.println(rp.quantityAtTime(3.5));

Note that [0, 1] and [3, 4] are also deficit periods, together of course with the period of [6, +∞).

4.5. Upper bound

Similarly to lower bound, a rate planner can have an upper bound which determines the maximum value to which quantity can grow at a positive rate. A good example of using the upper bound is modeling some storage with limited capacity. Although the storage cannot physically contain more contents than its capacity, we might be interested in periods when the we are likely to run out of the storage capacity. The following plot illustrates a rate planner with an upper bound of 4:

Example of a simple rate planner with upper bound

A rate planner for the plot above can be created by the following code:

// Upper bound is 4, lower bound is -Inf
RatePlanner rp= new RatePlanner(4, Double.NEGATIVE_INFINITY);
// A constant rate of +2 starting at time 0
rp.setRate(0, 2);
// A negative step change of -4 at time 3
rp.addQuantityChange(3, -4);

Note how the quantity is limited to 4 during the periods of [2, 3] and [5, +∞):

// Both these lines will print 4.0
System.out.println(rp.quantityAtTime(2.5));
System.out.println(rp.quantityAtTime(6));