Functional API#

Lactuca provides a functional API that mirrors the object-oriented interface. Every actuarial calculation method available on LifeTable, DisabilityTable, and ExitTable has an equivalent module-level function. All functional symbols are re-exported from the top-level lactuca package — from lactuca import ax is the canonical form; from lactuca.functional import ax is equally valid. Both styles delegate to the same underlying implementation.

OOP style vs functional style#

from lactuca import LifeTable, ax, config

lt = LifeTable("PASEM2020_Rel_1o", "m", interest_rate=0.03)
config.decimals.annuities = 4

# OOP style
value = lt.ax(65, n=10)
print(value)   # 8.0767

# Functional style — identical result
value = ax(lt, 65, n=10)
print(value)   # 8.0767

The functional form takes the table as its first positional argument. All remaining parameters are identical to the corresponding method signature.

Common parameters#

These parameters apply to both styles (lt.ax(...) and ax(lt, ...)).

Default interest rate (interest_rate=)#

Setting interest_rate= at construction establishes a default for every calculation on that instance. The per-call ir= argument overrides it when needed:

from lactuca import LifeTable, äx, Ax, config

lt = LifeTable("PASEM2020_Rel_1o", "m", interest_rate=0.03)
config.decimals.annuities  = 4
config.decimals.insurances = 4

print(äx(lt, 65))              # 16.0899  (uses ir=0.03 from the table)
print(Ax(lt, 65, n=20))        # 0.2631   (same)
print(äx(lt, 65, ir=0.04))     # 14.5792  (explicit ir= overrides for this call only)

Benefit growth (gr=)#

Annuity and insurance functions accept gr= (a lactuca.GrowthRate instance) to price benefits that increase at a constant rate. See Growth Rates for construction details and conventions.

Irregular cash flows#

For non-standard payment timing or variable benefit amounts, pass cashflow_times= and cashflow_amounts= to the relevant function. See Irregular Cashflows for a complete guide.

Controlling output precision#

Output precision is governed globally by config.decimals. Setting a field once affects all subsequent calls to that function family:

from lactuca import config

config.decimals.annuities  = 4   # ax, äx, axy, ajoint, äjoint, …
config.decimals.insurances = 4   # Ax, Axy, Afirst, nEx, nExy, nEjoint, …
config.decimals.ex         = 4   # ex, ex_continuous
config.decimals.Lx         = 4   # Lx, Lx_continuous
config.decimals.Tx         = 2   # Tx, Tx_continuous
config.decimals.Dx         = 4   # Dx  (each commutation function has its own field:
config.decimals.Nx         = 4   #      also Nx, Sx, Cx, Mx, Rx)

See Decimal Precision and Rounding for the full list of precision fields and their defaults.


Probability functions#

Common to all table types (lx, px, tpx, tqx, dx)#

These functions accept any table type — LifeTable, DisabilityTable, or ExitTable:

from lactuca import LifeTable, lx, px, tpx, tqx, dx, config

lt = LifeTable("PASEM2020_Rel_1o", "m", interest_rate=0.03)
config.decimals.lx  = 2
config.decimals.dx  = 2
config.decimals.px  = 6
config.decimals.tpx = 6
config.decimals.tqx = 6

print(lx(lt, 50))              # 979332.85
print(px(lt, 65))              # 0.992007
print(tpx(lt, 50, t=10))       # 0.965831
print(tqx(lt, 50, t=10))       # 0.034169
print(dx(lt, 50))              # 2158.74

With a DisabilityTable (lx models the active healthy lives):

from lactuca import DisabilityTable, lx, tpx, config

dt = DisabilityTable("PEAI2007_IAP_Ind", "m")
config.decimals.lx  = 2
config.decimals.tpx = 6

print(lx(dt, 40))              # 994559.89
print(tpx(dt, 40, t=5))        # 0.995728

qx — annual mortality (LifeTable only)#

qx returns the annual probability of death. Calling it on a DisabilityTable or ExitTable raises NotImplementedError:

from lactuca import LifeTable, qx, config

lt = LifeTable("PASEM2020_Rel_1o", "m", interest_rate=0.03)
config.decimals.qx = 6

print(qx(lt, 65))    # 0.007993
# qx(dt, 40)        # ← NotImplementedError: use ix() instead

ix — disability incidence probability (DisabilityTable only)#

from lactuca import DisabilityTable, ix, config

dt = DisabilityTable("PEAI2007_IAP_Ind", "m")
config.decimals.ix = 6

print(ix(dt, 40))    # 0.000682

