• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

paulmthompson / WhiskerToolbox / 17920603410

22 Sep 2025 03:39PM UTC coverage: 71.97% (-0.05%) from 72.02%
17920603410

push

github

paulmthompson
all tests pass

277 of 288 new or added lines in 8 files covered. (96.18%)

520 existing lines in 35 files now uncovered.

40275 of 55961 relevant lines covered (71.97%)

1225.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.33
/src/DataManager/AnalogTimeSeries/Analog_Time_Series.cpp
1
#include "Analog_Time_Series.hpp"
2

3
#include <algorithm>
4
#include <cmath>// std::nan, std::sqrt
5
#include <iostream>
6
#include <numeric>// std::iota
7
#include <span>
8
#include <stdexcept>
9
#include <vector>
10

11
// ========== Constructors ==========
12

13
AnalogTimeSeries::AnalogTimeSeries()
19✔
14
    : _data(),
19✔
15
      _time_storage(DenseTimeRange(TimeFrameIndex(0), 0)) {}
38✔
16

17
AnalogTimeSeries::AnalogTimeSeries(std::map<int, float> analog_map)
78✔
18
    : _data(),
78✔
19
      _time_storage(DenseTimeRange(TimeFrameIndex(0), 0)) {
156✔
20
    setData(std::move(analog_map));
78✔
21
}
78✔
22

23
AnalogTimeSeries::AnalogTimeSeries(std::vector<float> analog_vector, std::vector<TimeFrameIndex> time_vector)
286✔
24
    : _data(),
286✔
25
      _time_storage(DenseTimeRange(TimeFrameIndex(0), 0)) {
572✔
26
    setData(std::move(analog_vector), std::move(time_vector));
286✔
27
}
286✔
28

29
AnalogTimeSeries::AnalogTimeSeries(std::vector<float> analog_vector, size_t num_samples)
150✔
30
    : _data(),
150✔
31
      _time_storage(DenseTimeRange(TimeFrameIndex(0), num_samples)) {
300✔
32
    if (analog_vector.size() != num_samples) {
150✔
UNCOV
33
        std::cerr << "Error: size of analog vector and number of samples are not the same!" << std::endl;
×
34
        return;
×
35
    }
36
    setData(std::move(analog_vector));
150✔
UNCOV
37
}
×
38

39
void AnalogTimeSeries::setData(std::vector<float> analog_vector) {
150✔
40
    _data = std::move(analog_vector);
150✔
41
    // Use dense time storage for consecutive indices starting from 0
42
    _time_storage = DenseTimeRange(TimeFrameIndex(0), _data.size());
150✔
43
}
150✔
44

45
void AnalogTimeSeries::setData(std::vector<float> analog_vector, std::vector<TimeFrameIndex> time_vector) {
286✔
46
    if (analog_vector.size() != time_vector.size()) {
286✔
UNCOV
47
        std::cerr << "Error: size of analog vector and time vector are not the same!" << std::endl;
×
48
        return;
×
49
    }
50

51
    _data = std::move(analog_vector);
286✔
52

53
    // Check if we can use dense storage (consecutive indices)
54
    bool is_dense = true;
286✔
55
    if (!time_vector.empty()) {
286✔
56
        TimeFrameIndex expected_value = time_vector[0];
282✔
57
        for (size_t i = 0; i < time_vector.size(); ++i) {
307,047✔
58
            if (time_vector[i] != expected_value + TimeFrameIndex(static_cast<int64_t>(i))) {
306,897✔
59
                is_dense = false;
132✔
60
                break;
132✔
61
            }
62
        }
63
    }
64

65
    if (is_dense && !time_vector.empty()) {
286✔
66
        _time_storage = DenseTimeRange(TimeFrameIndex(time_vector[0]), time_vector.size());
150✔
67
    } else {
68
        _time_storage = SparseTimeIndices(std::move(time_vector));
136✔
69
    }
70
}
71

72
void AnalogTimeSeries::setData(std::map<int, float> analog_map) {
78✔
73
    _data.clear();
78✔
74
    _data = std::vector<float>();
78✔
75
    auto time_storage = std::vector<TimeFrameIndex>();
78✔
76
    for (auto & [key, value]: analog_map) {
160✔
77
        time_storage.push_back(TimeFrameIndex(key));
82✔
78
        _data.push_back(value);
82✔
79
    }
80
    _time_storage = SparseTimeIndices(std::move(time_storage));
78✔
81
}
156✔
82

