LifeTable#
LifeTable is the core actuarial table class in Lactuca.
It inherits from DecrementTable and adds the full
set of life annuity (\(\ddot{a}_x\) via äx(),
\(a_x\) via ax(), continuous \(\bar{a}_x\) via
Calculation Modes), life insurance (\(A_x\)),
pure endowment (\({}_{n}E_x\)), commutation functions (\(D_x\), \(N_x\), \(M_x\), …),
complete life expectancy (\(\mathring{e}_x\) via ex), and curtate \(e_x\) via ex_curtate.
A life table is identified by a repository table name (bundled with Lactuca or
installed as a .ltk file), a sex code
('m', 'f', or 'u'), and optionally a birth-year cohort for
generational tables that include mortality improvement factors.
See also
Getting Started — Quick-start guide with common patterns.
Life Annuities — Full annuity calculation reference.
Life Insurances — Insurance and endowment reference.
Commutation Functions — Commutation functions reference.
Calculation Modes — Discrete vs. continuous modes.
Joint-Life Calculations — Multi-life annuities, first-death
insurances (Axy, Axyz, Afirst), and derivable last-survivor formulas.
lx Interpolation — Fractional-age survival assumptions (UDD vs. CFM).
Deferred Life Contingencies — Deferred annuities and the d= parameter.
Mortality Improvement (MI) — Generational tables and improvement factors.
Batch Calculations — Batch mode: array ages, per-policy params, portfolio BEL.
- class lactuca.LifeTable(table_name: str | list | tuple, sex: Literal['m', 'f', 'u'] | Sequence[Literal['m', 'f', 'u']] | None = None, *, cohort: int | Sequence | None = None, interest_rate: object = None, unisex_blend: object = None, duration: int | str | Sequence | None = None, cartesian: bool = False, return_dict: bool = False, pending: bool = False)#
Bases:
DecrementTableConcrete implementation of DecrementTable for standard mortality life tables.
LifeTable represents actuarial mortality tables with qx (mortality probability), providing comprehensive life insurance and annuity calculations. It implements all standard commutation functions (Dx, Nx, Sx, Cx, Mx, Rx, Lx, Tx, ex) and continuous variants for fractional ages, along with life annuities, life insurances, and pure endowments.
LifeTable supports multiple calculation modes (discrete_precision, discrete_simplified, continuous_precision, continuous_simplified), interest rate integration (constant and variable curves), and actuarial conventions for mortality placement and force of mortality.
Construction accepts scalar arguments for a single instance, or sequences for vectorial creation (families, scenarios, pricing grids). See
__new__()for full dispatch details.- Parameters:
table_name (str, list, or tuple) – Name of one or more life table files (without
.ltkextension). A list or tuple enables multi-table vectorial creation.sex (str or sequence of str) – Single sex (
'm','f','u') or sequence for vectorial creation.cohort (int, sequence of int, or None, optional) – Birth year(s) for generational tables. If
None, loads a period table. Broadcast and zip-alignment rules match__new__().interest_rate (float, InterestRate, list, tuple, or None, optional) – Default interest rate for annuity/insurance calculations. A scalar (
float,InterestRate, orNone) is broadcast to every instance; alistortuplemust have exactly one element per instance in vectorial mode.intvalues are not accepted — usefloat(e.g.0.03) or anInterestRateobject. IfNone, rates must be passed explicitly in calculation methods.unisex_blend (float, list of float, or None, optional) – Male weight for unisex blending in \([0, 1]\) (
0.0= all female,1.0= all male). A list is accepted in zip mode (cartesian=False) to assign a distinct blend to each instance; scalarsex='u'broadcasts to the list length. See__new__().duration (int,
'ult', sequence, or None, optional) – Duration slice for select-ultimate tables. Pass an integer>= start_durationfor a select column,'ult'for the ultimate column, orNonefor non-select tables.cartesian (bool, optional) – If
True, create the full Cartesian product of alltable_name,sex,cohort, anddurationvalues. All table names must share the same generational structure and the same select structure. Withcartesian=True, aunisex_blendsequence is allowed only when everysexvalue is'u'(fifth cartesian axis). A scalarunisex_blendis replicated uniformly to all combinations wheresex='u'. Default isFalse(zip/broadcast alignment).return_dict (bool, optional) – If
True, return adictmappingTableKeyto eachLifeTableinstance instead of a plaintuple. Default isFalse.
- Returns:
Single
LifeTablewhensexis a scalar string,table_nameis a bare string (not alist/tuple),cohortanddurationare scalar orNone, no sequence dimensions are provided, andreturn_dict=False. Atuplewhen any parameter is a sequence orcartesian=Trueandreturn_dict=False(including length-1 tuples). Adictkeyed byTableKeywhenreturn_dict=True.- Return type:
Notes
LifeTable uses
qx(mortality probability) internally.All probability calculations (
px,tpx,tqx) inherit fromDecrementTable.Commutation functions are strictly defined for integer ages (annual periods).
Continuous variants (
*_continuous) support fractional ages via numerical integration.Interest rate can be constant (
float) or variable (InterestRate).Joint-life calculations assume independence between lives.
ix()andox()are intentionally blocked; useqx()and the life-contingency methods.Inherits the modification system via
modify_qx().Zip mode (default):
table_name,sex,cohort, anddurationare aligned element-wise (or broadcast when length is 1).Cartesian mode (
cartesian=True): every combination is created — suited to sensitivity grids and pricing studies, not sparse portfolio processing.A
ResourceWarningis emitted when more than 100 instances are created in a single constructor call.
- Raises:
FileNotFoundError – If a table file does not exist in the actuarial tables directory.
ValueError – If the table is not a life table; if
cohort,sex, ordurationsequences have incompatible lengths; ifunisex_blendis out of \([0, 1]\) or provided whensexis not'u'; ifsex='u'and the table lacks a native unisex column whileunisex_blendisNone; ifcartesian=Trueand table names mix generational and period tables or mix select and non-select tables; ifcartesian=Trueandunisex_blendis a sequence whilesexcontains any value other than'u'; or if aninterest_ratesequence length differs from the number of instances created.TypeError – If
interest_rate(or any element of its sequence) is notfloat,InterestRate, orNone; or ifcohort,duration,sex, orunisex_blendarguments have invalid types.
See also
DecrementTableAbstract base class providing core decrement logic.
TableKeyStructured lookup key for
return_dict=Trueresults.DisabilityTableDisability incidence table implementation using ix rates.
ExitTableExit/withdrawal table implementation using ox rates.
Examples
>>> from lactuca import LifeTable, InterestRate, TableKey >>> # Single period table >>> lt = LifeTable('PASEM2010', 'm') >>> # Generational table with default interest rate >>> lt_gen = LifeTable('PER2020_Ind_1o', 'm', cohort=1969, interest_rate=0.03) >>> # Zip mode: two sexes, different cohorts >>> lt_m, lt_f = LifeTable('PER2020_Ind_1o', ('m', 'f'), cohort=(1960, 1963)) >>> # Cartesian product: 2 sex × 2 cohorts → 4 instances >>> tables = LifeTable( ... 'PER2020_Ind_1o', ('m', 'f'), cohort=[1960, 1970], cartesian=True ... ) >>> len(tables) 4 >>> # Dict return keyed by TableKey >>> d = LifeTable('PER2020_Ind_1o', ('m', 'f'), cohort=1970, return_dict=True) >>> lt_m = d[TableKey('PER2020_Ind_1o', 'm', 1970)] >>> # Commutation and products (interest rate required per call if not set) >>> D65 = lt.Dx(65, ir=InterestRate(0.03)) >>> annuity = lt.ax(65, n=10, m=12, ir=0.03)
- Afirst(ages: object, tables_others: Sequence[LifeTable] | Sequence[Sequence[LifeTable]] | None = None, *, ts: object = 0.0, d: object = 0.0, n: object = None, m: Literal[1, 2, 3, 4, 6, 12, 14, 24, 26, 52, 365] | Sequence[int] | NDArray[int64] = 1, gr: GrowthRate | float | None | Sequence[GrowthRate | float | None] = None, ir: object = None, cashflow_times: Sequence[float] | None = None, cashflow_amounts: Sequence[float] | None = None, return_flows: bool = False, t_output: NDArray[float64] | None = None, benefits: Sequence[float] | NDArray[float64] | None = None, on_error: Literal['raise', 'nan'] = 'raise', record_ids: Sequence[Any] | None = None) float | dict[str, NDArray[float64]] | BatchResult#
Compute the present value of a first-death (first-to-die) insurance for multiple independent lives.
Pays one unit of benefits at the first death among the insured lives. Supports shift (
ts), deferment (d), term (n), payment frequency (m), growth (gr), custom cash flows (cashflow_times,cashflow_amounts), and all four calculation modes. Ifnis None the insurance is whole-life; otherwise it is a term insurance (nmay be fractional). All actuarial conventions follow international standards.- Parameters:
ages (Sequence[float] or list[Sequence[float]]) – Ages of the insured lives (at contract start). In batch mode, pass a list of r N-element arrays:
[x0_arr, x1_arr, ..., xr_arr]where each has shape(N,).tables_others (Sequence[LifeTable] or Sequence[Sequence[LifeTable]] or None, optional) –
Life tables for additional lives (all except the first, which uses
self). Three modes in batch:None: usesselffor all lives in all policies.Sequence[LifeTable]of lengthn_lives - 1: shared across all N policies.Sequence[Sequence[LifeTable]]of length N: per-policy; each inner list must have exactlyn_lives - 1elements. RaisesValueErrorif length != N.
ts (float or NDArray[np.float64], optional) – Shift (displacement) in years from origin. In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.d (float or NDArray[np.float64], optional) – Deferment period in years. In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.n (float or NDArray[np.float64] or None, optional) – Duration of the insurance (years). If None, whole life. In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.m (PaymentFrequencyLiteral, sequence of int, NDArray[np.int64], or array-like, optional) – Number of payments per year (1=annual, 12=monthly, etc.). Integer-valued floats (e.g.
12.0) are coerced toint. In batch mode, may be a scalar (broadcast to all N) or an array/list of length N for per-policy payment frequencies; distinct values are processed in sub-groups by the vectorised batch engine. Pandas and Polars Series are accepted; values are converted to int64.gr (GrowthRate, float, or None, optional) – Growth rate model. A scalar float is auto-wrapped as
GrowthRate(gr)(geometric growth by default). If None, no growth is applied. In batch mode, may be a scalar or a list of N values. Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asGrowthRatewhere applicable). An object-dtype Series ofGrowthRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.ir (float, InterestRate, list[float or InterestRate], NDArray[np.float64], or None, optional) – Interest rate (constant or curve). If None, uses
interest_rate. In batch mode, may be a scalar (broadcast to all N), a list of Nfloatvalues, or a list of NInterestRateobjects (one curve per policy). Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asInterestRatewhere applicable). An object-dtype Series ofInterestRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.cashflow_times (sequence of float, optional) – Times (years since origin) at which payments are made. Requires
calculation_mode='discrete_precision'; raisesValueErrorin other modes. If None, uses standard schedule. Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.cashflow_amounts (sequence of float, optional) – Capital insured for each payment period. If None, assumes unit capital. Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.
return_flows (bool, optional) –
If
True, returns a cashflow decomposition dict instead of the present value. The dict schema differs between single-policy and batch:Single-policy (scalar ages): per-payment engine dict. Same keys as
Ax(); see that method’sreturn_flowsparameter for the full key reference.Batch (array ages): aggregate portfolio dict
{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.total_pv == np.sum(pv_cf)is always satisfied. RaisesValueErrorindiscrete_simplified/continuous_simplifiedmodes. Default isFalse.t_output (NDArray[np.float64] or None, optional) – External bucketing grid for aggregate flow output. When provided, per-policy present-value contributions — already discounted at their exact payment times — are accumulated into the nearest bucket via
np.searchsortedwithout re-discounting. Controls only the shape ofexpected_cfandpv_cf;total_pvis always the exact sum of all per-payment present values and is identical to thet_output=Noneresult. Typical use: regulatory templates that require a fixed set of time buckets (e.g., annual IFRS 17 disclosure, Solvency II SCR projection). Only effective whenreturn_flows=True; raisesValueErrorifreturn_flows=False. Default isNone. Pandas and Polars Series are accepted; converted to NDArray[np.float64] before use.benefits (sequence of float or NDArray[np.float64] or None, optional) – Per-policy benefit amounts, shape
(N,). Scales each policy’s contribution to the aggregate flows (e.g., sum insured, PVDBO weight). Requires batch mode. Any sequence is accepted, including Pandas and Polars Series; values are converted to float64. Compatible withreturn_flows=False(returnsNDArray[np.float64]with per-policy scaled PVs) andon_error='nan'. Default is None.on_error ({'raise', 'nan'}, optional) – Error handling for invalid ages or parameters.
'raise'(default) raisesValueError;'nan'marks invalid policies withnp.nanand returnsBatchResult. Default is'raise'.record_ids (sequence or None, optional) – Identifiers for each policy in batch mode, used to populate the error report when
on_error='nan'. Length must equalN. Any sequence is accepted, including Pandas and Polars Series; elements are always accessed by positional integer index. Default is None.
- Returns:
float – When
ages[0]is scalar andreturn_flows=False.NDArray[np.float64] – When
ages[0]is an array or list andreturn_flows=False. Shape(N,).dict – When
return_flows=True. Single-policy: per-payment engine dict (same keys asAx()). Batch: aggregate portfolio dict{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.BatchResult – When
on_error='nan'.
- Raises:
ValueError – If
tables_otherslength does not matchlen(ages) - 1. If any age is outside[0, w]for the corresponding table. Ifn <= 0orts < 0(invalid timing parameters). If no interest rate provided andinterest_rateis None. Ifreturn_flows=Truein batch mode andcalculation_modeis'discrete_simplified'or'continuous_simplified'. Ifreturn_flows=Trueandmis a vector with heterogeneous values. Ift_outputis notNoneandreturn_flows=False. Ift_outputis notNoneand input is scalar (non-batch mode). Ifon_erroris not'raise'or'nan'. Ifon_error='nan'is passed with scalarx(oragesfor joint-life methods). Use a list or array of ages for batch mode. Ifrecord_idsis notNoneand input is scalar (non-batch mode).TypeError – If
agescontains non-numeric values. Iftables_otherscontains non-LifeTable instances. Ifiris not float, InterestRate, or None. If any numeric argument isbool.
Notes
Boolean values for age, duration, shift, deferment, and rate arguments raise
TypeError. Accepted types for each parameter are described in Parameters above.First-death (first-to-die) insurance for \(r\) lives under the independence assumption:
\[A_{x_1 x_2 \cdots x_r} = \sum_{k=0}^{\omega-1} v^{k+\alpha} \prod_{i=1}^{r} {}_{k}p_{x_i} \left(1 - \prod_{i=1}^{r} p_{x_i+k}\right)\]where \(\alpha\) is set by
self.mortality_placement('beginning'→ 0,'mid'→ 0.5,'end'→ 1). For a term insurance of duration \(n\), the sum is restricted to \(k = 0, \ldots, \lfloor nm \rfloor - 1\).Follows international actuarial practice for first-death insurance.
Supports any number of lives (n-heads).
If
cashflow_timesis provided,mis ignored and payments are made at the specified times.If
cashflow_amountsis provided, disables growth.Optimized for speed and memory using NumPy vectorization.
When
gris provided, the growth factor at the j-th payment interval is(1+g)^(j // m)— anniversaries counted from the first payment, not the contract origin.GrowthRate.shifted(ts)consumesint(ts)anniversary years; fractionaltsis actuarially correct (revaluations operate on complete policy years). See Growth Rate Conventions for the full reference.
Batch mode#
Batch mode is activated when either of these conditions is met:
ages[0]is a list or NDArray (age-triggered batch): pass ages as a list of r N-element arrays, each shape(N,).ages[0]is scalar but any ofn,ts,d,ir,gr, ormis a list/tuple/NDArray of length N (param-triggered batch): scalar ages broadcast to N copies.
In both cases the method returns
NDArray[np.float64]of shape(N,).Per-policy parameters
ts,d,n,ir, andgrmay each be a scalar or an array of length N.mmay be a scalar (broadcast to all N) or an array/list of length N for per-policy payment frequencies; the batch engine groups policies by uniquemvalue and vectorises each group independently.Pandas and Polars Series are accepted for all per-policy parameters (
n,ts,d,ir,gr,m,cashflow_times,cashflow_amounts,t_output, and each per-life age array withinages) without calling.to_numpy(); they are converted internally via the array protocol.on_error='nan'marks invalid policies withnp.nan.
See also
LifeTable.AxSingle-life insurance.
LifeTable.AxyFirst-death insurance for two lives.
LifeTable.AxyzFirst-death insurance for three lives.
Examples
>>> from lactuca import LifeTable >>> table = LifeTable('PER2020_Ind_1o', 'm', cohort=1960) >>> table.interest_rate = 0.03 >>> pv = table.Afirst([65, 60, 55, 50], n=20)
>>> table_m, table_f1, table_f2 = LifeTable( ... 'PER2020_Ind_1o', ('m', 'f', 'f'), cohort=1960 ... ) >>> table_m.interest_rate = 0.03 >>> pv = table_m.Afirst([65, 62, 58], [table_f1, table_f2], n=15)
Batch mode:
>>> import numpy as np >>> a0 = np.array([60.0, 65.0]) >>> a1 = np.array([55.0, 60.0]) >>> table.Afirst([a0, a1], n=20, ir=0.03)
- Ax(x: object, *, ts: object = 0.0, d: object = 0.0, n: object = None, m: Literal[1, 2, 3, 4, 6, 12, 14, 24, 26, 52, 365] | Sequence[int] | NDArray[int64] = 1, gr: GrowthRate | float | None | Sequence[GrowthRate | float | None] = None, ir: object = None, cashflow_times: Sequence[float] | None = None, cashflow_amounts: Sequence[float] | None = None, return_flows: bool = False, t_output: NDArray[float64] | None = None, benefits: Sequence[float] | NDArray[float64] | None = None, on_error: Literal['raise', 'nan'] = 'raise', record_ids: Sequence[Any] | None = None) float | NDArray[float64] | dict[str, NDArray[float64] | float] | BatchResult#
Compute the present value of a whole-life or term life insurance for a single life.
Pays one unit of benefits at death. Supports shift (
ts), deferment (d), term (n), payment frequency (m), growth (gr), custom cash flows (cashflow_times,cashflow_amounts), and all four calculation modes. Whencashflow_timesis provided,mis ignored for payment scheduling (it is still used for mortality placement).- Parameters:
x (float or list of float or NDArray[np.float64]) – Initial age. Pass an array or list of N ages to activate batch mode, which returns an
ndarrayof shape(N,).ts (float or NDArray[np.float64], optional) – Shift (displacement) in years from origin. In batch mode, may be a scalar (broadcast) or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.d (float or NDArray[np.float64], optional) – Deferment period in years. In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.n (float or NDArray[np.float64] or None, optional) – Duration of the insurance (years). If None, whole life. In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.m (PaymentFrequencyLiteral, sequence of int, NDArray[np.int64], or array-like, optional) – Number of payments per year (1=annual, 12=monthly, etc.). Integer-valued floats (e.g.
12.0) are coerced toint. In batch mode, may be a scalar (broadcast to all N) or an array/list of length N for per-policy payment frequencies; distinct values are processed in sub-groups by the vectorised batch engine. Pandas and Polars Series are accepted; values are converted to int64.gr (GrowthRate, float, or None, optional) – Growth rate model. A scalar float is auto-wrapped as
GrowthRate(gr)(geometric growth by default). If None, no growth is applied. In batch mode, may be a scalar or a list of N values. Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asGrowthRatewhere applicable). An object-dtype Series ofGrowthRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.ir (float, InterestRate, list[float or InterestRate], NDArray[np.float64], or None, optional) – Interest rate (constant or curve). If None, uses
interest_rate. In batch mode, may be a scalar (broadcast to all N), a list of Nfloatvalues, or a list of NInterestRateobjects (one curve per policy). Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asInterestRatewhere applicable). An object-dtype Series ofInterestRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.cashflow_times (sequence of float, optional) – Times (years since origin) at which payments are made. Requires
calculation_mode='discrete_precision'; raisesValueErrorin other modes. If None, uses standard schedule. Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.cashflow_amounts (sequence of float, optional) – Capital insured for each payment period. If None, assumes unit capital. Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.
return_flows (bool, optional) –
If
True, returns a cashflow decomposition dict instead of the present value. The dict schema differs between scalar and batch:Scalar
x,discrete_precision/discrete_simplifiedmode:{ "payment_index" : NDArray # payment period indices "time_grid" : NDArray # interval end-points (payment times) "discount_time" : NDArray # t + mortality_placement offset "interest_rate" : NDArray # i(t) at discount time "discount_factor" : NDArray # v^t at discount time "death_probability_raw" : NDArray # raw interval death probabilities "death_probability_adjustment": NDArray # fractional last-payment factor "death_probability" : NDArray # adjusted death probabilities "growth" or "amount" : NDArray # growth factors or custom amounts "present_value" : NDArray # pv per payment period }sum(result["present_value"])equals the rounded scalar present value.Batch (array
x),*_precisionmodes only:{ "time_grid" : NDArray # portfolio time grid "expected_cf" : NDArray # sum of undiscounted contributions across policies "pv_cf" : NDArray # sum of discounted contributions across policies "total_pv" : float # sum(pv_cf); equals sum of scalar present values }total_pv == np.sum(pv_cf)is always satisfied. RaisesValueErrorindiscrete_simplified/continuous_simplifiedmodes. Default isFalse.t_output (NDArray[np.float64] or None, optional) – External bucketing grid for aggregate flow output. When provided, per-policy present-value contributions — already discounted at their exact payment times — are accumulated into the nearest bucket via
np.searchsortedwithout re-discounting. Controls only the shape ofexpected_cfandpv_cf;total_pvis always the exact sum of all per-payment present values and is identical to thet_output=Noneresult. Typical use: regulatory templates that require a fixed set of time buckets (e.g., annual IFRS 17 disclosure, Solvency II SCR projection). Only effective whenreturn_flows=True; raisesValueErrorifreturn_flows=False. Default isNone. Pandas and Polars Series are accepted; converted to NDArray[np.float64] before use.benefits (sequence of float or NDArray[np.float64] or None, optional) – Per-policy benefit amounts, shape
(N,). Scales each policy’s contribution to the aggregate flows (e.g., sum insured, PVDBO weight). Requires batch mode. Any sequence is accepted, including Pandas and Polars Series; values are converted to float64. Compatible withreturn_flows=False(returnsNDArray[np.float64]with per-policy scaled PVs) andon_error='nan'. Default is None.on_error ({'raise', 'nan'}, optional) – Error handling for invalid ages or parameters.
'raise'(default) raisesValueError;'nan'marks invalid policies withnp.nanand returnsBatchResult. Default is'raise'.record_ids (sequence or None, optional) – Identifiers for each policy in batch mode, used to populate the error report when
on_error='nan'. Length must equalN. Any sequence is accepted, including Pandas and Polars Series; elements are always accessed by positional integer index. Default is None.
- Returns:
float – When
xis scalar andreturn_flows=False.NDArray[np.float64] – When
xis an array or list andreturn_flows=False. Shape(N,).dict – When
return_flows=True. Schema depends on scalar vs. batch and oncalculation_mode; see thereturn_flowsparameter description above for the full key listing of each variant.BatchResult – When
on_error='nan', wraps the result array and an error report.
- Raises:
ValueError – If
xis outside[0, w]for the mortality table. Ifn <= 0orts < 0(invalid timing parameters). If no interest rate provided andinterest_rateis None. Ifreturn_flows=Truein batch mode andcalculation_modeis'discrete_simplified'or'continuous_simplified'. Ifreturn_flows=Trueandmis a vector with heterogeneous values; use a scalarmor split the batch by payment frequency. Ift_outputis notNoneandreturn_flows=False. Ift_outputis notNoneand input is scalar (non-batch mode). Ifon_erroris not'raise'or'nan'. Ifon_error='nan'is passed with scalarx(oragesfor joint-life methods). Use a list or array of ages for batch mode. Ifrecord_idsis notNoneand input is scalar (non-batch mode).TypeError – If
xis not a numeric type (includingbool). Ifiris not float, InterestRate, or None.
Notes
Boolean values for age, duration, shift, deferment, and rate arguments raise
TypeError. Accepted types for each parameter are described in Parameters above.Discrete whole-life insurance \(\Ax{x}\) with mortality placement offset \(\alpha\):
\[\Ax{x} = \sum_{k=0}^{\omega-x-1} v^{k+\alpha} \cdot {}_k p_x \cdot q_{x+k}\]where \(\alpha\) is set by
self.mortality_placement('beginning': 0,'mid': 0.5,'end': 1). For integer ages with annual payments, the commutation identity is \(\Ax{x} = \Mx{x} / \Dx{x}\).ncan be fractional;lxinterpolation is used as defined in config.If
cashflow_timesis provided, overrides standard payment schedule andmis ignored.If
cashflow_amountsis provided, disables growth.cashflow_timesmust be non-negative, sorted, and without duplicates.Follows international actuarial practice.
When
gris provided, the growth factor at the j-th payment interval is(1+g)^(j // m)— anniversaries counted from the first payment, not the contract origin.GrowthRate.shifted(ts)consumesint(ts)anniversary years; fractionaltsis actuarially correct (revaluations operate on complete policy years). See Growth Rate Conventions for the full reference.
Batch mode#
When
xis alist, sequence, orndarray, or when any ofn,ts,d,ir,gr, ormis a sequence or array with scalarx, the method operates in batch mode and returns anndarrayof shape(N,):A list of 1 element still returns
NDArrayshape(1,), never a scalar.Per-policy parameters
ts,d,n,ir, andgrmay each be a scalar (broadcast to all N policies) or an array of length N.mmay be a scalar (broadcast to all N policies) or an array/list of length N for per-policy payment frequencies; the batch engine groups policies by uniquemvalue and vectorises each group independently.Pandas and Polars Series are accepted for all per-policy parameters (
x,n,ts,d,ir,gr,m,cashflow_times,cashflow_amounts,t_output) without calling.to_numpy(); they are converted internally via the array protocol.discrete_precisionmode uses a vectorised engine (50–250× vs scalar loop).on_error='nan'marks invalid policies withnp.naninstead of raising.
See also
LifeTable.AxyFirst-death insurance for two lives.
LifeTable.AxyzFirst-death insurance for three lives.
LifeTable.AfirstFirst-death insurance for any number of lives.
Examples
>>> from lactuca import LifeTable >>> lt = LifeTable('PASEM2010', 'm') >>> lt.interest_rate = 0.03 >>> lt.Ax(65, n=20) >>> lt.Ax(65) # whole life
Batch mode:
>>> import numpy as np >>> ages = np.array([55.0, 60.0, 65.0, 70.0]) >>> lt.Ax(ages, n=20, ir=0.03) >>> lt.Ax(ages, n=np.array([25.0, 20.0, 15.0, 10.0]), ir=0.03)
- Axy(ages: object, table_y: LifeTable | Sequence[LifeTable] | None = None, *, ts: object = 0.0, d: object = 0.0, n: object = None, m: Literal[1, 2, 3, 4, 6, 12, 14, 24, 26, 52, 365] | Sequence[int] | NDArray[int64] = 1, gr: GrowthRate | float | None | Sequence[GrowthRate | float | None] = None, ir: object = None, cashflow_times: Sequence[float] | None = None, cashflow_amounts: Sequence[float] | None = None, return_flows: bool = False, t_output: NDArray[float64] | None = None, benefits: Sequence[float] | NDArray[float64] | None = None, on_error: Literal['raise', 'nan'] = 'raise', record_ids: Sequence[Any] | None = None) float | NDArray[float64] | dict[str, NDArray[float64]] | BatchResult#
Compute the present value of a first-death insurance for two lives.
Pays one unit of benefits upon the first death among lives (x, y).
- Parameters:
ages (Sequence[float] or tuple[Sequence[float], Sequence[float]]) – Ages of the two lives
[x, y]. Must have exactly 2 elements. In batch mode, pass a pair of N-element arrays:(np.array([x0, …, xN]), np.array([y0, …, yN])).table_y (LifeTable or Sequence[LifeTable] or None, optional) –
Life table for the second life (y). Three modes in batch:
None: usesselffor all policies.LifeTable: single shared table for all N policies.Sequence[LifeTable]of length N: per-policy table; policy i uses element i. RaisesValueErrorif length != N.
ts (float or NDArray[np.float64], optional) – Shift (displacement) in years from origin (default 0.0). In batch mode, may be a scalar (broadcast) or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.d (float or NDArray[np.float64], optional) – Deferment period in years (default 0.0). In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.n (float or NDArray[np.float64] or None, optional) – Duration of the insurance (years). If None, whole life. In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.m (PaymentFrequencyLiteral, sequence of int, NDArray[np.int64], or array-like, optional) – Number of payments per year (1=annual, 12=monthly, etc., default 1). Integer-valued floats (e.g.
12.0) are coerced toint. In batch mode, may be a scalar (broadcast to all N) or an array/list of length N for per-policy payment frequencies; distinct values are processed in sub-groups by the vectorised batch engine. Pandas and Polars Series are accepted; values are converted to int64.gr (GrowthRate, float, or None, optional) – Growth rate model (default None). A scalar float is auto-wrapped as
GrowthRate(gr)(geometric growth by default). If None, no growth is applied. In batch mode, may be a scalar or a list of N values. Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asGrowthRatewhere applicable). An object-dtype Series ofGrowthRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.ir (float, InterestRate, list[float or InterestRate], NDArray[np.float64], or None, optional) – Interest rate (constant or curve). If None, uses
interest_rate. In batch mode, may be a scalar (broadcast to all N), a list of Nfloatvalues, or a list of NInterestRateobjects (one curve per policy). Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asInterestRatewhere applicable). An object-dtype Series ofInterestRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.cashflow_times (Sequence[float], optional) – Times (years since origin) at which payments are made. Requires
calculation_mode='discrete_precision'; raisesValueErrorin other modes. If None, uses standard schedule. Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.cashflow_amounts (Sequence[float], optional) – Capital insured for each payment period. If None, assumes unit capital. Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.
return_flows (bool, optional) –
If
True, returns a cashflow decomposition dict instead of the present value. The dict schema differs between single-policy and batch:Single-policy (scalar ages): per-payment engine dict. Same keys as
Ax(); see that method’sreturn_flowsparameter for the full key reference.Batch (array ages): aggregate portfolio dict
{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.total_pv == np.sum(pv_cf)is always satisfied. RaisesValueErrorindiscrete_simplified/continuous_simplifiedmodes. Default isFalse.t_output (NDArray[np.float64] or None, optional) – External bucketing grid for aggregate flow output. When provided, per-policy present-value contributions — already discounted at their exact payment times — are accumulated into the nearest bucket via
np.searchsortedwithout re-discounting. Controls only the shape ofexpected_cfandpv_cf;total_pvis always the exact sum of all per-payment present values and is identical to thet_output=Noneresult. Typical use: regulatory templates that require a fixed set of time buckets (e.g., annual IFRS 17 disclosure, Solvency II SCR projection). Only effective whenreturn_flows=True; raisesValueErrorifreturn_flows=False. Default isNone. Pandas and Polars Series are accepted; converted to NDArray[np.float64] before use.benefits (sequence of float or NDArray[np.float64] or None, optional) – Per-policy benefit amounts, shape
(N,). Scales each policy’s contribution to the aggregate flows (e.g., sum insured, PVDBO weight). Requires batch mode. Any sequence is accepted, including Pandas and Polars Series; values are converted to float64. Compatible withreturn_flows=False(returnsNDArray[np.float64]with per-policy scaled PVs) andon_error='nan'. Default is None.on_error ({'raise', 'nan'}, optional) – Error handling for invalid ages or parameters.
'raise'(default) raisesValueError;'nan'marks invalid policies withnp.nanand returnsBatchResult. Default is'raise'.record_ids (sequence or None, optional) – Identifiers for each policy in batch mode, used to populate the error report when
on_error='nan'. Length must equalN. Any sequence is accepted, including Pandas and Polars Series; elements are always accessed by positional integer index. Default is None.
- Returns:
float – When
ages[0]is scalar andreturn_flows=False.NDArray[np.float64] – When
ages[0]is an array or list andreturn_flows=False. Shape(N,).dict – When
return_flows=True. Single-policy: per-payment engine dict (same keys asAx()). Batch: aggregate portfolio dict{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.BatchResult – When
on_error='nan'.
- Raises:
ValueError – If
agesdoes not contain exactly 2 elements. If any age is outside[0, w]for corresponding table. Ifreturn_flows=Truein batch mode andcalculation_modeis'discrete_simplified'or'continuous_simplified'. Ifreturn_flows=Trueandmis a vector with heterogeneous values. Ift_outputis notNoneandreturn_flows=False. Ift_outputis notNoneand input is scalar (non-batch mode). Ifon_erroris not'raise'or'nan'. Ifon_error='nan'is passed with scalarx(oragesfor joint-life methods). Use a list or array of ages for batch mode. Ifrecord_idsis notNoneand input is scalar (non-batch mode).TypeError – If
agescontains non-numeric values. Iftable_yis not a LifeTable instance or None. If any numeric argument isbool.
Notes
Boolean values for age, duration, shift, deferment, and rate arguments raise
TypeError. Accepted types for each parameter are described in Parameters above.First-death (first-to-die) insurance for two lives under the independence assumption:
\[A_{xy} = \sum_{k=0}^{\omega-1} v^{k+\alpha} \cdot {}_{k}p_x \cdot {}_{k}p_y \cdot \left(1 - p_{x+k} \cdot p_{y+k}\right)\]where \(\alpha\) is set by
self.mortality_placement('beginning': 0,'mid': 0.5,'end': 1). For a term insurance of duration \(n\), the sum is restricted to \(k = 0, \ldots, \lfloor nm \rfloor - 1\).Benefit is paid upon the first death among the two lives.
Uses independence assumption for joint survival probabilities.
When
gris provided, the growth factor at the j-th payment interval is(1+g)^(j // m)— anniversaries counted from the first payment, not the contract origin.GrowthRate.shifted(ts)consumesint(ts)anniversary years; fractionaltsis actuarially correct (revaluations operate on complete policy years). See Growth Rate Conventions for the full reference.
Batch mode#
Batch mode is activated when either of these conditions is met:
ages[0]is a list or NDArray (age-triggered batch): pass ages as a pair of N-element arrays(x_arr, y_arr), each shape(N,).ages[0]is scalar but any ofn,ts,d,ir,gr, ormis a list/tuple/NDArray of length N (param-triggered batch): scalar ages broadcast to N copies.
In both cases the method returns
NDArray[np.float64]of shape(N,).Per-policy parameters
ts,d,n,ir, andgrmay each be a scalar (broadcast to all N) or an array of length N.mmay be a scalar (broadcast to all N policies) or an array/list of length N for per-policy payment frequencies; the batch engine groups policies by uniquemvalue and vectorises each group independently.discrete_precisionmode uses a vectorised matrix engine.Pandas and Polars Series are accepted for all per-policy parameters (
n,ts,d,ir,gr,m,cashflow_times,cashflow_amounts,t_output, and each per-life age array withinages) without calling.to_numpy(); they are converted internally via the array protocol.on_error='nan'marks invalid policies withnp.nan.
See also
LifeTable.AfirstFirst-death insurance for any number of lives.
LifeTable.AxyzFirst-death insurance for three lives.
LifeTable.AxSingle-life insurance.
Examples
>>> from lactuca import LifeTable >>> table = LifeTable('PER2020_Ind_1o', 'm', cohort=1960) >>> table.interest_rate = 0.03 >>> pv = table.Axy([65, 60], n=20)
Batch mode:
>>> import numpy as np >>> x_ages = np.array([60.0, 65.0, 70.0]) >>> y_ages = np.array([55.0, 60.0, 65.0]) >>> table.Axy((x_ages, y_ages), n=20, ir=0.03) >>> table_m, table_f = LifeTable('PER2020_Ind_1o', ('m', 'f'), cohort=1960) >>> table_m.interest_rate = 0.03 >>> pv = table_m.Axy([65, 62], table_f, n=15)
- Axyz(ages: object, tables_yz: Sequence[LifeTable] | Sequence[Sequence[LifeTable]] | None = None, *, ts: object = 0.0, d: object = 0.0, n: object = None, m: Literal[1, 2, 3, 4, 6, 12, 14, 24, 26, 52, 365] | Sequence[int] | NDArray[int64] = 1, gr: GrowthRate | float | None | Sequence[GrowthRate | float | None] = None, ir: object = None, cashflow_times: Sequence[float] | None = None, cashflow_amounts: Sequence[float] | None = None, return_flows: bool = False, t_output: NDArray[float64] | None = None, benefits: Sequence[float] | NDArray[float64] | None = None, on_error: Literal['raise', 'nan'] = 'raise', record_ids: Sequence[Any] | None = None) float | dict[str, NDArray[float64]] | BatchResult#
Compute the present value of a first-death insurance for three lives.
Pays one unit of benefits upon the first death among lives (x, y, z).
- Parameters:
ages (Sequence[float] or tuple[Sequence[float], Sequence[float], Sequence[float]]) – Ages of the three lives
[x, y, z]. Must have exactly 3 elements. In batch mode, pass a triple of N-element arrays:(np.array([x0, …]), np.array([y0, …]), np.array([z0, …])).tables_yz (Sequence[LifeTable] or Sequence[Sequence[LifeTable]] or None, optional) –
Life tables for the second and third lives [y, z]. Three modes in batch:
None: usesselffor all three lives in all policies.[LifeTable, LifeTable]: 2 shared tables (y and z) for all N policies.[[t_y0, t_z0], …]of length N: per-policy; each inner list must have exactly 2 elements. RaisesValueErrorif outer length != N.
ts (float or NDArray[np.float64], optional) – Shift (displacement) in years from origin (default 0.0). In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.d (float or NDArray[np.float64], optional) – Deferment period in years (default 0.0). In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.n (float or NDArray[np.float64] or None, optional) – Duration of the insurance (years). If None, whole life. In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.m (PaymentFrequencyLiteral, sequence of int, NDArray[np.int64], or array-like, optional) – Number of payments per year (1=annual, 12=monthly, etc., default 1). Integer-valued floats (e.g.
12.0) are coerced toint. In batch mode, may be a scalar (broadcast to all N) or an array/list of length N for per-policy payment frequencies; distinct values are processed in sub-groups by the vectorised batch engine. Pandas and Polars Series are accepted; values are converted to int64.gr (GrowthRate, float, or None, optional) – Growth rate model (default None). A scalar float is auto-wrapped as
GrowthRate(gr)(geometric growth by default). If None, no growth is applied. In batch mode, may be a scalar or a list of N values. Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asGrowthRatewhere applicable). An object-dtype Series ofGrowthRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.ir (float, InterestRate, list[float or InterestRate], NDArray[np.float64], or None, optional) – Interest rate (constant or curve). If None, uses
interest_rate. In batch mode, may be a scalar (broadcast to all N), a list of Nfloatvalues, or a list of NInterestRateobjects (one curve per policy). Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asInterestRatewhere applicable). An object-dtype Series ofInterestRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.cashflow_times (Sequence[float], optional) – Times (years since origin) at which payments are made. Requires
calculation_mode='discrete_precision'; raisesValueErrorin other modes. If None, uses standard schedule. Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.cashflow_amounts (Sequence[float], optional) – Capital insured for each payment period. If None, assumes unit capital. Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.
return_flows (bool, optional) –
If
True, returns a cashflow decomposition dict instead of the present value. The dict schema differs between single-policy and batch:Single-policy (scalar ages): per-payment engine dict. Same keys as
Ax(); see that method’sreturn_flowsparameter for the full key reference.Batch (array ages): aggregate portfolio dict
{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.total_pv == np.sum(pv_cf)is always satisfied. RaisesValueErrorindiscrete_simplified/continuous_simplifiedmodes. Default isFalse.t_output (NDArray[np.float64] or None, optional) – External bucketing grid for aggregate flow output. When provided, per-policy present-value contributions — already discounted at their exact payment times — are accumulated into the nearest bucket via
np.searchsortedwithout re-discounting. Controls only the shape ofexpected_cfandpv_cf;total_pvis always the exact sum of all per-payment present values and is identical to thet_output=Noneresult. Typical use: regulatory templates that require a fixed set of time buckets (e.g., annual IFRS 17 disclosure, Solvency II SCR projection). Only effective whenreturn_flows=True; raisesValueErrorifreturn_flows=False. Default isNone. Pandas and Polars Series are accepted; converted to NDArray[np.float64] before use.
- Returns:
float – When
ages[0]is scalar andreturn_flows=False.NDArray[np.float64] – When
ages[0]is an array or list andreturn_flows=False. Shape(N,).dict – When
return_flows=True. Single-policy: per-payment engine dict (same keys asAx()). Batch: aggregate portfolio dict{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.BatchResult – When
on_error='nan'.
- Raises:
ValueError – If
agesdoes not contain exactly 3 elements. Iftables_yzis provided but does not contain exactly 2 LifeTable instances. If any age is outside[0, w]for corresponding table. Ifreturn_flows=Trueandmis a vector with heterogeneous values. Ift_outputis notNoneandreturn_flows=False. Ift_outputis notNoneand input is scalar (non-batch mode). Ifon_erroris not'raise'or'nan'. Ifon_error='nan'is passed with scalarx(oragesfor joint-life methods). Use a list or array of ages for batch mode. Ifrecord_idsis notNoneand input is scalar (non-batch mode).TypeError – If
agescontains non-numeric values. Iftables_yzcontains non-LifeTable instances. If any numeric argument isbool.
Notes
Boolean values for age, duration, shift, deferment, and rate arguments raise
TypeError. Accepted types for each parameter are described in Parameters above.First-death (first-to-die) insurance for three lives under the independence assumption:
\[A_{xyz} = \sum_{k=0}^{\omega-1} v^{k+\alpha} \cdot {}_{k}p_x \cdot {}_{k}p_y \cdot {}_{k}p_z \cdot \left(1 - p_{x+k} \cdot p_{y+k} \cdot p_{z+k}\right)\]where \(\alpha\) is set by
self.mortality_placement('beginning': 0,'mid': 0.5,'end': 1). For a term insurance of duration \(n\), the sum is restricted to \(k = 0, \ldots, \lfloor nm \rfloor - 1\).Benefit is paid upon the first death among the three lives.
Uses independence assumption for joint survival probabilities.
When
gris provided, the growth factor at the j-th payment interval is(1+g)^(j // m)— anniversaries counted from the first payment, not the contract origin.GrowthRate.shifted(ts)consumesint(ts)anniversary years; fractionaltsis actuarially correct (revaluations operate on complete policy years). See Growth Rate Conventions for the full reference.
Batch mode#
Batch mode is activated when either of these conditions is met:
ages[0]is a list or NDArray (age-triggered batch): pass ages as a triple of N-element arrays(x_arr, y_arr, z_arr), each(N,).ages[0]is scalar but any ofn,ts,d,ir,gr, ormis a list/tuple/NDArray of length N (param-triggered batch): scalar ages broadcast to N copies.
In both cases the method returns
NDArray[np.float64]of shape(N,).Per-policy parameters
ts,d,n,ir, andgrmay each be a scalar or an array of length N.mmay be a scalar (broadcast to all N) or an array/list of length N for per-policy payment frequencies; the batch engine groups policies by uniquemvalue and vectorises each group independently.Pandas and Polars Series are accepted for all per-policy parameters (
n,ts,d,ir,gr,m,cashflow_times,cashflow_amounts,t_output, and each per-life age array withinages) without calling.to_numpy(); they are converted internally via the array protocol.on_error='nan'marks invalid policies withnp.nan.
See also
LifeTable.AfirstFirst-death insurance for any number of lives.
LifeTable.AxyFirst-death insurance for two lives.
LifeTable.AxSingle-life insurance.
Examples
>>> from lactuca import LifeTable >>> table = LifeTable('PER2020_Ind_1o', 'm', cohort=1960) >>> table.interest_rate = 0.03 >>> pv = table.Axyz([65, 60, 55], n=20)
>>> table_m, table_f1, table_f2 = LifeTable('PER2020_Ind_1o', ('m', 'f', 'f'), cohort=1960) >>> table_m.interest_rate = 0.03 >>> pv = table_m.Axyz([65, 62, 58], [table_f1, table_f2], n=15)
Batch mode:
>>> import numpy as np >>> x_arr = np.array([60.0, 65.0]) >>> y_arr = np.array([55.0, 60.0]) >>> z_arr = np.array([50.0, 55.0]) >>> table.Axyz((x_arr, y_arr, z_arr), n=20, ir=0.03)
- Cx(x: object = None, *, ir: object = None, x0: object = 0) object#
Commutation function \(C_x = v^{x+\alpha} \cdot d_x\) (integer ages only).
Following standard actuarial practice, commutation functions are defined for integer ages and annual payment periods only.
- Parameters:
x (int, sequence of int, NDArray[np.float64], or None) – Integer age(s). Pandas and Polars Series of int are accepted. Pass
Noneto compute the full table (all ages 0..ω).ir (InterestRate or None, optional) – Interest rate curve. If
None, usesinterest_rate.x0 (int, optional) – Reference age for discounting, consistent with
Dx()(default 0).
- Returns:
\(C_x\) value(s), rounded to
decimals.Cx. Full-table output (x=None) is unrounded (used internally byMx()andRx()).- Return type:
float or np.ndarray
- Raises:
ValueError – If
xcontains non-integer ages. If no interest rate is set andirisNone.
Notes
\(d_x = l_x - l_{x+1}\) is the number of deaths between ages \(x\) and \(x+1\).
The mortality placement offset \(\alpha\) is set by
self.mortality_placement:'beginning': \(\alpha = 0\),'mid': \(\alpha = 0.5\), and'end': \(\alpha = 1\).Insurance identity: \(A_x = M_x / D_x\) (whole-life, end-of-year payments).
See also
LifeTable.DxCommutation function \(D_x = v^x \cdot l_x\).
LifeTable.MxCommutation function \(M_x = \sum_{k \geq x} C_k\).
LifeTable.RxCommutation function \(R_x = \sum_{k \geq x} M_k\).
Examples
>>> from lactuca import LifeTable >>> from lactuca import InterestRate >>> lt = LifeTable('PASEM2010', 'm') >>> lt.Cx(65, ir=InterestRate(0.03)) >>> lt.Cx([60, 65, 70], ir=InterestRate(0.03)) # vectorized
- Dx(x: object = None, *, ir: object = None, x0: object = 0) object#
Commutation function \(D_x = v^x \cdot l_x\) (integer ages only).
Following standard actuarial practice, commutation functions are defined for integer ages and annual payment periods only.
- Parameters:
x (int, sequence of int, NDArray[np.float64], or None) – Integer age(s). Pandas and Polars Series of int are accepted. Pass
Noneto compute the full table (all ages 0..ω).ir (InterestRate or None, optional) – Interest rate curve. If
None, usesinterest_rate.x0 (int, optional) – Reference age for discounting; discount factors are computed as \(v^{x - x_0}\) relative to age
x0(default 0). Used internally byNx()andSx()to ensure \(a_x = N_{x+1} / D_x\) holds for variable interest curves.
- Returns:
\(D_x\) value(s), rounded to
decimals.Dxfor single/array input. Full-table output (x=None) is unrounded (used internally byNx()andSx()).- Return type:
float or np.ndarray
- Raises:
ValueError – If
xcontains non-integer ages. If no interest rate is set andirisNone.
Notes
Commutation identities such as \(N_x = \\sum_{t \\geq x} D_t\) and \(a_x = N_{x+1}/D_x\) hold exactly when
iris a flat annual rate. With a variableInterestRatecurve, useNx()/ax()for product valuations rather than assuming textbook flat-rate commutation algebra.For constant interest \(i\), the discount factor is \(v^{x-x_0} = (1+i)^{-(x-x_0)}\).
For variable interest, the factor is the product of annual discount factors from age
x0to agex.
See also
LifeTable.NxCommutation function \(N_x = \sum_{k \geq x} D_k\).
LifeTable.SxCommutation function \(S_x = \sum_{k \geq x} N_k\).
LifeTable.CxCommutation function \(C_x = v^{x+\alpha} \cdot d_x\).
Examples
>>> from lactuca import LifeTable >>> from lactuca import InterestRate >>> lt = LifeTable('PASEM2010', 'm') >>> lt.Dx(65, ir=InterestRate(0.03)) >>> lt.Dx([60, 65, 70], ir=InterestRate(0.03)) # vectorized
- Lx(x: object) object#
Person-years lived in [x, x+1) under UDD (integer ages only).
Following standard actuarial practice, commutation functions are defined for integer ages only. For fractional ages, use
Lx_continuous().- Parameters:
x (int, sequence of int, or NDArray[np.float64]) – Integer age(s). Pandas and Polars Series of int are accepted. Non-integer ages will raise ValueError.
- Returns:
Person-years lived at age interval [x, x+1).
- Return type:
float or np.ndarray
- Raises:
ValueError – If any
xis outside[0, w]or contains non-integer ages.TypeError – If
xis not numeric or a sequence of numerics.
Notes
Lxis a commutation function strictly defined for integer ages.Uses UDD (Uniform Distribution of Deaths) formula: \(L_x = l_x \left(1 - \frac{q_x}{2}\right)\).
\(L_{\omega} = 0\) by convention (no person-years beyond terminal age).
All results are rounded to the configured number of decimals (
decimals.Lx).Fully vectorized and optimized for speed and memory.
Follows international actuarial practice (UDD assumption).
See also
LifeTable.TxTotal person-years lived from age x onwards (integer ages only).
LifeTable.Lx_continuousContinuous version of Lx for fractional ages.
LifeTable.exComplete expectation of life (integer ages only).
Examples
>>> from lactuca import LifeTable >>> tbl = LifeTable('PASEM2010', 'm') >>> tbl.Lx(65) >>> tbl.Lx([60, 65, 70]) # vectorized
- Lx_continuous(x: object, *, m: object = 12) object#
Compute Lx at fractional ages using numerical integration.
Accepts only fractional ages; for integer ages use
Lx()instead. The integral \(L_x(x) = \int_x^{x+1} l_t \, dt\) is evaluated via the trapezoidal rule withm+1equally-spaced nodes.- Parameters:
x (float, list, or np.ndarray) – Fractional age(s) to evaluate. Integer ages will raise ValueError. Use Lx() for integer ages.
m (object, optional) – Trapezoidal integration density (default
12). Interpreted as the number of subintervals per unit age span; must be a value fromVALID_PAYMENT_FREQUENCIES:1, 2, 3, 4, 6, 12, 14, 24, 26, 52, 365. Highermincreases precision at greater computational cost.boolis rejected.
- Returns:
Person-years lived at age interval [x, x+1).
- Return type:
float or np.ndarray
- Raises:
ValueError – If any
xis an integer age, outside[0, w], ormis not a valid payment frequency. For integer ages, useLx()instead.TypeError – If
xis not numeric or a sequence of numerics, or ifmis abool.
Notes
Computes \(L_x(x) = \int_x^{x+1} l_t \, dt\) via the trapezoidal rule.
Strictly for fractional (non-integer) ages; use
Lx()for integer ages.Each \(l_t\) evaluation uses the configured interpolation method.
\(L_{\omega} = 0\) by convention (no person-years beyond terminal age).
All results are rounded to the configured number of decimals (
decimals.Lx).Fully vectorized and optimized for speed and memory.
Follows international actuarial practice for continuous age calculations.
See also
LifeTable.LxStandard commutation function for integer ages only (required for integer ages).
LifeTable.Tx_continuousContinuous version of Tx.
LifeTable.ex_continuousContinuous version of ex.
Examples
>>> from lactuca import LifeTable >>> tbl = LifeTable("PASEM2010", "m") >>> tbl.Lx_continuous(30.5) >>> tbl.Lx_continuous(30) # integer age — raises ValueError
- Mx(x: object = None, *, ir: object = None, x0: object = 0) object#
Commutation function \(M_x = \sum_{k \geq x} C_k\) (suffix sums of Cx).
Following standard actuarial practice, commutation functions are defined for integer ages and annual payment periods only.
- Parameters:
x (int, sequence of int, NDArray[np.float64], or None) – Integer age(s). Pandas and Polars Series of int are accepted. Pass
Noneto compute the full table (all ages 0..ω).ir (InterestRate or None, optional) – Interest rate curve. If
None, usesinterest_rate.x0 (int, optional) – Reference age for discounting, consistent with
Dx()(default 0).
- Returns:
\(M_x\) value(s), rounded to
decimals.Mx. Full-table output (x=None) is unrounded (used internally byRx()and insurance calculations).- Return type:
float or np.ndarray
- Raises:
ValueError – If
xcontains non-integer ages. If no interest rate is set andirisNone.
Notes
Built via reversed cumsum of \(C_k\) in O(ω) time.
Insurance identity: \(A_x = M_x / D_x\) (whole-life, end-of-year payments).
See also
LifeTable.CxCommutation function \(C_x = v^{x+\alpha} \cdot d_x\).
LifeTable.RxCommutation function \(R_x = \sum_{k \geq x} M_k\).
LifeTable.NxCommutation function \(N_x\) (annuity analog of \(M_x\)).
Examples
>>> from lactuca import LifeTable >>> from lactuca import InterestRate >>> lt = LifeTable('PASEM2010', 'm') >>> lt.Mx(65, ir=InterestRate(0.03)) >>> lt.Mx([60, 65, 70], ir=InterestRate(0.03)) # vectorized
- Nx(x: object = None, *, ir: object = None, x0: object = 0) object#
Commutation function \(N_x = \sum_{k \geq x} D_k\) (suffix sums of Dx).
Following standard actuarial practice, commutation functions are defined for integer ages and annual payment periods only.
- Parameters:
x (int, sequence of int, NDArray[np.float64], or None) – Integer age(s). Pandas and Polars Series of int are accepted. Pass
Noneto compute the full table (all ages 0..ω).ir (InterestRate or None, optional) – Interest rate curve. If
None, usesinterest_rate.x0 (int, optional) – Reference age for discounting, consistent with
Dx()(default 0).
- Returns:
\(N_x\) value(s), rounded to
decimals.Nx. Full-table output (x=None) is unrounded (used internally bySx()and annuity calculations).- Return type:
float or np.ndarray
- Raises:
ValueError – If
xcontains non-integer ages. If no interest rate is set andirisNone.
Notes
Built via reversed cumsum of \(D_k\) in O(ω) time.
Annuity-due identity: \(\ddot{a}_x = N_{x+1} / D_x\).
See also
LifeTable.DxCommutation function \(D_x = v^x \cdot l_x\).
LifeTable.SxCommutation function \(S_x = \sum_{k \geq x} N_k\).
LifeTable.MxCommutation function \(M_x\) (insurance analog of \(N_x\)).
Examples
>>> from lactuca import LifeTable >>> from lactuca import InterestRate >>> lt = LifeTable('PASEM2010', 'm') >>> lt.Nx(65, ir=InterestRate(0.03)) >>> lt.Nx([60, 65, 70], ir=InterestRate(0.03)) # vectorized
- Rx(x: object = None, *, ir: object = None, x0: object = 0) object#
Commutation function \(R_x = \sum_{k \geq x} M_k\) (suffix sums of Mx).
Following standard actuarial practice, commutation functions are defined for integer ages and annual payment periods only.
- Parameters:
x (int, sequence of int, NDArray[np.float64], or None) – Integer age(s). Pandas and Polars Series of int are accepted. Pass
Noneto compute the full table (all ages 0..ω).ir (InterestRate or None, optional) – Interest rate curve. If
None, usesinterest_rate.x0 (int, optional) – Reference age for discounting, consistent with
Dx()(default 0).
- Returns:
\(R_x\) value(s), rounded to
decimals.Rx. Full-table output (x=None) is unrounded (used internally).- Return type:
float or np.ndarray
- Raises:
ValueError – If
xcontains non-integer ages. If no interest rate is set andirisNone.
Notes
Second-order suffix sum: built via reversed cumsum of \(M_k\) in O(ω) time.
Related identity for increasing insurance (when
mortality_placement='end', i.e. \(\alpha = 1\)): \((IA)_x = R_x / D_x\).
See also
LifeTable.MxCommutation function \(M_x = \sum_{k \geq x} C_k\).
LifeTable.CxCommutation function \(C_x = v^{x+\alpha} \cdot d_x\).
LifeTable.SxCommutation function \(S_x\) (annuity analog of \(R_x\)).
Examples
>>> from lactuca import LifeTable >>> from lactuca import InterestRate >>> lt = LifeTable('PASEM2010', 'm') >>> lt.Rx(65, ir=InterestRate(0.03)) >>> lt.Rx([60, 65, 70], ir=InterestRate(0.03)) # vectorized
- Sx(x: object = None, *, ir: object = None, x0: object = 0) object#
Commutation function \(S_x = \sum_{k \geq x} N_k\) (suffix sums of Nx).
Following standard actuarial practice, commutation functions are defined for integer ages and annual payment periods only.
- Parameters:
x (int, sequence of int, NDArray[np.float64], or None) – Integer age(s). Pandas and Polars Series of int are accepted. Pass
Noneto compute the full table (all ages 0..ω).ir (InterestRate or None, optional) – Interest rate curve. If
None, usesinterest_rate.x0 (int, optional) – Reference age for discounting, consistent with
Dx()(default 0).
- Returns:
\(S_x\) value(s), rounded to
decimals.Sx. Full-table output (x=None) is unrounded (used internally).- Return type:
float or np.ndarray
- Raises:
ValueError – If
xcontains non-integer ages. If no interest rate is set andirisNone.
Notes
Second-order suffix sum: built via reversed cumsum of \(N_k\) in O(ω) time.
The terminal age is included in the suffix, so \(S_{\omega} = N_{\omega}\).
For
x=None, the full suffix vector is returned at float64 precision (no rounding), which is the internal path used by dependent commutation/annuity routines.Related annuity identity: \((I\ddot{a})_x = S_{x+1} / D_x\).
See also
LifeTable.NxCommutation function \(N_x = \sum_{k \geq x} D_k\).
LifeTable.DxCommutation function \(D_x = v^x \cdot l_x\).
LifeTable.RxCommutation function \(R_x\) (insurance analog of \(S_x\)).
Examples
>>> from lactuca import LifeTable >>> from lactuca import InterestRate >>> lt = LifeTable('PASEM2010', 'm') >>> lt.Sx(65, ir=InterestRate(0.03)) >>> lt.Sx([60, 65, 70], ir=InterestRate(0.03)) # vectorized
- Tx(x: object) object#
Return total person-years \(T_x\) from age x onwards (integer ages only).
Defined as \(T_x = \sum_{k=x}^{\omega-1} L_k\), i.e. the sum of Lx from age x to omega - 1 (inclusive). Satisfies the recurrence \(T_x = L_x + T_{x+1}\).
Following standard actuarial practice, commutation functions are defined for integer ages only. For fractional ages, use
Tx_continuous().- Parameters:
x (int, sequence of int, or NDArray[np.float64]) – Integer age(s). Pandas and Polars Series of int are accepted. Non-integer ages will raise ValueError.
- Returns:
Tx value(s) at integer age(s) x.
- Return type:
float or np.ndarray
- Raises:
ValueError – If any
xis outside[0, w]or contains non-integer ages.TypeError – If
xis not numeric or a sequence of numerics.
Notes
Txis a commutation function strictly defined for integer ages.Recurrence: \(T_x = L_x + T_{x+1}\); \(T_{\omega} = 0\).
All results are rounded to the configured number of decimals (
decimals.Tx).Fully vectorized and optimized for speed and memory.
Follows international actuarial practice.
See also
LifeTable.LxPerson-years lived in [x, x+1) under UDD (integer ages only).
LifeTable.Tx_continuousContinuous version of Tx for fractional ages.
LifeTable.exComplete expectation of life (integer ages only).
Examples
>>> from lactuca import LifeTable >>> tbl = LifeTable('PASEM2010', 'm') >>> tbl.Tx(65) >>> tbl.Tx([60, 65, 70]) # vectorized
- Tx_continuous(x: object, *, m: object = 12) object#
Compute Tx at fractional ages using numerical integration.
Accepts only fractional ages; for integer ages use
Tx()instead.- Parameters:
x (float, list, or np.ndarray) – Fractional age(s) to evaluate. Integer ages will raise ValueError. Use Tx() for integer ages.
m (object, optional) – Trapezoidal integration density (default
12). Interpreted as the number of subintervals per unit age span; must be a value fromVALID_PAYMENT_FREQUENCIES:1, 2, 3, 4, 6, 12, 14, 24, 26, 52, 365. Highermincreases precision at greater computational cost.boolis rejected.
- Returns:
Present value of future years lived at age(s) x.
- Return type:
float or np.ndarray
- Raises:
ValueError – If any
xis an integer age, outside[0, w], ormis not a valid payment frequency. For integer ages, useTx()instead.TypeError – If
xis not numeric or a sequence of numerics, or ifmis abool.
Notes
Computes \(T_x = \int_x^{\infty} l_t \, dt\) via the trapezoidal rule.
Strictly for fractional (non-integer) ages; use
Tx()for integer ages.Each \(l_t\) evaluation uses the configured interpolation method.
All results are rounded to the configured number of decimals (
decimals.Tx).Fully vectorized and optimized for speed and memory.
Follows international actuarial practice for continuous ages.
See also
LifeTable.TxStandard commutation function for integer ages only (required for integer ages).
LifeTable.Lx_continuousContinuous version of Lx.
LifeTable.ex_continuousContinuous version of ex.
Examples
>>> from lactuca import LifeTable >>> tbl = LifeTable("PASEM2010", "m") >>> tbl.Tx_continuous(30.5) >>> tbl.Tx_continuous(30.5, m=365) # higher precision >>> tbl.Tx_continuous(30) # integer age — raises ValueError
- ajoint(ages: object, tables_others: Sequence[LifeTable] | Sequence[Sequence[LifeTable]] | None = None, *, ts: object = 0.0, d: object = 0.0, n: object = None, m: Literal[1, 2, 3, 4, 6, 12, 14, 24, 26, 52, 365] | Sequence[int] | NDArray[int64] = 1, gr: GrowthRate | float | None | Sequence[GrowthRate | float | None] = None, cashflow_times: Sequence[float] | None = None, cashflow_amounts: Sequence[float] | None = None, ir: object = None, return_flows: bool = False, t_output: NDArray[float64] | None = None, benefits: Sequence[float] | NDArray[float64] | None = None, on_error: Literal['raise', 'nan'] = 'raise', record_ids: Sequence[Any] | None = None) float | dict[str, NDArray[float64]] | BatchResult#
Compute the present value of a joint-life annuity-immediate (postpayable) for multiple lives.
- Parameters:
ages (Sequence[float] or list[Sequence[float]]) – Ages of all insured lives. Must have at least 2 elements. In batch mode, pass a list of r N-element arrays:
[x0_arr, x1_arr, ..., xr_arr]where each has shape(N,).tables_others (Sequence[LifeTable] or Sequence[Sequence[LifeTable]] or None, optional) –
Life tables for additional lives (all except the first, which uses
self). Three modes in batch:None: usesselffor all lives in all policies.Sequence[LifeTable]of lengthn_lives - 1: shared across all N policies.Sequence[Sequence[LifeTable]]of length N: per-policy; each inner list must have exactlyn_lives - 1elements. RaisesValueErrorif length != N.
ts (float or NDArray[np.float64], optional) – Shift (displacement) in years from origin (default 0.0). In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.d (float or NDArray[np.float64], optional) – Deferment period in years (default 0.0). In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.n (float or NDArray[np.float64] or None, optional) – Total duration of the annuity (in years) from contract start. If None, perpetual annuity up to limiting age. In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.m (PaymentFrequencyLiteral, sequence of int, NDArray[np.int64], or array-like, optional) – Number of payments per year (1=annual, 12=monthly, etc., default 1). Integer-valued floats (e.g.
12.0) are coerced toint. In batch mode, may be a scalar (broadcast to all N) or an array/list of length N for per-policy payment frequencies; distinct values are processed in sub-groups by the vectorised batch engine. Pandas and Polars Series are accepted; values are converted to int64.gr (GrowthRate, float, or None, optional) – Growth rate model (default None). A scalar float is auto-wrapped as
GrowthRate(gr)(geometric growth by default). If None, no growth is applied. In batch mode, may be a scalar or a list of N values. Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asGrowthRatewhere applicable). An object-dtype Series ofGrowthRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.cashflow_times (Sequence[float], optional) – Custom payment times (years since origin). Overrides m when provided. Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.
cashflow_amounts (Sequence[float], optional) – Custom payment amounts. Disables growth when provided. Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.
ir (float, InterestRate, list[float or InterestRate], NDArray[np.float64], or None, optional) – Interest rate (constant or curve). If None, uses
interest_rate. In batch mode, may be a scalar (broadcast to all N), a list of Nfloatvalues, or a list of NInterestRateobjects (one curve per policy). Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asInterestRatewhere applicable). An object-dtype Series ofInterestRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.return_flows (bool, optional) –
If
True, returns a cashflow decomposition dict instead of the present value. The dict schema differs between single-policy and batch:Single-policy (scalar ages): per-payment engine dict. Same keys as
ax(); see that method’sreturn_flowsparameter for the full key reference.Batch (array ages): aggregate portfolio dict
{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.total_pv == np.sum(pv_cf)is always satisfied. RaisesValueErrorindiscrete_simplified/continuous_simplifiedmodes. Default isFalse.t_output (NDArray[np.float64] or None, optional) – External bucketing grid for aggregate flow output. When provided, per-policy present-value contributions — already discounted at their exact payment times — are accumulated into the nearest bucket via
np.searchsortedwithout re-discounting. Controls only the shape ofexpected_cfandpv_cf;total_pvis always the exact sum of all per-payment present values and is identical to thet_output=Noneresult. Typical use: regulatory templates that require a fixed set of time buckets (e.g., annual IFRS 17 disclosure, Solvency II SCR projection). Only effective whenreturn_flows=True; raisesValueErrorifreturn_flows=False. Default isNone. Pandas and Polars Series are accepted; converted to NDArray[np.float64] before use.benefits (sequence of float or NDArray[np.float64] or None, optional) – Per-policy benefit amounts, shape
(N,). Scales each policy’s contribution to the aggregate flows (e.g., sum insured, PVDBO weight). Requires batch mode. Any sequence is accepted, including Pandas and Polars Series; values are converted to float64. Compatible withreturn_flows=False(returnsNDArray[np.float64]with per-policy scaled PVs) andon_error='nan'. Default is None.on_error ({'raise', 'nan'}, optional) – Error handling for invalid ages or parameters.
'raise'(default) raisesValueError;'nan'marks invalid policies withnp.nanand returnsBatchResult. Default is'raise'.record_ids (sequence or None, optional) – Identifiers for each policy in batch mode, used to populate the error report when
on_error='nan'. Length must equalN. Any sequence is accepted, including Pandas and Polars Series; elements are always accessed by positional integer index. Default is None.
- Returns:
float – When
ages[0]is scalar andreturn_flows=False.NDArray[np.float64] – When
ages[0]is an array or list andreturn_flows=False. Shape(N,).dict – When
return_flows=True. Single-policy: per-payment engine dict (same keys asax()). Batch: aggregate portfolio dict{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.BatchResult – When
on_error='nan'.
- Raises:
ValueError – If
ageshas fewer than 2 elements. If any age is outside[0, w]for corresponding table. Ifreturn_flows=Truein batch mode andcalculation_modeis'discrete_simplified'or'continuous_simplified'. Ifreturn_flows=Trueandmis a vector with heterogeneous values. Ift_outputis notNoneandreturn_flows=False. Ift_outputis notNoneand input is scalar (non-batch mode). Ifon_erroris not'raise'or'nan'. Ifon_error='nan'is passed with scalarx(oragesfor joint-life methods). Use a list or array of ages for batch mode. Ifrecord_idsis notNoneand input is scalar (non-batch mode).TypeError – If
agesortables_otherscontain invalid types (includingboolfor numeric kwargs).
Notes
Boolean values for age, duration, shift, deferment, and rate arguments raise
TypeError. Accepted types for each parameter are described in Parameters above.Joint-life annuity-immediate for r lives, under the independence assumption:
\[a_{x_1 x_2 \cdots x_r}^{(m)} = \frac{1}{m} \sum_{k=1}^{\infty} v^{k/m} \prod_{i=1}^{r} {}_{k/m}p_{x_i}\]Payments occur at the END of each period (immediate/postpayable).
Annuity continues while ALL lives survive (joint-life status).
Uses independence assumption for joint survival probabilities.
When
gris provided, the growth factor at the j-th payment interval is(1+g)^(j // m)— anniversaries counted from the first payment, not the contract origin.GrowthRate.shifted(ts)consumesint(ts)anniversary years; fractionaltsis actuarially correct (revaluations operate on complete policy years). See Growth Rate Conventions for the full reference.
Batch mode#
Batch mode is activated when either of these conditions is met:
ages[0]is a list or NDArray (age-triggered batch): pass ages as a list of r N-element arrays, each shape(N,).ages[0]is scalar but any ofn,ts,d,ir,gr, ormis a list/tuple/NDArray of length N (param-triggered batch): scalar ages broadcast to N copies.
In both cases the method returns
NDArray[np.float64]of shape(N,).Per-policy parameters
ts,d,n,ir, andgrmay each be a scalar or an array of length N.mmay be a scalar (broadcast to all N) or an array/list of length N for per-policy payment frequencies; the batch engine groups policies by uniquemvalue and vectorises each group independently.Pandas and Polars Series are accepted for all per-policy parameters (
n,ts,d,ir,gr,m,cashflow_times,cashflow_amounts,t_output, and each per-life age array withinages) without calling.to_numpy(); they are converted internally via the array protocol.on_error='nan'marks invalid policies withnp.nan.In
discrete_precision, irregularcashflow_timesschedules are vectorised in batch (same grid shared across all policies); see Irregular Cashflows.
See also
LifeTable.äjointJoint-life annuity-due (prepayable) for multiple lives.
LifeTable.axyJoint-life annuity-immediate for two lives.
LifeTable.axyzJoint-life annuity-immediate for three lives.
LifeTable.axSingle-life annuity-immediate.
Examples
>>> from lactuca import LifeTable >>> # Four lives with same table >>> table = LifeTable('PER2020_Ind_1o', 'm', cohort=1960) >>> table.interest_rate = 0.03 >>> pv = table.ajoint([65, 60, 55, 50], n=20, m=12)
>>> # Multiple lives with different tables >>> table_m, table_f1, table_f2 = LifeTable('PER2020_Ind_1o', ('m', 'f', 'f'), cohort=1960) >>> table_m.interest_rate = 0.03 >>> pv = table_m.ajoint([65, 62, 58], [table_f1, table_f2], n=15, m=12)
Batch mode:
>>> import numpy as np >>> a0 = np.array([60.0, 65.0]) >>> a1 = np.array([55.0, 60.0]) >>> table.ajoint([a0, a1], n=20, ir=0.03)
- ax(x: object, *, ts: object = 0.0, d: object = 0.0, n: object = None, m: Literal[1, 2, 3, 4, 6, 12, 14, 24, 26, 52, 365] | Sequence[int] | NDArray[int64] = 1, gr: GrowthRate | float | None | Sequence[GrowthRate | float | None] = None, cashflow_times: Sequence[float] | None = None, cashflow_amounts: Sequence[float] | None = None, ir: object = None, return_flows: bool = False, t_output: NDArray[float64] | None = None, benefits: Sequence[float] | NDArray[float64] | None = None, on_error: Literal['raise', 'nan'] = 'raise', record_ids: Sequence[Any] | None = None) float | NDArray[float64] | dict[str, NDArray[float64] | float] | BatchResult#
Compute the present value of an annuity-immediate (postpayable) for a single life.
- Parameters:
x (float or list of float or NDArray[np.float64]) – Initial age. Pass an array or list of N ages to activate batch mode, which returns an
ndarrayof shape(N,).ts (float or NDArray[np.float64], optional) – Shift (displacement) in years from origin. The calculation is performed as if ts years have already elapsed since the contract start. Default is 0.0. In batch mode, may be a scalar (broadcast to all N) or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.d (float or NDArray[np.float64], optional) – Deferment period in years. The first payment is made after d years (plus any shift ts). Default is 0.0. In batch mode, may be a scalar (broadcast to all N) or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.n (float or NDArray[np.float64] or None, optional) – Total duration of the annuity (in years) from the contract start. In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.m (PaymentFrequencyLiteral, sequence of int, NDArray[np.int64], or array-like, optional) – Number of payments per year (1=annual, 12=monthly, etc.). Integer-valued floats (e.g.
12.0) are coerced toint. In batch mode, may be a scalar (broadcast to all N) or an array/list of length N for per-policy payment frequencies; distinct values are processed in sub-groups by the vectorised batch engine. Pandas and Polars Series are accepted; values are converted to int64.gr (GrowthRate, float, or None, optional) – Growth rate model. A scalar float is auto-wrapped as
GrowthRate(gr)(geometric growth by default). If None, no growth is applied. In batch mode, may be a scalar or a list of N values. Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asGrowthRatewhere applicable). An object-dtype Series ofGrowthRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.cashflow_times (sequence of float, optional) – Custom payment times (years since contract origin). When provided, overrides the regular payment grid generated by
m. Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.cashflow_amounts (sequence of float, optional) – Custom payment amounts on the payment grid. When
cashflow_timesisNone, length must equal the regular-grid payment count (\lfloor n_\text{eff} \cdot m \rfloor, plus one whenn_\text{eff} \cdot mis not an integer — see Last Payment Adjustment); whencashflow_timesis provided, length must equallen(cashflow_times). Disables growth (gris ignored) when provided. Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.ir (float, InterestRate, list[float or InterestRate], NDArray[np.float64], or None, optional) – Interest rate (constant or curve). If None, uses
interest_rate. In batch mode, may be a scalar (broadcast to all N), a list of Nfloatvalues, or a list of NInterestRateobjects (one curve per policy). Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asInterestRatewhere applicable). An object-dtype Series ofInterestRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.return_flows (bool, optional) –
If
True, returns a cashflow decomposition dict instead of the present value. The dict schema differs between scalar and batch:Scalar
x,discrete_precision/discrete_simplifiedmode:{ "time_grid" : NDArray # payment times "interest_rate" : NDArray # i(t) at each payment "discount_factor" : NDArray # v^t at each payment "survival_probability" : NDArray # tpx at each payment "growth" or "amount" : NDArray # growth factors or custom amounts "payment_adjustment" : NDArray # fractional last-payment factor "present_value_raw" : NDArray # pv before /m scaling "present_value" : NDArray # final pv per payment (/m applied) }sum(result["present_value"])equals the rounded scalar present value.Scalar
x,continuous_precisionmode:{ "time_grid" : NDArray # integration points "expected_cf" : NDArray # undiscounted density × dt "pv_cf" : NDArray # discounted density × dt }np.sum(result["pv_cf"])approximates the present value.Batch (array
x),*_precisionmodes only:{ "time_grid" : NDArray # portfolio time grid "expected_cf" : NDArray # sum of undiscounted contributions across policies "pv_cf" : NDArray # sum of discounted contributions across policies "total_pv" : float # sum(pv_cf); equals sum of scalar present values }total_pv == np.sum(pv_cf)is always satisfied. RaisesValueErrorindiscrete_simplified/continuous_simplifiedmodes. Default isFalse.t_output (NDArray[np.float64] or None, optional) – External bucketing grid for aggregate flow output. When provided, per-policy present-value contributions — already discounted at their exact payment times — are accumulated into the nearest bucket via
np.searchsortedwithout re-discounting. Controls only the shape ofexpected_cfandpv_cf;total_pvis always the exact sum of all per-payment present values and is identical to thet_output=Noneresult. Typical use: regulatory templates that require a fixed set of time buckets (e.g., annual IFRS 17 disclosure, Solvency II SCR projection). Only effective whenreturn_flows=True; raisesValueErrorifreturn_flows=False. Default isNone. Pandas and Polars Series are accepted; converted to NDArray[np.float64] before use.benefits (sequence of float or NDArray[np.float64] or None, optional) – Per-policy benefit amounts, shape
(N,). Scales each policy’s contribution to the aggregate flows (e.g., sum insured, PVDBO weight). Requires batch mode. Any sequence is accepted, including Pandas and Polars Series; values are converted to float64. Compatible withreturn_flows=False(returnsNDArray[np.float64]with per-policy scaled PVs) andon_error='nan'. Default is None.on_error ({'raise', 'nan'}, optional) – Error handling for invalid ages or parameters.
'raise'(default) raisesValueError;'nan'marks invalid policies withnp.nanand returnsBatchResult. Default is'raise'.record_ids (sequence or None, optional) – Identifiers for each policy in batch mode, used to populate the error report when
on_error='nan'. Length must equalN. Any sequence is accepted, including Pandas and Polars Series; elements are always accessed by positional integer index. Default is None.
- Returns:
float – When
xis scalar andreturn_flows=False.NDArray[np.float64] – When
xis an array or list andreturn_flows=False. Shape(N,). A one-element list returns shape(1,), never float.dict – When
return_flows=True. Schema depends on scalar vs. batch and oncalculation_mode; see thereturn_flowsparameter description above for the full key listing of each variant.BatchResult – When
on_error='nan', wraps the result array and an error report.
- Raises:
ValueError – If no interest rate is provided and
interest_rateisNone. Ifts,d, ornfail timing validation. Ifreturn_flows=Truein batch mode andcalculation_modeis'discrete_simplified'or'continuous_simplified'. Ifreturn_flows=Trueandmis a vector with heterogeneous values; use a scalarmor split the batch by payment frequency. Ift_outputis notNoneandreturn_flows=False. Ift_outputis notNoneand input is scalar (non-batch mode). Ifon_erroris not'raise'or'nan'. Ifon_error='nan'is passed with scalarx(oragesfor joint-life methods). Use a list or array of ages for batch mode. Ifrecord_idsis notNoneand input is scalar (non-batch mode).TypeError – If any argument has an incompatible type (including
boolfor numeric kwargs).
Notes
Boolean values for age, duration, shift, deferment, and rate arguments raise
TypeError. Accepted types for each parameter are described in Parameters above.Discrete whole-life annuity-immediate (\(m\) payments per year):
\[a_x^{(m)} = \frac{1}{m} \sum_{k=1}^{\infty} v^{k/m} \cdot {}_{k/m}p_x\]For a temporary n-year annuity with optional d-year deferment:
\[{}_{d|}a_{x:\overline{n}|}^{(m)} = \frac{1}{m} \sum_{k=dm+1}^{(d+n)m} v^{k/m} \cdot {}_{k/m}p_x\]The calculation is performed at age
x + ts, with effective defermentmax(d - ts, 0)and effective durationmax(n - max(ts - d, 0), 0).If
ts >= d + n(shift absorbs the entire deferred annuity), the value is zero.When
gris provided, the growth factor at the j-th payment is(1+g)^(j // m)— anniversaries are counted from the first payment of the call, not from the contract origin. Defermentdshifts payment times but does not affect this anniversary index. Fractionaltsvalues are accepted;GrowthRate.shifted(ts)discards the firstint(ts)anniversary years of the schedule, which is actuarially correct because revaluations operate on complete policy years. See Growth Rate Conventions for the full reference.
Batch mode#
When
xis alist, sequence, orndarray, or when any ofn,ts,d,ir,gr, ormis a sequence or array with scalarx, the method operates in batch mode and returns anndarrayof shape(N,):A list of 1 element still returns
NDArrayshape(1,), never a scalar.Per-policy parameters
ts,d,n,ir, andgrmay each be a scalar (broadcast to all N policies) or an array of length N.mmay be a scalar (broadcast to all N policies) or an array/list of length N for per-policy payment frequencies; the batch engine groups policies by uniquemvalue and vectorises each group independently.Pandas and Polars Series are accepted for all per-policy parameters (
x,n,ts,d,ir,gr,m,cashflow_times,cashflow_amounts,t_output) without calling.to_numpy(); they are converted internally via the array protocol.discrete_precisionmode dispatches to a vectorised matrix engine (50–250× faster than a Python scalar loop for N ≥ 100).continuous_*modes use a serial fallback (~1.5× vs loop).on_error='nan'marks invalid policies withnp.naninstead of raising; combine withrecord_idsto trace failures.
See also
LifeTable.äxAnnuity-due (prepayable) equivalent.
LifeTable.axyJoint-life annuity-immediate for two lives.
LifeTable.ajointJoint-life annuity-immediate for multiple lives.
Examples
>>> from lactuca import LifeTable >>> lt = LifeTable('PASEM2010', 'm') >>> lt.interest_rate = 0.03 >>> lt.ax(65, n=10, m=12) >>> lt.ax(65) # whole life
Batch mode:
>>> import numpy as np >>> ages = np.array([55.0, 60.0, 65.0, 70.0]) >>> lt.ax(ages, n=20, ir=0.03) >>> lt.ax([65], n=20, ir=0.03) # NDArray shape (1,) >>> lt.ax(ages, n=np.array([25.0, 20.0, 15.0, 10.0]), ir=0.03) >>> Ax = lt.Ax(ages, n=20, ir=0.03) >>> ax_ = lt.ax(ages, n=20, ir=0.03) >>> P = Ax / ax_ # batch net premium, NDArray (4,)
- axy(ages: object, table_y: LifeTable | Sequence[LifeTable] | None = None, *, ts: object = 0.0, d: object = 0.0, n: object = None, m: Literal[1, 2, 3, 4, 6, 12, 14, 24, 26, 52, 365] | Sequence[int] | NDArray[int64] = 1, gr: GrowthRate | float | None | Sequence[GrowthRate | float | None] = None, cashflow_times: Sequence[float] | None = None, cashflow_amounts: Sequence[float] | None = None, ir: object = None, return_flows: bool = False, t_output: NDArray[float64] | None = None, benefits: Sequence[float] | NDArray[float64] | None = None, on_error: Literal['raise', 'nan'] = 'raise', record_ids: Sequence[Any] | None = None) float | NDArray[float64] | dict[str, NDArray[float64]] | BatchResult#
Compute the present value of a joint-life annuity-immediate (postpayable) for two lives.
- Parameters:
ages (Sequence[float] or tuple[Sequence[float], Sequence[float]]) – Ages of the two lives
[x, y]. Must have exactly 2 elements. In batch mode, pass a pair of N-element arrays:(np.array([x0, …, xN]), np.array([y0, …, yN])).table_y (LifeTable or Sequence[LifeTable] or None, optional) –
Life table for the second life (y). Three modes in batch:
None: usesselffor all policies.LifeTable: single shared table for all N policies.Sequence[LifeTable]of length N: per-policy table; policy i uses element i. RaisesValueErrorif length != N.
ts (float or NDArray[np.float64], optional) – Shift (displacement) in years from origin (default 0.0). In batch mode, may be a scalar (broadcast) or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.d (float or NDArray[np.float64], optional) – Deferment period in years (default 0.0). In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.n (float or NDArray[np.float64] or None, optional) – Total duration of the annuity (in years) from contract start. If None, perpetual annuity up to limiting age. In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.m (PaymentFrequencyLiteral, sequence of int, NDArray[np.int64], or array-like, optional) – Number of payments per year (1=annual, 12=monthly, etc., default 1). Integer-valued floats (e.g.
12.0) are coerced toint. In batch mode, may be a scalar (broadcast to all N) or an array/list of length N for per-policy payment frequencies; distinct values are processed in sub-groups by the vectorised batch engine. Pandas and Polars Series are accepted; values are converted to int64.gr (GrowthRate, float, or None, optional) – Growth rate model (default None). A scalar float is auto-wrapped as
GrowthRate(gr)(geometric growth by default). If None, no growth is applied. In batch mode, may be a scalar or a list of N values. Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asGrowthRatewhere applicable). An object-dtype Series ofGrowthRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.cashflow_times (Sequence[float], optional) – Custom payment times (years since origin). Overrides m when provided. Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.
cashflow_amounts (Sequence[float], optional) – Custom payment amounts. Disables growth when provided. Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.
ir (float, InterestRate, list[float or InterestRate], NDArray[np.float64], or None, optional) – Interest rate (constant or curve). If None, uses
interest_rate. In batch mode, may be a scalar (broadcast to all N), a list of Nfloatvalues, or a list of NInterestRateobjects (one curve per policy). Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asInterestRatewhere applicable). An object-dtype Series ofInterestRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.return_flows (bool, optional) –
If
True, returns a cashflow decomposition dict instead of the present value. The dict schema differs between single-policy and batch:Single-policy (scalar ages): per-payment engine dict. Same keys as
ax(); see that method’sreturn_flowsparameter for the full key reference.Batch (array ages): aggregate portfolio dict
{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.total_pv == np.sum(pv_cf)is always satisfied. RaisesValueErrorindiscrete_simplified/continuous_simplifiedmodes. Default isFalse.t_output (NDArray[np.float64] or None, optional) – External bucketing grid for aggregate flow output. When provided, per-policy present-value contributions — already discounted at their exact payment times — are accumulated into the nearest bucket via
np.searchsortedwithout re-discounting. Controls only the shape ofexpected_cfandpv_cf;total_pvis always the exact sum of all per-payment present values and is identical to thet_output=Noneresult. Typical use: regulatory templates that require a fixed set of time buckets (e.g., annual IFRS 17 disclosure, Solvency II SCR projection). Only effective whenreturn_flows=True; raisesValueErrorifreturn_flows=False. Default isNone. Pandas and Polars Series are accepted; converted to NDArray[np.float64] before use.benefits (sequence of float or NDArray[np.float64] or None, optional) – Per-policy benefit amounts, shape
(N,). Scales each policy’s contribution to the aggregate flows (e.g., sum insured, PVDBO weight). Requires batch mode. Any sequence is accepted, including Pandas and Polars Series; values are converted to float64. Compatible withreturn_flows=False(returnsNDArray[np.float64]with per-policy scaled PVs) andon_error='nan'. Default is None.on_error ({'raise', 'nan'}, optional) – Error handling for invalid ages or parameters.
'raise'(default) raisesValueError;'nan'marks invalid policies withnp.nanand returnsBatchResult. Default is'raise'.record_ids (sequence or None, optional) – Identifiers for each policy in batch mode, used to populate the error report when
on_error='nan'. Length must equalN. Any sequence is accepted, including Pandas and Polars Series; elements are always accessed by positional integer index. Default is None.
- Returns:
float – When
ages[0]is scalar andreturn_flows=False.NDArray[np.float64] – When
ages[0]is an array or list andreturn_flows=False. Shape(N,).dict – When
return_flows=True. Single-policy: per-payment engine dict (same keys asax()). Batch: aggregate portfolio dict{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.BatchResult – When
on_error='nan'.
- Raises:
ValueError – If
agesdoes not contain exactly 2 elements. If any age is outside[0, w]for corresponding table. Ifreturn_flows=Truein batch mode andcalculation_modeis'discrete_simplified'or'continuous_simplified'. Ifreturn_flows=Trueandmis a vector with heterogeneous values. Ift_outputis notNoneandreturn_flows=False. Ift_outputis notNoneand input is scalar (non-batch mode). Ifon_erroris not'raise'or'nan'. Ifon_error='nan'is passed with scalarx(oragesfor joint-life methods). Use a list or array of ages for batch mode. Ifrecord_idsis notNoneand input is scalar (non-batch mode).TypeError – If
agescontains non-numeric values. Iftable_yis not a LifeTable instance or None. If any numeric argument isbool.
Notes
Boolean values for age, duration, shift, deferment, and rate arguments raise
TypeError. Accepted types for each parameter are described in Parameters above.Joint-life annuity-immediate for two lives, under the independence assumption:
\[a_{xy}^{(m)} = \frac{1}{m} \sum_{k=1}^{\infty} v^{k/m} \cdot {}_{k/m}p_x \cdot {}_{k/m}p_y\]Payments occur at the END of each period (immediate/postpayable).
Annuity continues while BOTH lives survive (joint-life status).
Uses independence assumption for joint survival probabilities.
When
gris provided, the growth factor at the j-th payment interval is(1+g)^(j // m)— anniversaries counted from the first payment, not the contract origin.GrowthRate.shifted(ts)consumesint(ts)anniversary years; fractionaltsis actuarially correct (revaluations operate on complete policy years). See Growth Rate Conventions for the full reference.
Batch mode#
Batch mode is activated when either of these conditions is met:
ages[0]is a list or NDArray (age-triggered batch): pass ages as a pair of N-element arrays(x_arr, y_arr), each shape(N,).ages[0]is scalar but any ofn,ts,d,ir,gr, ormis a list/tuple/NDArray of length N (param-triggered batch): scalar ages broadcast to N copies.
In both cases the method returns
NDArray[np.float64]of shape(N,).Per-policy parameters
ts,d,n,ir, andgrmay each be a scalar (broadcast to all N) or an array of length N.mmay be a scalar (broadcast to all N policies) or an array/list of length N for per-policy payment frequencies; the batch engine groups policies by uniquemvalue and vectorises each group independently.discrete_precisionmode uses a vectorised matrix engine (50–250× faster than a Python scalar loop for N ≥ 100).continuous_*modes use a serial fallback (~1.5× vs loop).return_flows=Truewith batch ages returns aggregate portfolio flows.Pandas and Polars Series are accepted for all per-policy parameters (
n,ts,d,ir,gr,m,cashflow_times,cashflow_amounts,t_output, and each per-life age array withinages) without calling.to_numpy(); they are converted internally via the array protocol.on_error='nan'marks invalid policies withnp.nan.
See also
LifeTable.äxyJoint-life annuity-due for two lives.
LifeTable.ajointJoint-life annuity-immediate for multiple lives.
LifeTable.axyzJoint-life annuity-immediate for three lives.
LifeTable.axSingle-life annuity-immediate.
Examples
>>> from lactuca import LifeTable >>> # Two lives with same table >>> table = LifeTable('PER2020_Ind_1o', 'm', cohort=1960) >>> table.interest_rate = 0.03 >>> pv = table.axy([65, 60], n=20, m=12)
>>> # Two lives with different tables >>> table_m, table_f = LifeTable('PER2020_Ind_1o', ('m', 'f'), cohort=1960) >>> table_m.interest_rate = 0.03 >>> pv = table_m.axy([65, 62], table_f, n=15, m=12)
Batch mode:
>>> import numpy as np >>> x_ages = np.array([60.0, 65.0, 70.0]) >>> y_ages = np.array([55.0, 60.0, 65.0]) >>> table.axy((x_ages, y_ages), n=20, ir=0.03) >>> table.axy((x_ages, y_ages), n=np.array([25.0, 20.0, 15.0]), ir=0.03)
- axyz(ages: object, tables_yz: Sequence[LifeTable] | Sequence[Sequence[LifeTable]] | None = None, *, ts: object = 0.0, d: object = 0.0, n: object = None, m: Literal[1, 2, 3, 4, 6, 12, 14, 24, 26, 52, 365] | Sequence[int] | NDArray[int64] = 1, gr: GrowthRate | float | None | Sequence[GrowthRate | float | None] = None, cashflow_times: Sequence[float] | None = None, cashflow_amounts: Sequence[float] | None = None, ir: object = None, return_flows: bool = False, t_output: NDArray[float64] | None = None, benefits: Sequence[float] | NDArray[float64] | None = None, on_error: Literal['raise', 'nan'] = 'raise', record_ids: Sequence[Any] | None = None) float | dict[str, NDArray[float64]] | BatchResult#
Compute the present value of a joint-life annuity-immediate (postpayable) for three lives.
- Parameters:
ages (Sequence[float] or tuple[Sequence[float], Sequence[float], Sequence[float]]) – Ages of the three lives
[x, y, z]. Must have exactly 3 elements. In batch mode, pass a triple of N-element arrays:(np.array([x0, …]), np.array([y0, …]), np.array([z0, …])).tables_yz (Sequence[LifeTable] or Sequence[Sequence[LifeTable]] or None, optional) –
Life tables for the second and third lives [y, z]. Three modes in batch:
None: usesselffor all three lives in all policies.[LifeTable, LifeTable]: 2 shared tables (y and z) for all N policies.[[t_y0, t_z0], …]of length N: per-policy; each inner list must have exactly 2 elements. RaisesValueErrorif outer length != N.
ts (float or NDArray[np.float64], optional) – Shift (displacement) in years from origin (default 0.0). In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.d (float or NDArray[np.float64], optional) – Deferment period in years (default 0.0). In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.n (float or NDArray[np.float64] or None, optional) – Total duration of the annuity (in years) from contract start. If None, perpetual annuity up to limiting age. In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.m (PaymentFrequencyLiteral, sequence of int, NDArray[np.int64], or array-like, optional) – Number of payments per year (1=annual, 12=monthly, etc., default 1). Integer-valued floats (e.g.
12.0) are coerced toint. In batch mode, may be a scalar (broadcast to all N) or an array/list of length N for per-policy payment frequencies; distinct values are processed in sub-groups by the vectorised batch engine. Pandas and Polars Series are accepted; values are converted to int64.gr (GrowthRate, float, or None, optional) – Growth rate model (default None). A scalar float is auto-wrapped as
GrowthRate(gr)(geometric growth by default). If None, no growth is applied. In batch mode, may be a scalar or a list of N values. Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asGrowthRatewhere applicable). An object-dtype Series ofGrowthRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.cashflow_times (Sequence[float], optional) – Custom payment times (years since origin). Overrides m when provided. Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.
cashflow_amounts (Sequence[float], optional) – Custom payment amounts. Disables growth when provided. Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.
ir (float, InterestRate, list[float or InterestRate], NDArray[np.float64], or None, optional) – Interest rate (constant or curve). If None, uses
interest_rate. In batch mode, may be a scalar (broadcast to all N), a list of Nfloatvalues, or a list of NInterestRateobjects (one curve per policy). Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asInterestRatewhere applicable). An object-dtype Series ofInterestRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.return_flows (bool, optional) –
If
True, returns a cashflow decomposition dict instead of the present value. The dict schema differs between single-policy and batch:Single-policy (scalar ages): per-payment engine dict. Same keys as
ax(); see that method’sreturn_flowsparameter for the full key reference.Batch (array ages): aggregate portfolio dict
{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.total_pv == np.sum(pv_cf)is always satisfied. RaisesValueErrorindiscrete_simplified/continuous_simplifiedmodes. Default isFalse.t_output (NDArray[np.float64] or None, optional) – External bucketing grid for aggregate flow output. When provided, per-policy present-value contributions — already discounted at their exact payment times — are accumulated into the nearest bucket via
np.searchsortedwithout re-discounting. Controls only the shape ofexpected_cfandpv_cf;total_pvis always the exact sum of all per-payment present values and is identical to thet_output=Noneresult. Typical use: regulatory templates that require a fixed set of time buckets (e.g., annual IFRS 17 disclosure, Solvency II SCR projection). Only effective whenreturn_flows=True; raisesValueErrorifreturn_flows=False. Default isNone. Pandas and Polars Series are accepted; converted to NDArray[np.float64] before use.
- Returns:
float – When
ages[0]is scalar andreturn_flows=False.NDArray[np.float64] – When
ages[0]is an array or list andreturn_flows=False. Shape(N,).dict – When
return_flows=True. Single-policy: per-payment engine dict (same keys asax()). Batch: aggregate portfolio dict{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.BatchResult – When
on_error='nan'.
- Raises:
ValueError – If
agesdoes not contain exactly 3 elements. Iftables_yzis provided but does not contain exactly 2 LifeTable instances. If any age is outside[0, w]for corresponding table. Ifreturn_flows=Trueandmis a vector with heterogeneous values. Ift_outputis notNoneandreturn_flows=False. Ift_outputis notNoneand input is scalar (non-batch mode). Ifon_erroris not'raise'or'nan'. Ifon_error='nan'is passed with scalarx(oragesfor joint-life methods). Use a list or array of ages for batch mode. Ifrecord_idsis notNoneand input is scalar (non-batch mode).TypeError – If
agescontains non-numeric values. Iftables_yzcontains non-LifeTable instances. If any numeric argument isbool.
Notes
Boolean values for age, duration, shift, deferment, and rate arguments raise
TypeError. Accepted types for each parameter are described in Parameters above.Joint-life annuity-immediate for three lives, under the independence assumption:
\[a_{xyz}^{(m)} = \frac{1}{m} \sum_{k=1}^{\infty} v^{k/m} \cdot {}_{k/m}p_x \cdot {}_{k/m}p_y \cdot {}_{k/m}p_z\]Payments occur at the END of each period (immediate/postpayable).
Annuity continues while ALL THREE lives survive (joint-life status).
Uses independence assumption for joint survival probabilities.
When
gris provided, the growth factor at the j-th payment interval is(1+g)^(j // m)— anniversaries counted from the first payment, not the contract origin.GrowthRate.shifted(ts)consumesint(ts)anniversary years; fractionaltsis actuarially correct (revaluations operate on complete policy years). See Growth Rate Conventions for the full reference.
Batch mode#
Batch mode is activated when either of these conditions is met:
ages[0]is a list or NDArray (age-triggered batch): pass ages as a triple of N-element arrays(x_arr, y_arr, z_arr), each(N,).ages[0]is scalar but any ofn,ts,d,ir,gr, ormis a list/tuple/NDArray of length N (param-triggered batch): scalar ages broadcast to N copies.
In both cases the method returns
NDArray[np.float64]of shape(N,).Per-policy parameters
ts,d,n,ir, andgrmay each be a scalar or an array of length N.mmay be a scalar (broadcast to all N) or an array/list of length N for per-policy payment frequencies; the batch engine groups policies by uniquemvalue and vectorises each group independently.Pandas and Polars Series are accepted for all per-policy parameters (
n,ts,d,ir,gr,m,cashflow_times,cashflow_amounts,t_output, and each per-life age array withinages) without calling.to_numpy(); they are converted internally via the array protocol.on_error='nan'marks invalid policies withnp.nan.
See also
LifeTable.äxyzJoint-life annuity-due for three lives.
LifeTable.ajointJoint-life annuity-immediate for multiple lives.
LifeTable.axyJoint-life annuity-immediate for two lives.
LifeTable.axSingle-life annuity-immediate.
Examples
>>> from lactuca import LifeTable >>> # Three lives with same table >>> table = LifeTable('PER2020_Ind_1o', 'm', cohort=1960) >>> table.interest_rate = 0.03 >>> pv = table.axyz([65, 60, 55], n=20, m=12)
>>> # Three lives with different tables >>> table_m, table_f1, table_f2 = LifeTable('PER2020_Ind_1o', ('m', 'f', 'f'), cohort=1960) >>> table_m.interest_rate = 0.03 >>> pv = table_m.axyz([65, 62, 58], [table_f1, table_f2], n=15, m=12)
Batch mode:
>>> import numpy as np >>> x_arr = np.array([60.0, 65.0]) >>> y_arr = np.array([55.0, 60.0]) >>> z_arr = np.array([50.0, 55.0]) >>> table.axyz((x_arr, y_arr, z_arr), n=20, ir=0.03)
- configure(sex: ~typing.Literal['m', 'f', 'u'] | None = None, cohort: int | None = None, duration: object = None, unisex_blend: object = None, interest_rate: object = <object object>, **_extra) LifeTable#
Configure table metadata and optional interest rate atomically.
Extends
configure()withinterest_ratesupport. The interest rate is applied after the structural rebuild so it never triggers an additional rebuild.- Parameters:
sex ({'m', 'f', 'u'} or None, optional) – Sex for calculations.
Nonemeans “keep current value”.cohort (int or None, optional) – Birth cohort year for generational tables.
Nonemeans “keep current value”.duration (int, 'ult', or None, optional) – Select-table duration.
Nonemeans “keep current value”.unisex_blend (float or None, optional) – Male weight for unisex blending in
[0.0, 1.0].Nonemeans “keep current value”.interest_rate (float, InterestRate, or None, optional) – Default interest rate for annuity/insurance calculations. Omitting this argument leaves the current interest rate unchanged. Pass
Noneexplicitly to clear the interest rate.
- Returns:
self, enabling method chaining.- Return type:
- Raises:
ValueError – If called with no arguments, or if a supplied value is invalid.
TypeError – If a supplied value has an incorrect type.
Notes
interest_rateis applied after the structural rebuild (sex,cohort,duration,unisex_blend) so it never triggers an additional recomputation. Passing onlyinterest_rateis valid and does not perform any structural rebuild.Omitting
interest_rateentirely (the default) leaves the current rate unchanged. Passinterest_rate=Noneexplicitly to clear the rate so that subsequent calculations require an explicitir=argument.See also
configure()Base implementation (without
interest_rate).batch_update()Context manager for imperative setter-based configuration.
LifeTable.interest_rateProperty setter for updating the rate without a full rebuild.
Examples
>>> from lactuca import LifeTable >>> lt = LifeTable('PER2020_Ind_1o', pending=True) >>> lt.configure(sex='m', cohort=1969, interest_rate=0.03).ax(65)
- ex(x: object) object#
Compute the complete expectation of life
exat integer ages.For fractional ages use
ex_continuous(). Defined for integer ages only.- Parameters:
x (int, sequence of int, or NDArray[np.float64]) – Integer age(s). Pandas and Polars Series of int are accepted. Non-integer ages will raise ValueError.
- Returns:
Complete expectation of life at integer age(s) x.
- Return type:
float or np.ndarray
- Raises:
ValueError – If any
xis outside[0, w]or contains non-integer ages.TypeError – If
xis not numeric or a sequence of numerics.
Notes
The method name
exis a short API alias; the actuarial symbol is \(\mathring{e}_x\) (complete expectation of life), not curtate \(e_x = \sum_{k=1}^{\omega-x} {}_kp_x\).Defined as \(\mathring{e}_x = T_x / l_x\), the complete expectation of life.
\(e_{\omega} = 0\) if all decrements occur at age \(\omega\).
All results are rounded to the configured number of decimals (
decimals.ex).Fully vectorized and optimized for speed and memory.
Follows international actuarial practice.
See also
LifeTable.TxTotal person-years lived from age x (integer ages only).
LifeTable.ex_curtateCurtate expectation \(e_x\) at integer ages.
LifeTable.ex_continuousContinuous version of ex for fractional ages.
LifeTable.LxPerson-years lived in [x, x+1) under UDD (integer ages only).
Examples
>>> from lactuca import LifeTable >>> tbl = LifeTable('PASEM2010', 'm') >>> tbl.ex(65) >>> tbl.ex([60, 65, 70]) # vectorized
- ex_continuous(x: object, *, m: object = 12) object#
Compute ex at fractional ages using numerical integration.
Accepts only fractional ages; for integer ages use
ex()instead. Computes \(\mathring{e}_x = T_x / l_x\) where \(T_x\) is evaluated via numerical integration.- Parameters:
x (float, list, or np.ndarray) – Fractional age(s) to evaluate. Integer ages will raise ValueError. Use ex() for integer ages.
m (object, optional) – Trapezoidal integration density (default
12). Interpreted as the number of subintervals per unit age span; must be a value fromVALID_PAYMENT_FREQUENCIES:1, 2, 3, 4, 6, 12, 14, 24, 26, 52, 365. Highermincreases precision at greater computational cost.boolis rejected.
- Returns:
Complete expectation of life at age(s) x.
- Return type:
float or np.ndarray
- Raises:
ValueError – If any
xis an integer age, outside[0, w], ormis not a valid payment frequency. For integer ages, useex()instead.TypeError – If
xis not numeric or a sequence of numerics, or ifmis abool.
Notes
Computes \(\mathring{e}_x = T_x / l_x\) using numerical integration for \(T_x = \int_x^{\infty} l_t \, dt\).
Strictly for fractional (non-integer) ages; use
ex()for integer ages.Each \(l_t\) evaluation uses the configured interpolation method.
This is the complete (entire) expectation of life, not the curtate expectation.
\(e_{\omega} = 0\) if all decrements occur at age \(\omega\).
All results are rounded to the configured number of decimals (
decimals.ex).Fully vectorized and optimized for speed and memory.
Follows international actuarial practice for continuous ages.
See also
LifeTable.exStandard commutation function for integer ages only (required for integer ages).
LifeTable.ex_curtateCurtate expectation \(e_x\) at integer ages.
LifeTable.Tx_continuousContinuous version of Tx.
LifeTable.Lx_continuousContinuous version of Lx.
Examples
>>> from lactuca import LifeTable >>> tbl = LifeTable("PASEM2010", "m") >>> tbl.ex_continuous(30.5) >>> tbl.ex_continuous(30.5, m=365) # higher precision >>> tbl.ex_continuous(30) # integer age — raises ValueError
- ex_curtate(x: object) object#
Compute the curtate expectation of life \(e_x\) at integer ages.
For fractional ages use
ex_continuous()(complete only). Defined for integer ages only.- Parameters:
x (int, sequence of int, or NDArray[np.float64]) – Integer age(s). Pandas and Polars Series of int are accepted. Non-integer ages will raise ValueError.
- Returns:
Curtate expectation of life at integer age(s) x.
- Return type:
float or np.ndarray
- Raises:
ValueError – If any
xis outside[0, w]or contains non-integer ages.TypeError – If
xis not numeric or a sequence of numerics.
Notes
Computes \(e_x = \sum_{k=1}^{\omega-x} {}_k p_x\) as the sum of
tpx()values (each term rounded perdecimals.tpx).Under UDD, the complete expectation from
ex()satisfies \(\mathring{e}_x \approx e_x + \tfrac{1}{2}\) (approximation after rounding; not an exact identity).\(e_{\omega} = 0\) if all decrements occur at age \(\omega\).
If \(l_x = 0\), returns 0 (consistent with
ex()).All results are rounded to
decimals.ex.Fully vectorized and optimized for speed and memory.
See also
LifeTable.exComplete expectation \(\mathring{e}_x = T_x / l_x\).
LifeTable.ex_continuousComplete expectation at fractional ages.
LifeTable.tpxSurvival probabilities used in the sum.
Examples
>>> from lactuca import LifeTable >>> tbl = LifeTable('PASEM2010', 'm') >>> tbl.ex_curtate(65) >>> tbl.ex_curtate([60, 65, 70]) # vectorized
- ix(x=None, m: object = 1) None#
Disability incidence rate (blocked for LifeTable).
- Parameters:
x (optional) – Ignored placeholder maintained for API compatibility with other decrement-table subclasses.
m (object, optional) – Ignored payment-frequency placeholder maintained for API compatibility. Must be a valid payment frequency if provided;
boolis rejected.
- Raises:
TypeError – If
mis abool.NotImplementedError – Always raised. LifeTable uses qx (mortality) instead of ix (disability incidence).
Notes
ixis only available in DisabilityTable.For mortality tables, use
qx()instead.
See also
DisabilityTableTable type that provides ix rates.
Examples
>>> from lactuca import LifeTable >>> lt = LifeTable('PASEM2010', 'm') >>> lt.ix(65) NotImplementedError: ix is not available for LifeTable. Use qx() instead.
- nEjoint(ages: object, tables_others: Sequence[LifeTable] | Sequence[Sequence[LifeTable]] | None = None, *, n: object, ts: object = 0.0, ir: object = None, return_flows: bool = False, t_output: NDArray[float64] | None = None, benefits: Sequence[float] | NDArray[float64] | None = None, on_error: Literal['raise', 'nan'] = 'raise', record_ids: Sequence[Any] | None = None) float | dict[str, NDArray[float64]] | BatchResult#
Compute the present value of a joint-life pure endowment for multiple lives.
Pays one unit of benefits conditional on survival of ALL lives to time
n, discounted to the contract start. Joint survival is computed under the independence assumption.- Parameters:
ages (Sequence[float] or list[Sequence[float]]) – Ages of all lives at contract start. Must contain at least 2 elements. In batch mode, pass a list of r N-element arrays:
[x0_arr, x1_arr, ..., xr_arr]where each has shape(N,).tables_others (Sequence[LifeTable] or Sequence[Sequence[LifeTable]] or None, optional) –
Life tables for additional lives (all except the first, which uses
self). Three modes in batch:None: usesselffor all lives in all policies.Sequence[LifeTable]of lengthn_lives - 1: shared across all N policies.Sequence[Sequence[LifeTable]]of length N: per-policy; each inner list must have exactlyn_lives - 1elements. RaisesValueErrorif length != N.
n (float or NDArray[np.float64]) – Duration of the endowment in years. Must be positive. Can be fractional (e.g., 10.5 years). In batch mode, may be a scalar or an array of length N. Scalar
intliterals (e.g.n=10) are coerced tofloat.ts (float or NDArray[np.float64], optional) – Time shift in years from contract origin. Default is 0.0. Used for reserve calculations. Adjusts all ages (
ages + ts) and effective duration (n - ts). Must be non-negative. In batch mode, may be a scalar or an array of length N.ir (float, InterestRate, NDArray[np.float64], or None, optional) – Interest rate for discounting. If None, uses
interest_rate. In batch mode, may be a scalar or a list of N values. Pandas and Polars Series are accepted; values are converted to float64. Scalarintliterals (e.g.n=10) are coerced tofloat.return_flows (bool, optional) –
If
True, returns a cashflow decomposition dict instead of the present value. The dict schema differs between single-policy and batch:Single-policy (scalar ages): per-payment engine dict. Same keys as
nEx(); see that method’sreturn_flowsparameter for the full key reference.Batch (array ages): aggregate portfolio dict
{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.total_pv == np.sum(pv_cf)is always satisfied. RaisesValueErrorindiscrete_simplified/continuous_simplifiedmodes. Default isFalse.t_output (NDArray[np.float64] or None, optional) – External bucketing grid for aggregate flow output. When provided, per-policy present-value contributions — already discounted at their exact payment times — are accumulated into the nearest bucket via
np.searchsortedwithout re-discounting. Controls only the shape ofexpected_cfandpv_cf;total_pvis always the exact sum of all per-payment present values and is identical to thet_output=Noneresult. Typical use: regulatory templates that require a fixed set of time buckets (e.g., annual IFRS 17 disclosure, Solvency II SCR projection). Only effective whenreturn_flows=True; raisesValueErrorifreturn_flows=False. Default isNone. Pandas and Polars Series are accepted; converted to NDArray[np.float64] before use.benefits (sequence of float or NDArray[np.float64] or None, optional) – Per-policy benefit amounts, shape
(N,). Scales each policy’s contribution to the aggregate flows (e.g., sum insured, PVDBO weight). Requires batch mode. Any sequence is accepted, including Pandas and Polars Series; values are converted to float64. Compatible withreturn_flows=False(returnsNDArray[np.float64]with per-policy scaled PVs) andon_error='nan'. Default is None.on_error ({'raise', 'nan'}, optional) – Error handling for invalid ages or parameters.
'raise'(default) raisesValueError;'nan'marks invalid policies withnp.nanand returnsBatchResult. Default is'raise'.record_ids (sequence or None, optional) – Identifiers for each policy in batch mode, used to populate the error report when
on_error='nan'. Length must equalN. Any sequence is accepted, including Pandas and Polars Series; elements are always accessed by positional integer index. Default is None.
- Returns:
float – When
ages[0]is scalar andreturn_flows=False.NDArray[np.float64] – When
ages[0]is an array or list andreturn_flows=False. Shape(N,).dict – When
return_flows=True. Single-policy: per-payment engine dict (same keys asnEx()). Batch: aggregate portfolio dict{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.BatchResult – When
on_error='nan'.
- Raises:
ValueError – If
agescontains fewer than 2 elements. Iftables_othersis provided but length does not matchlen(ages) - 1. If any age is outside[0, w]for the corresponding mortality table. Ifn <= 0. Ifts < 0. If no interest rate is provided andinterest_rateis None. If anyage + nexceeds the table limitw. Ifreturn_flows=Truein batch mode andcalculation_modeis'discrete_simplified'or'continuous_simplified'. Ift_outputis notNoneandreturn_flows=False. Ift_outputis notNoneand input is scalar (non-batch mode). Ifon_erroris not'raise'or'nan'. Ifon_error='nan'is passed with scalarx(oragesfor joint-life methods). Use a list or array of ages for batch mode. Ifrecord_idsis notNoneand input is scalar (non-batch mode).TypeError – If
agescontains non-numeric values. Iftables_otherscontains non-LifeTable instances. Ifiris not float, InterestRate, or None. If any numeric argument isbool.
Notes
Boolean values for age, duration, shift, and rate arguments raise
TypeError. Accepted types for each parameter are described in Parameters above.Joint pure endowment formula for \(k\) lives: \({}_{n}E_{x_1 \cdots x_k} = \vx{n} \cdot \prod_{i=1}^{k} \px{n}{x_i}\).
Joint survival computed under the independence assumption: \(\prod_{i=1}^{k} {}_{n}p_{x_i}\).
Uses
self.calculation_modefromConfigto select the method:discrete_precision,discrete_simplified,continuous_precision, orcontinuous_simplified.
Batch mode#
Batch mode is activated when either of these conditions is met:
ages[0]is a list or NDArray (age-triggered batch): pass ages as a list of r N-element arrays, each shape(N,).ages[0]is scalar but any ofn,ts, oriris a list/tuple/NDArray of length N (param-triggered batch): scalar ages broadcast to N copies.
In both cases the method returns
NDArray[np.float64]of shape(N,).Per-policy parameters
n,ts, andirmay each be a scalar or an array of length N.Pandas and Polars Series are accepted for
n,ts, andirwithout calling.to_numpy(); they are converted internally via the array protocol.Each per-life age array within
agesmay also be a Pandas or Polars Series; it is converted internally via the array protocol.on_error='nan'marks invalid policies withnp.nan.
Examples
>>> from lactuca import LifeTable >>> table = LifeTable('PER2020_Ind_1o', 'm', cohort=1960) >>> table.interest_rate = 0.03 >>> pv = table.nEjoint([65, 60, 55, 50], n=10)
Batch mode:
>>> import numpy as np >>> a0 = np.array([60.0, 65.0]) >>> a1 = np.array([55.0, 60.0]) >>> a2 = np.array([50.0, 55.0]) >>> table.nEjoint([a0, a1, a2], n=10, ir=0.03)
See also
LifeTable.nExSingle-life pure endowment.
LifeTable.nExyJoint-life pure endowment for two lives.
LifeTable.nExyzJoint-life pure endowment for three lives.
- nEx(x: object, *, n: object, ts: object = 0.0, ir: object = None, return_flows: bool = False, t_output: NDArray[float64] | None = None, benefits: Sequence[float] | NDArray[float64] | None = None, on_error: Literal['raise', 'nan'] = 'raise', record_ids: Sequence[Any] | None = None) float | NDArray[float64] | dict[str, NDArray[float64] | float] | BatchResult#
Compute the present value of a pure endowment for a single life.
Pays one unit of benefits conditional on the insured’s survival to time
n, discounted to the contract start.- Parameters:
x (float or list of float or NDArray[np.float64]) – Initial age at contract start. Pass an array or list of N ages to activate batch mode, which returns an
ndarrayof shape(N,).n (float or NDArray[np.float64]) – Duration of the endowment in years. Must be positive. Can be fractional (e.g., 10.5 years) with appropriate interpolation. In batch mode, may be a scalar (broadcast to all N) or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.ts (float or NDArray[np.float64], optional) – Time shift (displacement) in years from contract origin. Default is 0.0. Used for reserve calculations where some time has elapsed since contract start. In batch mode, may be a scalar or an array of length N. Adjusts both age (
x + ts) and effective duration (n - ts). Must be non-negative. Pandas and Polars Series are accepted; values are converted to float64. Scalarintliterals (e.g.n=10) are coerced tofloat.ir (float, InterestRate, or None, optional) – Interest rate for discounting. If None, uses
interest_rate. Can be a constant rate (float) or an interest rate curve (InterestRate). In batch mode, may be a scalar or a list of N values. Pandas and Polars Series are accepted; values are converted to float64. Scalarintliterals (e.g.n=10) are coerced tofloat.return_flows (bool, optional) –
If
True, returns a cashflow decomposition dict instead of the present value. The dict schema differs between scalar and batch:Scalar
x(all modes):{ "time_grid" : NDArray # [n] — single payment at maturity "joint_survival_probability" : float # tpx(n) "discount_factor" : float # v^n "expected_cf" : NDArray # [tpx(n)] — undiscounted unit cf "pv_cf" : NDArray # [tpx(n) * v^n] — discounted cf "total_pv" : float # = pv_cf[0] }result["total_pv"]equals the rounded scalar present value.Batch (array
x),*_precisionmodes only:{ "time_grid" : NDArray # portfolio time grid "expected_cf" : NDArray # sum of undiscounted contributions across policies "pv_cf" : NDArray # sum of discounted contributions across policies "total_pv" : float # sum(pv_cf); equals sum of scalar present values }total_pv == np.sum(pv_cf)is always satisfied. RaisesValueErrorindiscrete_simplified/continuous_simplifiedmodes. Default isFalse.t_output (NDArray[np.float64] or None, optional) – External bucketing grid for aggregate flow output. When provided, per-policy present-value contributions — already discounted at their exact payment times — are accumulated into the nearest bucket via
np.searchsortedwithout re-discounting. Controls only the shape ofexpected_cfandpv_cf;total_pvis always the exact sum of all per-payment present values and is identical to thet_output=Noneresult. Typical use: regulatory templates that require a fixed set of time buckets (e.g., annual IFRS 17 disclosure, Solvency II SCR projection). Only effective whenreturn_flows=True; raisesValueErrorifreturn_flows=False. Default isNone. Pandas and Polars Series are accepted; converted to NDArray[np.float64] before use.benefits (sequence of float or NDArray[np.float64] or None, optional) – Per-policy benefit amounts, shape
(N,). Scales each policy’s contribution to the aggregate flows (e.g., sum insured, PVDBO weight). Requires batch mode. Any sequence is accepted, including Pandas and Polars Series; values are converted to float64. Compatible withreturn_flows=False(returnsNDArray[np.float64]with per-policy scaled PVs) andon_error='nan'. Default is None.on_error ({'raise', 'nan'}, optional) – Error handling for invalid ages or parameters.
'raise'(default) raisesValueError;'nan'marks invalid policies withnp.nanand returnsBatchResult. Default is'raise'.record_ids (sequence or None, optional) – Identifiers for each policy in batch mode, used to populate the error report when
on_error='nan'. Length must equalN. Any sequence is accepted, including Pandas and Polars Series; elements are always accessed by positional integer index. Default is None.
- Returns:
float – When
xis scalar andreturn_flows=False.NDArray[np.float64] – When
xis an array or list andreturn_flows=False. Shape(N,).dict – When
return_flows=True. Schema depends on scalar vs. batch; see thereturn_flowsparameter description above for the full key listing. Scalar returns a single-element time grid (one payment at maturityn). Batch returns a portfolio aggregate over the shared time grid.BatchResult – When
on_error='nan', wraps the result array and an error report.
- Raises:
ValueError – If
xis outside[0, w]for the mortality table. Ifn <= 0. Ifts < 0. If no interest rate is provided andinterest_rateis None. Ifx + nexceeds the table limitw. Ifreturn_flows=Truein batch mode andcalculation_modeis'discrete_simplified'or'continuous_simplified'. Ift_outputis notNoneandreturn_flows=False. Ift_outputis notNoneand input is scalar (non-batch mode). Ifon_erroris not'raise'or'nan'. Ifon_error='nan'is passed with scalarx(oragesfor joint-life methods). Use a list or array of ages for batch mode. Ifrecord_idsis notNoneand input is scalar (non-batch mode).TypeError – If
xornare not numeric. Ifiris not float, InterestRate, or None. If any numeric argument isbool.
Notes
Boolean values for age, duration, shift, and rate arguments raise
TypeError. Accepted types for each parameter are described in Parameters above.Pure endowment formula: \({}_{n}E_x = \vx{n} \cdot \px{n}{x}\).
nExdoes not accept agrparameter (pure endowments are not revalued).Uses
self.calculation_modefromConfigto select the method:discrete_precision,discrete_simplified,continuous_precision, orcontinuous_simplified.Fractional
tsis accepted; setConfig.force_integer_ts = Trueto reject non-integer shifts.
Batch mode#
When
xis alist, sequence, orndarray, or when any ofn,ts, oriris a sequence or array with scalarx, the method operates in batch mode and returns anndarrayof shape(N,):A list of 1 element still returns
NDArrayshape(1,), never a scalar.Per-policy parameters
n,ts, andirmay each be a scalar (broadcast to all N policies) or an array of length N.discrete_precisionmode uses a vectorised engine (50–250× vs scalar loop).continuous_*modes use a serial fallback (~1.5× vs loop).Pandas and Polars Series are accepted for
n,ts, andirwithout calling.to_numpy(); they are converted internally via the array protocol.on_error='nan'marks invalid policies withnp.naninstead of raising.
Examples
>>> from lactuca import LifeTable >>> table = LifeTable('PER2020_Ind_1o', 'm', cohort=1960) >>> table.interest_rate = 0.03 >>> pv = table.nEx(65, n=10)
>>> # With time shift (reserve after 5 years have elapsed) >>> pv_t5 = table.nEx(65, n=10, ts=5)
Batch mode:
>>> import numpy as np >>> ages = np.array([55.0, 60.0, 65.0, 70.0]) >>> table.nEx(ages, n=10, ir=0.03) >>> table.nEx(ages, n=np.array([15.0, 12.0, 10.0, 8.0]), ir=0.03)
See also
LifeTable.nExyJoint-life pure endowment for two lives.
LifeTable.nExyzJoint-life pure endowment for three lives.
LifeTable.nEjointJoint-life pure endowment for multiple lives.
- nExy(ages: object, table_y: LifeTable | Sequence[LifeTable] | None = None, *, n: object, ts: object = 0.0, ir: object = None, return_flows: bool = False, t_output: NDArray[float64] | None = None, benefits: Sequence[float] | NDArray[float64] | None = None, on_error: Literal['raise', 'nan'] = 'raise', record_ids: Sequence[Any] | None = None) float | NDArray[float64] | dict[str, NDArray[float64]] | BatchResult#
Compute the present value of a joint-life pure endowment for two lives.
Pays one unit of benefits conditional on survival of BOTH lives to time
n, discounted to the contract start. Joint survival is computed under the independence assumption.- Parameters:
ages (Sequence[float] or tuple[Sequence[float], Sequence[float]]) – Ages
[x, y]at contract start. Must have exactly 2 elements. In batch mode, pass a pair of N-element arrays:(np.array([x0, …, xN]), np.array([y0, …, yN])).table_y (LifeTable or Sequence[LifeTable] or None, optional) –
Life table for the second life (y). Three modes in batch:
None: usesselffor all policies.LifeTable: single shared table for all N policies.Sequence[LifeTable]of length N: per-policy table; policy i uses element i. RaisesValueErrorif length != N.
n (float or NDArray[np.float64]) – Duration of the endowment in years. Must be positive. Can be fractional (e.g., 10.5 years). In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.ts (float or NDArray[np.float64], optional) – Time shift in years from contract origin. Default is 0.0. Used for reserve calculations. Adjusts both ages (
ages + ts) and effective duration (n - ts). Must be non-negative. In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalarintliterals (e.g.n=10) are coerced tofloat.ir (float, InterestRate, NDArray[np.float64], or None, optional) – Interest rate for discounting. If None, uses
interest_rate. In batch mode, may be a scalar or a list of N values. Pandas and Polars Series are accepted; values are converted to float64. Scalarintliterals (e.g.n=10) are coerced tofloat.return_flows (bool, optional) –
If
True, returns a cashflow decomposition dict instead of the present value. The dict schema differs between single-policy and batch:Single-policy (scalar ages): per-payment engine dict. Same keys as
nEx(); see that method’sreturn_flowsparameter for the full key reference.Batch (array ages): aggregate portfolio dict
{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.total_pv == np.sum(pv_cf)is always satisfied. RaisesValueErrorindiscrete_simplified/continuous_simplifiedmodes. Default isFalse.t_output (NDArray[np.float64] or None, optional) – External bucketing grid for aggregate flow output. When provided, per-policy present-value contributions — already discounted at their exact payment times — are accumulated into the nearest bucket via
np.searchsortedwithout re-discounting. Controls only the shape ofexpected_cfandpv_cf;total_pvis always the exact sum of all per-payment present values and is identical to thet_output=Noneresult. Typical use: regulatory templates that require a fixed set of time buckets (e.g., annual IFRS 17 disclosure, Solvency II SCR projection). Only effective whenreturn_flows=True; raisesValueErrorifreturn_flows=False. Default isNone. Pandas and Polars Series are accepted; converted to NDArray[np.float64] before use.benefits (sequence of float or NDArray[np.float64] or None, optional) – Per-policy benefit amounts, shape
(N,). Scales each policy’s contribution to the aggregate flows (e.g., sum insured, PVDBO weight). Requires batch mode. Any sequence is accepted, including Pandas and Polars Series; values are converted to float64. Compatible withreturn_flows=False(returnsNDArray[np.float64]with per-policy scaled PVs) andon_error='nan'. Default is None.on_error ({'raise', 'nan'}, optional) – Error handling for invalid ages or parameters.
'raise'(default) raisesValueError;'nan'marks invalid policies withnp.nanand returnsBatchResult. Default is'raise'.record_ids (sequence or None, optional) – Identifiers for each policy in batch mode, used to populate the error report when
on_error='nan'. Length must equalN. Any sequence is accepted, including Pandas and Polars Series; elements are always accessed by positional integer index. Default is None.
- Returns:
float – When
ages[0]is scalar andreturn_flows=False.NDArray[np.float64] – When
ages[0]is an array or list andreturn_flows=False. Shape(N,).dict – When
return_flows=True. Single-policy: per-payment engine dict (same keys asnEx()). Batch: aggregate portfolio dict{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.BatchResult – When
on_error='nan'.
- Raises:
ValueError – If
agesdoes not contain exactly 2 elements. If any age is outside[0, w]for the corresponding mortality table. Ifn <= 0. Ifts < 0. If no interest rate is provided andinterest_rateis None. If anyage + nexceeds the table limitw. Ifreturn_flows=Truein batch mode andcalculation_modeis'discrete_simplified'or'continuous_simplified'. Ift_outputis notNoneandreturn_flows=False. Ift_outputis notNoneand input is scalar (non-batch mode). Ifon_erroris not'raise'or'nan'. Ifon_error='nan'is passed with scalarx(oragesfor joint-life methods). Use a list or array of ages for batch mode. Ifrecord_idsis notNoneand input is scalar (non-batch mode).TypeError – If
agescontains non-numeric values. Iftable_yis not a LifeTable instance or None. Ifiris not float, InterestRate, or None. If any numeric argument isbool.
Notes
Boolean values for age, duration, shift, and rate arguments raise
TypeError. Accepted types for each parameter are described in Parameters above.Joint pure endowment formula: \({}_{n}E_{xy} = \vx{n} \cdot \px{n}{x} \cdot \px{n}{y}\).
Joint survival computed under the independence assumption: \({}_{n}p_x \cdot {}_{n}p_y\).
Uses
self.calculation_modefromConfigto select the method:discrete_precision,discrete_simplified,continuous_precision, orcontinuous_simplified.
Batch mode#
Batch mode is activated when either of these conditions is met:
ages[0]is a list or NDArray (age-triggered batch): pass ages as a pair of N-element arrays, each shape(N,).ages[0]is scalar but any ofn,ts, oriris a list/tuple/NDArray of length N (param-triggered batch): scalar ages broadcast to N copies.
In both cases the method returns
NDArray[np.float64]of shape(N,).Per-policy parameters
n,ts, andirmay each be a scalar (broadcast to all N) or an array of length N.Pandas and Polars Series are accepted for
n,ts,ir, and each per-life age array withinageswithout calling.to_numpy(); they are converted internally via the array protocol.on_error='nan'marks invalid policies withnp.nan.
Examples
>>> from lactuca import LifeTable >>> table = LifeTable('PER2020_Ind_1o', 'm', cohort=1960) >>> table.interest_rate = 0.03 >>> pv = table.nExy([65, 60], n=10)
>>> # With time shift >>> pv_t5 = table.nExy([65, 60], n=10, ts=5)
Batch mode:
>>> import numpy as np >>> x_ages = np.array([60.0, 65.0, 70.0]) >>> y_ages = np.array([55.0, 60.0, 65.0]) >>> table.nExy((x_ages, y_ages), n=10, ir=0.03)
See also
LifeTable.nExSingle-life pure endowment.
LifeTable.nExyzJoint-life pure endowment for three lives.
LifeTable.nEjointJoint-life pure endowment for multiple lives.
- nExyz(ages: object, tables_yz: Sequence[LifeTable] | Sequence[Sequence[LifeTable]] | None = None, *, n: object, ts: object = 0.0, ir: object = None, return_flows: bool = False, t_output: NDArray[float64] | None = None, benefits: Sequence[float] | NDArray[float64] | None = None, on_error: Literal['raise', 'nan'] = 'raise', record_ids: Sequence[Any] | None = None) float | dict[str, NDArray[float64]] | BatchResult#
Compute the present value of a joint-life pure endowment for three lives.
Pays one unit of benefits conditional on survival of ALL THREE lives to time
n, discounted to the contract start. Joint survival is computed under the independence assumption.- Parameters:
ages (sequence of float) – Ages
[x, y, z]at contract start. Must have exactly 3 elements. Each age must be within[0, w]for the corresponding table.tables_yz (Sequence[LifeTable] or Sequence[Sequence[LifeTable]] or None, optional) –
Life tables for the second and third lives
[y, z]. Three modes in batch:None: usesselffor all three lives in all policies.[LifeTable, LifeTable]: 2 shared tables (y and z) for all N policies.[[t_y0, t_z0], …]of length N: per-policy; each inner list must have exactly 2 elements. RaisesValueErrorif outer length != N.
n (float) – Duration of the endowment in years. Must be positive. Can be fractional (e.g., 10.5 years). In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.ts (float or NDArray[np.float64], optional) – Time shift in years from contract origin. Default is 0.0. Used for reserve calculations. Adjusts both ages (
ages + ts) and effective duration (n - ts). Must be non-negative. In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalarintliterals (e.g.n=10) are coerced tofloat.ir (float, InterestRate, NDArray[np.float64], or None, optional) – Interest rate for discounting. If None, uses
interest_rate. In batch mode, may be a scalar or a list of N values. Pandas and Polars Series are accepted; values are converted to float64. Scalarintliterals (e.g.n=10) are coerced tofloat.return_flows (bool, optional) –
If
True, returns a cashflow decomposition dict instead of the present value. The dict schema differs between single-policy and batch:Single-policy (scalar ages): per-payment engine dict. Same keys as
nEx(); see that method’sreturn_flowsparameter for the full key reference.Batch (array ages): aggregate portfolio dict
{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.total_pv == np.sum(pv_cf)is always satisfied. RaisesValueErrorindiscrete_simplified/continuous_simplifiedmodes. Default isFalse.t_output (NDArray[np.float64] or None, optional) – External bucketing grid for aggregate flow output. When provided, per-policy present-value contributions — already discounted at their exact payment times — are accumulated into the nearest bucket via
np.searchsortedwithout re-discounting. Controls only the shape ofexpected_cfandpv_cf;total_pvis always the exact sum of all per-payment present values and is identical to thet_output=Noneresult. Typical use: regulatory templates that require a fixed set of time buckets (e.g., annual IFRS 17 disclosure, Solvency II SCR projection). Only effective whenreturn_flows=True; raisesValueErrorifreturn_flows=False. Default isNone. Pandas and Polars Series are accepted; converted to NDArray[np.float64] before use.
- Returns:
float – When
ages[0]is scalar andreturn_flows=False.NDArray[np.float64] – When
ages[0]is an array or list andreturn_flows=False. Shape(N,).dict – When
return_flows=True. Single-policy: per-payment engine dict (same keys asnEx()). Batch: aggregate portfolio dict{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.BatchResult – When
on_error='nan'.
- Raises:
ValueError – If
agesdoes not contain exactly 3 elements. Iftables_yzis provided but does not contain exactly 2 LifeTable instances. If any age is outside[0, w]for the corresponding mortality table. Ifn <= 0. Ifts < 0. If no interest rate is provided andinterest_rateis None. If anyage + nexceeds the table limitw. Ift_outputis notNoneandreturn_flows=False. Ift_outputis notNoneand input is scalar (non-batch mode). Ifon_erroris not'raise'or'nan'. Ifon_error='nan'is passed with scalarx(oragesfor joint-life methods). Use a list or array of ages for batch mode. Ifrecord_idsis notNoneand input is scalar (non-batch mode).TypeError – If
agescontains non-numeric values. Iftables_yzcontains non-LifeTable instances. Ifiris not float, InterestRate, or None. If any numeric argument isbool.
Notes
Boolean values for age, duration, shift, and rate arguments raise
TypeError. Accepted types for each parameter are described in Parameters above.Joint pure endowment formula: \({}_{n}E_{xyz} = \vx{n} \cdot \px{n}{x} \cdot \px{n}{y} \cdot \px{n}{z}\).
Joint survival computed under the independence assumption: \({}_{n}p_x \cdot {}_{n}p_y \cdot {}_{n}p_z\).
Uses
self.calculation_modefromConfigto select the method:discrete_precision,discrete_simplified,continuous_precision, orcontinuous_simplified.
Batch mode#
Batch mode is activated when either of these conditions is met:
ages[0]is a list or NDArray (age-triggered batch): pass ages as a triple of N-element arrays, each shape(N,).ages[0]is scalar but any ofn,ts, oriris a list/tuple/NDArray of length N (param-triggered batch): scalar ages broadcast to N copies.
In both cases the method returns
NDArray[np.float64]of shape(N,).Per-policy parameters
n,ts, andirmay each be a scalar or an array of length N.Pandas and Polars Series are accepted for
n,ts,ir, and each per-life age array withinageswithout calling.to_numpy(); they are converted internally via the array protocol.on_error='nan'marks invalid policies withnp.nan.
Examples
>>> from lactuca import LifeTable >>> table = LifeTable('PER2020_Ind_1o', 'm', cohort=1960) >>> table.interest_rate = 0.03 >>> pv = table.nExyz([65, 60, 55], n=10)
>>> # With time shift >>> pv_t5 = table.nExyz([65, 60, 55], n=10, ts=5)
Batch mode:
>>> import numpy as np >>> x_arr = np.array([60.0, 65.0]) >>> y_arr = np.array([55.0, 60.0]) >>> z_arr = np.array([50.0, 55.0]) >>> table.nExyz((x_arr, y_arr, z_arr), n=10, ir=0.03)
See also
LifeTable.nExSingle-life pure endowment.
LifeTable.nExyJoint-life pure endowment for two lives.
LifeTable.nEjointJoint-life pure endowment for multiple lives.
- ox(x=None, m: object = 1) None#
Exit/turnover rate (blocked for LifeTable).
- Parameters:
x (optional) – Ignored placeholder maintained for API compatibility with other decrement-table subclasses.
m (object, optional) – Ignored payment-frequency placeholder maintained for API compatibility. Must be a valid payment frequency if provided;
boolis rejected.
- Raises:
TypeError – If
mis abool.NotImplementedError – Always raised. LifeTable uses qx (mortality) instead of ox (exit probability).
Notes
oxis only available in ExitTable.For mortality tables, use
qx()instead.
See also
ExitTableTable type that provides ox rates.
Examples
>>> from lactuca import LifeTable >>> lt = LifeTable('PASEM2010', 'm') >>> lt.ox(65) NotImplementedError: ox is not available for LifeTable. Use qx() instead.
- summary() str#
Return a formatted summary of the life table state and sample qx values.
Calls
summary()for the common header block, then appends:Decimals: qx=<n>Sample qx values for the first and last 5 ages. When a modification is active, each line shows both the current and the original base value:
qx(0) = 0.00012642 (original: 0.0020038)
Without a modification the parenthetical is omitted:
qx(0) = 0.0020038
- Returns:
Multi-line summary string.
- Return type:
str
Notes
Use
modify_qx()to apply modifications before calling this method.Use
reset_modifications()to restore base rates and clear modification state.When
metadata_pending=True, this method delegates entirely tosummary()(no sample qx values are computed or shown).
Examples
>>> from lactuca import LifeTable >>> lt = LifeTable("PASEM2020_Rel_1o", sex="m") >>> lt.modify_qx({"age_shift": 2, "decrement_multiplier": 1.05}) >>> print(lt.summary()) LifeTable: PASEM2020_Rel_1er.orden Sex: m ... w (current): 107 Modified: True Modifications applied: age_shift=2, decrement_multiplier=1.05 Decimals: qx=15 Sample qx values (first 5): qx(0) = 0.00012642 (original: 0.0020038) ...
- äjoint(ages: object, tables_others: Sequence[LifeTable] | Sequence[Sequence[LifeTable]] | None = None, *, ts: object = 0.0, d: object = 0.0, n: object = None, m: Literal[1, 2, 3, 4, 6, 12, 14, 24, 26, 52, 365] | Sequence[int] | NDArray[int64] = 1, gr: GrowthRate | float | None | Sequence[GrowthRate | float | None] = None, cashflow_amounts: Sequence[float] | None = None, ir: object = None, return_flows: bool = False, t_output: NDArray[float64] | None = None, benefits: Sequence[float] | NDArray[float64] | None = None, on_error: Literal['raise', 'nan'] = 'raise', record_ids: Sequence[Any] | None = None) float | dict[str, NDArray[float64]] | BatchResult#
Compute the present value of a joint-life annuity-due (prepayable) for multiple lives.
- Parameters:
ages (Sequence[float] or list[Sequence[float]]) – Ages of all insured lives. Must have at least 2 elements. In batch mode, pass a list of r N-element arrays:
[x0_arr, x1_arr, ..., xr_arr]where each has shape(N,).tables_others (Sequence[LifeTable] or Sequence[Sequence[LifeTable]] or None, optional) –
Life tables for additional lives (all except the first, which uses
self). Three modes in batch:None: usesselffor all lives in all policies.Sequence[LifeTable]of lengthn_lives - 1: shared across all N policies.Sequence[Sequence[LifeTable]]of length N: per-policy; each inner list must have exactlyn_lives - 1elements. RaisesValueErrorif length != N.
ts (float or NDArray[np.float64], optional) – Shift (displacement) in years from origin (default 0.0). In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.d (float or NDArray[np.float64], optional) – Deferment period in years (default 0.0). In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.n (float or NDArray[np.float64] or None, optional) – Total duration of the annuity (in years) from contract start. If None, perpetual annuity up to limiting age. In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.m (PaymentFrequencyLiteral, sequence of int, NDArray[np.int64], or array-like, optional) – Number of payments per year (1=annual, 12=monthly, etc., default 1). Integer-valued floats (e.g.
12.0) are coerced toint. In batch mode, may be a scalar (broadcast to all N) or an array/list of length N for per-policy payment frequencies; distinct values are processed in sub-groups by the vectorised batch engine. Pandas and Polars Series are accepted; values are converted to int64.gr (GrowthRate, float, or None, optional) – Growth rate model (default None). A scalar float is auto-wrapped as
GrowthRate(gr)(geometric growth by default). If None, no growth is applied. In batch mode, may be a scalar or a list of N values. Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asGrowthRatewhere applicable). An object-dtype Series ofGrowthRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.cashflow_amounts (Sequence[float], optional) – Custom payment amounts (one per payment period). When provided, disables growth (
gris ignored). Note: due-annuity methods do not acceptcashflow_times; for custom payment timing use the postpayable counterpart (ajoint()). Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.ir (float, InterestRate, list[float or InterestRate], NDArray[np.float64], or None, optional) – Interest rate (constant or curve). If None, uses
interest_rate. In batch mode, may be a scalar (broadcast to all N), a list of Nfloatvalues, or a list of NInterestRateobjects (one curve per policy). Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asInterestRatewhere applicable). An object-dtype Series ofInterestRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.return_flows (bool, optional) –
If
True, returns a cashflow decomposition dict instead of the present value. The dict schema differs between single-policy and batch:Single-policy (scalar ages): per-payment engine dict. Same keys as
ax(); see that method’sreturn_flowsparameter for the full key reference.Batch (array ages): aggregate portfolio dict
{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.total_pv == np.sum(pv_cf)is always satisfied. RaisesValueErrorindiscrete_simplified/continuous_simplifiedmodes. Default isFalse.t_output (NDArray[np.float64] or None, optional) – External bucketing grid for aggregate flow output. When provided, per-policy present-value contributions — already discounted at their exact payment times — are accumulated into the nearest bucket via
np.searchsortedwithout re-discounting. Controls only the shape ofexpected_cfandpv_cf;total_pvis always the exact sum of all per-payment present values and is identical to thet_output=Noneresult. Typical use: regulatory templates that require a fixed set of time buckets (e.g., annual IFRS 17 disclosure, Solvency II SCR projection). Only effective whenreturn_flows=True; raisesValueErrorifreturn_flows=False. Default isNone. Pandas and Polars Series are accepted; converted to NDArray[np.float64] before use.benefits (sequence of float or NDArray[np.float64] or None, optional) – Per-policy benefit amounts, shape
(N,). Scales each policy’s contribution to the aggregate flows (e.g., sum insured, PVDBO weight). Requires batch mode. Any sequence is accepted, including Pandas and Polars Series; values are converted to float64. Compatible withreturn_flows=False(returnsNDArray[np.float64]with per-policy scaled PVs) andon_error='nan'. Default is None.on_error ({'raise', 'nan'}, optional) – Error handling for invalid ages or parameters.
'raise'(default) raisesValueError;'nan'marks invalid policies withnp.nanand returnsBatchResult. Default is'raise'.record_ids (sequence or None, optional) – Identifiers for each policy in batch mode, used to populate the error report when
on_error='nan'. Length must equalN. Any sequence is accepted, including Pandas and Polars Series; elements are always accessed by positional integer index. Default is None.
- Returns:
float – When
ages[0]is scalar andreturn_flows=False.NDArray[np.float64] – When
ages[0]is an array or list andreturn_flows=False. Shape(N,).dict – When
return_flows=True. Single-policy: per-payment engine dict (same keys asax()). Batch: aggregate portfolio dict{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.BatchResult – When
on_error='nan'.
- Raises:
ValueError – If
ageshas fewer than 2 elements. Iftables_otherslength does not matchlen(ages)-1. If any age is outside[0, w]for corresponding table. Ifreturn_flows=Truein batch mode andcalculation_modeis'discrete_simplified'or'continuous_simplified'. Ifreturn_flows=Trueandmis a vector with heterogeneous values. Ift_outputis notNoneandreturn_flows=False. Ift_outputis notNoneand input is scalar (non-batch mode). Ifon_erroris not'raise'or'nan'. Ifon_error='nan'is passed with scalarx(oragesfor joint-life methods). Use a list or array of ages for batch mode. Ifrecord_idsis notNoneand input is scalar (non-batch mode).TypeError – If
agesortables_otherscontain invalid types (includingboolfor numeric kwargs).
Notes
Boolean values for age, duration, shift, deferment, and rate arguments raise
TypeError. Accepted types for each parameter are described in Parameters above.Joint-life annuity-due for r lives, under the independence assumption:
\[\ddot{a}_{x_1 x_2 \cdots x_r}^{(m)} = \frac{1}{m} \sum_{k=0}^{\infty} v^{k/m} \prod_{i=1}^{r} {}_{k/m}p_{x_i}\]Payments occur at the BEGINNING of each period (due/prepayable).
Annuity continues while ALL lives survive (joint-life status).
Uses independence assumption for joint survival probabilities.
cashflow_timesis not supported for annuities-due;cashflow_amountsscales benefits on the standard due payment grid. Useajoint()for custom payment times.When
gris provided, the growth factor at the j-th payment interval is(1+g)^(j // m)— anniversaries counted from the first payment, not the contract origin.GrowthRate.shifted(ts)consumesint(ts)anniversary years; fractionaltsis actuarially correct (revaluations operate on complete policy years). See Growth Rate Conventions for the full reference.
Batch mode#
Batch mode is activated when either of these conditions is met:
ages[0]is a list or NDArray (age-triggered batch): pass ages as a list of r N-element arrays, each shape(N,).ages[0]is scalar but any ofn,ts,d,ir,gr, ormis a list/tuple/NDArray of length N (param-triggered batch): scalar ages broadcast to N copies.
In both cases the method returns
NDArray[np.float64]of shape(N,).Per-policy parameters
ts,d,n,ir, andgrmay each be a scalar or an array of length N.mmay be a scalar (broadcast to all N) or an array/list of length N for per-policy payment frequencies; the batch engine groups policies by uniquemvalue and vectorises each group independently.Pandas and Polars Series are accepted for all per-policy parameters (
n,ts,d,ir,gr,m,cashflow_amounts,t_output, and each per-life age array withinages) without calling.to_numpy(); they are converted internally via the array protocol.on_error='nan'marks invalid policies withnp.nan.
See also
LifeTable.ajointJoint-life annuity-immediate (postpayable) for multiple lives.
LifeTable.äxyJoint-life annuity-due for two lives.
LifeTable.äxyzJoint-life annuity-due for three lives.
LifeTable.äxSingle-life annuity-due.
Examples
>>> from lactuca import LifeTable >>> # Four lives with same table >>> table = LifeTable('PER2020_Ind_1o', 'm', cohort=1960) >>> table.interest_rate = 0.03 >>> pv = table.äjoint([65, 60, 55, 50], n=20, m=12)
>>> # Multiple lives with different tables >>> table_m, table_f1, table_f2 = LifeTable('PER2020_Ind_1o', ('m', 'f', 'f'), cohort=1960) >>> table_m.interest_rate = 0.03 >>> pv = table_m.äjoint([65, 62, 58], [table_f1, table_f2], n=15, m=12)
Batch mode:
>>> import numpy as np >>> a0 = np.array([60.0, 65.0]) >>> a1 = np.array([55.0, 60.0]) >>> table.äjoint([a0, a1], n=20, ir=0.03)
- äx(x: object, *, ts: object = 0.0, d: object = 0.0, n: object = None, m: Literal[1, 2, 3, 4, 6, 12, 14, 24, 26, 52, 365] | Sequence[int] | NDArray[int64] = 1, gr: GrowthRate | float | None | Sequence[GrowthRate | float | None] = None, cashflow_amounts: Sequence[float] | None = None, ir: object = None, return_flows: bool = False, t_output: NDArray[float64] | None = None, benefits: Sequence[float] | NDArray[float64] | None = None, on_error: Literal['raise', 'nan'] = 'raise', record_ids: Sequence[Any] | None = None) float | NDArray[float64] | dict[str, NDArray[float64] | float] | BatchResult#
Compute the present value of an annuity-due (prepayable) for a single life.
- Parameters:
x (float or list of float or NDArray[np.float64]) – Initial age. Pass an array or list of N ages to activate batch mode, which returns an
ndarrayof shape(N,).ts (float or NDArray[np.float64], optional) – Shift (displacement) in years from origin. Default is 0.0. In batch mode, may be a scalar (broadcast) or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.d (float or NDArray[np.float64], optional) – Deferment period in years. Default is 0.0. In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.n (float or NDArray[np.float64] or None, optional) – Total duration of the annuity (in years) from the contract start. In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.m (PaymentFrequencyLiteral, sequence of int, NDArray[np.int64], or array-like, optional) – Number of payments per year (1=annual, 12=monthly, etc.). Integer-valued floats (e.g.
12.0) are coerced toint. In batch mode, may be a scalar (broadcast to all N) or an array/list of length N for per-policy payment frequencies; distinct values are processed in sub-groups by the vectorised batch engine. Pandas and Polars Series are accepted; values are converted to int64.gr (GrowthRate, float, or None, optional) – Growth rate model. A scalar float is auto-wrapped as
GrowthRate(gr)(geometric growth by default). If None, no growth is applied. In batch mode, may be a scalar or a list of N values. Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asGrowthRatewhere applicable). An object-dtype Series ofGrowthRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.cashflow_amounts (Sequence[float], optional) – Custom payment amounts (one per payment period). When provided, disables growth (
gris ignored). Length must match the due-annuity payment count on the regular grid (\lfloor n_\text{eff} \cdot m \rfloor, plus one whenn_\text{eff} \cdot mis fractional, and plus one for whole-life due grids — see Last Payment Adjustment). Note: due-annuity methods do not acceptcashflow_times; for custom payment timing use the postpayable counterpart (ax()). Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.ir (float, InterestRate, list[float or InterestRate], NDArray[np.float64], or None, optional) – Interest rate (constant or curve). If None, uses
interest_rate. In batch mode, may be a scalar (broadcast to all N), a list of Nfloatvalues, or a list of NInterestRateobjects (one curve per policy). Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asInterestRatewhere applicable). An object-dtype Series ofInterestRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.return_flows (bool, optional) –
If
True, returns a cashflow decomposition dict instead of the present value. The dict schema differs between scalar and batch:Scalar
x,discrete_precision/discrete_simplifiedmode:{ "time_grid" : NDArray # payment times "interest_rate" : NDArray # i(t) at each payment "discount_factor" : NDArray # v^t at each payment "survival_probability" : NDArray # tpx at each payment "growth" or "amount" : NDArray # growth factors or custom amounts "payment_adjustment" : NDArray # fractional last-payment factor "present_value_raw" : NDArray # pv before /m scaling "present_value" : NDArray # final pv per payment (/m applied) }sum(result["present_value"])equals the rounded scalar present value.Scalar
x,continuous_precisionmode:{ "time_grid" : NDArray # integration points "expected_cf" : NDArray # undiscounted density × dt "pv_cf" : NDArray # discounted density × dt }np.sum(result["pv_cf"])approximates the present value.Batch (array
x),*_precisionmodes only:{ "time_grid" : NDArray # portfolio time grid "expected_cf" : NDArray # sum of undiscounted contributions across policies "pv_cf" : NDArray # sum of discounted contributions across policies "total_pv" : float # sum(pv_cf); equals sum of scalar present values }total_pv == np.sum(pv_cf)is always satisfied. RaisesValueErrorindiscrete_simplified/continuous_simplifiedmodes. Default isFalse.t_output (NDArray[np.float64] or None, optional) – External bucketing grid for aggregate flow output. When provided, per-policy present-value contributions — already discounted at their exact payment times — are accumulated into the nearest bucket via
np.searchsortedwithout re-discounting. Controls only the shape ofexpected_cfandpv_cf;total_pvis always the exact sum of all per-payment present values and is identical to thet_output=Noneresult. Typical use: regulatory templates that require a fixed set of time buckets (e.g., annual IFRS 17 disclosure, Solvency II SCR projection). Only effective whenreturn_flows=True; raisesValueErrorifreturn_flows=False. Default isNone. Pandas and Polars Series are accepted; converted to NDArray[np.float64] before use.benefits (sequence of float or NDArray[np.float64] or None, optional) – Per-policy benefit amounts, shape
(N,). Scales each policy’s contribution to the aggregate flows (e.g., sum insured, PVDBO weight). Requires batch mode. Any sequence is accepted, including Pandas and Polars Series; values are converted to float64. Compatible withreturn_flows=False(returnsNDArray[np.float64]with per-policy scaled PVs) andon_error='nan'. Default is None.on_error ({'raise', 'nan'}, optional) – Error handling for invalid ages or parameters.
'raise'(default) raisesValueError;'nan'marks invalid policies withnp.nanand returnsBatchResult. Default is'raise'.record_ids (sequence or None, optional) – Identifiers for each policy in batch mode, used to populate the error report when
on_error='nan'. Length must equalN. Any sequence is accepted, including Pandas and Polars Series; elements are always accessed by positional integer index. Default is None.
- Returns:
float – When
xis scalar andreturn_flows=False.NDArray[np.float64] – When
xis an array or list andreturn_flows=False. Shape(N,).dict – When
return_flows=True. Schema depends on scalar vs. batch and oncalculation_mode; see thereturn_flowsparameter description above for the full key listing of each variant.BatchResult – When
on_error='nan', wraps the result array and an error report.
- Raises:
ValueError – If no interest rate is provided and
interest_rateisNone. Ifts,d, ornfail timing validation. Ifreturn_flows=Truein batch mode andcalculation_modeis'discrete_simplified'or'continuous_simplified'. Ifreturn_flows=Trueandmis a vector with heterogeneous values; use a scalarmor split the batch by payment frequency. Ift_outputis notNoneandreturn_flows=False. Ift_outputis notNoneand input is scalar (non-batch mode). Ifon_erroris not'raise'or'nan'. Ifon_error='nan'is passed with scalarx(oragesfor joint-life methods). Use a list or array of ages for batch mode. Ifrecord_idsis notNoneand input is scalar (non-batch mode).TypeError – If any argument has an incompatible type (including
boolfor numeric kwargs).
Notes
Boolean values for age, duration, shift, deferment, and rate arguments raise
TypeError. Accepted types for each parameter are described in Parameters above.Discrete whole-life annuity-due (\(m\) payments per year):
\[\ddot{a}_x^{(m)} = \frac{1}{m} \sum_{k=0}^{\infty} v^{k/m} \cdot {}_{k/m}p_x\]For a temporary n-year annuity with optional d-year deferment:
\[{}_{d|}\ddot{a}_{x:\overline{n}|}^{(m)} = \frac{1}{m} \sum_{k=dm}^{(d+n)m-1} v^{k/m} \cdot {}_{k/m}p_x\]The calculation is performed at age
x + ts, with effective defermentmax(d - ts, 0)and effective durationmax(n - max(ts - d, 0), 0).If
ts >= d + n(shift absorbs the entire deferred annuity), the value is zero.cashflow_timesis not supported for annuities-due;cashflow_amountsscales benefits on the standard due payment grid (payment_timing=0). Useax()for custom payment times.When
gris provided, the growth factor at the j-th payment is(1+g)^(j // m)— anniversaries are counted from the first payment of the call, not from the contract origin. Defermentdshifts payment times but does not affect this anniversary index. Fractionaltsvalues are accepted;GrowthRate.shifted(ts)discards the firstint(ts)anniversary years of the schedule, which is actuarially correct because revaluations operate on complete policy years. See Growth Rate Conventions for the full reference.
Batch mode#
When
xis alist, sequence, orndarray, or when any ofn,ts,d,ir,gr, ormis a sequence or array with scalarx, the method operates in batch mode and returns anndarrayof shape(N,):A list of 1 element still returns
NDArrayshape(1,), never a scalar.Per-policy parameters
ts,d,n,ir, andgrmay each be a scalar (broadcast to all N policies) or an array of length N.mmay be a scalar (broadcast to all N policies) or an array/list of length N for per-policy payment frequencies; the batch engine groups policies by uniquemvalue and vectorises each group independently.Pandas and Polars Series are accepted for all per-policy parameters (
x,n,ts,d,ir,gr,m,cashflow_amounts,t_output) without calling.to_numpy(); they are converted internally via the array protocol.discrete_precisionmode uses a vectorised engine (50–250× vs scalar loop).continuous_*modes use a serial fallback (~1.5× vs loop).on_error='nan'marks invalid policies withnp.naninstead of raising.
See also
LifeTable.axAnnuity-immediate (postpayable) equivalent.
LifeTable.äxyJoint-life annuity-due for two lives.
LifeTable.äjointJoint-life annuity-due for multiple lives.
Examples
>>> from lactuca import LifeTable >>> lt = LifeTable('PASEM2010', 'm') >>> lt.interest_rate = 0.03 >>> lt.äx(65, n=10, m=12) >>> lt.äx(65) # whole life
Batch mode:
>>> import numpy as np >>> ages = np.array([55.0, 60.0, 65.0, 70.0]) >>> lt.äx(ages, n=10, ir=0.03) >>> lt.äx(ages, n=np.array([20.0, 15.0, 10.0, 8.0]), ir=0.03)
- äxy(ages: object, table_y: LifeTable | Sequence[LifeTable] | None = None, *, ts: object = 0.0, d: object = 0.0, n: object = None, m: Literal[1, 2, 3, 4, 6, 12, 14, 24, 26, 52, 365] | Sequence[int] | NDArray[int64] = 1, gr: GrowthRate | float | None | Sequence[GrowthRate | float | None] = None, cashflow_amounts: Sequence[float] | None = None, ir: object = None, return_flows: bool = False, t_output: NDArray[float64] | None = None, benefits: Sequence[float] | NDArray[float64] | None = None, on_error: Literal['raise', 'nan'] = 'raise', record_ids: Sequence[Any] | None = None) float | NDArray[float64] | dict[str, NDArray[float64]] | BatchResult#
Compute the present value of a joint-life annuity-due (prepayable) for two lives.
- Parameters:
ages (Sequence[float] or tuple[Sequence[float], Sequence[float]]) – Ages of the two lives
[x, y]. Must have exactly 2 elements. In batch mode, pass a pair of N-element arrays:(np.array([x0, …, xN]), np.array([y0, …, yN])).table_y (LifeTable or Sequence[LifeTable] or None, optional) –
Life table for the second life (y). Three modes in batch:
None: usesselffor all policies.LifeTable: single shared table for all N policies.Sequence[LifeTable]of length N: per-policy table; policy i uses element i. RaisesValueErrorif length != N.
ts (float or NDArray[np.float64], optional) – Shift (displacement) in years from origin (default 0.0). In batch mode, may be a scalar (broadcast) or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.d (float or NDArray[np.float64], optional) – Deferment period in years (default 0.0). In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.n (float or NDArray[np.float64] or None, optional) – Total duration of the annuity (in years) from contract start. If None, perpetual annuity up to limiting age. In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.m (PaymentFrequencyLiteral, sequence of int, NDArray[np.int64], or array-like, optional) – Number of payments per year (1=annual, 12=monthly, etc., default 1). Integer-valued floats (e.g.
12.0) are coerced toint. In batch mode, may be a scalar (broadcast to all N) or an array/list of length N for per-policy payment frequencies; distinct values are processed in sub-groups by the vectorised batch engine. Pandas and Polars Series are accepted; values are converted to int64.gr (GrowthRate, float, or None, optional) – Growth rate model (default None). A scalar float is auto-wrapped as
GrowthRate(gr)(geometric growth by default). If None, no growth is applied. In batch mode, may be a scalar or a list of N values. Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asGrowthRatewhere applicable). An object-dtype Series ofGrowthRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.cashflow_amounts (Sequence[float], optional) – Custom payment amounts (one per payment period). When provided, disables growth (
gris ignored). Note: due-annuity methods do not acceptcashflow_times; for custom payment timing use the postpayable counterpart (axy()). Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.ir (float, InterestRate, list[float or InterestRate], NDArray[np.float64], or None, optional) – Interest rate (constant or curve). If None, uses
interest_rate. In batch mode, may be a scalar (broadcast to all N), a list of Nfloatvalues, or a list of NInterestRateobjects (one curve per policy). Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asInterestRatewhere applicable). An object-dtype Series ofInterestRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.return_flows (bool, optional) –
If
True, returns a cashflow decomposition dict instead of the present value. The dict schema differs between single-policy and batch:Single-policy (scalar ages): per-payment engine dict. Same keys as
ax(); see that method’sreturn_flowsparameter for the full key reference.Batch (array ages): aggregate portfolio dict
{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.total_pv == np.sum(pv_cf)is always satisfied. RaisesValueErrorindiscrete_simplified/continuous_simplifiedmodes. Default isFalse.t_output (NDArray[np.float64] or None, optional) – External bucketing grid for aggregate flow output. When provided, per-policy present-value contributions — already discounted at their exact payment times — are accumulated into the nearest bucket via
np.searchsortedwithout re-discounting. Controls only the shape ofexpected_cfandpv_cf;total_pvis always the exact sum of all per-payment present values and is identical to thet_output=Noneresult. Typical use: regulatory templates that require a fixed set of time buckets (e.g., annual IFRS 17 disclosure, Solvency II SCR projection). Only effective whenreturn_flows=True; raisesValueErrorifreturn_flows=False. Default isNone. Pandas and Polars Series are accepted; converted to NDArray[np.float64] before use.benefits (sequence of float or NDArray[np.float64] or None, optional) – Per-policy benefit amounts, shape
(N,). Scales each policy’s contribution to the aggregate flows (e.g., sum insured, PVDBO weight). Requires batch mode. Any sequence is accepted, including Pandas and Polars Series; values are converted to float64. Compatible withreturn_flows=False(returnsNDArray[np.float64]with per-policy scaled PVs) andon_error='nan'. Default is None.on_error ({'raise', 'nan'}, optional) – Error handling for invalid ages or parameters.
'raise'(default) raisesValueError;'nan'marks invalid policies withnp.nanand returnsBatchResult. Default is'raise'.record_ids (sequence or None, optional) – Identifiers for each policy in batch mode, used to populate the error report when
on_error='nan'. Length must equalN. Any sequence is accepted, including Pandas and Polars Series; elements are always accessed by positional integer index. Default is None.
- Returns:
float – When
ages[0]is scalar andreturn_flows=False.NDArray[np.float64] – When
ages[0]is an array or list andreturn_flows=False. Shape(N,).dict – When
return_flows=True. Single-policy: per-payment engine dict (same keys asax()). Batch: aggregate portfolio dict{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.BatchResult – When
on_error='nan'.
- Raises:
ValueError – If
agesdoes not contain exactly 2 elements. If any age is outside[0, w]for corresponding table. Ifreturn_flows=Truein batch mode andcalculation_modeis'discrete_simplified'or'continuous_simplified'. Ifreturn_flows=Trueandmis a vector with heterogeneous values. Ift_outputis notNoneandreturn_flows=False. Ift_outputis notNoneand input is scalar (non-batch mode). Ifon_erroris not'raise'or'nan'. Ifon_error='nan'is passed with scalarx(oragesfor joint-life methods). Use a list or array of ages for batch mode. Ifrecord_idsis notNoneand input is scalar (non-batch mode).TypeError – If
agescontains non-numeric values. Iftable_yis not a LifeTable instance or None. If any numeric argument isbool.
Notes
Boolean values for age, duration, shift, deferment, and rate arguments raise
TypeError. Accepted types for each parameter are described in Parameters above.Joint-life annuity-due for two lives, under the independence assumption:
\[\ddot{a}_{xy}^{(m)} = \frac{1}{m} \sum_{k=0}^{\infty} v^{k/m} \cdot {}_{k/m}p_x \cdot {}_{k/m}p_y\]Payments occur at the BEGINNING of each period (due/prepayable).
Annuity continues while BOTH lives survive (joint-life status).
Uses independence assumption for joint survival probabilities.
cashflow_timesis not supported for annuities-due;cashflow_amountsscales benefits on the standard due payment grid. Useaxy()for custom payment times.When
gris provided, the growth factor at the j-th payment interval is(1+g)^(j // m)— anniversaries counted from the first payment, not the contract origin.GrowthRate.shifted(ts)consumesint(ts)anniversary years; fractionaltsis actuarially correct (revaluations operate on complete policy years). See Growth Rate Conventions for the full reference.
Batch mode#
Batch mode is activated when either of these conditions is met:
ages[0]is a list or NDArray (age-triggered batch): pass ages as a pair of N-element arrays(x_arr, y_arr), each shape(N,).ages[0]is scalar but any ofn,ts,d,ir,gr, ormis a list/tuple/NDArray of length N (param-triggered batch): scalar ages broadcast to N copies.
In both cases the method returns
NDArray[np.float64]of shape(N,).Per-policy parameters
ts,d,n,ir, andgrmay each be a scalar (broadcast to all N) or an array of length N.mmay be a scalar (broadcast to all N policies) or an array/list of length N for per-policy payment frequencies; the batch engine groups policies by uniquemvalue and vectorises each group independently.discrete_precisionmode uses a vectorised matrix engine.Pandas and Polars Series are accepted for all per-policy parameters (
n,ts,d,ir,gr,m,cashflow_amounts,t_output, and each per-life age array withinages) without calling.to_numpy(); they are converted internally via the array protocol.on_error='nan'marks invalid policies withnp.nan.
See also
LifeTable.axyJoint-life annuity-immediate for two lives.
LifeTable.äjointJoint-life annuity-due for multiple lives.
LifeTable.äxyzJoint-life annuity-due for three lives.
LifeTable.äxSingle-life annuity-due.
Examples
>>> from lactuca import LifeTable >>> # Two lives with same table >>> table = LifeTable('PER2020_Ind_1o', 'm', cohort=1960) >>> table.interest_rate = 0.03 >>> pv = table.äxy([65, 60], n=20, m=12)
>>> # Two lives with different tables >>> table_m, table_f = LifeTable('PER2020_Ind_1o', ('m', 'f'), cohort=1960) >>> table_m.interest_rate = 0.03 >>> pv = table_m.äxy([65, 62], table_f, n=15, m=12)
Batch mode:
>>> import numpy as np >>> x_ages = np.array([60.0, 65.0, 70.0]) >>> y_ages = np.array([55.0, 60.0, 65.0]) >>> table.äxy((x_ages, y_ages), n=20, ir=0.03)
- äxyz(ages: object, tables_yz: Sequence[LifeTable] | Sequence[Sequence[LifeTable]] | None = None, *, ts: object = 0.0, d: object = 0.0, n: object = None, m: Literal[1, 2, 3, 4, 6, 12, 14, 24, 26, 52, 365] | Sequence[int] | NDArray[int64] = 1, gr: GrowthRate | float | None | Sequence[GrowthRate | float | None] = None, cashflow_amounts: Sequence[float] | None = None, ir: object = None, return_flows: bool = False, t_output: NDArray[float64] | None = None, benefits: Sequence[float] | NDArray[float64] | None = None, on_error: Literal['raise', 'nan'] = 'raise', record_ids: Sequence[Any] | None = None) float | dict[str, NDArray[float64]] | BatchResult#
Compute the present value of a joint-life annuity-due (prepayable) for three lives.
- Parameters:
ages (Sequence[float] or tuple[Sequence[float], Sequence[float], Sequence[float]]) – Ages of the three lives
[x, y, z]. Must have exactly 3 elements. In batch mode, pass a triple of N-element arrays:(np.array([x0, …]), np.array([y0, …]), np.array([z0, …])).tables_yz (Sequence[LifeTable] or Sequence[Sequence[LifeTable]] or None, optional) –
Life tables for the second and third lives [y, z]. Three modes in batch:
None: usesselffor all three lives in all policies.[LifeTable, LifeTable]: 2 shared tables (y and z) for all N policies.[[t_y0, t_z0], …]of length N: per-policy; each inner list must have exactly 2 elements. RaisesValueErrorif outer length != N.
ts (float or NDArray[np.float64], optional) – Shift (displacement) in years from origin (default 0.0). In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.d (float or NDArray[np.float64], optional) – Deferment period in years (default 0.0). In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.n (float or NDArray[np.float64] or None, optional) – Total duration of the annuity (in years) from contract start. If None, perpetual annuity up to limiting age. In batch mode, may be a scalar or an array of length N. Pandas and Polars Series are accepted; values are converted to float64. Scalar
intliterals (e.g.n=10) are coerced tofloat.m (PaymentFrequencyLiteral, sequence of int, NDArray[np.int64], or array-like, optional) – Number of payments per year (1=annual, 12=monthly, etc., default 1). Integer-valued floats (e.g.
12.0) are coerced toint. In batch mode, may be a scalar (broadcast to all N) or an array/list of length N for per-policy payment frequencies; distinct values are processed in sub-groups by the vectorised batch engine. Pandas and Polars Series are accepted; values are converted to int64.gr (GrowthRate, float, or None, optional) – Growth rate model (default None). A scalar float is auto-wrapped as
GrowthRate(gr)(geometric growth by default). If None, no growth is applied. In batch mode, may be a scalar or a list of N values. Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asGrowthRatewhere applicable). An object-dtype Series ofGrowthRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.cashflow_amounts (Sequence[float], optional) – Custom payment amounts (one per payment period). When provided, disables growth (
gris ignored). Note: due-annuity methods do not acceptcashflow_times; for custom payment timing use the postpayable counterpart (axyz()). Pandas and Polars Series are accepted; converted to NDArray[np.float64] internally.ir (float, InterestRate, list[float or InterestRate], NDArray[np.float64], or None, optional) – Interest rate (constant or curve). If None, uses
interest_rate. In batch mode, may be a scalar (broadcast to all N), a list of Nfloatvalues, or a list of NInterestRateobjects (one curve per policy). Pandas and Polars Series are accepted without.to_numpy(). A numeric Series is converted to per-policy floats (auto-wrapped asInterestRatewhere applicable). An object-dtype Series ofInterestRateinstances is treated like a list of length N (including piecewise curves). Mixed numeric and object elements in one array-like sequence raiseValueError.return_flows (bool, optional) –
If
True, returns a cashflow decomposition dict instead of the present value. The dict schema differs between single-policy and batch:Single-policy (scalar ages): per-payment engine dict. Same keys as
ax(); see that method’sreturn_flowsparameter for the full key reference.Batch (array ages): aggregate portfolio dict
{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.total_pv == np.sum(pv_cf)is always satisfied. RaisesValueErrorindiscrete_simplified/continuous_simplifiedmodes. Default isFalse.t_output (NDArray[np.float64] or None, optional) – External bucketing grid for aggregate flow output. When provided, per-policy present-value contributions — already discounted at their exact payment times — are accumulated into the nearest bucket via
np.searchsortedwithout re-discounting. Controls only the shape ofexpected_cfandpv_cf;total_pvis always the exact sum of all per-payment present values and is identical to thet_output=Noneresult. Typical use: regulatory templates that require a fixed set of time buckets (e.g., annual IFRS 17 disclosure, Solvency II SCR projection). Only effective whenreturn_flows=True; raisesValueErrorifreturn_flows=False. Default isNone. Pandas and Polars Series are accepted; converted to NDArray[np.float64] before use.
- Returns:
float – When
ages[0]is scalar andreturn_flows=False.NDArray[np.float64] – When
ages[0]is an array or list andreturn_flows=False. Shape(N,).dict – When
return_flows=True. Single-policy: per-payment engine dict (same keys asax()). Batch: aggregate portfolio dict{'time_grid', 'expected_cf', 'pv_cf', 'total_pv'}.BatchResult – When
on_error='nan'.
- Raises:
ValueError – If
agesdoes not contain exactly 3 elements. Iftables_yzis provided but does not contain exactly 2 LifeTable instances. If any age is outside[0, w]for corresponding table. Ifreturn_flows=Trueandmis a vector with heterogeneous values. Ift_outputis notNoneandreturn_flows=False. Ift_outputis notNoneand input is scalar (non-batch mode). Ifon_erroris not'raise'or'nan'. Ifon_error='nan'is passed with scalarx(oragesfor joint-life methods). Use a list or array of ages for batch mode. Ifrecord_idsis notNoneand input is scalar (non-batch mode).TypeError – If
agescontains non-numeric values. Iftables_yzcontains non-LifeTable instances. If any numeric argument isbool.
Notes
Boolean values for age, duration, shift, deferment, and rate arguments raise
TypeError. Accepted types for each parameter are described in Parameters above.Joint-life annuity-due for three lives, under the independence assumption:
\[\ddot{a}_{xyz}^{(m)} = \frac{1}{m} \sum_{k=0}^{\infty} v^{k/m} \cdot {}_{k/m}p_x \cdot {}_{k/m}p_y \cdot {}_{k/m}p_z\]Payments occur at the BEGINNING of each period (due/prepayable).
Annuity continues while ALL THREE lives survive (joint-life status).
Uses independence assumption for joint survival probabilities.
cashflow_timesis not supported for annuities-due;cashflow_amountsscales benefits on the standard due payment grid. Useaxyz()for custom payment times.When
gris provided, the growth factor at the j-th payment interval is(1+g)^(j // m)— anniversaries counted from the first payment, not the contract origin.GrowthRate.shifted(ts)consumesint(ts)anniversary years; fractionaltsis actuarially correct (revaluations operate on complete policy years). See Growth Rate Conventions for the full reference.
Batch mode#
Batch mode is activated when either of these conditions is met:
ages[0]is a list or NDArray (age-triggered batch): pass ages as a triple of N-element arrays(x_arr, y_arr, z_arr), each(N,).ages[0]is scalar but any ofn,ts,d,ir,gr, ormis a list/tuple/NDArray of length N (param-triggered batch): scalar ages broadcast to N copies.
In both cases the method returns
NDArray[np.float64]of shape(N,).Per-policy parameters
ts,d,n,ir, andgrmay each be a scalar or an array of length N.mmay be a scalar (broadcast to all N) or an array/list of length N for per-policy payment frequencies; the batch engine groups policies by uniquemvalue and vectorises each group independently.Pandas and Polars Series are accepted for all per-policy parameters (
n,ts,d,ir,gr,m,cashflow_amounts,t_output, and each per-life age array withinages) without calling.to_numpy(); they are converted internally via the array protocol.on_error='nan'marks invalid policies withnp.nan.
See also
LifeTable.axyzJoint-life annuity-immediate for three lives.
LifeTable.äjointJoint-life annuity-due for multiple lives.
LifeTable.äxyJoint-life annuity-due for two lives.
LifeTable.äxSingle-life annuity-due.
Examples
>>> from lactuca import LifeTable >>> # Three lives with same table >>> table = LifeTable('PER2020_Ind_1o', 'm', cohort=1960) >>> table.interest_rate = 0.03 >>> pv = table.äxyz([65, 60, 55], n=20, m=12)
>>> # Three lives with different tables >>> table_m, table_f1, table_f2 = LifeTable('PER2020_Ind_1o', ('m', 'f', 'f'), cohort=1960) >>> table_m.interest_rate = 0.03 >>> pv = table_m.äxyz([65, 62, 58], [table_f1, table_f2], n=15, m=12)
Batch mode:
>>> import numpy as np >>> x_arr = np.array([60.0, 65.0]) >>> y_arr = np.array([55.0, 60.0]) >>> z_arr = np.array([50.0, 55.0]) >>> table.äxyz((x_arr, y_arr, z_arr), n=20, ir=0.03)
- property calculation_mode: str#
Return the current calculation mode from config.
- Returns:
One of
'discrete_precision','discrete_simplified','continuous_precision', or'continuous_simplified'.- Return type:
str
Notes
Controls how annuities, insurances, and endowments are calculated.
Discrete modes use commutation functions (integer ages, annual periods).
Continuous modes use numerical integration (fractional ages supported).
Precision modes preserve maximum accuracy; simplified modes use approximations.
Set via
config.calculation_mode = 'mode_name'.
See also
ConfigGlobal configuration singleton for all LifeTable settings.
Examples
>>> from lactuca import LifeTable, Config, config >>> lt = LifeTable('PASEM2010', 'm') >>> lt.calculation_mode 'discrete_precision' >>> config.calculation_mode = 'continuous_precision' >>> lt.calculation_mode 'continuous_precision' >>> Config().reset()
- property decimals: _DecimalsConfig#
Access decimal precision settings for life table actuarial functions.
This property provides convenient attribute-based access to global decimal precision configuration. All values are proxied dynamically from the
Configsingleton — no state is stored locally in the table instance. Accessingdecimals.ixordecimals.oxraisesAttributeError(not meaningful for life tables).- Returns:
Decimal-precision proxy with attributes for life-table actuarial columns. Available attributes:
lx: int — Survival function \(\ell_x\) precisiondx: int — Deaths \(d_x\) precisionqx: int — Mortality rate \(q_x\) precisionpx: int — Survival probability \(p_x\) precisiontpx: int — Multi-year survival \({}_t p_x\) precisiontqx: int — Multi-year mortality \({}_t q_x\) precisionex: int — Life expectancy \(\mathring{e}_x\) precisionDx,Nx,Sx: int — Commutation functions precisionCx,Mx,Rx: int — Insurance commutation functions precisionLx,Tx: int — Stationary population and total future lifetime precisionax,Ax: int — Annuities and insurances precision
- Return type:
object
- Raises:
AttributeError – If
decimals.ixordecimals.oxare accessed (disability incidence and exit rates are not available for LifeTable).
Notes
Read-only proxy:
self.decimals.xxx = valueraisesAttributeErrorby design; useConfig().decimals.xxx = valueto change precision globally.Global scope: Changes to
Config.decimalsare immediately visible through all table instances.For permanent changes, use
Config().decimals.lx = valueorConfig().set('decimals_lx', value).
Examples
>>> from lactuca import LifeTable, Config >>> lt = LifeTable('PASEM2010', 'm') >>> Config().decimals.annuities = 4 >>> lt.decimals.annuities 4 >>> Config().reset()
See also
Config.decimalsGlobal decimal precision configuration.
- property force_integer_ts: bool#
Return whether fractional
ts(shift) values are rejected.- Returns:
True if fractional
tsraisesValueError; False otherwise.- Return type:
bool
Notes
When
True, any non-integertsraisesValueErrorat validation time.When
False, fractionaltsis silently accepted. AUserWarningis emitted only when aGrowthRateis active, because growth factors use whole-year anniversary indices and a fractional shift would be applied to an integer-indexed schedule.Set via
config.force_integer_ts = True/False.
See also
ConfigGlobal configuration singleton for all LifeTable settings.
Examples
>>> from lactuca import LifeTable, Config >>> lt = LifeTable('PASEM2010', 'm') >>> lt.force_integer_ts False >>> Config().force_integer_ts = True >>> lt.force_integer_ts True >>> Config().reset()
- property force_mortality_method: str#
Return the current force of mortality method from config.
- Returns:
One of
'finite_difference','spline', or'kernel'.- Return type:
str
Notes
Controls how the force of mortality \(\mu_x\) is approximated from discrete survival probabilities in continuous calculation modes.
'finite_difference': finite difference approximation (default).'spline': spline-based approximation.'kernel': kernel smoothing approximation.Used in
continuous_precisionandcontinuous_simplifiedmodes.Set via
config.force_mortality_method = 'method_name'.
See also
ConfigGlobal configuration singleton for all LifeTable settings.
Examples
>>> from lactuca import LifeTable, Config >>> lt = LifeTable('PASEM2010', 'm') >>> lt.force_mortality_method 'finite_difference' >>> Config().force_mortality_method = 'spline' >>> lt.force_mortality_method 'spline' >>> Config().reset()
- property interest_rate: InterestRate | None#
Get the default interest rate for annuity/insurance calculations.
- Returns:
Default interest rate object stored on the table, or None if no default rate has been configured. Constant scalar inputs are normalized to
InterestRatewhen assigned.- Return type:
InterestRate or None
Notes
Provides default interest rate for
ax,Ax,nEx, and related methods.Individual calculation methods can override with their
irparameter.Nonevalue requires explicitirargument in all calculation methods.Float values assigned through
__init__()or the setter are automatically converted to constantInterestRatecurves.
See also
InterestRateInterest rate class supporting constant and variable rate curves.
Examples
>>> from lactuca import LifeTable >>> from lactuca import InterestRate >>> lt = LifeTable('PASEM2010', 'm') >>> lt.interest_rate = 0.03 # Constant rate >>> isinstance(lt.interest_rate, InterestRate) True >>> lt.interest_rate = InterestRate(terms=[5, 10], rates=[0.02, 0.025, 0.03]) # Variable curve >>> isinstance(lt.interest_rate, InterestRate) True
- property mortality_placement: str#
Return the current mortality placement from config.
- Returns:
One of
'beginning','mid', or'end'.- Return type:
str
Notes
Controls the discounting exponent \(\alpha\) in \(C_x = v^{x+\alpha} d_x\).
'beginning': \(\alpha = 0\); benefit discounted as \(v^x d_x\).'mid': \(\alpha = 0.5\); benefit discounted as \(v^{x+1/2} d_x\).'end': \(\alpha = 1\); benefit discounted as \(v^{x+1} d_x\).Affects
Cx()commutation function and all insurance values derived from it.Set via
config.mortality_placement = 'placement_name'.
See also
ConfigGlobal configuration singleton for all LifeTable settings.
LifeTable.CxCommutation function \(C_x = v^{x+\alpha} \cdot d_x\) affected by this setting.
Examples
>>> from lactuca import LifeTable, Config >>> lt = LifeTable('PASEM2010', 'm') >>> lt.mortality_placement 'mid' >>> Config().mortality_placement = 'end' >>> lt.mortality_placement 'end' >>> Config().reset()
- property table_type: str#
Return the type of table.
- Returns:
Always
'life'for LifeTable instances.- Return type:
str
Examples
>>> from lactuca import LifeTable >>> lt = LifeTable('PASEM2010', 'm') >>> lt.table_type 'life'
Inherited from DecrementTable#
The following members are inherited from DecrementTable
without modification. See the DecrementTable reference
for full documentation of each member.
Actuarial methods#
Return lx values (number of lives at age x) for the specified age(s). |
|
Return probability of surviving one period (px) at age x. |
|
Return probability of decrement at age x. |
|
Return interval survival probability tpx for all (x, t) combinations. |
|
Return interval death probability tqx for all (x, t) combinations. |
|
Return \(d_x\) values (number of decrements between ages \(x\) and \(x+1\)). |
Modification#
Apply modifications to the table's decrement rates. |
|
Restore decrement to the original base values and clear all caches. |
|
Return a deep copy of the instance. |
Display#
State#
Current sex used for calculations. |
|
Current cohort (year of birth) for generational tables. |
|
Return the current policy duration for select or non-select tables. |
|
Male weight used to blend male/female rates into unisex rates. |
|
Current upper age limit (omega), accounting for modifications. |
|
True if a modification is currently active on this table. |
|
List of modification descriptors applied in the current call. |
|
Return True if the underlying table is a select-ultimate table. |
|
True when required metadata has not yet been supplied via configure() or setters. |
Deferred construction#
Configure table metadata atomically with a single rebuild. |
|
Context manager to batch multiple setter assignments into one rebuild. |
See also TableRegistry and configure_all() in
Table registry utilities and Deferred construction: pending, configure, and TableRegistry in Using Actuarial Tables.
Table metadata#
Name of the underlying actuarial table. |
|
Access the underlying TableSource instance with table data and metadata. |
|
Whether the table includes cohort-based improvement factors. |
|
Base year for generational improvement. |
|
Original terminal age from metadata (immutable). |
|
Minimum defined age in the original table. |
|
Human-readable table description from metadata. |
|
Valid sexes for this table (e.g. |
|
Whether rates are identical for both sexes. |
|
Generational improvement formula type. |
|
Whether this is a select-ultimate table. |
|
Select period in years. |
|
Calendar-year index for generational improvement on select columns. |
|
Minimum integer duration. |
|
Whether improvement factors are stored by policy duration. |
|
Mortality improvement column layout tag. |
|
Projection calendar years for year-indexed MI tables. |
|
Basename of the loaded table file. |
|
Absolute path to the loaded table file. |
|
Raw underlying table data (all columns, all sexes). |
|
Raw metadata dictionary from the table file. |