ox — exit / turnover probability (ExitTable only)#

from lactuca import ExitTable, ox, config

et = ExitTable("DummyEXIT", "m")
config.decimals.ox = 6

print(ox(et, 40))    # 0.004000

Life expectancy (LifeTable only)#

ex computes the complete expectation of life \(\mathring{e}_x\) at integer ages (discrete UDD approximation). ex_continuous uses trapezoidal numerical integration and requires a fractional (non-integer) starting age — see Numerical Precision for details:

from lactuca import LifeTable, ex, ex_continuous, config

lt = LifeTable("PASEM2020_Rel_1o", "m", interest_rate=0.03)
config.decimals.ex = 4

print(ex(lt, 65))                        # 22.1308
print(ex_continuous(lt, 65.5))           # 21.7186
print(ex_continuous(lt, 65.5, m=52))     # 21.7186  (finer grid, same result)

Person-years functions (LifeTable only)#

Lx and Tx require integer ages. Lx_continuous and Tx_continuous require fractional ages and use numerical integration (integer ages raise ValueError):

from lactuca import LifeTable, Lx, Tx, Lx_continuous, Tx_continuous, config

lt = LifeTable("PASEM2020_Rel_1o", "m", interest_rate=0.03)
config.decimals.Lx = 4
config.decimals.Tx = 2

print(Lx(lt, 65))                        # 912531.7226
print(Tx(lt, 65))                        # 20276056.41
print(Lx_continuous(lt, 50.5))           # 977145.7488
print(Tx_continuous(lt, 50.5))           # 34095551.11

Life annuities (LifeTable only)#

from lactuca import LifeTable, ax, äx, config

lt = LifeTable("PASEM2020_Rel_1o", "m", interest_rate=0.03)
config.decimals.annuities = 4

print(ax(lt, 65))                # 15.0899  (whole-life, postpayable)
print(äx(lt, 65, n=20, m=12))   # 13.2805  (temporary, prepayable, monthly)
print(äx(lt, 65, d=5, n=15))    # 8.9446   (deferred 5y, 15y term)

Life insurances (LifeTable only)#

from lactuca import LifeTable, Ax, nEx, config

lt = LifeTable("PASEM2020_Rel_1o", "m", interest_rate=0.03)
config.decimals.insurances = 4

print(Ax(lt, 65))           # 0.5393  (whole-life)
print(Ax(lt, 65, n=20))     # 0.2631  (term, 20 years)
print(nEx(lt, 65, n=10))    # 0.6583  (pure endowment, 10 years)

Commutation functions (LifeTable only)#

from lactuca import LifeTable, Dx, Nx, Cx, Mx, config

lt = LifeTable("PASEM2020_Rel_1o", "m", interest_rate=0.03)
config.decimals.Dx = 4
config.decimals.Nx = 4
config.decimals.Cx = 6
config.decimals.Mx = 4

print(Dx(lt, 65))     # 134142.8681
print(Nx(lt, 65))     # 2158347.9610
print(Cx(lt, 65))     # 1056.526809
print(Mx(lt, 65))     # 72339.6391

Multi-life functions (LifeTable only)#

Note

All multi-life functions follow the same ordering convention: the principal life occupies index 0 in both the tables list and the ages list; remaining lives follow in the same order.

Two-life joint-life annuities (axy, äxy)#

Paid while both lives are simultaneously alive (payments stop at the first death):

from lactuca import LifeTable, axy, äxy, config

ltx, lty = LifeTable("PASEM2020_Rel_1o", ["m", "f"], interest_rate=0.03)
config.decimals.annuities = 4

print(axy([ltx, lty], ages=[65, 62]))    # 13.7589
print(äxy([ltx, lty], ages=[65, 62]))    # 14.7589

Generic n-life joint-life annuities (ajoint, äjoint)#

ajoint and äjoint generalise to any number of lives. When called with two tables they produce the same result as axy / äxy:

from lactuca import LifeTable, ajoint, äjoint, config

ltx, lty = LifeTable("PASEM2020_Rel_1o", ["m", "f"], interest_rate=0.03)
config.decimals.annuities = 4

print(ajoint([ltx, lty], ages=[65, 62], n=20))   # 12.3405
print(äjoint([ltx, lty], ages=[65, 62], n=20))   # 13.0518

Three-life joint-life annuities (axyz, äxyz)#

Payments continue while all three lives are simultaneously alive:

from lactuca import LifeTable, axyz, äxyz, config