83
// ========== Overwriting Data ==========
84

85
void AnalogTimeSeries::overwriteAtTimeIndexes(std::vector<float> & analog_data, std::vector<TimeFrameIndex> & time_indices) {
4✔
86
    if (analog_data.size() != time_indices.size()) {
4✔
87
        std::cerr << "Analog data and time indices vectors must be the same size" << std::endl;
1✔
88
        return;
1✔
89
    }
90

91
    for (size_t i = 0; i < time_indices.size(); ++i) {
9✔
92
        // Find the DataArrayIndex that corresponds to this TimeFrameIndex
93
        std::optional<DataArrayIndex> data_index = findDataArrayIndexForTimeFrameIndex(time_indices[i]);
6✔
94

95
        // Only overwrite if we found a corresponding DataArrayIndex
96
        if (data_index.has_value()) {
6✔
97
            _data[data_index.value().getValue()] = analog_data[i];
4✔
98
        } else {
99
            std::cerr << "TimeFrameIndex " << time_indices[i].getValue() << " not found in time series" << std::endl;
2✔
100
        }
101
    }
102
}
103

104
void AnalogTimeSeries::overwriteAtDataArrayIndexes(std::vector<float> & analog_data, std::vector<DataArrayIndex> & data_indices) {
3✔
105
    if (analog_data.size() != data_indices.size()) {
3✔
106
        std::cerr << "Analog data and data indices vectors must be the same size" << std::endl;
1✔
107
        return;
1✔
108
    }
109

110
    for (size_t i = 0; i < data_indices.size(); ++i) {
6✔
111
        if (data_indices[i].getValue() < _data.size()) {
4✔
112
            _data[data_indices[i].getValue()] = analog_data[i];
2✔
113
        } else {
114
            std::cerr << "DataArrayIndex " << data_indices[i].getValue() << " is out of bounds (data size: " << _data.size() << ")" << std::endl;
2✔
115
        }
116
    }
117
}
118

119
// ========== Getting Data ==========
120

121
std::span<float const> AnalogTimeSeries::getDataInTimeFrameIndexRange(TimeFrameIndex start_time, TimeFrameIndex end_time) const {
1,118✔
122
    // Find the start and end indices using our boundary-finding methods
123
    auto start_index_opt = findDataArrayIndexGreaterOrEqual(start_time);
1,118✔
124
    auto end_index_opt = findDataArrayIndexLessOrEqual(end_time);
1,118✔
125

126
    // Check if both boundaries were found
127
    if (!start_index_opt.has_value() || !end_index_opt.has_value()) {
1,118✔
128
        // Return empty span if either boundary is not found
129
        return std::span<float const>();
32✔
130
    }
131

132
    size_t start_idx = start_index_opt.value().getValue();
1,086✔
133
    size_t end_idx = end_index_opt.value().getValue();
1,086✔
134

135
    // Validate that start <= end (should always be true if our logic is correct)
136
    if (start_idx > end_idx) {
1,086✔
137
        // Return empty span for invalid range
138
        return std::span<float const>();
51✔
139
    }
140

141
    // Calculate the size of the range (inclusive of both endpoints)
142
    size_t range_size = end_idx - start_idx + 1;
1,035✔
143

144
    // Return span from start_idx with range_size elements
145
    return std::span<float const>(_data.data() + start_idx, range_size);
1,035✔
146
}
147

148

149
[[nodiscard]] std::span<float const> AnalogTimeSeries::getDataInTimeFrameIndexRange(TimeFrameIndex start_time,
907✔
150
                                                                                    TimeFrameIndex end_time,
151
                                                                                    TimeFrame const * source_timeFrame,
152
                                                                                    TimeFrame const * analog_timeFrame) const {
153
    if (source_timeFrame == analog_timeFrame) {
907✔
154
        return getDataInTimeFrameIndexRange(start_time, end_time);
694✔
155
    }
156

157
    // If either timeframe is null, fall back to original behavior
158
    if (!source_timeFrame || !analog_timeFrame) {
213✔
UNCOV
159
        return getDataInTimeFrameIndexRange(start_time, end_time);
×
160
    }
161

162
    // Convert the time index from source timeframe to target timeframe
163
    // 1. Get the time value from the source timeframe
164
    auto start_time_value = source_timeFrame->getTimeAtIndex(start_time);
213✔
165
    auto end_time_value = source_timeFrame->getTimeAtIndex(end_time);
213✔
166

167
    // 2. Convert that time value to an index in the analog timeframe
168
    auto target_start_index = analog_timeFrame->getIndexAtTime(static_cast<float>(start_time_value), false);
213✔
169
    auto target_end_index = analog_timeFrame->getIndexAtTime(static_cast<float>(end_time_value));
213✔
170

171
    // 3. Use the converted indices to get the data in the target timeframe
172
    return getDataInTimeFrameIndexRange(target_start_index, target_end_index);
213✔
173
}
174

