Library: integrate into the Lumiera code base

- reformat in Lumieara-GNU style
- use the Lumiera exceptions
- use Lumiera format-string frontend
- use lib/util

NOTE: I am the original author of the code introduced here,
and thus I can re-license it under GPL 2+
This commit is contained in:
Fischlurch 2024-03-11 01:52:49 +01:00
parent 8c344b6a51
commit 0e88dec28a
12 changed files with 1142 additions and 1387 deletions

View file

@ -79,6 +79,9 @@ def configure(env):
if not conf.CheckCXXHeader('functional'):
problems.append('We rely on the C++11 functor objects.')
if not conf.CheckLibWithHeader('stdc++fs', 'filesystem', 'C++'):
problems.append('We need the C++17 filesystem support.')
if not conf.CheckCXXHeader('boost/config.hpp'):
problems.append('We need the C++ boost-libraries.')
else:

View file

@ -1,21 +1,24 @@
/*
* csv - parser and encoder
*
* Copyright 2021, Hermann Vosseler <Ichthyostega@web.de>
*
* This file is part of the Yoshimi-Testsuite, which is free software:
* you can redistribute and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* Yoshimi-Testsuite is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with yoshimi. If not, see <http://www.gnu.org/licenses/>.
***************************************************************/
CSV.hpp - Parser and Encoder for CSV data
Copyright (C) Lumiera.org
2022, Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/** @file csv.hpp
@ -36,159 +39,171 @@
** - only quoted fields may contain whitespace or comma
** - no escaping of quotes, i.e. no quotes within quotes
** [RFC 4180]: https://datatracker.ietf.org/doc/html/rfc4180
**
** @todo WIP as of 9/21
** @see util::DataFile used for [Timing statistics](\ref TimingObservation.hpp)
** @see lib::stat::DataFile
**
*/
#ifndef LIB_STAT_CSV_H
#define LIB_STAT_CSV_H
#ifndef TESTRUNNER_UTIL_CSV_HPP_
#define TESTRUNNER_UTIL_CSV_HPP_
#include "util/error.hpp"
#include "util/format.hpp"
#include "util/regex.hpp"
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "lib/format-string.hpp"
#include "lib/format-obj.hpp"
#include "lib/stat/regex.hpp"
#include <limits>
#include <string>
namespace lib {
namespace stat {
namespace util {
namespace error = lumiera::error;
using std::regex;
using std::string;
using util::_Fmt;
using util::toString;
using std::string;
using std::regex;
namespace { // Implementation details...
const string MATCH_SINGLE_TOKEN {R"~(([^,;"\s]*)\s*)~"};
const string MATCH_QUOTED_TOKEN {R"~("([^"]*)"\s*)~"};
const string MATCH_DELIMITER {R"~((?:^|,|;)\s*)~"};
namespace { // Implementation details...
const string MATCH_SINGLE_TOKEN { R"~(([^,;"\s]*)\s*)~"};
const string MATCH_QUOTED_TOKEN { R"~("([^"]*)"\s*)~" };
const string MATCH_DELIMITER { R"~((?:^|,|;)\s*)~" };
const regex ACCEPT_FIELD{ MATCH_DELIMITER + "(?:"+ MATCH_QUOTED_TOKEN +"|"+ MATCH_SINGLE_TOKEN +")"
, regex::optimize};
template<typename VAL>
inline string format4Csv(VAL const& val)
inline string
format4Csv (VAL const& val)
{
std::ostringstream oss;
oss.precision(std::numeric_limits<VAL>::digits10);
oss << val;
return oss.str();
}
inline string format4Csv(string const& val)
{
return '"'+val+'"';
}
inline string format4Csv(bool boo)
{
return formatVal(boo);
std::ostringstream oss;
oss.precision (std::numeric_limits<VAL>::digits10); /////////////////////////////OOO herausfinden ob hier lexical_cast genügt ==> dann toString()
oss << val;
return oss.str();
}
}//(End)Implementation
inline string
format4Csv (string const& val)
{
return '"'+val+'"';
}
inline string
format4Csv (bool boo)
{
return util::showBool(boo); ///////////////////////OOO würde toSting() das korrekt hinbekommen
}
}//(End)Implementation
/**
* Parser to split one line of CSV data into fields.
* @remarks iterator-like throw-away object
* - the `bool` evaluation indicates more fields to extract
* - dereference to get the field as string
* - increment to move to the next field
* @throws error::Invalid on CSV format violation
*/
class CsvLine
/**
* Format and append a data value to a CSV string representation
*/
template<typename VAL>
inline void
appendCsvField (string& csv, VAL const& val)
{
csv += (0 == csv.length()? "":",")
+ format4Csv(val);
}
/**
* Parser to split one line of CSV data into fields.
* @remarks iterator-like throw-away object
* - the `bool` evaluation indicates more fields to extract
* - dereference to get the field as string
* - increment to move to the next field
* @throws error::Invalid on CSV format violation
* @todo 3/24 should be rewritten as Lumiera Forward Iterator
*/
class CsvLine
: util::NonCopyable
, MatchSeq
{
string const& line_;
size_t field_;
iterator curr_;
size_t pos_;
, util::MatchSeq
{
string const& line_;
size_t field_;
iterator curr_;
size_t pos_;
public:
CsvLine(string const& line)
public:
CsvLine (string const& line)
: MatchSeq(line, ACCEPT_FIELD)
, line_{line}
, field_{0}
, curr_{MatchSeq::begin()}
, pos_{0}
{ }
{ }
explicit operator bool()
{
return isValid();
}
explicit operator bool() const
{
return isValid ();
}
string operator*()
{
if (not isValid()) fail();
auto& mat = *curr_;
return mat[2].matched? mat[2]
: mat[1];
}
string operator*() const
{
if (not isValid ()) fail();
auto& mat = *curr_;
return mat[2].matched? mat[2]
: mat[1];
}
void operator++()
{
if (not isValid())
void
operator++()
{
if (not isValid())
fail();
pos_ = curr_->position() + curr_->length();
++curr_;
if (pos_ < line_.length() and not isValid())
fail();
++field_;
}
pos_ = curr_->position() + curr_->length();
++curr_;
if (pos_ < line_.length() and not isValid())
fail ();
++field_;
}
size_t getParsedFieldCnt()
{
return field_;
}
size_t
getParsedFieldCnt()
{
return field_;
}
bool isValid()
{
return curr_ != end()
and curr_->position() == pos_
and not curr_->empty();
}
bool
isValid() const
{
return curr_ != end()
and pos_ == size_t(curr_->position())
and not curr_->empty();
}
bool isParseFail()
{
return curr_ != end()
and not isValid();
}
bool
isParseFail() const
{
return curr_ != end()
and not isValid();
}
void fail()
{
if (curr_ == end())
if (pos_ >= line_.length())
throw error::Invalid("Only "+formatVal(field_)+" data fields. Line:"+line_);
else
throw error::Invalid("Garbage after last field. Line:"
+line_.substr(0,pos_)+"|↯|"+line_.substr(pos_));
else
if (pos_ != curr_->position())
throw error::Invalid("Garbage before field("+formatVal(field_+1)+"):"
+line_.substr(0,pos_)+"|↯|"+line_.substr(pos_));
else
throw error::Invalid("CSV parse floundered. Line:"+line_);
}
};
void
fail() const
{
if (curr_ == end())
if (pos_ >= line_.length())
throw error::Invalid{_Fmt{"Only %d data fields. Line:%s"}
% field_ % line_};
else
throw error::Invalid{_Fmt{"Garbage after last field. Line:%s|↯|%s"}
% line_.substr(0,pos_) % line_.substr(pos_)};
else
if (pos_ != curr_->position())
throw error::Invalid{_Fmt{"Garbage before field(%d):%s|↯|%s"}
% (field_+1)
% line_.substr(0,pos_) % line_.substr(pos_)};
else
throw error::Invalid{"CSV parse floundered. Line:"+toString(line_)};
}
};
/**
* Format and append a data value to a CSV string representation
*/
template<typename VAL>
inline void appendCsvField(string& csv, VAL const& val)
{
csv += (0 == csv.length()? "":",")
+ format4Csv(val);
}
} // namespace util
#endif /*TESTRUNNER_UTIL_CSV_HPP_*/
}} // namespace lib::stat
#endif /*LIB_STAT_CSV_H*/

View file

