Source code for basiclife.BasicTerm_SC.Projection

"""
The :mod:`~basiclife.BasicTerm_SC.Projection` space includes
projection logic for individual model points.

:mod:`~basiclife.BasicTerm_SC.Projection` is derived from
:mod:`basiclife.BasicTerm_S.Projection`
by applying changes
to make its compiled model run faster.

Policy attributes and other input data are read from
:mod:`~basiclife.BasicTerm_SC.Data`, which is referenced as
:attr:`data` in :mod:`~basiclife.BasicTerm_SC.Projection`.

In :mod:`~basiclife.BasicTerm_SC.Data`,
policy attributes, such as :func:`~basiclife.BasicTerm_SC.Data.policy_term`,
are returned as 1-dimensional numpy arrays.
Consequently, :mod:`~basiclife.BasicTerm_SC.Projection` is parameterized with
:attr:`idx`, which represents the array index to identify model points.


.. rubric:: Parameters and References

(In all the sample code below,
the global variable `BasicTerm_SC` refers to the
:mod:`~basiclife.BasicTerm_SC` model.)

Attributes:

    idx: Array index to identify a model point.
        Policy attributes, such as :func:`~basiclife.BasicTerm_SC.Data.policy_term`,
        are returned as 1-dimensional numpy arrays
        in :mod:`~basiclife.BasicTerm_SC.Data`.
        ``idx`` is defined as a Reference, and its value
        is used for determining the selected model point.
        By default, ``0`` is assigned. To select another model point,
        assign its array index to it::

            >>> BasicTerm_SC.Projection.idx = 2

        ``idx`` is also defined as the parameter of the
        :mod:`~basiclife.BasicTerm_SC.Projection` Space,
        which makes it possible to create dynamic child space
        for multiple model points::

            >>> BasicTerm_SC.Projection.parameters
            ('idx',)

            >>> BasicTerm_SC.Projection[1]
            <ItemSpace BasicTerm_SC.Projection[1]>

            >>> BasicTerm_SC.Projection[2]
            <ItemSpace BasicTerm_SC.Projection[2]>

    data: The :mod:`~basiclife.BasicTerm_SC.Data` space.
    np: The `numpy`_ module.
    pd: The `pandas`_ module.

.. _numpy:
   https://numpy.org/

.. _pandas:
   https://pandas.pydata.org/

.. _new_pandas:
   https://docs.modelx.io/en/latest/reference/space/generated/modelx.core.space.UserSpace.new_pandas.html

"""

from modelx.serialize.jsonvalues import *

_formula = lambda idx: None

_bases = []

_allow_none = None

_spaces = []

# ---------------------------------------------------------------------------
# Cells

[docs] def age(t): """The attained age at time t. Defined as:: age_at_entry() + duration(t) .. seealso:: * :func:`age_at_entry` * :func:`duration` """ return age_at_entry() + duration(t)
[docs] def age_at_entry(): """The age at entry of the selected model point The element of :func:`basiclife.BasicTerm_SC.Data.age_at_entry` at index :attr:`idx`. """ return data.age_at_entry()[idx]
[docs] def check_pv_net_cf(): """Check present value summation Check if the present value of :func:`net_cf` matches the sum of the present values each cashflows. Returns the check result as :obj:`True` or :obj:`False`. .. seealso:: * :func:`net_cf` * :func:`pv_net_cf` """ import math pv: float = 0 for t in range(proj_len()): pv += net_cf(t) * disc_factor(t) return math.isclose(pv, pv_net_cf())
[docs] def claim_pp(t): """Claim per policy The claim amount per plicy. Defaults to :func:`sum_assured`. """ return sum_assured()
[docs] def claims(t): """Claims Claims during the period from ``t`` to ``t+1`` defined as:: claim_pp(t) * pols_death(t) .. seealso:: * :func:`claim_pp` * :func:`pols_death` """ return claim_pp(t) * pols_death(t)
[docs] def commissions(t): """Commissions By default, 100% premiums for the first year, 0 otherwise. .. seealso:: * :func:`premiums` * :func:`duration` """ return premiums(t) if duration(t) == 0 else 0
[docs] def disc_factor(t): """Discount factor at time ``t``. Defined as the inverse of ``(1 + disc_rate_mth(t))`` to the ``t``-th power. .. seealso:: :func:`disc_rate_mth` """ return (1 + disc_rate_mth(t))**(-t)
[docs] def disc_rate_mth(t): """Monthly discount rate Nummpy array of monthly discount rates from time 0 to :func:`proj_len` - 1 defined as:: (1 + disc_rate_ann)**(1/12) - 1 .. seealso:: :func:`~basiclife.BasicTerm_SC.Data.disc_rate_ann_array` """ return (1 + data.disc_rate_ann_array()[t//12])**(1/12) - 1
[docs] def duration(t): """Duration in force in years""" return t//12
[docs] def expense_acq(): """Acquisition expense per policy ``300`` by default. """ return 300
[docs] def expense_maint(): """Annual maintenance expense per policy ``60`` by default. """ return 60
[docs] def expenses(t): """Acquisition and maintenance expenses Expense cashflow during the period from ``t`` to ``t+1``. For any ``t``, the maintenance expense is recognized, which is defined as:: pols_if(t) * expense_maint()/12 * inflation_factor(t) At ``t=0`` only, the acquisition expense, defined as :func:`expense_acq`, is recognized. .. seealso:: * :func:`pols_if` * :func:`expense_maint` * :func:`inflation_factor` .. versionchanged:: 0.2.0 The maintenance expense is also recognized for ``t=0``. """ maint = pols_if(t) * expense_maint()/12 * inflation_factor(t) if t == 0: return expense_acq() + maint else: return maint
[docs] def inflation_factor(t): """The inflation factor at time t .. seealso:: * :func:`inflation_rate` """ return (1 + inflation_rate())**(t/12)
[docs] def inflation_rate(): """Inflation rate""" return 0.01
[docs] def lapse_rate(t): """Lapse rate By default, the lapse rate assumption is defined by duration as:: max(0.1 - 0.02 * duration(t), 0.02) .. seealso:: :func:`duration` """ return max(0.1 - 0.02 * duration(t), 0.02)
[docs] def loading_prem(): """Loading per premium ``0.5`` by default. .. seealso:: * :func:`premium_pp` """ return 0.50
[docs] def mort_rate(t): """Mortality rate to be applied at time t .. seealso:: * :func:`~basiclife.BasicTerm_SC.Data.mort_table_array` """ return data.mort_table_array()[age(t), max(min(5, duration(t)),0)]
[docs] def mort_rate_mth(t): """Monthly mortality rate to be applied at time t .. seealso:: * :func:`mort_rate` """ return 1-(1- mort_rate(t))**(1/12)
[docs] def net_cf(t): """Net cashflow Net cashflow for the period from ``t`` to ``t+1`` defined as:: premiums(t) - claims(t) - expenses(t) - commissions(t) .. seealso:: * :func:`premiums` * :func:`claims` * :func:`expenses` * :func:`commissions` """ return premiums(t) - claims(t) - expenses(t) - commissions(t)
[docs] def net_premium_pp(): """Net premium per policy The net premium per policy is defined so that the present value of net premiums equates to the present value of claims:: pv_claims() / pv_pols_if() .. seealso:: * :func:`pv_claims` * :func:`pv_pols_if` """ return pv_claims() / pv_pols_if()
[docs] def policy_term(): """The policy term of the selected model point. The element of :func:`~basiclife.BasicTerm_SC.Data.policy_term` at index :attr:`idx`. """ return data.policy_term()[idx]
[docs] def pols_death(t): """Number of death occurring at time t""" return pols_if(t) * mort_rate_mth(t)
[docs] def pols_if(t): """Number of policies in-force Number of in-force policies calculated recursively. The initial value is read from :func:`pols_if_init`. Subsequent values are defined recursively as:: pols_if(t-1) - pols_lapse(t-1) - pols_death(t-1) - pols_maturity(t) .. seealso:: * :func:`pols_lapse` * :func:`pols_death` * :func:`pols_maturity` """ if t==0: return pols_if_init() elif t > policy_term() * 12: return 0 else: return pols_if(t-1) - pols_lapse(t-1) - pols_death(t-1) - pols_maturity(t)
[docs] def pols_if_init(): """Initial Number of Policies In-force Number of in-force policies at time 0 referenced from :func:`pols_if`. Defaults to 1. """ return 1
[docs] def pols_lapse(t): """Number of lapse occurring at time t .. seealso:: * :func:`pols_if` * :func:`lapse_rate` """ return (pols_if(t) - pols_death(t)) * (1-(1 - lapse_rate(t))**(1/12))
[docs] def pols_maturity(t): """Number of maturing policies The policy maturity occurs at ``t == 12 * policy_term()``, after death and lapse during the last period:: pols_if(t-1) - pols_lapse(t-1) - pols_death(t-1) otherwise ``0``. """ if t == policy_term() * 12: return pols_if(t-1) - pols_lapse(t-1) - pols_death(t-1) else: return 0
[docs] def premium_pp(): """Monthly premium per policy Monthly premium amount per policy defined as:: round((1 + loading_prem()) * net_premium(), 2) .. versionchanged:: 0.2.0 The ``t`` parameter is removed. .. seealso:: * :func:`loading_prem` * :func:`net_premium_pp` """ return round((1 + loading_prem()) * net_premium_pp(), 2)
[docs] def premiums(t): """Premium income Premium income during the period from ``t`` to ``t+1`` defined as:: premium_pp(t) * pols_if(t) .. seealso:: * :func:`premium_pp` * :func:`pols_if` """ return premium_pp() * pols_if(t)
[docs] def proj_len(): """Projection length in months Projection length in months defined as:: 12 * policy_term() + 1 .. seealso:: :func:`policy_term` """ return 12 * policy_term() + 1
[docs] def pv_claims(): """Present value of claims .. seealso:: * :func:`claims` """ pv = 0.0 for t in range(proj_len()): pv += claims(t) * disc_factor(t) return pv
[docs] def pv_commissions(): """Present value of commissions .. seealso:: * :func:`expenses` """ pv = 0.0 for t in range(proj_len()): pv += commissions(t) * disc_factor(t) return pv
[docs] def pv_expenses(): """Present value of expenses .. seealso:: * :func:`expenses` """ pv = 0.0 for t in range(proj_len()): pv += expenses(t) * disc_factor(t) return pv
[docs] def pv_net_cf(): """Present value of net cashflows. Defined as:: pv_premiums() - pv_claims() - pv_expenses() - pv_commissions() .. seealso:: * :func:`pv_premiums` * :func:`pv_claims` * :func:`pv_expenses` * :func:`pv_commissions` """ return pv_premiums() - pv_claims() - pv_expenses() - pv_commissions()
[docs] def pv_pols_if(): """Present value of policies in-force The discounted sum of the number of in-force policies at each month. It is used as the annuity factor for calculating :func:`net_premium_pp`. """ pv = 0.0 for t in range(proj_len()): pv += pols_if(t) * disc_factor(t) return pv
[docs] def pv_premiums(): """Present value of premiums .. seealso:: * :func:`premiums` """ pv = 0.0 for t in range(proj_len()): pv += premiums(t) * disc_factor(t) return pv
[docs] def result_cf(): """Result table of cashflows .. seealso:: * :func:`premiums` * :func:`claims` * :func:`expenses` * :func:`commissions` * :func:`net_cf` """ t_len = range(proj_len()) data = { "Premiums": [premiums(t) for t in t_len], "Claims": [claims(t) for t in t_len], "Expenses": [expenses(t) for t in t_len], "Commissions": [commissions(t) for t in t_len], "Net Cashflow": [net_cf(t) for t in t_len] } return pd.DataFrame.from_dict(data)
[docs] def result_pv(): """Result table of present value of cashflows .. seealso:: * :func:`pv_premiums` * :func:`pv_claims` * :func:`pv_expenses` * :func:`pv_commissions` * :func:`pv_net_cf` """ cols = ["Premiums", "Claims", "Expenses", "Commissions", "Net Cashflow"] pvs = [pv_premiums(), pv_claims(), pv_expenses(), pv_commissions(), pv_net_cf()] per_prem = [x / pv_premiums() for x in pvs] return pd.DataFrame.from_dict( data={"PV": pvs, "% Premium": per_prem}, columns=cols, orient='index')
[docs] def sex(): """The sex of the selected model point .. note:: This cells is not used by default. The element of :func:`~basiclife.BasicTerm_SC.Data.sex` at index :attr:`idx`. """ return data.sex()[idx]
[docs] def sum_assured(): """The sum assured of the selected model point The element of :func:`~basiclife.BasicTerm_SC.Data.sum_assured` at index :attr:`idx`. """ return data.sum_assured()[idx]
# --------------------------------------------------------------------------- # References pd = ("Module", "pandas") np = ("Module", "numpy") data = ("Interface", ("..", "Data"), "auto") idx = 0