Array variables#

Array variables can be defined by decorating a function with the @variable decorator, with the array parameter set to True.

For example:

@variable(array=True)
def pv_net_cf():
    return pv_premiums() - pv_claims() - pv_expenses()

Array variables significantly optimize calculation runtimes by leveraging the NumPy package and its broadcasting mechanism. Instead of performing calculations for each period separately, array variables compute results in a single operation for the entire variable.

When to use

Array variables offer improved runtime performance compared to regular variables but come with increased complexity in their construction. The decision of whether to use array variables ultimately rests with the actuarial modeller. If your model has a limited number of model points, and runtime is satisfactory, it may be best to stick with regular variables for readability.

On the other hand, if runtime is critical, array variables can be beneficial. It’s advisable to start arrayizing variables with simple logic, such as those that involve only addition or multiplication of other variables or scalars.


Construction#

Consider this array variable example:

from settings import settings

@variable(array=True)
def array_var():
    return [x for x in range(settings["T_MAX_CALCULATION"]+1)]

Breaking down each line:

@variable(array=True)

Array variables use the @variable decorator, but they require setting the array parameter to True.

def array_var():

The function does not take the t parameter; it remains empty.

return [x for x in range(settings["T_MAX_CALCULATION"]+1)]

The array variable should return a numeric iterable of a size equal to the T_MAX_CALCULATION+1 setting (by default 721). This iterable will be internally converted to a NumPy array of type float64.


Defining array variable using results of regular variables

You can create array variables using results of regular variables:

@variable(array=True)
def pv_net_cf():
    return pv_premiums() - pv_claims() - pv_expenses()

For example, pv_premiums is a regular t-dependent variable.

@variable()
def pv_premiums(t):
    if t == settings["T_MAX_CALCULATION"]:
        return premiums(t) * discount(t)
    return premiums(t) * discount(t) + pv_premiums(t+1)

Calling pv_premiums with a specific t value returns the result for that period:

print(pv_premiums(t=10))
# 126.12

But calling pv_premiums without any argument will return the NumPy array of results:

print(pv_premium())
# np.array([145.45, 142.37, ..., 9.35])

The results are based on NumPy arrays, so they utilize the broadcasting mechanism. That’s why they can be used in the creation of pv_net_cf with simple subtraction and addition operations.


Cycle limitations#

Variables cannot be arrayized if they are part of a cycle. A cycle refers to a group of variables that depend on each other cyclically. For example:

@variable()
def a(t):
    return 2 * b(t)


@variable()
def b(t):
    if t == 0:
        return 0
    return c(t-1)


@variable()
def c(t):
    return a(t) + 2

In this case, variable a relies on b, b relies on c, and c in turn relies on a.

For example, trying to set up the variable a as an array variable like this won’t work:

@variable(array=True)
def a():
    return 2 * b()

This causes an error because cyclical variables are calculated simultaneously for each t separately. The full results of b are not known before calculating a.

You can identify variables that are part of a cycle by inspecting the diagnostic file.