ltx, lty = LifeTable("PASEM2020_Rel_1o", ["m", "f"], interest_rate=0.03)
ltz      = LifeTable("PASEM2020_Rel_1o", "f", interest_rate=0.03)
config.decimals.annuities = 4

print(axyz([ltx, lty, ltz], ages=[65, 62, 60]))   # 12.9700
print(äxyz([ltx, lty, ltz], ages=[65, 62, 60]))   # 13.9700

Two-life first-death insurance (Axy)#

Axy pays on the first death among the two lives:

from lactuca import LifeTable, Axy, config

ltx, lty = LifeTable("PASEM2020_Rel_1o", ["m", "f"], interest_rate=0.03)
config.decimals.insurances = 4

print(Axy([ltx, lty], ages=[65, 62]))   # 0.5786

Three-life first-death insurance (Axyz)#

from lactuca import LifeTable, Axyz, config

ltx, lty = LifeTable("PASEM2020_Rel_1o", ["m", "f"], interest_rate=0.03)
ltz      = LifeTable("PASEM2020_Rel_1o", "f", interest_rate=0.03)
config.decimals.insurances = 4

print(Axyz([ltx, lty, ltz], ages=[65, 62, 60]))   # 0.6019

Generic n-life first-death insurance (Afirst)#

from lactuca import LifeTable, Afirst, config

ltx, lty = LifeTable("PASEM2020_Rel_1o", ["m", "f"], interest_rate=0.03)
config.decimals.insurances = 4

print(Afirst([ltx, lty], ages=[65, 62]))   # 0.5786

Two-life joint pure endowment (nExy)#

nExy pays 1 if both lives survive \(n\) years:

from lactuca import LifeTable, nExy, config

ltx, lty = LifeTable("PASEM2020_Rel_1o", ["m", "f"], interest_rate=0.03)
config.decimals.insurances = 4

print(nExy([ltx, lty], ages=[65, 62], n=10))   # 0.6308

Three-life joint pure endowment (nExyz)#

from lactuca import LifeTable, nExyz, config

ltx, lty = LifeTable("PASEM2020_Rel_1o", ["m", "f"], interest_rate=0.03)
ltz      = LifeTable("PASEM2020_Rel_1o", "f", interest_rate=0.03)
config.decimals.insurances = 4

print(nExyz([ltx, lty, ltz], ages=[65, 62, 60], n=10))   # 0.6088

Generic n-life joint pure endowment (nEjoint)#

from lactuca import LifeTable, nEjoint, config

ltx, lty = LifeTable("PASEM2020_Rel_1o", ["m", "f"], interest_rate=0.03)
config.decimals.insurances = 4

print(nEjoint([ltx, lty], ages=[65, 62], n=10))   # 0.6308

Inspecting payment flows (return_flows)#

All annuity and insurance functions accept return_flows=True to return a per-period breakdown instead of a scalar present value. See Inspecting Cash Flows for the full key reference and worked examples.

Full function list#

Function

Equivalent method

Applicable to

lx

table.lx(x)

All

dx

table.dx(x)

All

px

table.px(x, m)

All

tpx

table.tpx(x, t=)

All

tqx

table.tqx(x, t=)

All

qx

table.qx(x, m)

LifeTable only

ix

table.ix(x, m)

DisabilityTable only

ox

table.ox(x, m)

ExitTable only

ex

table.ex(x)

LifeTable

ex_continuous

table.ex_continuous(x, m=)

LifeTable

Lx, Tx

table.Lx(x), table.Tx(x)

LifeTable

Lx_continuous, Tx_continuous

table.Lx_continuous(x, m=), table.Tx_continuous(x, m=)

LifeTable

Dx, Nx, Sx

table.Dx(x, ir=), …

LifeTable

Cx, Mx, Rx

table.Cx(x, ir=), …

LifeTable

ax

table.ax(x, …)

LifeTable

äx

table.äx(x, …)

LifeTable

Ax

table.Ax(x, …)

LifeTable

nEx

table.nEx(x, …)

LifeTable

axy, äxy

table.axy(…), table.äxy(…)

LifeTable

axyz, äxyz

table.axyz(…), table.äxyz(…)

LifeTable

ajoint, äjoint

table.ajoint(…), table.äjoint(…)

LifeTable

Axy

table.Axy(…)

LifeTable

Axyz

table.Axyz(…)

LifeTable

Afirst

table.Afirst(…)

LifeTable

nExy

table.nExy(…)

LifeTable

nExyz

table.nExyz(…)

LifeTable

nEjoint

table.nEjoint(…)

LifeTable

See also#