lmi
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[lmi] [PATCH 4/4] Implement loading of XML actuarial tables.


From: Vaclav Slavik
Subject: [lmi] [PATCH 4/4] Implement loading of XML actuarial tables.
Date: Thu, 17 May 2012 17:36:46 +0200
User-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:12.0) Gecko/20120428 Thunderbird/12.0.1

Implement loading of XML actuarial tables.

This is only the first phase implementation where the XML file contains
only a single SOA table. It replaces binary tables with XML .xtable
files (created using a conversion tool I'll post in a moment). It does
not yet implement parallel use of both implementations.

Regards,
Vaclav


---
 actuarial_table.cpp |  333 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 actuarial_table.hpp |   52 +++++++-
 2 files changed, 378 insertions(+), 7 deletions(-)

diff --git a/actuarial_table.cpp b/actuarial_table.cpp
index b875dac..ac7912f 100644
--- a/actuarial_table.cpp
+++ b/actuarial_table.cpp
@@ -33,6 +33,10 @@
 #include "miscellany.hpp"
 #include "oecumenic_enumerations.hpp" // methuselah
 #include "path_utility.hpp" // fs::path inserter
+#include "value_cast.hpp"
+#include "xml_lmi.hpp"
+
+#include <xmlwrapp/nodes_view.h>
 
 #include <boost/cstdint.hpp>
 #include <boost/filesystem/convenience.hpp>
@@ -45,6 +49,7 @@
 #include <climits>   // CHAR_BIT
 #include <ios>
 #include <istream>
+#include <iterator>  // std::distance()
 #include <limits>
 
 namespace
@@ -171,6 +176,324 @@ std::vector<double> 
actuarial_table_base::values_elaborated
         }
 }
 
