CashValue_ME_EX1 vs CashValue_ME_EX4
def age(t):
def age(t):
    """The attained age at time t.
    """The attained age at time t.
    Defined as::
    Defined as::
        age_at_entry() + duration(t)
        age_at_entry() + duration(t)
    .. seealso::
    .. seealso::
        * :func:`age_at_entry`
        * :func:`age_at_entry`
        * :func:`duration`
        * :func:`duration`
    """
    """
    return age_at_entry() + duration(t)
    return age_at_entry() + duration(t)
def age_at_entry():
def age_at_entry():
    """The age at entry of the model points
    """The age at entry of the model points
    The ``age_at_entry`` column of the DataFrame returned by
    The ``age_at_entry`` column of the DataFrame returned by
    :func:`model_point`.
    :func:`model_point`.
    """
    """
    return model_point()["age_at_entry"]
    return model_point()["age_at_entry"].values
def av_at(t, timing):
def av_at(t, timing):
    """Account value in-force
    """Account value in-force
    :func:`av_at(t, timing)<av_at>` calculates
    :func:`av_at(t, timing)<av_at>` calculates
    the total amount of account value at time ``t`` for the policies represented
    the total amount of account value at time ``t`` for the policies represented
    by a model point.
    by a model point.
    At each ``t``, the events that change the account value balance
    At each ``t``, the events that change the account value balance
    occur in the following order:
    occur in the following order:
        * Maturity
        * Maturity
        * New business and premium payment
        * New business and premium payment
        * Fee deduction
        * Fee deduction
    The second parameter ``timing`` takes a string to
    The second parameter ``timing`` takes a string to
    indicate the timing of the account value, which is either
    indicate the timing of the account value, which is either
    ``"BEF_MAT"``, ``"BEF_NB"`` or ``"BEF_FEE"``.
    ``"BEF_MAT"``, ``"BEF_NB"`` or ``"BEF_FEE"``.
    .. rubric:: BEF_MAT
    .. rubric:: BEF_MAT
    The amount of account value before maturity, defined as::
    The amount of account value before maturity, defined as::
        av_pp_at(t, "BEF_PREM") * pols_if_at(t, "BEF_MAT")
        av_pp_at(t, "BEF_PREM") * pols_if_at(t, "BEF_MAT")
    .. rubric:: BEF_NB
    .. rubric:: BEF_NB
    The amount of account value before new business after maturity,
    The amount of account value before new business after maturity,
    defined as::
    defined as::
        av_pp_at(t, "BEF_PREM") * pols_if_at(t, "BEF_NB")
        av_pp_at(t, "BEF_PREM") * pols_if_at(t, "BEF_NB")
    .. rubric:: BEF_FEE
    .. rubric:: BEF_FEE
    The amount of account value before lapse and death after new business,
    The amount of account value before lapse and death after new business,
    defined as::
    defined as::
        av_pp_at(t, "BEF_FEE") * pols_if_at(t, "BEF_DECR")
        av_pp_at(t, "BEF_FEE") * pols_if_at(t, "BEF_DECR")
    .. seealso::
    .. seealso::
        * :func:`pols_if_at`
        * :func:`pols_if_at`
        * :func:`av_pp_at`
        * :func:`av_pp_at`
    """
    """
    if timing == "BEF_MAT":
    if timing == "BEF_MAT":
        return av_pp_at(t, "BEF_PREM") * pols_if_at(t, "BEF_MAT")
        return av_pp_at(t, "BEF_PREM") * pols_if_at(t, "BEF_MAT")
    elif timing == "BEF_NB":
    elif timing == "BEF_NB":
        return av_pp_at(t, "BEF_PREM") * pols_if_at(t, "BEF_NB")
        return av_pp_at(t, "BEF_PREM") * pols_if_at(t, "BEF_NB")
    elif timing == "BEF_FEE":
    elif timing == "BEF_FEE":
        return av_pp_at(t, "BEF_FEE") * pols_if_at(t, "BEF_DECR")
        return av_pp_at(t, "BEF_FEE") * pols_if_at(t, "BEF_DECR")
    else:
    else:
        raise ValueError("invalid timing")
        raise ValueError("invalid timing")
def av_change(t):
def av_change(t):
    """Change in account value
    """Change in account value
    Change in account value during each period, defined as::
    Change in account value during each period, defined as::
        av_at(t+1, 'BEF_MAT') - av_at(t, 'BEF_MAT')
        av_at(t+1, 'BEF_MAT') - av_at(t, 'BEF_MAT')
    .. seealso::
    .. seealso::
        * :func:`net_cf`
        * :func:`net_cf`
    """
    """
    return av_at(t+1, 'BEF_MAT') - av_at(t, 'BEF_MAT')
    return av_at(t+1, 'BEF_MAT') - av_at(t, 'BEF_MAT')