175

176
// ========== TimeFrame Support ==========
177

178
std::optional<DataArrayIndex> AnalogTimeSeries::findDataArrayIndexForTimeFrameIndex(TimeFrameIndex time_index) const {
48✔
179
    return std::visit([time_index](auto const & time_storage) -> std::optional<DataArrayIndex> {
144✔
180
        if constexpr (std::is_same_v<std::decay_t<decltype(time_storage)>, DenseTimeRange>) {
181
            // For dense storage, check if the TimeFrameIndex falls within our range
182
            TimeFrameIndex start = time_storage.start_time_frame_index;
16✔
183
            auto end = TimeFrameIndex(start.getValue() + static_cast<int64_t>(time_storage.count) - 1);
16✔
184

185
            if (time_index >= start && time_index <= end) {
16✔
186
                // Calculate the DataArrayIndex
187
                auto offset = static_cast<size_t>(time_index.getValue() - start.getValue());
11✔
188
                return DataArrayIndex(offset);
11✔
189
            }
190
            return std::nullopt;
5✔
191
        } else {
192
            // For sparse storage, search for the TimeFrameIndex
193
            auto it = std::find(time_storage.time_frame_indices.begin(), time_storage.time_frame_indices.end(), time_index);
32✔
194
            if (it != time_storage.time_frame_indices.end()) {
32✔
195
                auto index = static_cast<size_t>(std::distance(time_storage.time_frame_indices.begin(), it));
22✔
196
                return DataArrayIndex(index);
22✔
197
            }
198
            return std::nullopt;
10✔
199
        }
200
    },
201
                      _time_storage);
96✔
202
}
203

204
std::optional<DataArrayIndex> AnalogTimeSeries::findDataArrayIndexGreaterOrEqual(TimeFrameIndex target_time) const {
1,245✔
205
    return std::visit([target_time](auto const & time_storage) -> std::optional<DataArrayIndex> {
3,735✔
206
        if constexpr (std::is_same_v<std::decay_t<decltype(time_storage)>, DenseTimeRange>) {
207
            // For dense storage, calculate the position if target_time falls within our range
208
            TimeFrameIndex start = time_storage.start_time_frame_index;
1,172✔
209
            auto end = TimeFrameIndex(start.getValue() + static_cast<int64_t>(time_storage.count) - 1);
1,172✔
210

211
            if (target_time <= end) {
1,172✔
212
                // Find the first index >= target_time
213
                TimeFrameIndex effective_start = (target_time >= start) ? target_time : start;
1,153✔
214
                auto offset = static_cast<size_t>(effective_start.getValue() - start.getValue());
1,153✔
215
                return DataArrayIndex(offset);
1,153✔
216
            }
217
            return std::nullopt;
19✔
218
        } else {
219
            // For sparse storage, use lower_bound to find first element >= target_time
220
            auto it = std::lower_bound(time_storage.time_frame_indices.begin(), time_storage.time_frame_indices.end(), target_time);
73✔
221
            if (it != time_storage.time_frame_indices.end()) {
73✔
222
                auto index = static_cast<size_t>(std::distance(time_storage.time_frame_indices.begin(), it));
62✔
223
                return DataArrayIndex(index);
62✔
224
            }
225
            return std::nullopt;
11✔
226
        }
227
    },
228
                      _time_storage);
2,490✔
229
}
230

