LCOV - code coverage report
Current view: top level - elsa/core/Utilities - DataContainerFormatter.hpp (source / functions) Hit Total Coverage
Test: coverage-all.lcov Lines: 128 171 74.9 %
Date: 2024-05-16 04:22:26 Functions: 8 41 19.5 %

          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           3 :         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             :         format_config get_default_format_config()
      47           3 :         {
      48           3 :             format_config cfg;
      49           3 :             if constexpr (isComplex<data_t>) {
      50           3 :                 cfg.summary_items /= 2;
      51           3 :             }
      52             : 
      53           3 :             return cfg;
      54           3 :         }
      55             : 
      56             :         /// stream a pretty format to is using the current configuration.
      57             :         void format(std::ostream& os, const DataContainer<data_t>& dc)
      58           3 :         {
      59           3 :             using namespace std::string_literals;
      60             : 
      61           3 :             auto dims = dc.getDataDescriptor().getNumberOfDimensions();
      62           3 :             auto shape = dc.getDataDescriptor().getNumberOfCoefficientsPerDimension();
      63           3 :             os << "DataContainer<dims=" << dims << ", shape=(" << shape.transpose() << ")>"
      64           3 :                << std::endl;
      65             : 
      66           3 :             auto format_element = this->get_element_formatter<data_t>(dc);
      67             : 
      68           3 :             std::function<void(std::ostream & stream, index_t current_dim, IndexVector_t & index,
      69           3 :                                std::string_view indent_prefix)>
      70           3 :                 recurser;
      71           3 :             recurser = [this, &recurser, &dc, &dims, &shape,
      72           3 :                         &format_element](std::ostream& os, index_t current_dim,
      73          45 :                                          IndexVector_t& index, std::string_view hanging_indent) {
      74          45 :                 index_t dims_left = dims - current_dim;
      75          45 :                 index_t next_dim = current_dim + 1;
      76             : 
      77          45 :                 if (dims_left == 0) {
      78             :                     // get the actual element, recursion terminator! \o/
      79          37 :                     format_element(os, dc(index));
      80          37 :                     return;
      81          37 :                 }
      82             : 
      83           8 :                 std::string next_hanging_indent = std::string(hanging_indent) + " "s;
      84             : 
      85           8 :                 index_t dim_size = shape(current_dim);
      86          36 :                 auto backidx = [dim_size](index_t idx) { return dim_size - idx; };
      87             : 
      88           8 :                 index_t leading_items, trailing_items;
      89           8 :                 bool do_summary =
      90           8 :                     (this->config.summary_enabled and 2 * this->config.summary_items < dim_size);
      91           8 :                 if (do_summary) {
      92           1 :                     leading_items = this->config.summary_items;
      93           1 :                     trailing_items = this->config.summary_items;
      94           7 :                 } else {
      95           7 :                     leading_items = 0;
      96           7 :                     trailing_items = dim_size;
      97           7 :                 }
      98             : 
      99           8 :                 os << "[";
     100             : 
     101             :                 // last dimension, i.e. the rows
     102           8 :                 if (dims_left == 1) {
     103             : 
     104             :                     // TODO c++20 use views::iota
     105          13 :                     for (index_t i = 0; i < leading_items; i++) {
     106           6 :                         index(current_dim) = i;
     107           6 :                         recurser(os, next_dim, index, next_hanging_indent);
     108           6 :                         os << this->config.separator;
     109           6 :                     }
     110             : 
     111           7 :                     if (do_summary) {
     112           1 :                         os << this->config.summary_elem << this->config.separator;
     113           1 :                     }
     114             : 
     115          31 :                     for (index_t i = trailing_items; i > 1; i--) {
     116          24 :                         index(current_dim) = backidx(i);
     117          24 :                         recurser(os, next_dim, index, next_hanging_indent);
     118          24 :                         os << this->config.separator;
     119          24 :                     }
     120             : 
     121           7 :                     index(current_dim) = backidx(1);
     122           7 :                     recurser(os, next_dim, index, next_hanging_indent);
     123           7 :                 } else {
     124             :                     // newlines between rows
     125             :                     // the more dimensions, the more newlines.
     126           1 :                     auto line_separator = (std::string(this->rstrip(this->config.separator))
     127           1 :                                            + std::string(static_cast<size_t>(dims_left) - 1, '\n'));
     128             : 
     129           1 :                     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           0 :                         }
     134           0 :                         recurser(os, next_dim, index, next_hanging_indent);
     135           0 :                         os << line_separator;
     136           0 :                     }
     137             : 
     138           1 :                     if (do_summary) {
     139           0 :                         os << hanging_indent << " " << this->config.summary_elem_vertical
     140           0 :                            << line_separator;
     141           0 :                     }
     142             : 
     143             :                     // remaining but the last element
     144           5 :                     for (index_t i = trailing_items; i > 1; i--) {
     145           4 :                         index(current_dim) = backidx(i);
     146           4 :                         if (do_summary or i != trailing_items) {
     147           3 :                             os << hanging_indent;
     148           3 :                         }
     149           4 :                         recurser(os, next_dim, index, next_hanging_indent);
     150           4 :                         os << line_separator;
     151           4 :                     }
     152             : 
     153             :                     // indent for the current dim's last entry
     154             :                     // we skip it if it's the only entry
     155           1 :                     if (trailing_items > 1) {
     156           1 :                         os << hanging_indent;
     157           1 :                     }
     158             : 
     159             :                     // print the last element
     160           1 :                     index(current_dim) = backidx(1);
     161           1 :                     recurser(os, next_dim, index, next_hanging_indent);
     162           1 :                 }
     163             : 
     164           8 :                 os << "]";
     165           8 :             };
     166             : 
     167             :             // we'll modify this index on the fly when we walk over the datacontainer
     168           3 :             IndexVector_t dc_index{dims};
     169           3 :             auto prefix = " "s; // skip over initial [
     170           3 :             recurser(os, 0, dc_index, prefix);
     171           3 :         }
     172             : 
     173             :     private:
     174             :         /**
     175             :          * adjust a given string_view and remove all whitespace from the right side.
     176             :          */
     177             :         std::string_view rstrip(std::string_view text)
     178           1 :         {
     179           1 :             auto end_it = std::rbegin(text);
     180           2 :             while (end_it != std::rend(text) and *end_it == ' ') {
     181           1 :                 ++end_it;
     182           1 :             }
     183             : 
     184             :             // TODO c++20: use stringview iterator constructor directly
     185           1 :             const char* start = &(*std::begin(text));
     186           1 :             const char* end = &(*end_it) + 1;
     187             : 
     188           1 :             ssize_t len = end - start;
     189           1 :             if (len < 0) {
     190           0 :                 throw elsa::InternalError{"rstrip length sanity check failed"};
     191           0 :             }
     192           1 :             return std::string_view(start, static_cast<size_t>(len));
     193           1 :         }
     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             :             get_element_formatter(const DataContainer<T>& dc)
     209           3 :         {
     210           3 :             if constexpr (elsa::isComplex<T>) {
     211             :                 // format both components independently
     212           3 :                 auto real_formatter = get_element_formatter(real(dc));
     213           3 :                 auto imag_formatter = get_element_formatter(imag(dc));
     214             : 
     215           3 :                 return [real_formatter, imag_formatter](std::ostream & os, const T& elem) -> auto&
     216           3 :                 {
     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           3 :             } 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           3 :                 if (val_max > 1e7f
     234           3 :                     or (not suppress_small
     235           3 :                         and (val_min < 0.0001f or val_max / val_min > 1000.0f))) {
     236             : 
     237           0 :                     use_exp = true;
     238           0 :                 }
     239             : 
     240             :                 // TODO: possible optimization - no underlying string needed,
     241             :                 // could be a /dev/null-like storage,
     242             :                 // since we only use tellp.
     243           0 :                 std::ostringstream teststream;
     244           3 :                 if (use_exp) {
     245           0 :                     teststream << std::scientific;
     246           3 :                 } else {
     247           3 :                     teststream << std::defaultfloat;
     248           3 :                 }
     249             : 
     250             :                 // setw wants int...
     251           0 :                 int maxlen = 0;
     252          43 :                 for (elsa::index_t idx = 0; idx < dc.getSize(); ++idx) {
     253          40 :                     teststream.str("");
     254          40 :                     teststream.clear();
     255             : 
     256          40 :                     auto&& elem = config.suppress_close_to_zero
     257          40 :                                           && std::abs(dc[idx]) < config.suppression_epsilon
     258          40 :                                       ? static_cast<data_t>(0)
     259          40 :                                       : dc[idx];
     260             : 
     261          40 :                     teststream << elem;
     262          40 :                     auto len = static_cast<int>(teststream.tellp());
     263             : 
     264          40 :                     if (len > maxlen) {
     265           3 :                         maxlen = len;
     266           3 :                     }
     267          40 :                 }
     268             : 
     269           0 :                 auto streamflags = teststream.flags();
     270             : 
     271           0 :                 return [
     272           0 :                     maxlen, streamflags, do_suppress = config.suppress_close_to_zero,
     273           0 :                     eps = config.suppression_epsilon
     274           0 :                 ](std::ostream & os, const T& elem) -> auto&
     275          37 :                 {
     276          37 :                     os.flags(streamflags);
     277          37 :                     os << std::setw(maxlen);
     278          37 :                     os << (do_suppress && std::abs(elem) < eps ? static_cast<data_t>(0) : elem);
     279          37 :                     return os;
     280          37 :                 };
     281           3 :             } else {
     282             :                 // setw wants int, string::size returns size_t. great.
     283           0 :                 int maxlen = static_cast<int>(std::max(std::to_string(dc.maxElement()).size(),
     284           0 :                                                        std::to_string(dc.minElement()).size()));
     285           0 :                 return [maxlen](std::ostream & os, const T& elem) -> auto&
     286           0 :                 {
     287           0 :                     auto&& elem_str = std::to_string(elem);
     288           0 :                     os << std::setw(maxlen);
     289           0 :                     os << std::to_string(elem);
     290           0 :                     return os;
     291           0 :                 };
     292           0 :             }
     293           3 :         }
     294             : 
     295             :         /// formatting output configuration
     296             :         format_config config;
     297             :     };
     298             : 
     299             : } // namespace elsa

Generated by: LCOV version 1.14