Turning a ranvar into a grid

Distributions in Envision are practical because they are supported by an entire algebra of ranvars and zedfuncs. However, in order to generate inventory decisions, it is usually required to transform those distributions back into a grid, that is, a table that enumerates the values of the distribution, typically in the form of probabilities. By using such a grid, it becomes possible to create the final table that contains the suggested purchase order quantities or the suggested inventory movements. Here, we detail how the extend.ranvar() function can be used for this purpose.

Table of contents

Grids vs Ranvars

A grid is a plain table that happens to contain a list of histogram buckets: one histogram per original ranvar, and one line per bucket within the histogram. The generic representation of a grid is a table with three fields:

For a given item, then, the grid contains a series of lines that represent the entire histogram of the ranvar.

Grids are not as flexible as ranvars. In order to achieve the required level of performance, it is frequently not possible to maintain buckets having a width of 1. For this reason, larger buckets are used instead in order to keep the memory requirements of the grid manageable within Lokad. Non-unit buckets complicate calculations that are carried out on grids. Also, by design, grids have compact support (in the mathematical sense): their non-zero values are only defined for a finite number of points.

On the other hand, grids are plain tables and can be processed into other tables through the usual Envision operators. As a rule of thumb, Envision adopts an approach where all the economic and probabilistic modeling is carried out on ranvars, keeping the transformation into grids as one of the final steps that take place just before generating the final suggested supply chain decisions.

Syntax of extend.ranvar()

Envision ranvars offer a powerful algebra, which can be used to avoid convoluted calculations of probabilities over lists. However, there are situations where having a raw list of probabilities is just fine, and even desirable. The distribution extension extend.ranvar() turns a vector of ranvars into a table, as illustrated by the following syntax:

table G = extend.ranvars(T.R)
show table "Distribution details" with
  Id // the primary dimension of 'T'

The argument R is expected to be a ranvar (vector of distributions, containing non-negative values, which sum up to 1), as typically produced by Lokad’s probabilistic forecasting engine. Table G is typed as an extension of the originating table - the implicit Items table in the script above. Table G is populated with three fields:

In addition, the probability associated with each line of the grid can be easily calculated by integrating the original distribution over the segment [G.Min, G.Max], as follows:

G.Probability = int(T.D, G.Min, G.Max)

For relatively compact ranvars, segments have a length of 1, and hence G.Min == G.Max. However, if the ranvars spreads over higher values (i.e. very high values have non negligible probability of occuring), segments of length equal to 1 would end up generating possibly millions of lines, which becomes unmanageable. Hence, when dealing with such large-valued ranvars, Envision auto-aggregates these grids around larger segments. Algorithms are therefore fine-tuned to keep the size of the generated tables manageable.

By design, extend.ranvar() always singles out the zero segment. As a result, the [0;0] segment always gets its own line in the generated table. This behavior is indeed helpful in many business situations, where zero demand represents an edge case - such as infinite stock cover - which requires some dedicated logic.

Finally, three additional overloads for extend.ranvar() are supported in order to gain more control over the specific granularity of the generated table.

Gap option

The first overload is intended to help build a purchase prioritization list while taking into account the current stock levels. The syntax is the following:

table G = extend.ranvar(T.R, T.S)

The first argument R is as defined above. The second argument S is expected to be an integer number. When this second argument is present, the generated table always includes two lines dedicated to the two segments [0;0] and [1;S]. Additional segments are auto-generated starting from S+1 as detailed above. When left unspecified, the default value for this argument is zero.

In practice, argument S is frequently defined as the sum of the available stock on hand plus the stock on order. When reordering, only the demand which exceeds the current stock level should be considered.

Multiplier option

The second overload is intended for situations involving lot multipliers. In these situations, the table should iterate over segments of specific sizes. The relevant syntax is:

table G = extend.ranvar(T.R, T.S, T.M)

The arguments R and S are as defined above. The third argument M is expected to be an integer number. It represents the desired segment length. Thus, the table includes the list of segments [0;0], [1;S], [S+1;S+M] [S+M+1;S+2M] … If M is zero, then the function falls back on auto-sizing the segments.

In practice, forcing segments whose length is equal to 1 would possibly lead to performance issues as the size of the table can be arbitrarily large. Thus, Envision has the possibility to fall back on a multiple of M instead. Using a multiple ensures that the lot multiplier logic will keep working, all while preserving sane limits on the number of lines to be generated.

As a rule of thumb, we suggest not to use this specific overload unless lot multipliers are involved, and when they are involved, it is suggested to keep M at zero for any item that does not have a specific lot multiplier.

Reach option

The third overload is intended for situations involving MOQs. In these situations, the table should iterate long enough to reach certain desired values. The relevant syntax is:

table G = extend.ranvar(T.D, T.S, T.M, T.reach)

The arguments R, S and M are as defined above. The fourth argument reach is expected to be a non-negative integer. It represents the desired max value to be reached by the grid, meaning that there will be a line where G.Max is greater or equal to reach. When left unspecified, the default value for this argument is zero.

In practice, this argument is used to cope with large minimum order quantity (MOQ) constraints that can only be satisfied if the generated ranvars are reaching far enough to reach the MOQ values.

As a rule of thumb, we suggest not to use this overload unless there are specific MOQs to be reached, and when this is the case, it is suggested to keep reach as small as possible. A small reach value does not prevent table G from reaching higher values, it only ensures that larger values are reached.