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.