LCOV - code coverage report
Current view: top level - core/Utilities - DataContainerFormatter.hpp (source / functions) Hit Total Coverage
Test: test_coverage.info.cleaned Lines: 0 132 0.0 %
Date: 2022-08-04 03:43:28 Functions: 0 37 0.0 %

          Line data    Source code
       1             : #pragma once
       2             : 
       3             : #include "elsaDefines.h"
       4             : #include "DataContainer.h"
       5             : #include "FormatConfig.h"
       6             : 
       7             : #include <iomanip>
       8             : #include <iostream>
       9             : #include <iterator>
      10             : #include <sstream>
      11             : #include <string>
      12             : #include <string_view>
      13             : #include <type_traits>
      14             : 
      15             : namespace elsa
      16             : {
      17             : 
      18             :     /**
      19             :      * @brief Pretty printing for elsa::DataContainer.
      20             :      *
      21             :      * Inspired by numpy's formatter, supports n-dimensional string representation
      22             :      * of stored values in a `DataContainer`.
      23             :      *
      24             :      * @author Jonas Jelten - initial code
      25             :      *
      26             :      * @tparam data_t: datacontainer element type
      27             :      */
      28             :     template <typename data_t>
      29             :     class DataContainerFormatter
      30             :     {
      31             :     public:
      32             :         /**
      33             :          * Create a formatter with default config.
      34             :          */
      35           0 :         DataContainerFormatter() : config{this->get_default_format_config()} {}
      36             : 
      37             :         /**
      38             :          * Create a formatter with custom config.
      39             :          */
      40           0 :         DataContainerFormatter(const format_config& config) : config{config} {}
      41             : 
      42             :         /**
      43             :          * get the default formatting config.
      44             :          * the class defaults are adjusted due to the `data_t`.
      45             :          */
      46           0 :         format_config get_default_format_config()
      47             :         {
      48           0 :             format_config cfg;
      49             :             if constexpr (isComplex<data_t>) {
      50             :                 cfg.summary_items /= 2;
      51             :             }
      52             : 
      53           0 :             return cfg;
      54             :         }
      55             : 
      56             :         /// stream a pretty format to is using the current configuration.
      57           0 :         void format(std::ostream& os, const DataContainer<data_t>& dc)
      58             :         {
      59             :             using namespace std::string_literals;
      60             : 
      61           0 :             auto dims = dc.getDataDescriptor().getNumberOfDimensions();
      62           0 :             auto shape = dc.getDataDescriptor().getNumberOfCoefficientsPerDimension();
      63           0 :             os << "DataContainer<dims=" << dims << ", shape=(" << shape.transpose() << ")>"
      64           0 :                << std::endl;
      65             : 
      66           0 :             auto format_element = this->get_element_formatter<data_t>(dc);
      67             : 
      68             :             std::function<void(std::ostream & stream, index_t current_dim, IndexVector_t & index,
      69             :                                std::string_view indent_prefix)>
      70           0 :                 recurser;
      71           0 :             recurser = [this, &recurser, &dc, &dims, &shape,
      72             :                         &format_element](std::ostream& os, index_t current_dim,
      73             :                                          IndexVector_t& index, std::string_view hanging_indent) {
      74           0 :                 index_t dims_left = dims - current_dim;
      75           0 :                 index_t next_dim = current_dim + 1;
      76             : 
      77           0 :                 if (dims_left == 0) {
      78             :                     // get the actual element, recursion terminator! \o/
      79           0 :                     format_element(os, dc(index));
      80           0 :                     return;
      81             :                 }
      82             : 
      83           0 :                 std::string next_hanging_indent = std::string(hanging_indent) + " "s;
      84             : 
      85           0 :                 index_t dim_size = shape(current_dim);
      86           0 :                 auto backidx = [dim_size](index_t idx) { return dim_size - idx; };
      87             : 
      88             :                 index_t leading_items, trailing_items;
      89           0 :                 bool do_summary =
      90           0 :                     (this->config.summary_enabled and 2 * this->config.summary_items < dim_size);
      91           0 :                 if (do_summary) {
      92           0 :                     leading_items = this->config.summary_items;
      93           0 :                     trailing_items = this->config.summary_items;
      94             :                 } else {
      95           0 :                     leading_items = 0;
      96           0 :                     trailing_items = dim_size;
      97             :                 }
      98             : 
      99           0 :                 os << "[";
     100             : 
     101             :                 // last dimension, i.e. the rows
     102           0 :                 if (dims_left == 1) {
     103             : 
     104             :                     // TODO c++20 use views::iota
     105           0 :                     for (index_t i = 0; i < leading_items; i++) {
     106           0 :                         index(current_dim) = i;
     107           0 :                         recurser(os, next_dim, index, next_hanging_indent);
     108           0 :                         os << this->config.separator;
     109             :                     }
     110             : 
     111           0 :                     if (do_summary) {
     112           0 :                         os << this->config.summary_elem << this->config.separator;
     113             :                     }
     114             : 
     115           0 :                     for (index_t i = trailing_items; i > 1; i--) {
     116           0 :                         index(current_dim) = backidx(i);
     117           0 :                         recurser(os, next_dim, index, next_hanging_indent);
     118           0 :                         os << this->config.separator;
     119             :                     }
     120             : 
     121           0 :                     index(current_dim) = backidx(1);
     122           0 :                     recurser(os, next_dim, index, next_hanging_indent);
     123             :                 } else {
     124             :                     // newlines between rows
     125             :                     // the more dimensions, the more newlines.
     126           0 :                     auto line_separator = (std::string(this->rstrip(this->config.separator))
     127           0 :                                            + std::string(dims_left - 1, '\n'));
     128             : 
     129           0 :                     for (index_t i = 0; i < leading_items; i++) {
     130           0 :                         index(current_dim) = i;
     131           0 :                         if (i != 0) {
     132           0 :                             os << hanging_indent;
     133             :                         }
     134           0 :                         recurser(os, next_dim, index, next_hanging_indent);
     135           0 :                         os << line_separator;
     136             :                     }
     137             : 
     138           0 :                     if (do_summary) {
     139           0 :                         os << hanging_indent << " " << this->config.summary_elem_vertical
     140           0 :                            << line_separator;
     141             :                     }
     142             : 
     143             :                     // remaining but the last element
     144           0 :                     for (index_t i = trailing_items; i > 1; i--) {
     145           0 :                         index(current_dim) = backidx(i);
     146           0 :                         if (do_summary or i != trailing_items) {
     147           0 :                             os << hanging_indent;
     148             :                         }
     149           0 :                         recurser(os, next_dim, index, next_hanging_indent);
     150           0 :                         os << line_separator;
     151             :                     }
     152             : 
     153             :                     // indent for the current dim's last entry
     154             :                     // we skip it if it's the only entry
     155           0 :                     if (trailing_items > 1) {
     156           0 :                         os << hanging_indent;
     157             :                     }
     158             : 
     159             :                     // print the last element
     160           0 :                     index(current_dim) = backidx(1);
     161           0 :                     recurser(os, next_dim, index, next_hanging_indent);
     162           0 :                 }
     163             : 
     164           0 :                 os << "]";
     165           0 :             };
     166             : 
     167             :             // we'll modify this index on the fly when we walk over the datacontainer
     168           0 :             IndexVector_t dc_index{dims};
     169           0 :             auto prefix = " "s; // skip over initial [
     170           0 :             recurser(os, 0, dc_index, prefix);
     171           0 :         }
     172             : 
     173             :     private:
     174             :         /**
     175             :          * adjust a given string_view and remove all whitespace from the right side.
     176             :          */
     177           0 :         std::string_view rstrip(std::string_view text)
     178             :         {
     179           0 :             auto end_it = std::rbegin(text);
     180           0 :             while (end_it != std::rend(text) and *end_it == ' ') {
     181           0 :                 ++end_it;
     182             :             }
     183             : 
     184             :             // TODO c++20: use stringview iterator constructor directly
     185           0 :             const char* start = &(*std::begin(text));
     186           0 :             const char* end = &(*end_it) + 1;
     187             : 
     188           0 :             ssize_t len = end - start;
     189           0 :             if (len < 0) {
     190           0 :                 throw elsa::InternalError{"rstrip length sanity check failed"};
     191             :             }
     192           0 :             return std::string_view(start, static_cast<size_t>(len));
     193             :         }
     194             : 
     195             :         /**
     196             :          * Given a DataContainer, generate a function that will format one element beautifully.
     197             :          * This element is padded to the max width of the other elements.
     198             :          *
     199             :          * @param dc: data to get the formatter for.
     200             :          *
     201             :          * TODO add new features:
     202             :          * unique: display elements in such a way they are uniquely distinguishable
     203             :          *         (dynamically adjust precision)
     204             :          * precision: if unique is false, use this float precision.
     205             :          */
     206             :         template <typename T>
     207             :         std::function<std::ostream&(std::ostream& os, const T& elem)>
     208           0 :             get_element_formatter(const DataContainer<T>& dc)
     209             :         {
     210             :             if constexpr (elsa::isComplex<T>) {
     211             :                 // format both components independently
     212           0 :                 auto real_formatter = get_element_formatter(DataContainer<T>{real(dc)});
     213           0 :                 auto imag_formatter = get_element_formatter(DataContainer<T>{imag(dc)});
     214             : 
     215           0 :                 return [real_formatter, imag_formatter](std::ostream & os, const T& elem) -> auto&
     216             :                 {
     217           0 :                     real_formatter(os, elem.real());
     218           0 :                     os << "+";
     219           0 :                     imag_formatter(os, elem.imag());
     220           0 :                     os << "j";
     221           0 :                     return os;
     222           0 :                 };
     223           0 :             } else if constexpr (std::is_floating_point_v<T>) {
     224             :                 // TODO: handle non-finite (not nan, inf) elements
     225             :                 // TODO: align stuff at the . and do right and left padding
     226             : 
     227           0 :                 bool suppress_small = true;
     228           0 :                 bool use_exp = false;
     229             : 
     230           0 :                 T val_max = dc.maxElement();
     231           0 :                 T val_min = dc.minElement();
     232             : 
     233           0 :                 if (val_max > 1e7
     234           0 :                     or (not suppress_small and (val_min < 0.0001 or val_max / val_min > 1000.0))) {
     235             : 
     236           0 :                     use_exp = true;
     237             :                 }
     238             : 
     239             :                 // TODO: possible optimization - no underlying string needed,
     240             :                 // could be a /dev/null-like storage,
     241             :                 // since we only use tellp.
     242           0 :                 std::ostringstream teststream;
     243           0 :                 if (use_exp) {
     244           0 :                     teststream << std::scientific;
     245             :                 } else {
     246           0 :                     teststream << std::defaultfloat;
     247             :                 }
     248             : 
     249             :                 // setw wants int...
     250           0 :                 int maxlen = 0;
     251           0 :                 for (elsa::index_t idx = 0; idx < dc.getSize(); ++idx) {
     252           0 :                     teststream.str("");
     253           0 :                     teststream.clear();
     254             : 
     255           0 :                     auto&& elem = config.suppress_close_to_zero
     256           0 :                                           && std::abs(dc[idx]) < config.suppression_epsilon
     257           0 :                                       ? static_cast<data_t>(0)
     258           0 :                                       : dc[idx];
     259             : 
     260           0 :                     teststream << elem;
     261           0 :                     auto len = static_cast<int>(teststream.tellp());
     262             : 
     263           0 :                     if (len > maxlen) {
     264           0 :                         maxlen = len;
     265             :                     }
     266             :                 }
     267             : 
     268           0 :                 auto streamflags = teststream.flags();
     269             : 
     270           0 :                 return [
     271           0 :                     maxlen, streamflags, do_suppress = config.suppress_close_to_zero,
     272           0 :                     eps = config.suppression_epsilon
     273             :                 ](std::ostream & os, const T& elem) -> auto&
     274             :                 {
     275           0 :                     os.flags(streamflags);
     276           0 :                     os << std::setw(maxlen);
     277           0 :                     os << (do_suppress && std::abs(elem) < eps ? static_cast<data_t>(0) : elem);
     278           0 :                     return os;
     279           0 :                 };
     280           0 :             } else {
     281             :                 // setw wants int, string::size returns size_t. great.
     282           0 :                 int maxlen = static_cast<int>(std::max(std::to_string(dc.maxElement()).size(),
     283           0 :                                                        std::to_string(dc.minElement()).size()));
     284           0 :                 return [maxlen](std::ostream & os, const T& elem) -> auto&
     285             :                 {
     286           0 :                     auto&& elem_str = std::to_string(elem);
     287           0 :                     os << std::setw(maxlen);
     288           0 :                     os << std::to_string(elem);
     289           0 :                     return os;
     290           0 :                 };
     291             :             }
     292             :         }
     293             : 
     294             :         /// formatting output configuration
     295             :         format_config config;
     296             :     };
     297             : 
     298             : } // namespace elsa

Generated by: LCOV version 1.14