@ -1,33 +1,43 @@
/*
* data - read and write a table with CSV data
*
* Copyright 2021, Hermann Vosseler <Ichthyostega@web.de>
*
* This file is part of the Yoshimi-Testsuite, which is free software:
* you can redistribute and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* Yoshimi-Testsuite is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with yoshimi. If not, see <http://www.gnu.org/licenses/>.
***************************************************************/
DATA.hpp - read and write a table with CSV data
Copyright (C) Lumiera.org
2022, Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/** @file data.hpp
** Manage a table with time series data, stored persistently as CSV.
** The Yoshimi Testsuite captures timing data, to detect the possible performance
** impact of code reworking. Due to the statistical nature of timing measurements
** and the dependency on the run environment, it is not sufficient just to rely on
** a single measurement to establish the runtime characteristics of a given test;
** rather, the statistical trend of the timings observed over several consecutive
** runs of the Testsuite must be established. Short of using a database, a modest
** In the context of observations, configuration, calibration and QA, a series
** of measurement data taken over time is often evaluated statistically, to distill
** typical averages, variances and trends. Short of using a database, a modest
** amount of numeric data can be maintained in CSV files, which also allows for
** further manual evaluation within a spreadsheet or statistics application.
** The CSV format as such can be quite elaborate, yet for the purpose of
** saving and later reading back some values generated by the application
** itself, supporting a limited format flavour is sufficient:
** - first line is a header line and used to verify the storage format
** - one record per line, embedded line breaks prohibited
** - fields separated by comma, semicolon tolerated
** - fields are trimmed and may be empty
** - a field may be double quoted
** - only quoted fields may contain whitespace or comma
** - no escaping of quotes, i.e. no quotes within quotes
**
** As a fundamental building block, this header provides a data table template
** with flexible column configuration to hold arbitrary, explicitly typed values.
@ -43,16 +53,16 @@
** @note mandatory to define a method `allColumns()`
** \code
** struct Storage
** {
** {
** Column<string> name{"theName"};
** Column<int> n{"counter"};
** Column<double> x{"X value"};
** Column<double> y{"Y value"};
**
** auto allColumns(){ return std::tie(name,n,x,y); }
** };
** };
**
** using Dataz = util::DataFile<Storage>;
** using Dataz = lib::stat::DataFile<Storage>;
**
** Dataz daz("filename.csv");
**
@ -62,21 +72,20 @@
** std::vector<int>& counters = daz.n.data;
** \endcode
**
** @see TimingObservation.hpp usage
**
*/
#ifndef TESTRUNNER_UTIL_DATA_HPP_
#define TESTRUNNER_UTIL_DATA_HPP_
#ifndef LIB_STAT_DATA_H
#define LIB_STAT_DATA_H
#include "util/nocopy.hpp"
#include "util/error.hpp"
#include "util/utils.hpp"
#include "util/file.hpp"
#include "util/csv.hpp"
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "lib/stat/file.hpp"
#include "lib/stat/csv.hpp"
#include "lib/format-string.hpp"
#include "lib/util.hpp"
#include <type_traits>
#include <utility>
@ -88,331 +97,382 @@
#include <tuple>
namespace util {
namespace lib {
namespace stat{
using std::tuple;
using std::vector;
using std::string;
namespace error = lumiera::error;
using std::move;
using std::tuple;
using std::vector;
using std::string;
using util::isnil;
using util::unConst;
using util::_Fmt;
using util::min;
/**
* Helper: perform some arbitrary operation on each element of a tuple.
* @note the given functor must be generic, since each position of the tuple
* may hold a data element of different type.
* @remark credits to David Vandevoorde (member of C++ committee) for using
* std::apply to unpack the tuple's contents into an argument pack and
* then using a fold expression with the comma operator.
*/
template<class FUN, typename...ELMS>
void forEach(tuple<ELMS...>&& tuple, FUN fun)
{
std::apply([&fun](auto&... elms)
{
/**
* Helper: perform some arbitrary operation on each element of a tuple.
* @note the given functor must be generic, since each position of the tuple
* may hold a data element of different type.
* @remark credits to David Vandevoorde (member of C++ committee) for using
* std::apply to unpack the tuple's contents into an argument pack and
* then using a fold expression with the comma operator.
*/
template<class FUN, typename...ELMS>
void forEach(tuple<ELMS...>&& tuple, FUN fun)
{
std::apply([&fun](auto&... elms)
{
(fun(elms), ...);
}
,tuple);
}
}
,tuple);
}
/**
* Descriptor and Accessor for a data column within a DataFile table.
* @tparam VAL type of values contained within this column;
* this type must be _default constructible_ and _copyable._
*/
template<typename VAL>
struct Column : util::NonCopyable
{
string header;
vector<VAL> data;
/** parse string representation into typed value */
template<typename TAR>
inline TAR
parseAs (string const& encodedVal)
{
std::istringstream converter{encodedVal};
TAR value;
converter >> value;
if (converter.fail())
throw error::Invalid{_Fmt{"unable to parse \"%s\""} % encodedVal};
return value;
}
using ValueType = VAL;
template<>
inline bool
parseAs(string const& encodedBool)
{
return util::boolVal(encodedBool);
}
template<>
inline string
parseAs(string const& string)
{
return string; // pass-through (even if empty)
}
Column(string headerID)
/**
* Descriptor and Accessor for a data column within a DataFile table.
* @tparam VAL type of values contained within this column;
* this type must be _default constructible_ and _copyable._
*/
template<typename VAL>
struct Column
: util::NonCopyable
{
string header;
vector<VAL> data;
using ValueType = VAL;
Column (string headerID)
: header{headerID}
, data{}
{ }
{ }
VAL& get()
{
if (isnil(data))
throw error::State("No rows in DataTable yet");
return data.back();
}
operator VAL&()
{
return get();
}
operator VAL const&() const
{
return unConst(this)->get();
}
template<typename X>
VAL& operator=(X&& newVal)
{
return get() = std::forward<X>(newVal);
}
};
/**
* Table with data values, stored persistently as CSV file.
* Each row within the table represents a data record, holding a sequence
* of values. Values are statically typed per column, i.e. one column may hold
* strings, while the next column holds doubles. For actual usage it is thus necessary
* to define the column layout, through a sequence of [column Descriptors](\ref util::Column).
*
* # Usage
* Actually those Column objects serve as descriptors, but also as accessors -- and they hold
* the actual data storage for each column, which is a `std::vector<VAL>` of value type `VAL`.
* There is always a _current record_ -- corresponding to the actual data value and the newest
* data row. For persistent storage, the sequence of rows is _reversed,_ so the newest data
* appears at the top of the CSV file.
* @tparam TAB a struct comprised of several Column objects, which hold the data and
* provide access to values of this specific column. Moreover, this type _must define_
* a function `allColumns()` to return a tuple with references to these column fields;
* the order of fields within this tuple also defines the order of columns
* within the table and persistent CSV storage.
* @see suite::step::TimingObservation (relevant usage example)
*/
template<class TAB>
class DataFile
: public TAB
, util::NonCopyable
{
fs::path filename_;
public:
DataFile(fs::path csvFile)
: filename_{consolidated(csvFile)}
{
loadData();
}
/* === Data Access === */
static constexpr size_t columnCnt = std::tuple_size_v<decltype(std::declval<TAB>().allColumns())>;
bool empty() const
{
return 0 == this->size();
}
size_t size() const
{
if (0 == columnCnt) return 0;
size_t rowCnt = std::numeric_limits<size_t>::max();
forEach(unConst(this)->allColumns(),
[&](auto& col)
{
rowCnt = std::min(rowCnt, col.data.size());
}); // the smallest number of data points found in any column
return rowCnt;
}
string dumpCSV() const
{
string csv;
for (uint i=0; i < size(); ++i)
csv += formatCSVRow(i) + '\n';
return csv;
}
/* === Manipulation === */
void newRow()
{
forEach(TAB::allColumns(),
[](auto& col)
{
col.data.resize(col.data.size()+1);
});
}
void dupRow()
{
if (empty())
newRow();
else
forEach(TAB::allColumns(),
[](auto& col)
{
col.data.emplace_back(col.data.back());
});
}
void dropLastRow()
{
if (not empty())
forEach(TAB::allColumns(),
[](auto& col)
{
size_t siz = col.data.size();
col.data.resize(siz>0? siz-1 : 0);
});
}
void reserve(size_t expectedCapacity)
{
forEach(TAB::allColumns(),
[=](auto& col)
{
col.data.reserve(expectedCapacity);
});
}
/** @param lineLimit number of rows to retain, back from the newest */
void save(size_t lineLimit =std::numeric_limits<size_t>::max(), bool backupOld =false)
{
fs::path newFilename{filename_};
newFilename += ".tmp";
std::ofstream csvFile{newFilename, std::ios_base::out | std::ios_base::trunc};
if (not csvFile.good())
throw error::State("Unable to create CSV output file "+formatVal(newFilename));
saveData(csvFile, lineLimit);
if (backupOld)
VAL&
get()
{
fs::path oldFile{filename_};
oldFile += ".bak";
if (fs::exists(filename_))
fs::rename(filename_, oldFile);
if (isnil (data))
throw error::State{"No rows in DataTable yet"};
return data.back();
}
fs::rename(newFilename, filename_);
filename_ = consolidated(filename_); // lock onto absolute path
}
operator VAL&()
{
return get();
}
operator VAL const&() const
{
return unConst(this)->get();
}
template<typename X>
VAL& operator= (X&& newVal)
{
return get() = std::forward<X> (newVal);
}
};
private: /* === Implementation === */
void loadData()
/******************************************************************************************//**
* Table with data values, stored persistently as CSV file.
* Each row within the table represents a data record, holding a sequence
* of values. Values are statically typed per column, i.e. one column may hold
* strings, while the next column holds doubles. For actual usage it is thus necessary
* to define the column layout, through a sequence of [column Descriptors](\ref util::Column).
*
* # Usage
* Actually those Column objects serve as descriptors, but also as accessors and they hold
* the actual data storage for each column, which is a `std::vector<VAL>` of value type `VAL`.
* There is always a _current record_ corresponding to the actual data value and the newest
* data row. For persistent storage, the sequence of rows is _reversed,_ so the newest data
* appears at the top of the CSV file.
* @tparam TAB a struct comprised of several Column objects, which hold the data and
* provide access to values of this specific column. Moreover, this type _must define_
* a function `allColumns()` to return a tuple with references to these column fields;
* the order of fields within this tuple also defines the order of columns
* within the table and persistent CSV storage.
*/
template<class TAB>
class DataFile
: public TAB
, util::NonCopyable
{
if (not (filename_.parent_path().empty()
or fs::exists(filename_.parent_path())))
throw error::Invalid("DataFile("+formatVal(filename_.filename())
+") shall be placed into nonexistent directory "
+formatVal(filename_.parent_path()));
if (not fs::exists(filename_))
fs::path filename_;
public:
DataFile(fs::path csvFile)
: filename_{fs::consolidated (csvFile)}
{
loadData();
}
/* === Data Access === */
static constexpr size_t columnCnt = std::tuple_size_v<decltype(std::declval<TAB>().allColumns())>;
bool
empty() const
{
return 0 == this->size();
}
size_t
size() const
{
if (0 == columnCnt) return 0;
size_t rowCnt = std::numeric_limits<size_t>::max();
forEach (unConst(this)->allColumns()
,[&](auto& col)
{
rowCnt = min (rowCnt, col.data.size());
}); // the smallest number of data points found in any column
return rowCnt;
}
string
dumpCSV() const
{
string csv;
for (uint i=0; i < size(); ++i)
csv += formatCSVRow(i) + '\n';
return csv;
}
/* === Manipulation === */
void
newRow()
{
forEach (TAB::allColumns()
,[](auto& col)
{
col.data.resize (col.data.size()+1);
});
}
void
dupRow()
{
if (empty())
newRow();
else
forEach (TAB::allColumns()
,[](auto& col)
{
col.data.emplace_back (col.data.back());
});
}
void
dropLastRow()
{
if (not empty())
forEach (TAB::allColumns()
,[](auto& col)
{
size_t siz = col.data.size();
col.data.resize (siz>0? siz-1 : 0);
});
}
void
reserve (size_t expectedCapacity)
{
forEach (TAB::allColumns()
,[=](auto& col)
{
col.data.reserve(expectedCapacity);
});
}
/** @param lineLimit number of rows to retain, back from the newest */
void
save (size_t lineLimit =std::numeric_limits<size_t>::max()
,bool backupOld =false)
{
fs::path newFilename{filename_};
newFilename += ".tmp";
std::ofstream csvFile{newFilename, std::ios_base::out | std::ios_base::trunc};
if (not csvFile.good())
throw error::State{_Fmt{"Unable to create CSV output file %s"}
% newFilename};
saveData (csvFile, lineLimit);
if (backupOld)
{
fs::path oldFile{filename_};
oldFile += ".bak";
if (fs::exists (filename_))
fs::rename (filename_, oldFile);
}
fs::rename (newFilename, filename_);
filename_ = fs::consolidated(filename_);
} // lock onto absolute path
private: /* === Implementation === */
void
loadData()
{
if (not (filename_.parent_path().empty()
or fs::exists(filename_.parent_path())))
throw error::Invalid{_Fmt{"DataFile(%s) placed into nonexistent directory %s"}
% filename_.filename() % filename_.parent_path()};
if (not fs::exists(filename_))
return; // leave the table empty
std::ifstream csvFile(filename_);
if (not csvFile.good())
throw error::Misconfig{"unable to read CSV data file "+formatVal(filename_)};
std::ifstream csvFile{filename_};
if (not csvFile.good())
throw error::Config{_Fmt{"unable to read CSV data file %s"} % filename_};
std::deque<string> rawLines;
for (string line; std::getline(csvFile, line); )
rawLines.emplace_back(move(line));
std::deque<string> rawLines;
for (string line; std::getline(csvFile, line); )
rawLines.emplace_back (move(line));
if (rawLines.size() < 1) return;
verifyHeaderSpec(rawLines[0]);
if (rawLines.size() < 1) return;
verifyHeaderSpec (rawLines[0]);
// we know the number of rows now...
reserve(rawLines.size() - 1);
// we know the number of rows now...
reserve (rawLines.size() - 1);
// storage in file is backwards, with newest data on top
for (size_t row = rawLines.size()-1; 0<row; --row)
// storage in file is backwards, with newest data on top
for (size_t row = rawLines.size()-1; 0<row; --row)
if (not isnil(rawLines[row]))
appendRowFromCSV(rawLines[row]);
}
appendRowFromCSV (rawLines[row]);
}
void saveData(std::ofstream& csvFile, size_t lineLimit)
{
csvFile << generateHeaderSpec() << "\n";
if (empty())
void
saveData (std::ofstream& csvFile, size_t lineLimit)
{
csvFile << generateHeaderSpec() << "\n";
if (empty())
return;
lineLimit = size() > lineLimit? size()-lineLimit : 0;
// store newest data first, possibly discard old data
for (size_t row = size(); lineLimit < row; --row)
lineLimit = size() > lineLimit? size()-lineLimit : 0;
// store newest data first, possibly discard old data
for (size_t row = size(); lineLimit < row; --row)
csvFile << formatCSVRow(row-1) << "\n";
}
}
void verifyHeaderSpec(string headerLine)
{
CsvLine header(headerLine);
forEach(TAB::allColumns(),
[&](auto& col)
{
if (*header != col.header)
throw error::Invalid("Header mismatch in CSV file "+formatVal(filename_)
+". Expecting column("+formatVal(col.header)
+") but found "+formatVal(*header));
++header;
});
}
void
verifyHeaderSpec (string headerLine)
{
CsvLine header{headerLine};
forEach (TAB::allColumns()
,[&](auto& col)
{
if (*header != col.header)
throw error::Invalid{_Fmt{"Header mismatch in CSV file %s. "
"Expecting column(%s) but found \"%s\""}
% filename_ % col.header % *header};
++header;
});
}
string generateHeaderSpec()
{
string csv;
forEach(TAB::allColumns(),
[&](auto& col)
{
appendCsvField(csv, col.header);
});
return csv;
}
string
generateHeaderSpec()
{
string csv;
forEach (TAB::allColumns()
,[&](auto& col)
{
appendCsvField (csv, col.header);
});
return csv;
}
void appendRowFromCSV(string line)
{
newRow();
CsvLine csv(line);
forEach(TAB::allColumns(),
[&](auto& col)
{
if (!csv)
void
appendRowFromCSV (string line)
{
newRow();
CsvLine csv(line);
forEach (TAB::allColumns()
,[&](auto& col)
{
if (not csv)
if (csv.isParseFail())
csv.fail();
csv.fail();
else
throw error::Invalid("Insufficient data; only "
+str(csv.getParsedFieldCnt())
+" fields, "+str(columnCnt)
+" expected. Line="+line);
throw error::Invalid{_Fmt{"Insufficient data; only %d fields, %d expected. Line:%s"}
% csv.getParsedFieldCnt() % columnCnt % line};
using Value = typename std::remove_reference<decltype(col)>::type::ValueType;
col.get() = parseAs<Value>(*csv);
++csv;
});
if (csv)
throw error::Invalid("Excess data fields in CSV. Expect "+str(columnCnt)+" fields. Line="+line);
}
using Value = typename std::remove_reference<decltype(col)>::type::ValueType;
col.get() = parseAs<Value>(*csv);
++csv;
});
if (csv)
throw error::Invalid{_Fmt{"Excess data fields in CSV. Expect %d fields. Line:%s"}
% columnCnt % line};
}
string formatCSVRow(size_t rownum) const
{
if (this->empty())
throw error::LogicBroken("Attempt to access data from empty DataTable.");
if (rownum >= this->size())
throw error::LogicBroken("Attempt to access row #"+str(rownum)
+" beyond range [0.."+str(size()-1)+"].");
string
formatCSVRow (size_t rownum) const
{
if (this->empty())
throw error::Logic{"Attempt to access data from empty DataTable."};
if (rownum >= this->size())
throw error::Logic{_Fmt{"Attempt to access row #%d beyond range [0..%d]."}
% rownum % (size()-1)};
string csvLine;
forEach(unConst(this)->allColumns(),
[&](auto& col)
{
appendCsvField(csvLine, col.data.at(rownum));
});
return csvLine;
}
};
string csvLine;
forEach (unConst(this)->allColumns()
,[&](auto& col)
{
appendCsvField (csvLine, col.data.at(rownum));
});
return csvLine;
}
};
} // namespace util
#endif /*TESTRUNNER_UTIL_DATA_HPP_*/
}} // namespace lib::stat
#endif /*LIB_STAT_DATA_H*/