231
std::optional<DataArrayIndex> AnalogTimeSeries::findDataArrayIndexLessOrEqual(TimeFrameIndex target_time) const {
1,245✔
232
    return std::visit([target_time](auto const & time_storage) -> std::optional<DataArrayIndex> {
3,735✔
233
        if constexpr (std::is_same_v<std::decay_t<decltype(time_storage)>, DenseTimeRange>) {
234
            // For dense storage, calculate the position if target_time falls within our range
235
            TimeFrameIndex start = time_storage.start_time_frame_index;
1,172✔
236
            auto end = TimeFrameIndex(start.getValue() + static_cast<int64_t>(time_storage.count) - 1);
1,172✔
237

238
            if (target_time >= start) {
1,172✔
239
                // Find the last index <= target_time
240
                TimeFrameIndex effective_end = (target_time <= end) ? target_time : end;
1,144✔
241
                auto offset = static_cast<size_t>(effective_end.getValue() - start.getValue());
1,144✔
242
                return DataArrayIndex(offset);
1,144✔
243
            }
244
            return std::nullopt;
28✔
245
        } else {
246
            // For sparse storage, use upper_bound to find first element > target_time, then step back
247
            auto it = std::upper_bound(time_storage.time_frame_indices.begin(), time_storage.time_frame_indices.end(), target_time);
73✔
248
            if (it != time_storage.time_frame_indices.begin()) {
73✔
249
                --it;// Step back to get the last element <= target_time
71✔
250
                auto index = static_cast<size_t>(std::distance(time_storage.time_frame_indices.begin(), it));
71✔
251
                return DataArrayIndex(index);
71✔
252
            }
253
            return std::nullopt;
2✔
254
        }
255
    },
256
                      _time_storage);
2,490✔
257
}
258

259
// ========== Time-Value Range Access Implementation ==========
260

261
AnalogTimeSeries::TimeValueRangeIterator::TimeValueRangeIterator(AnalogTimeSeries const * series, DataArrayIndex start_index, DataArrayIndex end_index, bool is_end)
11✔
262
    : _series(series),
11✔
263
      _current_index(is_end ? end_index : start_index),
11✔
264
      _end_index(end_index),
11✔
265
      _is_end(is_end) {
11✔
266
    if (!_is_end && _current_index.getValue() < _end_index.getValue()) {
11✔
267
        _updateCurrentPoint();
5✔
268
    }
269
}
11✔
270

271
AnalogTimeSeries::TimeValueRangeIterator::reference AnalogTimeSeries::TimeValueRangeIterator::operator*() const {
15✔
272
    if (_is_end || _current_index.getValue() >= _end_index.getValue()) {
15✔
UNCOV
273
        throw std::out_of_range("TimeValueRangeIterator: attempt to dereference end iterator");
×
274
    }
275
    _updateCurrentPoint();
15✔
276
    return _current_point;
15✔
277
}
278

279
AnalogTimeSeries::TimeValueRangeIterator::pointer AnalogTimeSeries::TimeValueRangeIterator::operator->() const {
4✔
280
    return &(operator*());
4✔
281
}
282

283
AnalogTimeSeries::TimeValueRangeIterator & AnalogTimeSeries::TimeValueRangeIterator::operator++() {
12✔
284
    if (_is_end || _current_index.getValue() >= _end_index.getValue()) {
12✔
UNCOV
285
        _is_end = true;
×
UNCOV
286
        return *this;
×
287
    }
288

289
    _current_index = DataArrayIndex(_current_index.getValue() + 1);
12✔
290

291
    if (_current_index.getValue() >= _end_index.getValue()) {
12✔
292
        _is_end = true;
5✔
293
    }
294

295
    return *this;
12✔
296
}
297

298
AnalogTimeSeries::TimeValueRangeIterator AnalogTimeSeries::TimeValueRangeIterator::operator++(int) {
1✔
299
    auto temp = *this;
1✔
300
    ++(*this);
1✔
301
    return temp;
1✔
302
}
303

304
bool AnalogTimeSeries::TimeValueRangeIterator::operator==(TimeValueRangeIterator const & other) const {
16✔
305
    return _series == other._series &&
16✔
306
           _current_index.getValue() == other._current_index.getValue() &&
32✔
307
           _is_end == other._is_end;
21✔
308
}
309

310
bool AnalogTimeSeries::TimeValueRangeIterator::operator!=(TimeValueRangeIterator const & other) const {
14✔
311
    return !(*this == other);
14✔
312
}
313

314
void AnalogTimeSeries::TimeValueRangeIterator::_updateCurrentPoint() const {
20✔
315
    if (_is_end || _current_index.getValue() >= _end_index.getValue()) {
20✔
UNCOV
316
        return;
×
317
    }
318

319
    _current_point = TimeValuePoint(
40✔
320
            _series->getTimeFrameIndexAtDataArrayIndex(_current_index),
20✔
321
            _series->getDataAtDataArrayIndex(_current_index));
20✔
322
}
323