+actuarial_table::actuarial_table(std::string const& filename, int table_number)
+{
+    // TODO !! This is temporary code for API compatibility with 
soa_actuarial_table.
+    //         It should be changed so that the constructor takes only a single
+    //         argument, filename of the XML table file.
+    std::string xmlfile(filename, 0, filename.rfind('.'));
+    xmlfile += "_";
+    xmlfile += value_cast<std::string>(table_number);
+    xmlfile += ".xtable";
+    load_xml_table(xmlfile);
+}
+
+actuarial_table::~actuarial_table()
+{
+}
+
+void actuarial_table::load_xml_table(std::string const& filename)
+{
+    xml_lmi::dom_parser parser(filename);
+    xml::element root(parser.root_node("table"));
+
+    // TODO !! Implement loading of multi-dimensional tables as well.
+
+    // XMLWRAPP !! This should be const_iterator, but xmlwrapp < 0.7 lacks the
+    //             necessary operator!= for comparing const and non-const 
iterators.
+    xml::node::iterator i;
+
+    if (root.end() != (i = root.find("aggregate")))
+        {
+        load_xml_aggregate_table(*i);
+        }
+    else if (root.end() != (i = root.find("duration")))
+        {
+        load_xml_duration_table(*i);
+        }
+    else if (root.end() != (i = root.find("select")))
+        {
+        load_xml_select_table(*i);
+        }
+    else if (root.end() != (i = root.find("select-and-ultimate")))
+        {
+        load_xml_select_and_ultimate_table(*i);
+        }
+    else
+        {
+        fatal_error()
+            << "Required data element not found."
+            << LMI_FLUSH
+            ;
+        }
+}
+
+void actuarial_table::load_xml_table_with_ages
+    (xml::element const& node
+    ,std::vector<double>& data
+    ,int& min_age
+    ,int& max_age
+    )
+{
+    xml::const_nodes_view const values = node.elements("value");
+
+    data.reserve(std::distance(values.begin(), values.end()));
+
+    min_age = max_age = -1;
+
+    typedef xml::const_nodes_view::const_iterator cnvi;
+    for(cnvi i = values.begin(); i != values.end(); ++i)
+        {
+        data.push_back(value_cast<double>(xml_lmi::get_content(*i)));
+        int age;
+        if(!xml_lmi::get_attr(*i, "age", age))
+            {
+            fatal_error()
+                << "XML <value> node doesn't have 'age' attribute."
+                << LMI_FLUSH
+                ;
+            }
+        if(-1 == min_age)
+            {
+            min_age = age;
+            }
+        else
+            {
+            if(max_age + 1 != age)
+                {
+                fatal_error()
+                    << "XML <value> node has 'age' attribute value '"
+                    << age
+                    << "' but '"
+                    << max_age + 1
+                    << "' was expected."
+                    << LMI_FLUSH
+                    ;
+                }
+            }
+        max_age = age;
+        }
+
+    LMI_ASSERT(data.size() == size_t(max_age - min_age + 1));
+}
+
+void actuarial_table::load_xml_aggregate_table(xml::element const& node)
+{
+    load_xml_table_with_ages
+        (node
+        ,data_
+        ,min_age_
+        ,max_age_
+        );
+
+    table_type_ = e_table_aggregate;
+}
+
+void actuarial_table::load_xml_duration_table(xml::element const& node)
+{
+    xml::const_nodes_view const values = node.elements("value");
+
+    data_.reserve(std::distance(values.begin(), values.end()));
+
+    typedef xml::const_nodes_view::const_iterator cnvi;
+    for(cnvi i = values.begin(); i != values.end(); ++i)
+        {
+        data_.push_back(value_cast<double>(xml_lmi::get_content(*i)));
+        }
+
+    table_type_ = e_table_duration;
+}
+
+void actuarial_table::load_xml_select_table(xml::element const& node)
+{
+    xml::const_nodes_view const rows = node.elements("row");
+
+    if(!xml_lmi::get_attr(node, "period", select_period_))
+        {
+        fatal_error()
+            << "XML <select> node doesn't have 'period' attribute."
+            << LMI_FLUSH
+            ;
+        }
+
+    data_.reserve(std::distance(rows.begin(), rows.end()) * select_period_);
+
+    min_age_ = max_age_ = -1;
+
+    typedef xml::const_nodes_view::const_iterator cnvi;
+    for(cnvi i = rows.begin(); i != rows.end(); ++i)
+        {
+        int age;
+        if(!xml_lmi::get_attr(*i, "age", age))
+            {
+            fatal_error()
+                << "XML <row> node doesn't have 'age' attribute."
+                << LMI_FLUSH
+                ;
+            }
+        if(-1 == min_age_)
+            {
+            min_age_ = age;
+            }
+        else
+            {
+            if(max_age_ + 1 != age)
+                {
+                fatal_error()
+                    << "XML <row> node has 'age' attribute value '"
+                    << age
+                    << "' but '"
+                    << max_age_ + 1
+                    << "' was expected."
+                    << LMI_FLUSH
+                    ;
+                }
+            }
+        max_age_ = age;
+
+        xml::const_nodes_view const values = i->elements("value");
+        if(select_period_ != std::distance(values.begin(), values.end()))
+            {
+            fatal_error()
+                << "XML <row> node has "
+                << std::distance(values.begin(), values.end())
+                << " values but select period is "
+                << select_period_
+                << "."
+                << LMI_FLUSH
+                ;
+            }
+
+        for(cnvi v = values.begin(); v != values.end(); ++v)
+            {
+            data_.push_back(value_cast<double>(xml_lmi::get_content(*v)));
+            }
+        }
+
+    max_select_age_ = max_age_;
+
+    LMI_ASSERT(data_.size() == size_t((max_age_ - min_age_ + 1) * 
select_period_));
+
+    // Use the same type for select and select & ultimate tables: selected
+    // table is just a special case of the latter where max_age_ ==
+    // max_select_age_ and the ultimate table is empty.
+    table_type_ = e_table_select_and_ultimate;
+}
+
+void actuarial_table::load_xml_select_and_ultimate_table(xml::element const& 
node)
+{
+    load_xml_select_table(*xml_lmi::retrieve_element(node, "select"));
+
+    int ultimate_min_age;
+    load_xml_table_with_ages
+        (*xml_lmi::retrieve_element(node, "ultimate")
+        ,ultimate_
+        ,ultimate_min_age
+        ,max_age_
+        );
+
+    if(ultimate_min_age != min_age_ + select_period_)
+        {
+        fatal_error()
+            << "Ultimate table should have min. age "
+            << min_age_
+            << ", but has "
+            << ultimate_min_age
+            << "."
+            << LMI_FLUSH
+            ;
+        }
+
+    table_type_ = e_table_select_and_ultimate;
+}
+
+
+std::vector<double> actuarial_table::specific_values
+    (int issue_age
+    ,int length
+    ) const
+{
+    if(table_type_ != e_table_duration)
+        {
+        // min_age_ and max_age_ are invalid for duration tables
+        LMI_ASSERT(min_age_ <= issue_age && issue_age <= max_age_);
+        LMI_ASSERT(0 <= length && length <= 1 + max_age_ - issue_age);
+        }
+
+    std::vector<double> v;
+    switch(table_type_)
+        {
+        case e_table_aggregate:
+            {
+            // Parenthesize the offsets--addition in C and C++ is
+            // in effect left associative:
+            //   data_.begin() + issue_age - min_age_
+            // means
+            //   (data_.begin() + issue_age) - min_age_
+            // but the subexpression
+            //   (data_.begin() + issue_age)
+            // is likely to return a past-the-end iterator, which
+            // libstdc++'s debug mode will dislike.
+            //
+            v = std::vector<double>
+                (data_.begin() + (issue_age - min_age_)
+                ,data_.begin() + (issue_age - min_age_ + length)
+                );
+            }
+            break;
+        case e_table_duration:
+            {
+            v = std::vector<double>
+                (data_.begin()
+                ,data_.begin() + length
+                );
+            }
+            break;
+        case e_table_select_and_ultimate:
+            {
+            v.resize(length);
+            std::vector<double>::iterator cursor = v.begin();
+
+            int row_to_start_at;
+
+            // Write select portion:
+            if(issue_age < max_select_age_ + select_period_)
+                {
+                row_to_start_at = (std::min(max_select_age_, issue_age) - 
min_age_);
+                int offset_in_row = std::max(0, issue_age - max_select_age_);
+                int k = offset_in_row
+                    +   row_to_start_at * select_period_
+                    ;
+                for(int i = offset_in_row; cursor != v.end() && i < 
select_period_; i++, k++)
+                    *(cursor++) = data_[k];
+                }
+            else
+                {
+                const int min_ultimate_age = min_age_ + select_period_;
+                row_to_start_at = issue_age - min_ultimate_age;
+                }
+
+            // And ultimate:
+            for(int k = row_to_start_at; cursor != v.end(); k++)
+                *(cursor++) = ultimate_[k];
+            }
+            break;
+
+        default:
+            {
+            fatal_error()
+                << "Table type '"
+                << table_type_
+                << "' not recognized: must be one of 'A', 'D', or 'S'."
+                << LMI_FLUSH
+                ;
+            }
+        }
+    LMI_ASSERT(v.size() == static_cast<unsigned int>(length));
+    return v;
+}
+
+
 soa_actuarial_table::soa_actuarial_table(std::string const& filename, int 
table_number)
     :filename_       (filename)
     ,table_number_   (table_number)
@@ -519,7 +842,7 @@ std::vector<double> soa_actuarial_table::specific_values
     std::vector<double> v;
     switch(table_type_)
         {
-        case 'A':
+        case e_table_aggregate:
             {
             // Parenthesize the offsets--addition in C and C++ is
             // in effect left associative:
@@ -537,7 +860,7 @@ std::vector<double> soa_actuarial_table::specific_values
                 );
             }
             break;
-        case 'D':
+        case e_table_duration:
             {
             v = std::vector<double>
                 (data_.begin()
@@ -545,7 +868,7 @@ std::vector<double> soa_actuarial_table::specific_values
                 );
             }
             break;
-        case 'S':
+        case e_table_select_and_ultimate:
             {
             int const stride = 1 + select_period_;
             int k =
@@ -587,7 +910,7 @@ std::vector<double> actuarial_table_rates
     ,int                length
     )
 {
-    soa_actuarial_table z(table_filename, table_number);
+    actuarial_table z(table_filename, table_number);
     return z.values(issue_age, length);
 }
 
@@ -601,7 +924,7 @@ std::vector<double> actuarial_table_rates_elaborated
     ,int                      reset_duration
     )
 {
-    soa_actuarial_table z(table_filename, table_number);
+    actuarial_table z(table_filename, table_number);
     return z.values_elaborated
         (issue_age
         ,length
diff --git a/actuarial_table.hpp b/actuarial_table.hpp
index 94ec3e4..63b347f 100644
--- a/actuarial_table.hpp
+++ b/actuarial_table.hpp
@@ -28,6 +28,7 @@
 
 #include "obstruct_slicing.hpp"
 #include "uncopyable_lmi.hpp"
+#include "xml_lmi_fwd.hpp"
 
 #include <iosfwd>
 #include <string>
@@ -116,6 +117,16 @@ enum e_actuarial_table_method
     };
 
 
+/// Types of actuarial tables.
+
+enum e_table_type
+    {e_table_invalid               = -1
+    ,e_table_aggregate             = 'A'
+    ,e_table_duration              = 'D'
+    ,e_table_select_and_ultimate   = 'S'
+    };
+
+
 /// Base class for actuarial tables, both XML and binary.
 // SOA !! This is only temporary, merge with xml_actuarial_table into
 //        single class once we remove binary SOA format support
@@ -150,6 +161,45 @@ class actuarial_table_base
     int  max_select_age_ ;
 };
 
+/// Read actuarial table from XML file.
+
+class actuarial_table
+    :        public  actuarial_table_base
+    ,        private lmi::uncopyable <actuarial_table>
+    ,virtual private obstruct_slicing<actuarial_table>
+{
+  public:
+    actuarial_table(std::string const& filename, int table_number);
+    ~actuarial_table();
+
+  protected:
+    std::vector<double> specific_values(int issue_age, int length) const;
+
+  private:
+    void load_xml_table                    (std::string const& filename);
+    void load_xml_aggregate_table          (xml::element const& node);
+    void load_xml_duration_table           (xml::element const& node);
+    void load_xml_select_table             (xml::element const& node);
+    void load_xml_select_and_ultimate_table(xml::element const& node);
+    void load_xml_table_with_ages          (xml::element const& node
+                                            ,std::vector<double>& data
+                                            ,int& min_age
+                                            ,int& max_age
+                                            );
+
+    // Table data. For 1D tables (e_table_aggregate and e_table_duration), this
+    // is the vector of values from min_age_ to max_age_.
+    // For e_table_select_and_ultimate, the content is organized by rows, with
+    // select_period_ entries per row, with rows ranging from min_age_ to
+    // max_select_age_.
+    std::vector<double> data_;
+
+    // For e_table_select_and_ultimate, this vector contains the ultimate
+    // column. The first value, ultimate_[0], is for min_age_+select_period_,
+    // the last is for max_select_age_.
+    std::vector<double> ultimate_;
+};
+
 /// Read a table from a database in the binary format designed by the
 /// Society of Actuaries (SOA) and used for the tables SOA publishes.
 ///
@@ -196,8 +246,6 @@ class soa_actuarial_table
     std::streampos table_offset_;
 };
 
-typedef soa_actuarial_table actuarial_table;
-
 /// Convenience function: read particular values from a table stored
 /// in the SOA table-manager format.
 
-- 
1.7.10.2




reply via email to

[Prev in Thread] Current Thread [Next in Thread]