File RiseSetArray.cpp¶
File List > astrea > trace > trace > risesets > RiseSetArray.cpp
Go to the documentation of this file
/*
* The GNU Lesser General Public License (LGPL)
*
* Copyright (c) 2025 Jay Iuliano
*
* This file is part of Astrea.
* Astrea is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* Astrea 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 Lesser General Public License for more details. You should
* have received a copy of the GNU General Public License along with Astrea. If not, see <https://www.gnu.org/licenses/>.
*/
#include <trace/risesets/RiseSetArray.hpp>
#include <limits>
#include <stdexcept>
#include <mp-units/math.h>
#include <mp-units/systems/iau.h>
#include <mp-units/systems/si.h>
using namespace mp_units;
using mp_units::non_si::day;
using mp_units::si::unit_symbols::s;
namespace astrea {
namespace trace {
RiseSetArray::RiseSetArray(const std::vector<Time>& risesets)
{
validate_risesets(risesets);
_risesets = risesets;
}
RiseSetArray::RiseSetArray(const RiseSetArray& other) :
_risesets(other._risesets)
{
}
RiseSetArray::RiseSetArray(RiseSetArray&& other) noexcept :
_risesets(std::move(other._risesets))
{
}
RiseSetArray& RiseSetArray::operator=(RiseSetArray&& other) noexcept
{
if (this != &other) { _risesets = std::move(other._risesets); }
return *this;
}
bool RiseSetArray::operator==(const RiseSetArray& other) const
{
if (this == &other) { return true; }
return _risesets == other._risesets;
}
RiseSetArray::iterator RiseSetArray::begin() { return _risesets.begin(); }
RiseSetArray::iterator RiseSetArray::end() { return _risesets.end(); }
RiseSetArray::const_iterator RiseSetArray::begin() const { return _risesets.begin(); }
RiseSetArray::const_iterator RiseSetArray::end() const { return _risesets.end(); }
RiseSetArray::const_iterator RiseSetArray::cbegin() const { return _risesets.cbegin(); }
RiseSetArray::const_iterator RiseSetArray::cend() const { return _risesets.cend(); }
RiseSetArray& RiseSetArray::operator=(const RiseSetArray& other) { return *this = RiseSetArray(other); }
RiseSetArray RiseSetArray::operator|(const RiseSetArray& other) const { return riseset_union(*this, other); }
RiseSetArray& RiseSetArray::operator|=(const RiseSetArray& other) { return *this = riseset_union(*this, other); }
RiseSetArray RiseSetArray::operator&(const RiseSetArray& other) const { return riseset_intersection(*this, other); }
RiseSetArray& RiseSetArray::operator&=(const RiseSetArray& other) { return *this = riseset_intersection(*this, other); }
RiseSetArray RiseSetArray::operator-(const RiseSetArray& other) const { return riseset_difference(*this, other); }
RiseSetArray& RiseSetArray::operator-=(const RiseSetArray& other)
{
*this = riseset_difference(*this, other);
return *this;
}
Time& RiseSetArray::operator[](const std::size_t& ind) { return _risesets[ind]; }
const Time& RiseSetArray::operator[](const std::size_t& ind) const { return _risesets[ind]; }
std::size_t RiseSetArray::size() const { return _risesets.size(); }
void RiseSetArray::validate_risesets(const std::vector<Time>& risesets) const
{
if (risesets.size() % 2) {
throw std::runtime_error("RiseSetArrays must be constructed from an even-sized list of values.");
}
for (std::size_t ii = 1; ii < risesets.size(); ++ii) {
validate_riseset(risesets[ii - 1], risesets[ii]);
}
}
void RiseSetArray::validate_riseset(const Time& rise, const Time& set) const
{
if (set < rise) { throw std::runtime_error("RiseSetArrays must be constructed from a list of ascending values."); }
else if (set == rise) {
throw std::runtime_error("RiseSetArrays must be constructed from a list of unique values.");
}
}
void RiseSetArray::append(const Time& rise, const Time& set)
{
if (!_risesets.empty() && rise < _risesets.back()) {
insert(rise, set);
return;
}
validate_riseset(rise, set);
_risesets.push_back(rise);
_risesets.push_back(set);
}
void RiseSetArray::prepend(const Time& rise, const Time& set)
{
if (!_risesets.empty() && set > _risesets[0]) {
insert(rise, set);
return;
}
validate_riseset(rise, set);
_risesets.insert(_risesets.begin(), set);
_risesets.insert(_risesets.begin(), rise);
}
void RiseSetArray::insert(const Time& rise, const Time& set)
{
validate_riseset(rise, set);
*this = (*this) | RiseSetArray({ rise, set });
}
std::vector<std::string> RiseSetArray::to_string_vector() const
{
std::vector<std::string> retval;
retval.reserve(_risesets.size());
for (const auto t : _risesets) {
retval.push_back(to_formatted_string(t));
}
return retval;
}
Time RiseSetArray::gap(const StatType& stat, const Unitless percentile) const
{
return calculate_statistic(stat, percentile, RiseSetMetric::GAP);
}
std::vector<Time> RiseSetArray::get_gap_times() const
{
std::vector<Time> gaps;
gaps.reserve(_risesets.size() / 2 - 1);
for (std::size_t ii = 1; ii < _risesets.size() - 1; ii += 2) {
gaps.push_back(_risesets[ii + 1] - _risesets[ii]);
}
return gaps;
}
Time RiseSetArray::access_time(const StatType& stat, const Unitless percentile) const
{
return calculate_statistic(stat, percentile, RiseSetMetric::ACCESS_TIME);
}
std::vector<Time> RiseSetArray::get_access_times() const
{
std::vector<Time> accesses;
accesses.reserve(_risesets.size() / 2);
for (std::size_t ii = 0; ii < _risesets.size() - 1; ii += 2) {
accesses.push_back(_risesets[ii + 1] - _risesets[ii]);
}
return accesses;
}
Time RiseSetArray::average_daily_vis_time() const
{
// good luck parsing this jumbled mess
Time avgDailyVisTime = 0.0 * s;
std::size_t ii = 0;
unsigned iDay = 0;
bool accessCrossesMidnight = false;
while (ii < _risesets.size() - 1) {
Time dailyVisTime = 0.0 * s;
for (; ii < _risesets.size() - 1; ii += 2) {
Time start = _risesets[ii];
if (accessCrossesMidnight) {
start = Time(days(iDay - 1));
accessCrossesMidnight = false;
}
Time end = _risesets[ii + 1];
if (end > Time(days(iDay))) {
end = Time(days(iDay));
accessCrossesMidnight = true;
}
dailyVisTime += end - start;
if (accessCrossesMidnight) { break; }
}
avgDailyVisTime += dailyVisTime;
++iDay;
}
return avgDailyVisTime / (iDay - 1);
}
Time RiseSetArray::mean_time_to_access() const
{
quantity mtta = 0.0 * s * s;
for (const auto& gap : get_gap_times()) {
mtta += gap * gap;
}
return sqrt(mtta) * 0.5;
}
Time RiseSetArray::calculate_statistic(const StatType& stat, const Unitless& percentile, const RiseSetMetric metric) const
{
// TODO: Is this a good default value? Is it necessarily meaningless or should it be negative or error?
Time retval = 0.0 * s;
// Gap measures space between accesses so we can just shift the starting index
// This value also happens to represent the difference in number of each metric type so we use it to shift
// the array sizes for each accordingly
std::size_t idxOffset = metric == RiseSetMetric::GAP ? 1 : 0;
// Empty or not enough risesets for any gap
if (_risesets.size() < 2 + idxOffset) { return retval; }
// Percentile calcs
if (stat == StatType::PCT) {
if (percentile < 0 * one || percentile > 1 * one) {
throw std::runtime_error("Percentile must be between 0 and 1.");
}
const double nMetricIntervals = static_cast<double>(_risesets.size() / 2 - idxOffset);
const std::size_t index = static_cast<std::size_t>(std::ceil(percentile.numerical_value_in(one) * nMetricIntervals)) - 1;
std::vector<Time> durations; // TODO: Update this so the vector is built in sorted order
durations.reserve(nMetricIntervals);
for (std::size_t ii = idxOffset; ii < _risesets.size() - 1; ii += 2) {
durations.push_back(_risesets[ii + 1] - _risesets[ii]);
}
std::sort(durations.begin(), durations.end());
return durations[index];
}
// Non-percentile calcs
if (stat == StatType::MIN) { retval = std::numeric_limits<double>::max() * s; }
for (std::size_t ii = idxOffset; ii < _risesets.size() - 1; ii += 2) {
const Time duration = _risesets[ii + 1] - _risesets[ii];
if (ii == idxOffset) {
retval = duration;
continue;
}
switch (stat) {
case (StatType::MIN): {
if (duration < retval) { retval = duration; }
break;
}
case (StatType::MAX): {
if (duration > retval) { retval = duration; }
break;
}
case (StatType::AVG): {
retval += duration;
break;
}
default: throw std::runtime_error("Unknown access time statistic requested.");
}
}
if (stat == StatType::AVG) {
const std::size_t nMetricIntervals = _risesets.size() / 2 - idxOffset;
retval /= nMetricIntervals;
}
return retval;
}
bool RiseSetArray::has_access(const Time& time) const
{
const auto lower = std::lower_bound(_risesets.begin(), _risesets.end(), time);
return (std::distance(_risesets.begin(), lower) % 2);
}
std::ostream& operator<<(std::ostream& os, const RiseSetArray& risesets)
{
const auto& values = risesets._risesets;
os << "[" << values[0];
for (std::size_t ii = 1; ii < values.size(); ++ii) {
os << ", " << values[ii];
}
os << "]";
return os;
}
} // namespace trace
} // namespace astrea