View file

@ -1,114 +0,0 @@
/*
* error - exceptions and error handling helpers
*
* Copyright 2021, Hermann Vosseler <Ichthyostega@web.de>
*
* This file is part of the Yoshimi-Testsuite, which is free software:
* you can redistribute and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* Yoshimi-Testsuite is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with yoshimi. If not, see <http://www.gnu.org/licenses/>.
***************************************************************/
/** @file error.hpp
** Definition of semantic exception classes and helpers for error handling.
** - error::LogicBroken : violation of the application's internal logic assumptions.
** Typically raising this exception implies a programming error.
** - error::Misconfig : settings in configuration files or commandline miss expectations.
** - error::ToDo : marker for "Stubs" or planned functionality during development.
** - error::State : unexpected state or failure in system call.
**
** \par Shortcuts and Helpers
** - Macro \ref UNIMPLEMENTED : shortcut for raising a error::ToDo
**
** @todo WIP as of 7/21
**
*/
#ifndef TESTRUNNER_UTIL_ERROR_HPP_
#define TESTRUNNER_UTIL_ERROR_HPP_
#include <stdexcept>
#include <string>
using std::string;
namespace error {
using std::logic_error;
class LogicBroken : public logic_error
{
public:
LogicBroken(string msg)
: logic_error{"LogicBroken: " + msg}
{ }
};
class Misconfig : public logic_error
{
public:
Misconfig(string msg)
: logic_error{"Misconfig: "+msg}
{ }
};
class Invalid : public logic_error
{
public:
Invalid(string msg)
: logic_error{"Invalid Data: "+msg}
{ }
};
class State : public logic_error
{
public:
State(string msg)
: logic_error{"Unforeseen state: "+msg}
{ }
};
class FailedLaunch : public State
{
public:
FailedLaunch(string msg)
: State{"Launch of Test Case failed -- "+msg}
{ }
};
class ToDo : public logic_error
{
public:
ToDo(string msg) :
logic_error{"UNIMPLEMENTED: "+msg}
{ }
};
} // namespace error
#define UNIMPLEMENTED(_MSG_) \
throw error::ToDo(_MSG_)
#endif /*TESTRUNNER_UTIL_ERROR_HPP_*/

41
src/lib/stat/file.cpp Normal file
View file

@ -0,0 +1,41 @@
/*
File - collection of functions supporting unit testing
Copyright (C) Lumiera.org
2009, Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
* *****************************************************/
/** @file file.cpp
** Storage and implementation details for filesystem helpers.
** @todo 3/24 consider to establish some central location to define basic literals.
*/
#include "lib/stat/file.hpp"
namespace std::filesystem {
const string UNIX_HOMEDIR_SYMBOL{"~"};
lib::Literal UNIX_HOMEDIR_ENV{"HOME"};
}
namespace util {
using fs::UNIX_HOMEDIR_SYMBOL;
using fs::UNIX_HOMEDIR_ENV;
}

View file

@ -1,40 +1,46 @@
/*
* file - filesystem access and helpers
*
* Copyright 2021, Hermann Vosseler <Ichthyostega@web.de>
*
* This file is part of the Yoshimi-Testsuite, which is free software:
* you can redistribute and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* Yoshimi-Testsuite is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with yoshimi. If not, see <http://www.gnu.org/licenses/>.
***************************************************************/
FILE.hpp - Filesystem access and helpers
Copyright (C) Lumiera.org
2022, Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/** @file file.hpp
** Includes the C++ Filesystem library and provides some convenience helpers.
** The `std::filesystem` library allows for portable access to file and directory handling
** functions; this header maps these functions with a convenient `fs::` namespace prefix,
** and offers some convenience extensions, which are _"slightly non-portable"_ (they were
** developed on Linux and "should" work on Unix like systems; adapters for exotic operating
** functions; this header maps these functions with an concise `fs::` namespace prefix,
** and offers some convenience extensions, which are _slightly non-portable_ (meaning they
** were developed on Linux and should work on Unix like systems; adapters for exotic operating
** systems could be added here when necessary...)
**
** @todo 3/2024 should be part of generic utilities
*/
#ifndef TESTRUNNER_UTIL_FILE_HPP_
#define TESTRUNNER_UTIL_FILE_HPP_
#ifndef LIB_STAT_FILE_H
#define LIB_STAT_FILE_H
#include "util/error.hpp"
#include "lib/error.hpp"
#include "lib/symbol.hpp"
#include "lib/meta/util.hpp"
#include <filesystem>
#include <cstdlib>
@ -43,42 +49,53 @@
namespace fs = std::filesystem;
namespace std::filesystem {
const string UNIX_HOMEDIR_SYMBOL = "~";
const char * const UNIX_HOMEDIR_ENV = "HOME";
extern const string UNIX_HOMEDIR_SYMBOL;
extern lib::Literal UNIX_HOMEDIR_ENV;
inline fs::path getHomePath()
{
inline fs::path
getHomePath()
{
auto home = std::getenv(UNIX_HOMEDIR_ENV);
if (not home)
throw error::Misconfig("Program environment doesn't define $HOME (Unix home directory).");
throw lumiera::error::Config{"Program environment doesn't define $HOME (Unix home directory)."};
return fs::path{home};
}
}
/** resolves symlinks, `~` (Unix home dir) and relative specs
* @return absolute canonical form if the path exists;
* otherwise only the home directory is expanded */
inline fs::path consolidated(fs::path rawPath)
{
if (rawPath.empty())
return rawPath;
if (UNIX_HOMEDIR_SYMBOL == *rawPath.begin())
rawPath = getHomePath() / rawPath.lexically_proximate(UNIX_HOMEDIR_SYMBOL);
return fs::exists(rawPath)? fs::absolute(
fs::canonical(rawPath))
: rawPath;
}
/** resolves symlinks, `~` (Unix home dir) and relative specs
* @return absolute canonical form if the path exists;
* otherwise only the home directory is expanded */
inline fs::path
consolidated (fs::path rawPath)
{
if (rawPath.empty())
return rawPath;
if (UNIX_HOMEDIR_SYMBOL == *rawPath.begin())
rawPath = getHomePath() / rawPath.lexically_proximate(UNIX_HOMEDIR_SYMBOL);
return fs::exists(rawPath)? fs::absolute(
fs::canonical(rawPath))
: rawPath;
}
}//(End)namespace fs
namespace util {
inline string formatVal(fs::path path)
{
return "\""+string{path}+"\"";
}
/** specialisation: render filesystem path as double quoted string */
template<>
struct StringConv<fs::path, void>
{
static std::string
invoke (fs::path path) noexcept
try {
return "\""+std::string{path}+"\"";
}
catch(...)
{ return lib::meta::FAILURE_INDICATOR; }
};
}//(End)namespace util
#endif /*TESTRUNNER_UTIL_TEE_HPP_*/
#endif /*LIB_STAT_FILE_H*/

View file

@ -1,108 +0,0 @@
/*
* format - collection of test formatting helpers
*
* Copyright 2021, Hermann Vosseler <Ichthyostega@web.de>
*
* This file is part of the Yoshimi-Testsuite, which is free software:
* you can redistribute and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* Yoshimi-Testsuite is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with yoshimi. If not, see <http://www.gnu.org/licenses/>.
***************************************************************/
/** @file format.hpp
** Collection of helper functions for text and number output and formatting.
** @todo WIP as of 7/21
**
*/
#ifndef TESTRUNNER_UTIL_FORMAT_HPP_
#define TESTRUNNER_UTIL_FORMAT_HPP_
#include "util/utils.hpp"
#include <string>
#include <sstream>
using std::string;
namespace util
{
/** format number as string */
template<typename NUM>
inline string str(NUM n)
{
std::ostringstream oss;
oss << n;
return oss.str();
}
template<typename X>
inline string formatVal(X x)
{
return str(x);
}
inline string formatVal(string s)
{
return "\""+s+"\"";
}
inline string formatVal(bool yes)
{
return yes? "true":"false";
}
inline string formatVal(float f)
{
std::ostringstream oss;
oss.precision(3);
oss.width(5);
oss << f;
return oss.str();
}
/** parse string representation into typed value */
template<typename TAR>
inline TAR parseAs(string const& encodedVal)
{
std::istringstream converter{encodedVal};
TAR value;
converter >> value;
if (converter.fail())
throw error::Invalid("unable to parse "+formatVal(encodedVal));
return value;
}
template<>
inline bool parseAs(string const& encodedBool)
{
return util::boolVal(encodedBool);
}
template<>
inline string parseAs(string const& string)
{
return string; // pass-through (even if empty)
}
}//namespace util
#endif /*TESTRUNNER_UTIL_FORMAT_HPP_*/

View file

@ -1,33 +1,35 @@
/*
* regex - helpers for working with regular expressions
*
* Copyright 2021, Hermann Vosseler <Ichthyostega@web.de>
*
* This file is part of the Yoshimi-Testsuite, which is free software:
* you can redistribute and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* Yoshimi-Testsuite is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with yoshimi. If not, see <http://www.gnu.org/licenses/>.
***************************************************************/
REGEX.hpp - helpers for working with regular expressions
Copyright (C) Lumiera.org
2022, Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/** @file regex.hpp
** Convenience wrappers and helpers for dealing with regular expressions.
** @see suite::step::ExeLauncher::ExeLauncher
**
** @todo 3/2024 should be with the generic utils, might be a Lumiera Forward Iterator
*/
#ifndef TESTRUNNER_UTIL_REGEX_HPP_
#define TESTRUNNER_UTIL_REGEX_HPP_
#ifndef LIB_STAT_REGEX_H
#define LIB_STAT_REGEX_H
#include <regex>
@ -36,52 +38,24 @@
namespace util {
using std::regex;
using std::smatch;
using std::string;
using std::regex;
using std::smatch;
using std::string;
/** wrapped regex iterator to allow usage in foreach loops */
struct MatchSeq
/** wrapped regex iterator to allow usage in foreach loops */
struct MatchSeq
: std::sregex_iterator
{
MatchSeq(string const& toParse, regex const& regex)
{
MatchSeq (string const& toParse, regex const& regex)
: std::sregex_iterator{toParse.begin(), toParse.end(), regex}
{ }
{ }
using iterator = std::sregex_iterator;
iterator begin() { return *this; }
iterator end() { return iterator(); }
};
using iterator = std::sregex_iterator;
iterator begin() const { return *this; }
iterator end() const { return iterator(); }
};
/** encapsulated regex buildable from string */
class Matcher
{
std::optional<regex> pattern_;
public:
Matcher() = default;
Matcher(string const& regexDefinition)
: pattern_{regexDefinition.empty()? std::nullopt
: std::make_optional<regex>(regexDefinition, regex::optimize)}
{ }
// standard copy acceptable
explicit operator bool() const
{
return bool(pattern_);
}
bool matchesWithin(string probe) const
{
return pattern_? std::regex_search(probe, *pattern_)
: true; // undefined pattern matches everything
}
};
}//(End)namespace util
#endif /*TESTRUNNER_UTIL_REGEX_HPP_*/
} // namespace util
#endif/*LIB_STAT_REGEX_H*/

View file

@ -1,21 +1,24 @@
/*
* statistic - helpers for generic statistics calculations
*
* Copyright 2021, Hermann Vosseler <Ichthyostega@web.de>
*
* This file is part of the Yoshimi-Testsuite, which is free software:
* you can redistribute and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* Yoshimi-Testsuite is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with yoshimi. If not, see <http://www.gnu.org/licenses/>.
***************************************************************/
STATISTIC.hpp - helpers for generic statistics calculations
Copyright (C) Lumiera.org
2022, Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/** @file statistic.cpp
@ -28,215 +31,249 @@
#ifndef TESTRUNNER_UTIL_STATISTIC_HPP_
#define TESTRUNNER_UTIL_STATISTIC_HPP_
#ifndef LIB_STAT_STATISTIC_H
#define LIB_STAT_STATISTIC_H
#include "util/error.hpp"
#include "util/nocopy.hpp"
#include "util/format.hpp"
#include "util/utils.hpp"
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "lib/format-string.hpp"
#include "lib/util.hpp"
#include <utility>
#include <vector>
#include <array>
#include <tuple>
#include <cmath>
namespace util {
namespace lib {
namespace stat{
using std::fabs;
using std::array;
using std::tuple;
using std::make_tuple;
namespace error = lumiera::error;
using VecD = std::vector<double>;
using std::fabs;
using std::array;
using std::tuple;
using std::make_tuple;
using std::forward;
using std::move;
using util::min;
using util::max;
using util::isnil;
using util::_Fmt;
/** helper to unpack a std::tuple into a homogeneous std::array */
template<typename TUP>
constexpr auto array_from_tuple(TUP&& tuple)
{
constexpr auto makeArray = [](auto&& ... x){ return std::array{std::forward<decltype(x)>(x) ... }; };
return std::apply(makeArray, std::forward<TUP>(tuple));
}
using VecD = std::vector<double>;
template<size_t places>
inline double round(double val)
{
constexpr double shiftFac = pow(10.0,places);
return std::round(val * shiftFac)/shiftFac;
}
/** helper to unpack a std::tuple into a homogeneous std::array */
template<typename TUP>
constexpr auto
array_from_tuple (TUP&& tuple)
{
constexpr auto makeArray = [](auto&& ... x)
{
return std::array{forward<decltype(x)> (x) ...};
};
return std::apply (makeArray, forward<TUP> (tuple));
}
template<size_t places>
inline double
round (double val)
{
constexpr double shift{pow(10.0, places)};
return std::round(val*shift) / shift;
}
/**
* Read-only view into a segment within a sequence of data
* @tparam D value type of the data series
* @remark simplistic workaround since we don't support C++20 yet
* @todo replace by const std::span
*/
template<typename D>
class DataSpan
/**
* Read-only view into a segment within a sequence of data
* @tparam D value type of the data series
* @remark simplistic workaround since we don't support C++20 yet
* @todo replace by const std::span
*/
template<typename D>
class DataSpan
: util::Cloneable
{
const D* const b_{nullptr};
const D* const e_{nullptr};
{
const D* const b_{nullptr};
const D* const e_{nullptr};
public:
DataSpan() = default;
DataSpan(D const& begin, D const& end)
public:
DataSpan() = default;
DataSpan (D const& begin, D const& end)
: b_{&begin}
, e_{&end}
{ if (e_ < b_) throw error::Invalid("End point before begin."); }
{
if (e_ < b_)
throw error::Invalid{"End point before begin."};
}
template<class CON>
DataSpan(CON const& container)
template<class CON>
DataSpan (CON const& container)
: DataSpan{*std::begin(container), *std::end(container)}
{ }
{ }
using iterator = const D*;
using iterator = const D*;
size_t size() const { return e_ - b_; }
bool empty() const { return b_ == e_; }
size_t size() const { return e_ - b_; }
bool empty() const { return b_ == e_;}
iterator begin() const { return b_; }
iterator end() const { return e_; }
iterator begin() const { return b_; }
iterator end() const { return e_; }
D const& operator[](size_t i) const { return b_ + i; }
D const& at(size_t i) const
{
if (i >= size()) throw error::Invalid("Index "+str(i)+" beyond size="+str(size()));
return this->operator[](i);
}
};
D const& operator[](size_t i) const { return b_ + i; }
D const& at(size_t i) const
{
if (i >= size())
throw error::Invalid{_Fmt{"Index %d beyond size=%d"}
% i % size()};
return this->operator[](i);
}
};
/** summation of variances, for error propagation: √Σe² */
template<typename... NUMS>
inline double errorSum(NUMS ...vals)
{
/** summation of variances, for error propagation: √Σe² */
template<typename... NUMS>
inline double
errorSum (NUMS ...vals)
{
auto sqr = [](auto val){ return val*val; };
return sqrt((sqr(vals)+ ... + 0.0));
}
}
template<typename D>
inline double average(DataSpan<D> const& data)
{
template<typename D>
inline double
average (DataSpan<D> const& data)
{
if (isnil(data)) return 0.0;
double sum = 0.0;
for (auto val : data)
sum += val;
sum += val;
return sum / data.size();
}
}
template<typename D>
inline double sdev(DataSpan<D> const& data, D mean)
{
template<typename D>
inline double
sdev (DataSpan<D> const& data, D mean)
{
if (isnil(data)) return 0.0;
double sdev = 0.0;
for (auto val : data)
{
{
D offset = val - mean;
sdev += offset*offset;
}
}
size_t n = data.size();
sdev /= n<2? 1: n-1;
return sqrt(sdev);
}
return sqrt (sdev);
}
inline double sdev(VecD const& data, double mean)
{ return sdev(DataSpan<double>{data}, mean); }
inline double
sdev (VecD const& data, double mean)
{
return sdev(DataSpan<double>{data}, mean);
}
inline DataSpan<double> lastN(VecD const& data, size_t n)
{
n = std::min(n, data.size());
inline DataSpan<double>
lastN (VecD const& data, size_t n)
{
n = min (n, data.size());
size_t oldest = data.size() - n;
return DataSpan<double>{data[oldest], *data.end()};
}
}
inline double averageLastN(VecD const& data, size_t n)
{
return average(lastN(data,n));
}
inline double
averageLastN (VecD const& data, size_t n)
{
return average (lastN (data,n));
}
inline double sdevLastN(VecD const& data, size_t n, double mean)
{
return sdev(lastN(data,n), mean);
}
inline double
sdevLastN (VecD const& data, size_t n, double mean)
{
return sdev (lastN (data,n), mean);
}
/** "building blocks" for mean, variance and covariance of time series data */
template<typename D>
inline auto computeStatSums(DataSpan<D> const& series)
{
/** "building blocks" for mean, variance and covariance of time series data */
template<typename D>
inline auto
computeStatSums (DataSpan<D> const& series)
{
double ysum = 0.0;
double yysum = 0.0;
double xysum = 0.0;
size_t x = 0;
for (auto& y : series)
{
{
ysum += y;
yysum += y*y;
xysum += x*y;
++x;
}
return make_tuple(ysum,yysum, xysum);
}
}
return make_tuple (ysum,yysum, xysum);
}
/**
* Single data point used for linear regression.
* Simple case: single predictor variable (x).
* @remark including a weight factor
*/
struct RegressionPoint
{
double x;
double y;
double w;
};
/**
* Single data point used for linear regression.
* Simple case: single predictor variable (x).
* @remark including a weight factor
*/
struct RegressionPoint
{
double x;
double y;
double w;
};
using RegressionData = std::vector<RegressionPoint>;
using RegressionData = std::vector<RegressionPoint>;
/** "building blocks" for weighted mean, weighted variance and covariance */
inline auto computeWeightedStatSums(DataSpan<RegressionPoint> const& points)
{
/** "building blocks" for weighted mean, weighted variance and covariance */
inline auto
computeWeightedStatSums (DataSpan<RegressionPoint> const& points)
{
std::array<double,6> sums;
sums.fill(0.0);
auto& [wsum, wxsum, wysum, wxxsum, wyysum, wxysum] = sums;
for (auto& p : points)
{
{
wsum += p.w;
wxsum += p.w * p.x;
wysum += p.w * p.y;
wxxsum += p.w * p.x*p.x;
wyysum += p.w * p.y*p.y;
wxysum += p.w * p.x*p.y;
}
}
return sums;
}
}
/**
* Compute simple linear regression with a single predictor variable (x).
* @param points 2D data to fit the linear model with, including weights.
* @return the computed linear model `b + a·x`, and the resulting fit
* - socket (constant offset `b`)
* - gradient (linear factor `a`)
* - a vector with a predicted `y` value for each `x` value
* - a vector with the error, i.e `Δ = y - y_predicted`
* - correlation between x and y values
* - maximum absolute delta
* - delta standard deviation
*/
inline auto computeLinearRegression(DataSpan<RegressionPoint> const& points)
{
/**
* Compute simple linear regression with a single predictor variable (x).
* @param points 2D data to fit the linear model with, including weights.
* @return the computed linear model `b + a·x`, and the resulting fit
* - socket (constant offset `b`)
* - gradient (linear factor `a`)
* - a vector with a predicted `y` value for each `x` value
* - a vector with the error, i.e `Δ = y - y_predicted`
* - correlation between x and y values
* - maximum absolute delta
* - delta standard deviation
*/
inline auto
computeLinearRegression (DataSpan<RegressionPoint> const& points)
{
auto [wsum, wxsum, wysum, wxxsum, wyysum, wxysum] = computeWeightedStatSums(points);
double xm = wxsum / wsum; // weighted mean x = 1/Σw · Σwx
@ -259,42 +296,46 @@ inline auto computeLinearRegression(DataSpan<RegressionPoint> const& points)
double maxDelta = 0.0;
double variance = 0.0;
for (auto& p : points)
{
{
double y_pred = socket + gradient * p.x;
double delta = p.y - y_pred;
predicted.push_back(y_pred);
deltas.push_back(delta);
maxDelta = std::max(maxDelta, fabs(delta));
predicted.push_back (y_pred);
deltas.push_back (delta);
maxDelta = max (maxDelta, fabs(delta));
variance += p.w * delta*delta;
}
}
variance /= wsum * (n<=2? 1 : (n-2)/double(n)); // N-2 because it's an estimation,
// based on 2 other estimated values (socket,gradient)
return make_tuple(socket,gradient
,move(predicted)
,move(deltas)
,correlation
,maxDelta
,sqrt(variance)
);
}
return make_tuple (socket,gradient
,move(predicted)
,move(deltas)
,correlation
,maxDelta
,sqrt(variance)
);
}
inline auto computeLinearRegression(RegressionData const& points)
{ return computeLinearRegression(DataSpan<RegressionPoint>{points}); }
inline auto
computeLinearRegression (RegressionData const& points)
{
return computeLinearRegression(DataSpan<RegressionPoint>{points});
}
/**
* Compute linear regression over a time series with zero-based indices.
* @remark using the indices as x-values, the calculations for a regression line
* can be simplified, using the known closed formula for a sum of integers,
* shifting the indices to 0n-1 (leaving out the 0 and 0² term)
* - `1++n = n·(n+1)/2`
* - `1++n² = n·(n+1)·(2n+1)/6`
* @return `(socket,gradient)` to describe the regression line y = socket + gradient · i
*/
template<typename D>
inline auto computeTimeSeriesLinearRegression(DataSpan<D> const& series)
{
/**
* Compute linear regression over a time series with zero-based indices.
* @remark using the indices as x-values, the calculations for a regression line
* can be simplified, using the known closed formula for a sum of integers,
* shifting the indices to 0n-1 (leaving out the 0 and 0² term)
* - `1++n = n·(n+1)/2`
* - `1++n² = n·(n+1)·(2n+1)/6`
* @return `(socket,gradient)` to describe the regression line y = socket + gradient · i
*/
template<typename D>
inline auto
computeTimeSeriesLinearRegression (DataSpan<D> const& series)
{
if (series.size() < 2) return make_tuple(0.0,0.0,0.0);
auto [ysum,yysum, xysum] = computeStatSums(series);
@ -304,7 +345,7 @@ inline auto computeTimeSeriesLinearRegression(DataSpan<D> const& series)
double ym = ysum / n; // mean y
double varx = (n-1)*(n+1)/12.0; // variance of zero-based indices Σ(i-im)² / n
double vary = yysum/n - ym*ym; // variance of data values Σ(y-ym)² / n
double cova = xysum - ysum *(n-1)/2; // Time series Covariance = Σ(i-im)(y-ym) = Σiy + im·ym·n - ym·Σi - im·Σy; use n*ym = Σy
double cova = xysum - ysum *(n-1)/2; // Time series Covariance = Σ(i-im)(y-ym) = Σiy + im·ym·n - ym·Σi - im·Σy; use n·ym ≙ Σy
// Linear Regression minimising σ²
double gradient = cova / (n*varx); // Gradient = Correlation · σy / σx ; σ = √Variance; Correlation = Covariance /(√Σx √Σy)
@ -312,13 +353,14 @@ inline auto computeTimeSeriesLinearRegression(DataSpan<D> const& series)
// Correlation (Pearson's r)
double correlation = yysum==0.0? 1.0 : gradient * sqrt(varx/vary);
return make_tuple(socket,gradient,correlation);
}
return make_tuple (socket,gradient,correlation);
}
inline auto computeTimeSeriesLinearRegression(VecD const& series)
{ return computeTimeSeriesLinearRegression(DataSpan<double>{series}); }
inline auto
computeTimeSeriesLinearRegression (VecD const& series)
{
return computeTimeSeriesLinearRegression(DataSpan<double>{series});
}
}//(End)namespace util
#endif /*TESTRUNNER_UTIL_STATISTIC_HPP_*/
}} // namespace lib::stat
#endif /*LIB_STAT_STATISTIC_H*/

View file

@ -1,68 +0,0 @@
/*
* utils - collection of general purpose helpers and tools
*
* Copyright 2021, Hermann Vosseler <Ichthyostega@web.de>
*
* This file is part of the Yoshimi-Testsuite, which is free software:
* you can redistribute and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* Yoshimi-Testsuite is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with yoshimi. If not, see <http://www.gnu.org/licenses/>.
***************************************************************/
/** @file utils.cpp
** Implementation details for some of the generic utils.
**
** @todo WIP as of 7/21
**
*/
#include "util/utils.hpp"
#include "util/error.hpp"
#include <regex>
using std::regex;
namespace util {
namespace {
const regex TRUE_TOKENS { "\\s*(true|yes|on|1|\\+)\\s*", regex::icase | regex::optimize };
const regex FALSE_TOKENS{ "\\s*(false|no|off|0|\\-)\\s*", regex::icase | regex::optimize };
const regex TRIMMER{"\\s*(.*?)\\s*"};
}
bool boolVal(string const& textForm)
{
if (regex_match(textForm, TRUE_TOKENS))
return true;
if (regex_match(textForm, FALSE_TOKENS))
return false;
throw error::Invalid{"String '"+textForm+"' can not be interpreted as bool value" };
}
bool isYes (string const& textForm) noexcept
{
return regex_match (textForm, TRUE_TOKENS);
}
string trimmed(string text)
{
std::smatch mat;
regex_match(text, mat, TRIMMER);
return mat[1];
}
}//(End)namespace util

View file

@ -1,234 +0,0 @@
/*
* utils - collection of general purpose helpers and tools
*
* Copyright 2021, Hermann Vosseler <Ichthyostega@web.de>
*
* This file is part of the Yoshimi-Testsuite, which is free software:
* you can redistribute and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* Yoshimi-Testsuite is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with yoshimi. If not, see <http://www.gnu.org/licenses/>.
***************************************************************/
/** @file utils.hpp
** Collection of helper functions and abbreviations used to simplify code.
** - \ref isnil(arg) checks if the argument is "empty"; argument can be a string or a container
** - some helper functions for working with strings (`startsWith`, `endsWith`, `removePrefix|Suffix`)
** - \ref trim(string) extracts the content without leading and trailing whitespace
** - \ref boolVal() and \ref isYes() interpret a string as boolean value
** - \ref contains() generic containment check for maps, strings and iterable containers
** - Macro \ref STRINGIFY
** @todo WIP as of 7/21
**
*/
#ifndef TESTRUNNER_UTIL_UTILS_HPP_
#define TESTRUNNER_UTIL_UTILS_HPP_
#include <algorithm>
#include <string>
#include <set>
using std::string;
using uint = unsigned int;
namespace util {
/* ======== generic empty check ========= */
/** a family of util functions providing a "no value whatsoever" test.
* Works on strings and all STL containers, includes NULL test for pointers
*/
template<class CONT>
inline bool isnil(const CONT &container)
{
return container.empty();
}
template<class CONT>
inline bool isnil(const CONT *pContainer)
{
return !pContainer or pContainer->empty();
}
template<class CONT>
inline bool isnil(CONT *pContainer)
{
return !pContainer or pContainer->empty();
}
inline bool isnil(const char *pCStr)
{
return !pCStr or !(*pCStr);
}
/** check if string starts with a given prefix */
inline bool startsWith(string const &str, string const &prefix)
{
return 0 == str.rfind(prefix, 0);
}
inline bool startsWith(string const &str, const char *prefix)
{
return 0 == str.rfind(prefix, 0);
}
/** check if string ends with the given suffix */
inline bool endsWith(string const &str, string const &suffix)
{
size_t l = suffix.length();
if (l > str.length())
return false;
size_t pos = str.length() - l;
return pos == str.find(suffix, pos);
}
inline bool endsWith(string const &str, const char *suffix)
{
return endsWith(str, string(suffix));
}
inline void removePrefix(string &str, string const &prefix)
{
if (not startsWith(str, prefix))
return;
str = str.substr(prefix.length());
}
inline void removeSuffix(string &str, string const &suffix)
{
if (not endsWith(str, suffix))
return;
str.resize(str.length() - suffix.length());
}
inline string replace(string src, string toFind, string replacement)
{
for (size_t pos = src.find(toFind, 0);
pos != string::npos && toFind.size();
pos = src.find(toFind, pos+replacement.size())
)
src.replace(pos, toFind.size(), replacement);
return src;
}
/** shortcut for containment test on a map */
template<typename MAP>
inline bool contains(MAP &map, typename MAP::key_type const &key)
{
return map.find(key) != map.end();
}
/** shortcut for set value containment test */
template<typename T>
inline bool contains(std::set<T> const &set, T const &val)
{
return set.end() != set.find(val);
}
/** shortcut for string value containment test */
template<typename T>
inline bool contains(std::string const &str, const T &val)
{
return str.find(val) != std::string::npos;
}
/** shortcut for brute-force containment test
* in any sequential container */
template<typename SEQ>
inline bool contains(SEQ const &cont, typename SEQ::const_reference val)
{
typename SEQ::const_iterator begin = cont.begin();
typename SEQ::const_iterator end = cont.end();
return end != std::find(begin, end, val);
}
/** @internal helper type for #backwards */
template <typename ITA>
struct ReverseIterationAdapter { ITA& iterable; };
template <typename ITA>
auto begin (ReverseIterationAdapter<ITA> adapt)
{
return std::rbegin(adapt.iterable);
}
template <typename ITA>
auto end (ReverseIterationAdapter<ITA> adapt)
{
return std::rend(adapt.iterable);
}
/**
* Adapter to iterate backwards in a "foreach" loop.
* @tparam ITA a STL compatible container with back iteration capability.
* @remark solution based on the [Stackoverflow] from 2015 by [Prikso NAI].
*
* [Stackoverflow]: https://stackoverflow.com/a/28139075
* [Prikso NAI]: https://stackoverflow.com/users/3970469/prikso-nai
*/
template <typename ITA>
inline ReverseIterationAdapter<ITA>
backwards (ITA&& iterable)
{
return { iterable };
}
/**
* Shortcut for casting away `const`.
* @warning Use with care. Can be very handy to simplify defining
* const and non-const variants of member functions though.
*/
template<class OBJ>
inline OBJ* unConst (const OBJ* o)
{
return const_cast<OBJ*> (o);
}
template<class OBJ>
inline OBJ& unConst (OBJ const& ro)
{
return const_cast<OBJ&> (ro);
}
/** @return content without leading or trailing whitespace */
string trimmed(string);
/** interpret the given text as boolean value
* @throws error::Invalid when the text is not any valid bool token
* @remark allowed are `true false yes no on off 1 0 + -` in upper and lower case
*/
bool boolVal(string const& textForm);
/** evaluate the given text form as boolean value for `true`
* @note other than (\ref boolVal), this function treats _everything else_ as `false`
*/
bool isYes (string const& textForm) noexcept;
} // namespace util
/** this macro wraps its parameter into a cstring literal */
#define STRINGIFY(TOKEN) __STRNGFY(TOKEN)
#define __STRNGFY(TOKEN) #TOKEN
#endif /*TESTRUNNER_UTIL_UTILS_HPP_*/

View file

@ -111771,6 +111771,106 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node CREATED="1708744009942" ID="ID_1661435132" MODIFIED="1708744019908" TEXT="sicherstellen da&#xdf; die Grenzpunkte mit dabei sind"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710079791943" ID="ID_1402055509" MODIFIED="1710080437833" TEXT="Integration">
<linktarget COLOR="#9d4168" DESTINATION="ID_1402055509" ENDARROW="Default" ENDINCLINATION="-761;122;" ID="Arrow_ID_397225736" SOURCE="ID_1781115298" STARTARROW="None" STARTINCLINATION="-165;-9;"/>
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1710079800390" ID="ID_746814828" MODIFIED="1710124936401" TEXT="meine Statistik- und CSV Hilfsmittel von Yoshimi-Test einbringen">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1710085462347" ID="ID_963243204" MODIFIED="1710114559233" TEXT="zusammenh&#xe4;ngenden Code-Cluster identifizieren">
<icon BUILTIN="button_ok"/>
<node CREATED="1710085659317" ID="ID_1596614133" MODIFIED="1710087280098" TEXT="statistic.hpp"/>
<node CREATED="1710085720690" ID="ID_1192903835" MODIFIED="1710087283601" TEXT="data.hpp"/>
<node CREATED="1710085706676" ID="ID_1438550945" MODIFIED="1710085719148" TEXT="csv.hpp"/>
<node CREATED="1710085711443" ID="ID_661927366" MODIFIED="1710085717174" TEXT="file.hpp"/>
<node CREATED="1710087255899" ID="ID_1405213598" MODIFIED="1710087276129" TEXT="regex.hpp"/>
</node>
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#338800" CREATED="1710085818861" ID="ID_311148994" MODIFIED="1710114625741" TEXT="Historie extrahieren">
<icon BUILTIN="button_ok"/>
<node BACKGROUND_COLOR="#ccb59b" COLOR="#6e2a38" CREATED="1710089706576" ID="ID_902559893" MODIFIED="1710089727345" TEXT="eingesch&#xe4;tzt als Einmal-Aktion">
<font ITALIC="true" NAME="SansSerif" SIZE="14"/>
<icon BUILTIN="yes"/>
<node CREATED="1710089730179" ID="ID_597450586" MODIFIED="1710089743699" TEXT="kann daher einmalig eine vor-gefilterte Historie konstruieren"/>
<node CREATED="1710089754973" ID="ID_864520632" MODIFIED="1710089845717" TEXT="mache noch auf dem separaten Branch einen Vorbereitungs-Commit"/>
<node CREATED="1710089859192" ID="ID_449981342" MODIFIED="1710089932978" TEXT="danch per git subtree add einbringen"/>
<node CREATED="1710089895487" ID="ID_1694612812" MODIFIED="1710089925698" TEXT="eventuelle sp&#xe4;tere &#xc4;nderungen m&#xfc;ssen dann manuell per merge/rebase nachgezogen werden"/>
</node>
<node COLOR="#338800" CREATED="1710085830281" ID="ID_1502487564" MODIFIED="1710114568091" TEXT="Git filter-branch">
<icon BUILTIN="button_ok"/>
<node CREATED="1710090239621" ID="ID_1765645982" MODIFIED="1710090268743" TEXT="das Yoshimi-test - Repo als remote vorr&#xfc;bergehend ankoppeln"/>
<node CREATED="1710090274604" ID="ID_1300808535" MODIFIED="1710090290818" TEXT="einen git filter-branch im Lumiera-Repo lokal ausf&#xfc;hren"/>
</node>
<node COLOR="#338800" CREATED="1710086220247" ID="ID_1505106911" MODIFIED="1710114587320" TEXT="Git-Historien zusammenkoppeln">
<icon BUILTIN="button_ok"/>
<node COLOR="#435e98" CREATED="1710114589130" ID="ID_1762940850" MODIFIED="1710114618631" TEXT="ours-merge"/>
<node COLOR="#435e98" CREATED="1710114593043" ID="ID_1270108404" MODIFIED="1710114618631" TEXT="git read-tree"/>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1710085479914" ID="ID_1739991591" MODIFIED="1710114641924" TEXT="minimale Integration in die Lumiera-Codebase">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1710085492857" ID="ID_4363413" MODIFIED="1710124911666" TEXT="Lumiera-GNU-Style">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1710085504127" ID="ID_1115022840" MODIFIED="1710124914512" TEXT="Namespace lib::stat">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1710085513597" ID="ID_1623287366" MODIFIED="1710124916786" TEXT="Fehler durch Lumiera-Errors ausdr&#xfc;cken">
<icon BUILTIN="button_ok"/>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1710085528964" ID="ID_197737968" MODIFIED="1710124923723" TEXT="Lumiera&apos;s Format-Funktionen verwenden">
<icon BUILTIN="pencil"/>
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1710114655354" ID="ID_685893002" MODIFIED="1710114679967" TEXT="zu kl&#xe4;ren: vollst&#xe4;ndige floatingpoint-Repr&#xe4;sentation?">
<icon BUILTIN="help"/>
<node CREATED="1710114695652" ID="ID_323709862" MODIFIED="1710114697384" TEXT="std::numeric_limits&lt;VAL&gt;::digits10"/>
<node CREATED="1710114699873" ID="ID_496274436" MODIFIED="1710114711248" TEXT="util::toString sollte lexical_cast verwenden...">
<node CREATED="1710114712306" ID="ID_44388315" MODIFIED="1710114716318" TEXT="pr&#xfc;fen da&#xdf; dem so ist"/>
<node CREATED="1710114717256" ID="ID_894833593" MODIFIED="1710114727188" TEXT="herausfinden was lexical-cast macht"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710114730104" ID="ID_1025510498" MODIFIED="1710114735134" TEXT="empirisch vergleichen">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710115014482" ID="ID_1500611682" MODIFIED="1710115023467" TEXT="kl&#xe4;ren: bool-Format">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710085571918" ID="ID_1847964804" MODIFIED="1710085586443" TEXT="per Unit-Test dokumentieren">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710079820663" ID="ID_346209836" MODIFIED="1710079838390" TEXT="ein gnuplot-Skript generieren">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710079859600" ID="ID_1865864348" MODIFIED="1710079870844" TEXT="Skript aufbauen">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710079895431" ID="ID_129580336" MODIFIED="1710079910502" TEXT="inline-CSV lesen">
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710079911349" ID="ID_87181546" MODIFIED="1710079915901" TEXT="Diagramm zeichnen">
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710079916553" ID="ID_1466798395" MODIFIED="1710079922053" TEXT="Legende hinzuf&#xfc;gen">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710079848190" ID="ID_227233521" MODIFIED="1710079870844" TEXT="Darstellung ausarbeiten">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710079926851" ID="ID_1457237228" MODIFIED="1710079939594" TEXT="Scatter-Plot">
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710079932704" ID="ID_1073652291" MODIFIED="1710079939594" TEXT="Regressionslinie">
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710079942505" ID="ID_1703688749" MODIFIED="1710080060314" TEXT="Darstellung f&#xfc;r sekund&#xe4;re Werte">
<icon BUILTIN="flag-yellow"/>
<node CREATED="1710080061651" ID="ID_1708271153" MODIFIED="1710080077627" TEXT="&#x2205; concurrency"/>
<node CREATED="1710080078359" ID="ID_1753709562" MODIFIED="1710080082366" TEXT="&#x2205; job time"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710080104427" ID="ID_1362230214" MODIFIED="1710080121358" TEXT="sollte erweiterbar sein">
<icon BUILTIN="yes"/>
</node>
</node>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1708744078861" ID="ID_1023448351" MODIFIED="1708744085468" TEXT="Integration / Untersuchungen">
<icon BUILTIN="pencil"/>
@ -111913,8 +112013,8 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<icon BUILTIN="closed"/>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1709917304689" ID="ID_1675166139" MODIFIED="1709918087976" TEXT="Setup-2">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1709917304689" ID="ID_1675166139" MODIFIED="1710080405027" TEXT="Setup-2">
<icon BUILTIN="button_ok"/>
<node CREATED="1709917309616" ID="ID_790939741" MODIFIED="1709917341840" TEXT="&#x27f9; brauche also doch eine spezielle Regel">
<node CREATED="1709917609861" ID="ID_1607140185" MODIFIED="1709917789356" TEXT="jede Node prunen...">
<icon BUILTIN="idea"/>
@ -111983,9 +112083,36 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node CREATED="1709917935300" ID="ID_1144264892" MODIFIED="1709917943590" TEXT="Ergebnisse">
<node CREATED="1709917946410" ID="ID_1422608617" MODIFIED="1709917968051" TEXT="eine lineare Punktwolke mit ein paar &#xbb;outliern&#xab; oberhalb / unterhalb"/>
<node CREATED="1709917990340" ID="ID_178677368" MODIFIED="1709917997831" TEXT="Konstanter Offeset bei 2.5ms"/>
<node CREATED="1709918013177" ID="ID_115401143" MODIFIED="1709918025811" TEXT="Concurrency nur zwischen 1.5 und 2.5"/>
<node CREATED="1709918013177" ID="ID_115401143" MODIFIED="1709918025811" TEXT="Concurrency nur zwischen 1.5 und 2.5">
<node CREATED="1710080204406" HGAP="27" ID="ID_758385950" MODIFIED="1710080369175" TEXT="bei der kurzen Laufzeit nicht anders zu erwarten" VSHIFT="1">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
...da zur&#252;ckkehrende Worker die Vorfahrt haben, und die typsiche R&#252;stzeit ~100&#181;s betr&#228;gt, besteht noch hinreichend Wahrscheinlichkeit f&#252;r Contention
</p>
</body>
</html></richcontent>
<font NAME="SansSerif" SIZE="11"/>
</node>
</node>
<node CREATED="1709918027533" ID="ID_1460800650" MODIFIED="1709918071661" TEXT="&#x2205;t zeigt das &#xfc;bliche Verhalten (~700 ..900 &#xb5;s)"/>
</node>
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1710080150365" HGAP="7" ID="ID_704993753" MODIFIED="1710080186753" VSHIFT="21">
<richcontent TYPE="NODE"><html>
<head/>
<body>
<p>
<u>Stand</u>: nun bereit f&#252;r eigentliche Messungen
</p>
</body>
</html></richcontent>
<icon BUILTIN="forward"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710080381906" ID="ID_1781115298" MODIFIED="1710080445458" TEXT="brauche einfache Darstellbarkeit der Ergebnisse">
<arrowlink COLOR="#9d4168" DESTINATION="ID_1402055509" ENDARROW="Default" ENDINCLINATION="-761;122;" ID="Arrow_ID_397225736" STARTARROW="None" STARTINCLINATION="-165;-9;"/>
<icon BUILTIN="flag-yellow"/>
</node>
</node>
</node>