324
AnalogTimeSeries::TimeValueRangeView::TimeValueRangeView(AnalogTimeSeries const * series, DataArrayIndex start_index, DataArrayIndex end_index)
6✔
325
    : _series(series),
6✔
326
      _start_index(start_index),
6✔
327
      _end_index(end_index) {}
6✔
328

329
AnalogTimeSeries::TimeValueRangeIterator AnalogTimeSeries::TimeValueRangeView::begin() const {
5✔
330
    return {_series, _start_index, _end_index, false};
5✔
331
}
332

333
AnalogTimeSeries::TimeValueRangeIterator AnalogTimeSeries::TimeValueRangeView::end() const {
6✔
334
    return {_series, _start_index, _end_index, true};
6✔
335
}
336

337
size_t AnalogTimeSeries::TimeValueRangeView::size() const {
4✔
338
    if (_start_index.getValue() >= _end_index.getValue()) {
4✔
339
        return 0;
2✔
340
    }
341
    return _end_index.getValue() - _start_index.getValue();
2✔
342
}
343

344
bool AnalogTimeSeries::TimeValueRangeView::empty() const {
2✔
345
    return size() == 0;
2✔
346
}
347

348
// Dense and Sparse time index iterators for TimeIndexRange
349
namespace {
350
// Dense time index iterator implementation
351
class DenseTimeIndexIterator : public AnalogTimeSeries::TimeIndexIterator {
352
public:
353
    DenseTimeIndexIterator(TimeFrameIndex start_time, DataArrayIndex current_offset, DataArrayIndex end_offset, bool is_end)
62✔
354
        : _start_time(start_time),
124✔
355
          _current_offset(current_offset),
62✔
356
          _end_offset(end_offset),
62✔
357
          _current_value(TimeFrameIndex(0)),
62✔
358
          _is_end(is_end) {
62✔
359
        if (!_is_end) {
62✔
360
            _current_value = TimeFrameIndex(_start_time.getValue() + static_cast<int64_t>(_current_offset.getValue()));
62✔
361
        }
362
    }
62✔
363

364
    reference operator*() const override {
23,158✔
365
        if (_is_end || _current_offset.getValue() >= _end_offset.getValue()) {
23,158✔
366
            throw std::out_of_range("DenseTimeIndexIterator: attempt to dereference end iterator");
×
367
        }
368
        return _current_value;
23,158✔
369
    }
370

371
    TimeIndexIterator & operator++() override {
7,759✔
372
        if (_is_end || _current_offset.getValue() >= _end_offset.getValue()) {
7,759✔
UNCOV
373
            _is_end = true;
×
UNCOV
374
            return *this;
×
375
        }
376

377
        _current_offset = DataArrayIndex(_current_offset.getValue() + 1);
7,759✔
378

379
        if (_current_offset.getValue() >= _end_offset.getValue()) {
7,759✔
380
            _is_end = true;
59✔
381
        } else {
382
            _current_value = TimeFrameIndex(_start_time.getValue() + static_cast<int64_t>(_current_offset.getValue()));
7,700✔
383
        }
384

385
        return *this;
7,759✔
386
    }
387

UNCOV
388
    bool operator==(TimeIndexIterator const & other) const override {
×
389
        auto const * other_dense = dynamic_cast<DenseTimeIndexIterator const *>(&other);
×
390
        if (!other_dense) return false;
×
391

UNCOV
392
        return _start_time.getValue() == other_dense->_start_time.getValue() &&
×
393
               _current_offset.getValue() == other_dense->_current_offset.getValue() &&
×
394
               _is_end == other_dense->_is_end;
×
395
    }
396

UNCOV
397
    bool operator!=(TimeIndexIterator const & other) const override {
×
UNCOV
398
        return !(*this == other);
×
399
    }
400

UNCOV
401
    std::unique_ptr<TimeIndexIterator> clone() const override {
×
UNCOV
402
        return std::make_unique<DenseTimeIndexIterator>(_start_time, _current_offset, _end_offset, _is_end);
×
403
    }
404

405
private:
406
    TimeFrameIndex _start_time;
407
    DataArrayIndex _current_offset;
408
    DataArrayIndex _end_offset;
409
    mutable TimeFrameIndex _current_value;
410
    bool _is_end;
411
};
412

413
// Sparse time index iterator implementation
414
class SparseTimeIndexIterator : public AnalogTimeSeries::TimeIndexIterator {
415
public:
416
    SparseTimeIndexIterator(std::vector<TimeFrameIndex> const * time_indices, DataArrayIndex current_index, DataArrayIndex end_index, bool is_end)
1✔
417
        : _time_indices(time_indices),
2✔
418
          _current_index(current_index),
1✔
419
          _end_index(end_index),
1✔
420
          _is_end(is_end) {}
1✔
421

422
    reference operator*() const override {
3✔
423
        if (_is_end || _current_index.getValue() >= _end_index.getValue()) {
3✔
UNCOV
424
            throw std::out_of_range("SparseTimeIndexIterator: attempt to dereference end iterator");
×
425
        }
426
        return (*_time_indices)[_current_index.getValue()];
3✔
427
    }
428

429
    TimeIndexIterator & operator++() override {
2✔
430
        if (_is_end || _current_index.getValue() >= _end_index.getValue()) {
2✔
UNCOV
431
            _is_end = true;
×
UNCOV
432
            return *this;
×
433
        }
434

435
        _current_index = DataArrayIndex(_current_index.getValue() + 1);
2✔
436

437
        if (_current_index.getValue() >= _end_index.getValue()) {
2✔
438
            _is_end = true;
×
439
        }
440

441
        return *this;
2✔
442
    }
443

UNCOV
444
    bool operator==(TimeIndexIterator const & other) const override {
×
UNCOV
445
        auto const * other_sparse = dynamic_cast<SparseTimeIndexIterator const *>(&other);
×
446
        if (!other_sparse) return false;
×
447

UNCOV
448
        return _time_indices == other_sparse->_time_indices &&
×
UNCOV
449
               _current_index.getValue() == other_sparse->_current_index.getValue() &&
×
UNCOV
450
               _is_end == other_sparse->_is_end;
×
451
    }
452

UNCOV
453
    bool operator!=(TimeIndexIterator const & other) const override {
×
UNCOV
454
        return !(*this == other);
×
455
    }
456

UNCOV
457
    [[nodiscard]] std::unique_ptr<TimeIndexIterator> clone() const override {
×
UNCOV
458
        return std::make_unique<SparseTimeIndexIterator>(_time_indices, _current_index, _end_index, _is_end);
×
459
    }
460

461
private:
462
    std::vector<TimeFrameIndex> const * _time_indices;
463
    DataArrayIndex _current_index;
464
    DataArrayIndex _end_index;
465
    bool _is_end;
466
};
467
}// namespace
468

