[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[lmi-commits] [lmi] master b36b8c3 2/6: Add and use a modal Nx UL commut
From: |
Greg Chicares |
Subject: |
[lmi-commits] [lmi] master b36b8c3 2/6: Add and use a modal Nx UL commutation function |
Date: |
Fri, 16 Apr 2021 18:12:49 -0400 (EDT) |
branch: master
commit b36b8c30434746e9573f82b79b1fd8f3bb7262fe
Author: Gregory W. Chicares <gchicares@sbcglobal.net>
Commit: Gregory W. Chicares <gchicares@sbcglobal.net>
Add and use a modal Nx UL commutation function
This documentation in 'commutation_functions.hpp' always anticipated
providing a modal Nx:
/// All commutation functions are calculated on the mode specified
/// by mode_. Annual D and N are always also calculated because
/// premiums are often paid annually. Use monthly D and N for
/// monthly deductions in the numerator of an actuarial function,
/// but use their annual analogs in the denominator when premiums
/// are assumed to be paid annually.
Nx had never previously been wanted, because, given a vector V and a
scalar S = V[x], N[x] times S is just shorthand for the dot product
D·V in the common but not universal case that all V[k] are equal.
Now Nx is handy for a textbook example that assumes the common case.
---
commutation_functions.cpp | 2 +
commutation_functions.hpp | 2 +
commutation_functions_test.cpp | 107 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 111 insertions(+)
diff --git a/commutation_functions.cpp b/commutation_functions.cpp
index 8280b38..324bac0 100644
--- a/commutation_functions.cpp
+++ b/commutation_functions.cpp
@@ -129,6 +129,7 @@ ULCommFns::ULCommFns
kd.resize( length);
kc.resize( length);
an.resize( length);
+ kn.resize( length);
km.resize( length);
int periods_per_year = mode;
@@ -187,5 +188,6 @@ ULCommFns::ULCommFns
ad.pop_back();
std::partial_sum(ad.rbegin(), ad.rend(), an.rbegin());
+ std::partial_sum(kd.rbegin(), kd.rend(), kn.rbegin());
std::partial_sum(kc.rbegin(), kc.rend(), km.rbegin());
}
diff --git a/commutation_functions.hpp b/commutation_functions.hpp
index 47075e0..bf8907e 100644
--- a/commutation_functions.hpp
+++ b/commutation_functions.hpp
@@ -88,6 +88,7 @@ class LMI_SO ULCommFns final
std::vector<double> const& kD() const {return kd;}
std::vector<double> const& kC() const {return kc;}
std::vector<double> const& aN() const {return an;}
+ std::vector<double> const& kN() const {return kn;}
std::vector<double> const& kM() const {return km;}
private:
@@ -96,6 +97,7 @@ class LMI_SO ULCommFns final
std::vector<double> kd;
std::vector<double> kc;
std::vector<double> an;
+ std::vector<double> kn;
std::vector<double> km;
};
diff --git a/commutation_functions_test.cpp b/commutation_functions_test.cpp
index e8ae311..1056d98 100644
--- a/commutation_functions_test.cpp
+++ b/commutation_functions_test.cpp
@@ -679,6 +679,112 @@ void Test_1980_CSO_Male_ANB()
;
}
+/// Premiums for tables II-8 and V-4 from the SOA 7702 textbook.
+///
+/// The textbook uses OL rather than UL commutation functions.
+/// The OL touchstone values below are taken from the textbook,
+/// with corrections as noted.
+
+void test_7702_textbook_example()
+{
+ std::vector<double> q(sample_q());
+
+ // OL GLP
+ {
+ std::vector<double> i(q.size(), 0.045);
+ OLCommFns CF(q, i);
+ LMI_TEST(std::fabs( 0.3031861 - CF.M()[45] / CF.D()[45]) < 0.00000005);
+ LMI_TEST(std::fabs(16.1815675 - CF.N()[45] / CF.D()[45]) < 0.00000005);
+ double glp45 = (100000.0 * CF.M()[45] / CF.N()[45] + 60.0) / 0.95;
+ LMI_TEST(std::fabs( 2035.42 - glp45 ) < 0.005);
+ double glp70B = ( 50000.0 * CF.M()[70] / CF.N()[70] + 60.0) / 0.95;
+ LMI_TEST(std::fabs( 3903.43 - glp70B) < 0.005);
+ double glp70C = (100000.0 * CF.M()[70] / CF.N()[70] + 60.0) / 0.95;
+ // Here, Table V-4 has "3903.42", apparently a typo for "3903.43"
+ // (rounded from 3903.43398588527088577):
+ LMI_TEST(std::fabs( 7743.71 - glp70C) < 0.005);
+ // ...and thus here "-1804.87" should be "-1804.86":
+ LMI_TEST(std::fabs(-1804.86 - (glp45 + glp70B - glp70C)) < 0.01);
+#if 0 // optionally display results for confirmation:
+ std::cout
+ << glp45 << " glp45\n"
+ << glp70B << " glp70B\n"
+ << glp70C << " glp70C\n"
+ << glp45 + glp70B - glp70C << " glp45 + glp70B - glp70C\n"
+ << std::endl
+ ;
+#endif // 0
+ }
+
+ // GLP formulas above are written to follow the text more closely;
+ // GSP formulas below are written in such a way that replacing
+ // D by N in the denominator would give GLP.
+
+ // OL GSP
+ {
+ std::vector<double> i(q.size(), 0.060);
+ OLCommFns CF(q, i);
+ double gsp45 = (100000.0 * CF.M()[45] + 60.0 * CF.N()[45]) / (CF.D()[45]
* 0.95);
+ LMI_TEST(std::fabs(23883.74 - gsp45 ) < 0.005);
+ double gsp70B = ( 50000.0 * CF.M()[70] + 60.0 * CF.N()[70]) / (CF.D()[70]
* 0.95);
+ LMI_TEST(std::fabs(29453.12 - gsp70B) < 0.005);
+ double gsp70C = (100000.0 * CF.M()[70] + 60.0 * CF.N()[70]) / (CF.D()[70]
* 0.95);
+ LMI_TEST(std::fabs(58404.21 - gsp70C) < 0.005);
+ LMI_TEST(std::fabs(-5067.35 - (gsp45 + gsp70B - gsp70C)) < 0.01);
+ }
+
+ // UL commutation functions for the same assumptions
+
+ std::vector<double> q12;
+ q12 <<= apply_binary(coi_rate_from_q<double>(), q, 1.0 / 11.0);
+
+ // UL GLP
+ {
+ std::vector<double>ic(q.size(),
i_upper_12_over_12_from_i<double>()(0.045));
+ std::vector<double>ig(q.size(),
i_upper_12_over_12_from_i<double>()(0.045));
+ ULCommFns CF(q12, ic, ig, mce_option1_for_7702, mce_monthly);
+ double glp45 =
+ (100000.0 * (CF.kM()[45] + CF.aDomega()) + 5.0 * CF.kN()[45])
+ / (CF.aN()[45] * 0.95)
+ ;
+ LMI_TEST(std::fabs( 2074.40288 - glp45 ) < 0.01);
+ double glp70B =
+ ( 50000.0 * (CF.kM()[70] + CF.aDomega()) + 5.0 * CF.kN()[70])
+ / (CF.aN()[70] * 0.95)
+ ;
+ LMI_TEST(std::fabs( 3980.10414 - glp70B) < 0.01);
+ double glp70C =
+ (100000.0 * (CF.kM()[70] + CF.aDomega()) + 5.0 * CF.kN()[70])
+ / (CF.aN()[70] * 0.95)
+ ;
+ LMI_TEST(std::fabs( 7900.49522 - glp70C) < 0.01);
+ LMI_TEST(std::fabs(-1845.98820 - (glp45 + glp70B - glp70C)) < 0.01);
+ }
+
+ // UL GSP
+ {
+ std::vector<double>ic(q.size(),
i_upper_12_over_12_from_i<double>()(0.060));
+ std::vector<double>ig(q.size(),
i_upper_12_over_12_from_i<double>()(0.060));
+ ULCommFns CF(q12, ic, ig, mce_option1_for_7702, mce_monthly);
+ double gsp45 =
+ (100000.0 * (CF.kM()[45] + CF.aDomega()) + 5.0 * CF.kN()[45])
+ / (CF.aD()[45] * 0.95)
+ ;
+ LMI_TEST(std::fabs(24486.3207 - gsp45 ) < 0.01);
+ double gsp70B =
+ ( 50000.0 * (CF.kM()[70] + CF.aDomega()) + 5.0 * CF.kN()[70])
+ / (CF.aD()[70] * 0.95)
+ ;
+ LMI_TEST(std::fabs(30225.8816 - gsp70B) < 0.01);
+ double gsp70C =
+ (100000.0 * (CF.kM()[70] + CF.aDomega()) + 5.0 * CF.kN()[70])
+ / (CF.aD()[70] * 0.95)
+ ;
+ LMI_TEST(std::fabs(59979.4650 - gsp70C) < 0.01);
+ LMI_TEST(std::fabs(-5267.2627 - (gsp45 + gsp70B - gsp70C)) < 0.01);
+ }
+}
+
/// Test UL commutation functions in extreme cases.
///
/// For example, ic and ig can both be zero, and qc may round to zero
@@ -759,6 +865,7 @@ int test_main(int, char*[])
ULCommFnsTest();
OLCommFnsTest();
Test_1980_CSO_Male_ANB();
+ test_7702_textbook_example();
TestLimits();
assay_speed();
- [lmi-commits] [lmi] master updated (87a7956 -> 15ca198), Greg Chicares, 2021/04/16
- [lmi-commits] [lmi] master ce480d0 1/6: Revert "Resolve issue noted 20131011T1213Z", Greg Chicares, 2021/04/16
- [lmi-commits] [lmi] master a5716af 3/6: Define LMI_GCC symbol and use it for gcc-specific parts, Greg Chicares, 2021/04/16
- [lmi-commits] [lmi] master b36b8c3 2/6: Add and use a modal Nx UL commutation function,
Greg Chicares <=
- [lmi-commits] [lmi] master 1192bc5 4/6: Don't interleave diagnostic push/pop pragmas for clang, Greg Chicares, 2021/04/16
- [lmi-commits] [lmi] master 15ca198 6/6: Define and use LMI_CLANG in addition to LMI_GCC, Greg Chicares, 2021/04/16
- [lmi-commits] [lmi] master 97b7b71 5/6: Simplify compiler test by using LMI_GCC, Greg Chicares, 2021/04/16