def av_pp_at(t, timing):
def av_pp_at(t, timing):
    """Account value per policy
    """Account value per policy
    :func:`av_at(t, timing)<av_at>` calculates
    :func:`av_at(t, timing)<av_at>` calculates
    the total amount of account value at time ``t`` for the policies in-force.
    the total amount of account value at time ``t`` for the policies in-force.
    At each ``t``, the events that change the account value balance
    At each ``t``, the events that change the account value balance
    occur in the following order:
    occur in the following order:
        * Premium payment
        * Premium payment
        * Fee deduction
        * Fee deduction
    Investment income is assumed to be earned throughout each month,
    Investment income is assumed to be earned throughout each month,
    so at the middle of the month when death and lapse occur,
    so at the middle of the month when death and lapse occur,
    half the investment income for the month is credited.
    half the investment income for the month is credited.
    The second parameter ``timing`` takes a string to
    The second parameter ``timing`` takes a string to
    indicate the timing of the account value, which is either
    indicate the timing of the account value, which is either
    ``"BEF_PREM"``, ``"BEF_FEE"``, ``"BEF_INV"`` or ``"MID_MTH"``.
    ``"BEF_PREM"``, ``"BEF_FEE"``, ``"BEF_INV"`` or ``"MID_MTH"``.
    .. rubric:: BEF_PREM
    .. rubric:: BEF_PREM
    Account value before premium payment.
    Account value before premium payment.
    At the start of the projection (i.e. when ``t=0``),
    At the start of the projection (i.e. when ``t=0``),
    the account value is set to :func:`av_pp_init`.
    the account value is set to :func:`av_pp_init`.
    .. rubric:: BEF_FEE
    .. rubric:: BEF_FEE
    Account value after premium payment before fee deduction
    Account value after premium payment before fee deduction
    .. rubric:: BEF_INV
    .. rubric:: BEF_INV
    Account value after fee deduction before crediting investemnt return
    Account value after fee deduction before crediting investemnt return
    .. rubric:: MID_MTH
    .. rubric:: MID_MTH
    Account value at middle of month (``t+0.5``) when
    Account value at middle of month (``t+0.5``) when
    half the investment retun for the month is credited
    half the investment retun for the month is credited
    .. seealso::
    .. seealso::
        * :func:`av_pp_init`
        * :func:`av_pp_init`
        * :func:`inv_income_pp`
        * :func:`inv_income_pp`
        * :func:`prem_to_av_pp`
        * :func:`prem_to_av_pp`
        * :func:`maint_fee_pp`
        * :func:`maint_fee_pp`
        * :func:`coi_pp`
        * :func:`coi_pp`
        * :func:`av_at`
        * :func:`av_at`
    """
    """
    if timing == "BEF_PREM":
    if timing == "BEF_PREM":
        if t == 0:
        if t == 0:
            return av_pp_init()
            return av_pp_init()
        else:
        else:
            return av_pp_at(t-1, "BEF_INV") + inv_income_pp(t-1)
            return av_pp_at(t-1, "BEF_INV") + inv_income_pp(t-1)
    elif timing == "BEF_FEE":
    elif timing == "BEF_FEE":
        return av_pp_at(t, "BEF_PREM") + prem_to_av_pp(t)
        return av_pp_at(t, "BEF_PREM") + prem_to_av_pp(t)
    elif timing == "BEF_INV":
    elif timing == "BEF_INV":
        return av_pp_at(t, "BEF_FEE") - maint_fee_pp(t) - coi_pp(t)
        return av_pp_at(t, "BEF_FEE") - maint_fee_pp(t) - coi_pp(t)
    elif timing == "MID_MTH":
    elif timing == "MID_MTH":
        return av_pp_at(t, "BEF_INV") + 0.5 * inv_income_pp(t)
        return av_pp_at(t, "BEF_INV") + 0.5 * inv_income_pp(t)
    else:
    else:
        raise ValueError("invalid timing")
        raise ValueError("invalid timing")
def av_pp_init():
def av_pp_init():
    """Initial account value per policy
    """Initial account value per policy
    For existing business at time ``0``,
    For existing business at time ``0``,
    returns initial per-policy accout value read from
    returns initial per-policy accout value read from
    the ``av_pp_init`` column in :func:`model_point`.
    the ``av_pp_init`` column in :func:`model_point`.
    For new business, 0 should be entered in the column.
    For new business, 0 should be entered in the column.
    .. seealso::
    .. seealso::
        * :func:`model_point`
        * :func:`model_point`
        * :func:`av_pp_at`
        * :func:`av_pp_at`
    """
    """
    return model_point()["av_pp_init"]
    return model_point()["av_pp_init"].values
def check_av_roll_fwd():
def check_av_roll_fwd():
    """Check account value roll-forward
    """Check account value roll-forward
    Returns ``Ture`` if ``av_at(t+1, "BEF_NB")`` equates to
    Returns ``Ture`` if ``av_at(t+1, "BEF_NB")`` equates to
    the following expression for all ``t``, otherwise returns ``False``::
    the following expression for all ``t``, otherwise returns ``False``::
        av_at(t, "BEF_MAT")
        av_at(t, "BEF_MAT")
              + prem_to_av(t)
              + prem_to_av(t)
              - maint_fee(t)
              - maint_fee(t)
              - coi(t)
              - coi(t)
              + inv_income(t)
              + inv_income(t)
              - claims_from_av(t, "DEATH")
              - claims_from_av(t, "DEATH")
              - claims_from_av(t, "LAPSE")
              - claims_from_av(t, "LAPSE")
              - claims_from_av(t, "MATURITY"))
              - claims_from_av(t, "MATURITY"))
    .. seealso::
    .. seealso::
        * :func:`av_at`
        * :func:`av_at`
        * :func:`prem_to_av`
        * :func:`prem_to_av`
        * :func:`maint_fee`
        * :func:`maint_fee`
        * :func:`coi`
        * :func:`coi`
        * :func:`inv_income`
        * :func:`inv_income`
        * :func:`claims_from_av`
        * :func:`claims_from_av`
    """
    """
    for t in range(max_proj_len()):
    for t in range(max_proj_len()):
        av = (av_at(t, "BEF_MAT")
        av = (av_at(t, "BEF_MAT")
              + prem_to_av(t)
              + prem_to_av(t)
              - maint_fee(t)
              - maint_fee(t)
              - coi(t)
              - coi(t)
              + inv_income(t)
              + inv_income(t)
              - claims_from_av(t, "DEATH")
              - claims_from_av(t, "DEATH")
              - claims_from_av(t, "LAPSE")
              - claims_from_av(t, "LAPSE")
              - claims_from_av(t, "MATURITY"))
              - claims_from_av(t, "MATURITY"))
        if np.all(np.isclose(av_at(t+1, "BEF_MAT"), av)):
        if np.all(np.isclose(av_at(t+1, "BEF_MAT"), av)):
            continue
            continue
        else:
        else:
            return False
            return False
    return True
    return True
def check_margin():
def check_margin():
    """Check consistency between net cashflow and margins
    """Check consistency between net cashflow and margins
    Returns ``True`` if :func:`net_cf` equates to the sum of
    Returns ``True`` if :func:`net_cf` equates to the sum of
    :func:`margin_expense` and :func:`margin_mortality` for all ``t``,
    :func:`margin_expense` and :func:`margin_mortality` for all ``t``,
    otherwise, returns ``False``.
    otherwise, returns ``False``.
    .. seealso::
    .. seealso::
        * :func:`net_cf`
        * :func:`net_cf`
        * :func:`margin_expense`
        * :func:`margin_expense`
        * :func:`margin_mortality`
        * :func:`margin_mortality`
    """
    """
    res = []
    res = []
    for t in range(max_proj_len()):
    for t in range(max_proj_len()):
        res.append(np.all(np.isclose(net_cf(t), margin_expense(t) + margin_mortality(t))))
        res.append(np.all(np.isclose(net_cf(t), margin_expense(t) + margin_mortality(t))))
    return all(res)
    return all(res)
def check_pv_net_cf():
def check_pv_net_cf():
    """Check present value summation
    """Check present value summation
    Check if the present value of :func:`net_cf` matches the
    Check if the present value of :func:`net_cf` matches the
    sum of the present values of each cashflow.
    sum of the present values of each cashflow.
    Returns the check result as :obj:`True` or :obj:`False`.
    Returns the check result as :obj:`True` or :obj:`False`.
     .. seealso::
     .. seealso::
        * :func:`net_cf`
        * :func:`net_cf`
        * :func:`pv_net_cf`
        * :func:`pv_net_cf`
    """
    """
    cfs = np.array(list(net_cf(t) for t in range(max_proj_len()))).transpose()
    cfs = np.array(list(net_cf(t) for t in range(max_proj_len()))).transpose()
    pvs = cfs @ disc_factors()[:max_proj_len()]
    pvs = cfs @ disc_factors()[:max_proj_len()]
    return np.all(np.isclose(pvs, pv_net_cf()))
    return np.all(np.isclose(pvs, pv_net_cf()))
def claim_net_pp(t, kind):
def claim_net_pp(t, kind):
    if kind == "DEATH":
    if kind == "DEATH":
        return claim_pp(t, "DEATH") - av_pp_at(t, "MID_MTH")
        return claim_pp(t, "DEATH") - av_pp_at(t, "MID_MTH")
    elif kind == "LAPSE":
    elif kind == "LAPSE":
        return 0
        return 0
    elif kind == "MATURITY":
    elif kind == "MATURITY":
        return claim_pp(t, "MATURITY") - av_pp_at(t, "BEF_PREM")
        return claim_pp(t, "MATURITY") - av_pp_at(t, "BEF_PREM")
    else:
    else:
        raise ValueError("invalid kind")
        raise ValueError("invalid kind")
def claim_pp(t, kind):
def claim_pp(t, kind):
    """Claim per policy
    """Claim per policy
    The claim amount per policy. The second parameter
    The claim amount per policy. The second parameter
    is to indicate the type of the claim, and
    is to indicate the type of the claim, and
    it takes a string, which is either ``"DEATH"``, ``"LAPSE"`` or ``"MATURITY"``.
    it takes a string, which is either ``"DEATH"``, ``"LAPSE"`` or ``"MATURITY"``.
    The death benefit as denoted by ``"DEATH"``, is
    The death benefit as denoted by ``"DEATH"``, is
    the greater of :func:`sum_assured` and
    the greater of :func:`sum_assured` and
    mid-month account value (:func:`av_pp_at(t, "MID_MTH")<av_pp_at>`).
    mid-month account value (:func:`av_pp_at(t, "MID_MTH")<av_pp_at>`).
    The surrender benefit as denoted by ``"LAPSE"`` and
    The surrender benefit as denoted by ``"LAPSE"`` and
    the maturity benefit as denoted by ``"MATURITY"`` are
    the maturity benefit as denoted by ``"MATURITY"`` are
    equal to the mid-month account value.
    equal to the mid-month account value.
    .. seealso::
    .. seealso::
        * :func:`sum_assured`
        * :func:`sum_assured`
        * :func:`av_pp_at`
        * :func:`av_pp_at`
    """
    """
    if kind == "DEATH":
    if kind == "DEATH":
        return np.maximum(sum_assured(), av_pp_at(t, "MID_MTH"))
        return np.maximum(sum_assured(), av_pp_at(t, "MID_MTH"))
    elif kind == "LAPSE":
    elif kind == "LAPSE":
        return av_pp_at(t, "MID_MTH")
        return av_pp_at(t, "MID_MTH")
    elif kind == "MATURITY":
    elif kind == "MATURITY":
        return np.maximum(sum_assured(), av_pp_at(t, "BEF_PREM"))
        return np.maximum(sum_assured(), av_pp_at(t, "BEF_PREM"))
    else:
    else:
        raise ValueError("invalid kind")
        raise ValueError("invalid kind")
def claims(t, kind=None):
def claims(t, kind=None):
    """Claims
    """Claims
    The claim amount during the period from ``t`` to ``t+1``.
    The claim amount during the period from ``t`` to ``t+1``.
    The optional second parameter is for indicating the type of the claim, and
    The optional second parameter is for indicating the type of the claim, and
    it takes a string, which is either ``"DEATH"``, ``"LAPSE"`` or ``"MATURITY"``,
    it takes a string, which is either ``"DEATH"``, ``"LAPSE"`` or ``"MATURITY"``,
    or defaults to ``None`` to indicate the total of all the types of claims
    or defaults to ``None`` to indicate the total of all the types of claims
    during the period.
    during the period.
    The death benefit as denoted by ``"DEATH"`` is defined as::
    The death benefit as denoted by ``"DEATH"`` is defined as::
        claim_pp(t) * pols_death(t)
        claim_pp(t) * pols_death(t)
    The surrender benefit as denoted by ``"LAPSE"`` is defined as::
    The surrender benefit as denoted by ``"LAPSE"`` is defined as::
        claims_from_av(t, "LAPSE") - surr_charge(t)
        claims_from_av(t, "LAPSE") - surr_charge(t)
    The maturity benefit as denoted by ``"MATURITY"`` is defined as::
    The maturity benefit as denoted by ``"MATURITY"`` is defined as::
        claims_from_av(t, "MATURITY")
        claims_from_av(t, "MATURITY")
    .. seealso::
    .. seealso::
        * :func:`claim_pp`
        * :func:`claim_pp`
        * :func:`pols_death`
        * :func:`pols_death`
        * :func:`claims_from_av`
        * :func:`claims_from_av`
        * :func:`surr_charge`
        * :func:`surr_charge`
    """
    """
    if kind == "DEATH":
    if kind == "DEATH":
        return claim_pp(t, "DEATH") * pols_death(t)
        return claim_pp(t, "DEATH") * pols_death(t)
    elif kind == "LAPSE":
    elif kind == "LAPSE":
        return claims_from_av(t, "LAPSE") - surr_charge(t)
        return claims_from_av(t, "LAPSE") - surr_charge(t)
    elif kind == "MATURITY":
    elif kind == "MATURITY":
        return claim_pp(t, "MATURITY") * pols_maturity(t)
        return claim_pp(t, "MATURITY") * pols_maturity(t)
    elif kind is None:
    elif kind is None:
        return sum(claims[t, k] for k in ["DEATH", "LAPSE", "MATURITY"])
        return sum(claims[t, k] for k in ["DEATH", "LAPSE", "MATURITY"])
    else:
    else:
        raise ValueError("invalid kind")
        raise ValueError("invalid kind")