469
AnalogTimeSeries::TimeIndexRange::TimeIndexRange(AnalogTimeSeries const * series, DataArrayIndex start_index, DataArrayIndex end_index)
103✔
470
    : _series(series),
103✔
471
      _start_index(start_index),
103✔
472
      _end_index(end_index) {}
103✔
473

474
std::unique_ptr<AnalogTimeSeries::TimeIndexIterator> AnalogTimeSeries::TimeIndexRange::begin() const {
63✔
475
    return std::visit([this](auto const & time_storage) -> std::unique_ptr<TimeIndexIterator> {
126✔
476
        if constexpr (std::is_same_v<std::decay_t<decltype(time_storage)>, DenseTimeRange>) {
477
            // Dense storage - create iterator based on start time and offset
478
            return std::make_unique<DenseTimeIndexIterator>(
479
                    time_storage.start_time_frame_index,
62✔
480
                    _start_index,
62✔
481
                    _end_index,
62✔
482
                    false);
62✔
483
        } else {
484
            // Sparse storage - create iterator with direct index access
485
            return std::make_unique<SparseTimeIndexIterator>(
486
                    &time_storage.time_frame_indices,
1✔
487
                    _start_index,
1✔
488
                    _end_index,
1✔
489
                    false);
2✔
490
        }
491
    },
492
                      _series->getTimeStorage());
126✔
493
}
494