def claims_from_av(t, kind):
def claims_from_av(t, kind):
    """Account value taken out to pay claim
    """Account value taken out to pay claim
    The part of the claim amount that is paid from account value.
    The part of the claim amount that is paid from account value.
    The second parameter takes a string indicating the type of the claim,
    The second parameter takes a string indicating the type of the claim,
    which is either ``"DEATH"``, ``"LAPSE"`` or ``"MATURITY"``.
    which is either ``"DEATH"``, ``"LAPSE"`` or ``"MATURITY"``.
    Death benefit is denoted by ``"DEATH"``, is defined as::
    Death benefit is denoted by ``"DEATH"``, is defined as::
        av_pp_at(t, "MID_MTH") * pols_death(t)
        av_pp_at(t, "MID_MTH") * pols_death(t)
    When the account value is greater than the death benefit,
    When the account value is greater than the death benefit,
    the death benefit equates to the account value.
    the death benefit equates to the account value.
    Surrender benefit as denoted by ``"LAPSE"`` is defined as::
    Surrender benefit as denoted by ``"LAPSE"`` is defined as::
        av_pp_at(t, "MID_MTH") * pols_lapse(t)
        av_pp_at(t, "MID_MTH") * pols_lapse(t)
    As the surrender benefit is defined as account value less surrender
    As the surrender benefit is defined as account value less surrender
    charge, when there is no surrender charge the surrender benefit
    charge, when there is no surrender charge the surrender benefit
    equates to the account value.
    equates to the account value.
    Maturity benefit as denoted by ``"MATURITY"`` is defined as::
    Maturity benefit as denoted by ``"MATURITY"`` is defined as::
        av_pp_at(t, "BEF_PREM") * pols_maturity(t)
        av_pp_at(t, "BEF_PREM") * pols_maturity(t)
    By default, the maturity benefit equates to the account value
    By default, the maturity benefit equates to the account value
    of maturing policies.
    of maturing policies.
    .. seealso::
    .. seealso::
        * :func:`av_pp_at`
        * :func:`av_pp_at`
        * :func:`pols_death`
        * :func:`pols_death`
        * :func:`pols_lapse`
        * :func:`pols_lapse`
        * :func:`pols_maturity`
        * :func:`pols_maturity`
    """
    """
    if kind == "DEATH":
    if kind == "DEATH":
        return av_pp_at(t, "MID_MTH") * pols_death(t)
        return av_pp_at(t, "MID_MTH") * pols_death(t)
    elif kind == "LAPSE":
    elif kind == "LAPSE":
        return av_pp_at(t, "MID_MTH") * pols_lapse(t)
        return av_pp_at(t, "MID_MTH") * pols_lapse(t)
    elif kind == "MATURITY":
    elif kind == "MATURITY":
        return av_pp_at(t, "BEF_PREM") * pols_maturity(t)
        return av_pp_at(t, "BEF_PREM") * pols_maturity(t)
    else:
    else:
        raise ValueError("invalid kind")
        raise ValueError("invalid kind")
def claims_over_av(t, kind):
def claims_over_av(t, kind):
    """Claim in excess of account value
    """Claim in excess of account value
    The amount of death benefits in excess of account value.
    The amount of death benefits in excess of account value.
    :func:`coi` net of this amount represents mortality margin.
    :func:`coi` net of this amount represents mortality margin.
    .. seealso::
    .. seealso::
        * :func:`margin_mortality`
        * :func:`margin_mortality`
        * :func:`coi`
        * :func:`coi`
    """
    """
    return claims(t, kind) - claims_from_av(t, kind)
    return claims(t, kind) - claims_from_av(t, kind)
def coi(t):
def coi(t):
    """Cost of insurance charges
    """Cost of insurance charges
    The cost of insurance charges deducted from acccount values
    The cost of insurance charges deducted from acccount values
    each period.
    each period.
    .. seealso::
    .. seealso::
        * :func:`pols_if_at`
        * :func:`pols_if_at`
        * :func:`coi_pp`
        * :func:`coi_pp`
    """
    """
    return coi_pp(t) * pols_if_at(t, "BEF_DECR")
    return coi_pp(t) * pols_if_at(t, "BEF_DECR")
def coi_pp(t):
def coi_pp(t):
    """Cost of insurance charges per policy
    """Cost of insurance charges per policy
    The cost of insurance charges per policy.
    The cost of insurance charges per policy.
    Defined as the coi charge rate times net amount at risk per policy.
    Defined as the coi charge rate times net amount at risk per policy.
    .. seealso::
    .. seealso::
        * :func:`coi`
        * :func:`coi`
        * :func:`coi_rate`
        * :func:`coi_rate`
        * :func:`net_amt_at_risk`
        * :func:`net_amt_at_risk`
    """
    """
    return coi_rate(t) * net_amt_at_risk(t)
    return coi_rate(t) * net_amt_at_risk(t)
def coi_rate(t):
def coi_rate(t):
    """Cost of insurance rate per account value
    """Cost of insurance rate per account value
    The cost of insuranc rate per account value per month.
    The cost of insuranc rate per account value per month.
    By default, it is set to 1.1 times the monthly mortality rate.
    By default, it is set to 1.1 times the monthly mortality rate.
    .. seealso::
    .. seealso::
        * :func:`mort_rate_mth`
        * :func:`mort_rate_mth`
        * :func:`coi_pp`
        * :func:`coi_pp`
        * :func:`coi_rate`
        * :func:`coi_rate`
    """
    """
    return 0    #1.1 * mort_rate_mth(t)
    return 0    #1.1 * mort_rate_mth(t)
def commissions(t):
def commissions(t):
    """Commissions
    """Commissions
    By default, 100% premiums for the first year, 0 otherwise.
    By default, 100% premiums for the first year, 0 otherwise.
    .. seealso::
    .. seealso::
        * :func:`premiums`
        * :func:`premiums`
        * :func:`duration`
        * :func:`duration`
    """
    """
    return 0.05 * premiums(t)
    return 0.05 * premiums(t)
def disc_factors():
def disc_factors():
    """Discount factors.
    """Discount factors.
    Vector of the discount factors as a Numpy array. Used for calculating
    Vector of the discount factors as a Numpy array. Used for calculating
    the present values of cashflows.
    the present values of cashflows.
    .. seealso::
    .. seealso::
        :func:`disc_rate_mth`
        :func:`disc_rate_mth`
    """
    """
    return np.array(list((1 + disc_rate_mth()[t])**(-t) for t in range(max_proj_len())))
    return np.array(list((1 + disc_rate_mth()[t])**(-t) for t in range(max_proj_len())))
def disc_rate_mth():
def disc_rate_mth():
    """Monthly discount rate
    """Monthly discount rate
    Nummpy array of monthly discount rates from time 0 to :func:`max_proj_len` - 1
    Nummpy array of monthly discount rates from time 0 to :func:`max_proj_len` - 1
    defined as::
    defined as::
        (1 + disc_rate_ann)**(1/12) - 1
        (1 + disc_rate_ann)**(1/12) - 1
    .. seealso::
    .. seealso::
        :func:`disc_rate_ann`
        :func:`disc_rate_ann`
    """
    """
    return np.array(list((1 + disc_rate_ann[t//12])**(1/12) - 1 for t in range(max_proj_len())))
    return np.array(list((1 + disc_rate_ann[t//12])**(1/12) - 1 for t in range(max_proj_len())))
def duration(t):
def duration(t):
    """Duration of model points at ``t`` in years
    """Duration of model points at ``t`` in years
    .. seealso:: :func:`duration_mth`
    .. seealso:: :func:`duration_mth`
    """
    """
    return duration_mth(t) //12
    return duration_mth(t) //12
def duration_mth(t):
def duration_mth(t):
    """Duration of model points at ``t`` in months
    """Duration of model points at ``t`` in months
    Indicates how many months the policies have been in-force at ``t``.
    Indicates how many months the policies have been in-force at ``t``.
    The initial values at time 0 are read from the ``duration_mth`` column in
    The initial values at time 0 are read from the ``duration_mth`` column in
    :attr:`model_point_table` through :func:`model_point`.
    :attr:`model_point_table` through :func:`model_point`.
    Increments by 1 as ``t`` increments.
    Increments by 1 as ``t`` increments.
    Negative values of :func:`duration_mth` indicate future new business
    Negative values of :func:`duration_mth` indicate future new business
    policies. For example, If the :func:`duration_mth` is
    policies. For example, If the :func:`duration_mth` is
    -15 at time 0, the model point is issued at ``t=15``.
    -15 at time 0, the model point is issued at ``t=15``.
    .. seealso:: :func:`model_point`
    .. seealso:: :func:`model_point`
    """
    """
    if t == 0:
    if t == 0:
        return model_point()['duration_mth']
        return model_point()['duration_mth'].values
    else:
    else:
        return duration_mth(t-1) + 1
        return duration_mth(t-1) + 1
def expense_acq():
def expense_acq():
    """Acquisition expense per policy
    """Acquisition expense per policy
    ``5000`` by default.
    ``5000`` by default.
    """
    """
    return 5000
    return 5000
def expense_maint():
def expense_maint():
    """Annual maintenance expense per policy
    """Annual maintenance expense per policy
    ``500`` by default.
    ``500`` by default.
    """
    """
    return 500
    return 500
def expenses(t):
def expenses(t):
    """Expenses
    """Expenses
    Expenses during the period from ``t`` to ``t+1``
    Expenses during the period from ``t`` to ``t+1``
    defined as the sum of acquisition expenses and maintenance expenses.
    defined as the sum of acquisition expenses and maintenance expenses.
    The acquisition expenses are modeled as :func:`expense_acq`
    The acquisition expenses are modeled as :func:`expense_acq`
    times :func:`pols_new_biz`.
    times :func:`pols_new_biz`.
    The maintenance expenses are modeled as :func:`expense_maint`
    The maintenance expenses are modeled as :func:`expense_maint`
    times :func:`inflation_factor` times :func:`pols_if_at` before
    times :func:`inflation_factor` times :func:`pols_if_at` before
    decrement.
    decrement.
    .. seealso::
    .. seealso::
        * :func:`expense_acq`
        * :func:`expense_acq`
        * :func:`expense_maint`
        * :func:`expense_maint`
        * :func:`inflation_factor`
        * :func:`inflation_factor`
        * :func:`pols_new_biz`
        * :func:`pols_new_biz`
        * :func:`pols_if_at`
        * :func:`pols_if_at`
    """
    """
    return expense_acq() * pols_new_biz(t) \
    return expense_acq() * pols_new_biz(t) \
        + pols_if_at(t, "BEF_DECR") * expense_maint()/12 * inflation_factor(t)
        + pols_if_at(t, "BEF_DECR") * expense_maint()/12 * inflation_factor(t)
def formula_option_put(t):
def formula_option_put(t):
    """
    """
        S: prem_to_av
        S: prem_to_av
        X: claims
        X: claims
        sigma: 0.03
        sigma: 0.03
        r: 0.02
        r: 0.02
    """
    """
    mps = model_point_table_ext()
    mps = model_point_table_ext()
    cond = mps['policy_term'] * 12 == t
    cond = mps['policy_term'] * 12 == t
    mps = mps.loc[cond]
    mps = mps.loc[cond]
    T = t / 12
    T = t / 12
    S = av_at(0, 'BEF_FEE')[:, 1][cond]
    S = av_at(0, 'BEF_FEE')[:, 1][cond]
    X = (sum_assured() * pols_maturity(t))[:, 1][cond]
    X = (sum_assured() * pols_maturity(t))[:, 1][cond]
    sigma = 0.03
    sigma = 0.03
    r = 0.02
    r = 0.02
    N = stats.norm.cdf
    N = stats.norm.cdf
    e = np.exp
    e = np.exp
    d1 = (np.log(S/X) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
    d1 = (np.log(S/X) + (r + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)
    d2 = d1 - sigma * np.sqrt(T)
    return X * e(-r*T) * N(-d2) - S * N(-d1)
    return X * e(-r*T) * N(-d2) - S * N(-d1)
def has_surr_charge():
def has_surr_charge():
    """Whether surrender charge applies
    """Whether surrender charge applies
    ``True`` if surrender charge on account value applies upon lapse,
    ``True`` if surrender charge on account value applies upon lapse,
    ``False`` if other wise.
    ``False`` if other wise.
    By default, the value is read from the ``has_surr_charge`` column
    By default, the value is read from the ``has_surr_charge`` column
    in :func:`model_point`.
    in :func:`model_point`.
    .. seealso::
    .. seealso::
        * :func:`model_point`
        * :func:`model_point`
    """
    """
    return model_point()['has_surr_charge']
    return model_point()['has_surr_charge'].values
def inflation_factor(t):
def inflation_factor(t):
    """The inflation factor at time t
    """The inflation factor at time t
    .. seealso::
    .. seealso::
        * :func:`inflation_rate`
        * :func:`inflation_rate`
    """
    """
    return (1 + inflation_rate())**(t/12)
    return (1 + inflation_rate())**(t/12)
def inflation_rate():
def inflation_rate():
    """Inflation rate
    """Inflation rate
    The inflation rate to be applied to the expense assumption.
    The inflation rate to be applied to the expense assumption.
    By defualt it is set to ``0.01``.
    By defualt it is set to ``0.01``.
    .. seealso::
    .. seealso::
        * :func:`inflation_factor`
        * :func:`inflation_factor`
    """
    """
    return 0.01
    return 0.01
def inv_income(t):
def inv_income(t):
    """Investment income on account value
    """Investment income on account value
    Investment income earned on account value during each period.
    Investment income earned on account value during each period.
    For the plicies decreased by lapse and death, half
    For the plicies decreased by lapse and death, half
    the investment income is credited.
    the investment income is credited.
    .. seealso::
    .. seealso::
        * :func:`inv_income_pp`
        * :func:`inv_income_pp`
        * :func:`pols_if_at`
        * :func:`pols_if_at`
        * :func:`pols_death`
        * :func:`pols_death`
        * :func:`pols_lapse`
        * :func:`pols_lapse`
    """
    """
    return (inv_income_pp(t) * pols_if_at(t+1, "BEF_MAT")
    return (inv_income_pp(t) * pols_if_at(t+1, "BEF_MAT")
            + 0.5 * inv_income_pp(t) * (pols_death(t) + pols_lapse(t)))
            + 0.5 * inv_income_pp(t) * (pols_death(t) + pols_lapse(t)))
def inv_income_pp(t):
def inv_income_pp(t):
    """Investment income on account value per policy
    """Investment income on account value per policy
    Investment income on account value defined as::
    Investment income on account value defined as::
        inv_return_mth(t) * av_pp_at(t, "BEF_INV")
        inv_return_mth(t) * av_pp_at(t, "BEF_INV")
    .. seealso::
    .. seealso::
        * :func:`inv_return_mth`
        * :func:`inv_return_mth`
        * :func:`av_pp_at`
        * :func:`av_pp_at`
    """
    """
    return inv_return_mth(t) * av_pp_at(t, "BEF_INV")
    return inv_return_mth(t) * av_pp_at(t, "BEF_INV")
def inv_return_mth(t):
def inv_return_mth(t):
    """Rate of investment return
    """Rate of investment return
    Rate of monthly investment return for :attr:`scen_id` and ``t``
    Rate of monthly investment return for :attr:`scen_id` and ``t``
    read from :func:`inv_return_table`
    read from :func:`inv_return_table`
    .. seealso::
    .. seealso::
        * :func:`inv_return_table`
        * :func:`inv_return_table`
        * :attr:`scen_id`
        * :attr:`scen_id`
    """
    """
    return inv_return_table()[:, t]
    return inv_return_table()[:, t]
def inv_return_table():
def inv_return_table():
    r"""Table of investment return rates
    r"""Table of investment return rates
    Returns a Series of monthly investment retuns.
    Returns a Series of monthly investment retuns.
    The Series is indexed with ``scen_id`` and ``t`` which
    The Series is indexed with ``scen_id`` and ``t`` which
    is inherited from :attr:`std_norm_rand`.
    is inherited from :attr:`std_norm_rand`.
    .. math::
    .. math::
        \exp\left(\left(\mu-\frac{\sigma^{2}}{2}\right)\Delta{t}+\sigma\sqrt{\Delta{t}}\epsilon\right)-1
        \exp\left(\left(\mu-\frac{\sigma^{2}}{2}\right)\Delta{t}+\sigma\sqrt{\Delta{t}}\epsilon\right)-1
    where :math:`\mu=2\%`, :math:`\sigma=3\%`, :math:`\Delta{t}=\frac{1}{12}`, and
    where :math:`\mu=2\%`, :math:`\sigma=3\%`, :math:`\Delta{t}=\frac{1}{12}`, and
    :math:`\epsilon` is a randome number from the standard normal distribution.
    :math:`\epsilon` is a randome number from the standard normal distribution.
    .. seealso::
    .. seealso::
        * :attr:`std_norm_rand`
        * :attr:`std_norm_rand`
        * :attr:`scen_id`
        * :attr:`scen_id`
    """
    """
    mu = 0.02
    mu = 0.02
    sigma = 0.03
    sigma = 0.03
    dt = 1/12
    dt = 1/12
    return np.exp(
    return np.tile(np.exp(
        (mu - 0.5 * sigma**2) * dt + sigma * dt**0.5 * std_norm_rand()
        (mu - 0.5 * sigma**2) * dt + sigma * dt**0.5 * std_norm_rand()) - 1,
        ) - 1
        (point_size(), 1))
def is_wl():
def is_wl():
    """Whether the model point is whole life
    """Whether the model point is whole life
    ``True`` if the model point is whole life, ``False`` if other wise.
    ``True`` if the model point is whole life, ``False`` if other wise.
    By default, the value is read from the ``is_wl`` column
    By default, the value is read from the ``is_wl`` column
    in :func:`model_point`.
    in :func:`model_point`.
    This attribute is used to determin :func:`policy_term`.
    This attribute is used to determin :func:`policy_term`.
    If ``True``, :func:`policy_term` is defined
    If ``True``, :func:`policy_term` is defined
    as :func:`mort_table_last_age` minus :func:`age_at_entry`.
    as :func:`mort_table_last_age` minus :func:`age_at_entry`.
    If ``False``, :func:`policy_term` is read from :func:`model_point`.
    If ``False``, :func:`policy_term` is read from :func:`model_point`.
    .. seealso::
    .. seealso::
        * :func:`model_point`
        * :func:`model_point`
    """
    """
    return model_point()['is_wl']
    return model_point()['is_wl'].values
def lapse_rate(t):
def lapse_rate(t):
    """Lapse rate
    """Lapse rate
    By default, the lapse rate assumption is defined by duration as::
    By default, the lapse rate assumption is defined by duration as::
        max(0.1 - 0.02 * duration(t), 0.02)
        max(0.1 - 0.02 * duration(t), 0.02)
    .. seealso::
    .. seealso::
        :func:`duration`
        :func:`duration`
    """
    """
    # return np.maximum(0.1 - 0.02 * duration(t), 0.02)
    # return np.maximum(0.1 - 0.02 * duration(t), 0.02)
    return pd.Series(0, index=model_point().index)
    return pd.Series(0, index=model_point().index).values
def load_prem_rate():
def load_prem_rate():
    """Rate of premium loading
    """Rate of premium loading
    This rate times :func:`premium_pp` is collected from each premium
    This rate times :func:`premium_pp` is collected from each premium
    and the rest is added to the account value.
    and the rest is added to the account value.
    By default, the value is read from the ``load_prem_rate`` column
    By default, the value is read from the ``load_prem_rate`` column
    in :func:`model_point`.
    in :func:`model_point`.
    .. seealso::
    .. seealso::
        * :func:`premium_pp`
        * :func:`premium_pp`
    """
    """
    return model_point()['load_prem_rate']
    return model_point()['load_prem_rate'].values
def maint_fee(t):
def maint_fee(t):
    """Maintenance fee deducted from account value
    """Maintenance fee deducted from account value
    .. seealso::
    .. seealso::
        * :func:`maint_fee_pp`
        * :func:`maint_fee_pp`
    """
    """
    return maint_fee_pp(t) * pols_if_at(t, "BEF_DECR")
    return maint_fee_pp(t) * pols_if_at(t, "BEF_DECR")
def maint_fee_pp(t):
def maint_fee_pp(t):
    """Maintenance fee per policy
    """Maintenance fee per policy
    .. seealso::
    .. seealso::
        * :func:`maint_fee_rate`
        * :func:`maint_fee_rate`
        * :func:`av_pp_at`
        * :func:`av_pp_at`
    """
    """
    return maint_fee_rate() * av_pp_at(t, "BEF_FEE")
    return maint_fee_rate() * av_pp_at(t, "BEF_FEE")
def maint_fee_rate():
def maint_fee_rate():
    """Maintenance fee per account value
    """Maintenance fee per account value
    The rate of maintenance fee on account value each month.
    The rate of maintenance fee on account value each month.
    Set to ``0.01 / 12`` by default.
    Set to ``0.01 / 12`` by default.
    .. seealso::
    .. seealso::
        * :func:`maint_fee`
        * :func:`maint_fee`
    """
    """
    return 0    # 0.01 / 12
    return 0    # 0.01 / 12
def margin_expense(t):
def margin_expense(t):
    """Expense margin
    """Expense margin
    Expense margin is defined as the sum of
    Expense margin is defined as the sum of
    premium loading, surrender charge and maintenance fees
    premium loading, surrender charge and maintenance fees
    net of commissions and expenses.
    net of commissions and expenses.
    The sum of the expense margin and mortality margin add
    The sum of the expense margin and mortality margin add
    up to the net cashflow.
    up to the net cashflow.
    .. seealso::
    .. seealso::
        * :func:`load_prem_rate`
        * :func:`load_prem_rate`
        * :func:`premium_pp`
        * :func:`premium_pp`
        * :func:`pols_if_at`
        * :func:`pols_if_at`
        * :func:`surr_charge`
        * :func:`surr_charge`
        * :func:`maint_fee`
        * :func:`maint_fee`
        * :func:`commissions`
        * :func:`commissions`
        * :func:`expenses`
        * :func:`expenses`
        * :func:`check_margin`
        * :func:`check_margin`
    """
    """
    return (load_prem_rate()* premium_pp(t) * pols_if_at(t, "BEF_DECR")
    return (load_prem_rate()* premium_pp(t) * pols_if_at(t, "BEF_DECR")
            + surr_charge(t)
            + surr_charge(t)
            + maint_fee(t)
            + maint_fee(t)
            - commissions(t)
            - commissions(t)
            - expenses(t))
            - expenses(t))
def margin_mortality(t):
def margin_mortality(t):
    """Mortality margin
    """Mortality margin
    Mortality margin is defined :func:`coi` net of :func:`claims_over_av`.
    Mortality margin is defined :func:`coi` net of :func:`claims_over_av`.
    The sum of the expense margin and mortality margin add
    The sum of the expense margin and mortality margin add
    up to the net cashflow.
    up to the net cashflow.
    .. seealso::
    .. seealso::
        * :func:`coi`
        * :func:`coi`
        * :func:`claims_over_av`
        * :func:`claims_over_av`
    """
    """
    return coi(t) - claims_over_av(t, 'DEATH')
    return coi(t) - claims_over_av(t, 'DEATH')
max_proj_len = lambda: max(proj_len())
max_proj_len = lambda: max(proj_len())
"""The max of all projection lengths
"""The max of all projection lengths
Defined as ``max(proj_len())``
Defined as ``max(proj_len())``
.. seealso::
.. seealso::
    :func:`proj_len`
    :func:`proj_len`
"""
"""
def model_point():
def model_point():
    """Target model points
    """Target model points
    Returns as a DataFrame the model points to be in the scope of calculation.
    Returns as a DataFrame the model points to be in the scope of calculation.
    By default, this Cells returns the entire :func:`model_point_table_ext`
    By default, this Cells returns the entire :func:`model_point_table_ext`
    without change.
    without change.
    :func:`model_point_table_ext` is the extended model point table,
    :func:`model_point_table_ext` is the extended model point table,
    which extends :attr:`model_point_table` by joining the columns
    which extends :attr:`model_point_table` by joining the columns
    in :attr:`product_spec_table`. Do not directly refer to
    in :attr:`product_spec_table`. Do not directly refer to
    :attr:`model_point_table` in this formula.
    :attr:`model_point_table` in this formula.
    To select model points, change this formula so that this
    To select model points, change this formula so that this
    Cells returns a DataFrame that contains only the selected model points.
    Cells returns a DataFrame that contains only the selected model points.
    Examples:
    Examples:
        To select only the model point 1::
        To select only the model point 1::
            def model_point():
            def model_point():
                return model_point_table_ext().loc[1:1]
                return model_point_table_ext().loc[1:1]
        To select model points whose ages at entry are 40 or greater::
        To select model points whose ages at entry are 40 or greater::
            def model_point():
            def model_point():
                return model_point_table[model_point_table_ext()["age_at_entry"] >= 40]
                return model_point_table[model_point_table_ext()["age_at_entry"] >= 40]
    Note that the shape of the returned DataFrame must be the
    Note that the shape of the returned DataFrame must be the
    same as the original DataFrame, i.e. :func:`model_point_table_ext`.
    same as the original DataFrame, i.e. :func:`model_point_table_ext`.
    When selecting only one model point, make sure the
    When selecting only one model point, make sure the
    returned object is a DataFrame, not a Series, as seen in the example
    returned object is a DataFrame, not a Series, as seen in the example
    above where ``model_point_table_ext().loc[1:1]`` is specified
    above where ``model_point_table_ext().loc[1:1]`` is specified
    instead of ``model_point_table_ext().loc[1]``.
    instead of ``model_point_table_ext().loc[1]``.
    Be careful not to accidentally change the original table
    Be careful not to accidentally change the original table
    held in :func:`model_point_table_ext`.
    held in :func:`model_point_table_ext`.
    .. seealso::
    .. seealso::
        * :func:`model_point_table_ext`
        * :func:`model_point_table_ext`
    """
    """
    mps = model_point_table_ext()
    mps = model_point_table_ext()
    idx = pd.MultiIndex.from_product(
    idx = pd.MultiIndex.from_product(
            [mps.index, scen_index()],
            [mps.index, scen_index()],
            names = mps.index.names + scen_index().names
            names = mps.index.names + scen_index().names
            )
            )
    res = pd.DataFrame(
    res = pd.DataFrame(
            np.repeat(mps.values, len(scen_index()), axis=0),
            np.repeat(mps.values, len(scen_index()), axis=0),
            index=idx,
            index=idx,
            columns=mps.columns
            columns=mps.columns
        )
        )
    return res.astype(mps.dtypes)
    return res.astype(mps.dtypes)
def model_point_table_ext():
def model_point_table_ext():
    """Extended model point table
    """Extended model point table
    Returns an extended :attr:`model_point_table` by joining
    Returns an extended :attr:`model_point_table` by joining
    :attr:`product_spec_table` on the ``spec_id`` column.
    :attr:`product_spec_table` on the ``spec_id`` column.
    .. seealso::
    .. seealso::
        * :attr:`model_point_table`
        * :attr:`model_point_table`
        * :attr:`product_spec_table`
        * :attr:`product_spec_table`
    """
    """
    return model_point_table.join(product_spec_table, on='spec_id')
    return model_point_table.join(product_spec_table, on='spec_id')
def mort_rate(t):
def mort_rate(t):
    """Mortality rate to be applied at time t
    """Mortality rate to be applied at time t
    Returns a Series of the mortality rates to be applied at time t.
    Returns a Series of the mortality rates to be applied at time t.
    The index of the Series is ``point_id``,
    The index of the Series is ``point_id``,
    copied from :func:`model_point`.
    copied from :func:`model_point`.
    .. seealso::
    .. seealso::
       * :func:`mort_table_reindexed`
       * :func:`mort_table_reindexed`
       * :func:`mort_rate_mth`
       * :func:`mort_rate_mth`
       * :func:`model_point`
       * :func:`model_point`
    """
    """
    # mi is a MultiIndex whose values are
    # mi is a MultiIndex whose values are
    # pairs of age at t and duration at t capped at 5 for all the model points.
    # pairs of age at t and duration at t capped at 5 for all the model points.
    # ``mort_table_reindexed().reindex(mi, fill_value=0)`` returns
    # ``mort_table_reindexed().reindex(mi, fill_value=0)`` returns
    # a Series of mortality rates whose indexes match the MultiIndex values.
    # a Series of mortality rates whose indexes match the MultiIndex values.
    # The ``set_axis`` method replace the MultiIndex with ``point_id``
    # The ``set_axis`` method replace the MultiIndex with ``point_id``
    # mi = pd.MultiIndex.from_arrays([age(t), np.minimum(duration(t), 5)])
    # mi = pd.MultiIndex.from_arrays([age(t), np.minimum(duration(t), 5)])
    # return mort_table_reindexed().reindex(
    # return mort_table_reindexed().reindex(
    #     mi, fill_value=0).set_axis(model_point().index, inplace=False)
    #     mi, fill_value=0).set_axis(model_point().index, inplace=False)
    return pd.Series(0, index=model_point().index)
    return np.zeros(len(model_point().index))
def mort_rate_mth(t):
def mort_rate_mth(t):
    """Monthly mortality rate to be applied at time t
    """Monthly mortality rate to be applied at time t
    .. seealso::
    .. seealso::
       * :attr:`mort_table`
       * :attr:`mort_table`
       * :func:`mort_rate`
       * :func:`mort_rate`
    """
    """
    return 1-(1- mort_rate(t))**(1/12)
    return 1-(1- mort_rate(t))**(1/12)
def mort_table_last_age():
def mort_table_last_age():
    """The last age of mortality tables
    """The last age of mortality tables