UNCOV
495
std::unique_ptr<AnalogTimeSeries::TimeIndexIterator> AnalogTimeSeries::TimeIndexRange::end() const {
×
496
    return std::visit([this](auto const & time_storage) -> std::unique_ptr<TimeIndexIterator> {
×
497
        if constexpr (std::is_same_v<std::decay_t<decltype(time_storage)>, DenseTimeRange>) {
498
            // Dense storage - create end iterator
499
            return std::make_unique<DenseTimeIndexIterator>(
500
                    time_storage.start_time_frame_index,
×
UNCOV
501
                    _start_index,
×
502
                    _end_index,
×
UNCOV
503
                    true);
×
504
        } else {
505
            // Sparse storage - create end iterator
506
            return std::make_unique<SparseTimeIndexIterator>(
UNCOV
507
                    &time_storage.time_frame_indices,
×
UNCOV
508
                    _start_index,
×
UNCOV
509
                    _end_index,
×
UNCOV
510
                    true);
×
511
        }
512
    },
UNCOV
513
                      _series->getTimeStorage());
×
514
}
515

516
size_t AnalogTimeSeries::TimeIndexRange::size() const {
9✔
517
    if (_start_index.getValue() >= _end_index.getValue()) {
9✔
518
        return 0;
2✔
519
    }
520
    return _end_index.getValue() - _start_index.getValue();
7✔
521
}
522

523
bool AnalogTimeSeries::TimeIndexRange::empty() const {
3✔
524
    return size() == 0;
3✔
525
}
526

527
AnalogTimeSeries::TimeValueSpanPair::TimeValueSpanPair(std::span<float const> data_span, AnalogTimeSeries const * series, DataArrayIndex start_index, DataArrayIndex end_index)
103✔
528
    : values(data_span),
103✔
529
      time_indices(series, start_index, end_index) {}
103✔
530

531
AnalogTimeSeries::TimeValueRangeView AnalogTimeSeries::getTimeValueRangeInTimeFrameIndexRange(TimeFrameIndex start_time, TimeFrameIndex end_time) const {
6✔
532
    // Use existing boundary-finding logic
533
    auto start_index_opt = findDataArrayIndexGreaterOrEqual(start_time);
6✔
534
    auto end_index_opt = findDataArrayIndexLessOrEqual(end_time);
6✔
535

536
    // Handle cases where boundaries are not found
537
    if (!start_index_opt.has_value() || !end_index_opt.has_value()) {
6✔
538
        // Return empty range
539
        return {this, DataArrayIndex(0), DataArrayIndex(0)};
1✔
540
    }
541

542
    size_t start_idx = start_index_opt.value().getValue();
5✔
543
    size_t end_idx = end_index_opt.value().getValue();
5✔
544

545
    // Validate that start <= end
546
    if (start_idx > end_idx) {
5✔
547
        // Return empty range for invalid range
UNCOV
548
        return {this, DataArrayIndex(0), DataArrayIndex(0)};
×
549
    }
550

551
    // Create range view (end_idx + 1 because end is exclusive for the range)
552
    return {this, DataArrayIndex(start_idx), DataArrayIndex(end_idx + 1)};
5✔
553
}
554

555
AnalogTimeSeries::TimeValueSpanPair AnalogTimeSeries::getTimeValueSpanInTimeFrameIndexRange(TimeFrameIndex start_time, TimeFrameIndex end_time) const {
103✔
556
    // Use existing getDataInTimeFrameIndexRange for the span
557
    auto data_span = getDataInTimeFrameIndexRange(start_time, end_time);
103✔
558

559
    // Find the corresponding array indices for the time iterator
560
    auto start_index_opt = findDataArrayIndexGreaterOrEqual(start_time);
103✔
561
    auto end_index_opt = findDataArrayIndexLessOrEqual(end_time);
103✔
562

563
    // Handle cases where boundaries are not found
564
    if (!start_index_opt.has_value() || !end_index_opt.has_value()) {
103✔
565
        // Return empty span pair
566
        return {std::span<float const>(), this, DataArrayIndex(0), DataArrayIndex(0)};
19✔
567
    }
568

569
    size_t start_idx = start_index_opt.value().getValue();
84✔
570
    size_t end_idx = end_index_opt.value().getValue();
84✔
571

572
    // Validate that start <= end
573
    if (start_idx > end_idx) {
84✔
574
        // Return empty span pair for invalid range
UNCOV
575
        return {std::span<float const>(), this, DataArrayIndex(0), DataArrayIndex(0)};
×
576
    }
577

578
    // Create span pair (end_idx + 1 because end is exclusive for the range)
579
    return {data_span, this, DataArrayIndex(start_idx), DataArrayIndex(end_idx + 1)};
84✔
580
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc