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

BlueBrain / libsonata / 5949210620

23 Aug 2023 08:58AM UTC coverage: 95.385% (-0.9%) from 96.3%
5949210620

push

github

web-flow
Create non-tuple Spike Report access (#285)

* Create python method for the spikes to return dict of numpy arrays

27 of 27 new or added lines in 1 file covered. (100.0%)

1798 of 1885 relevant lines covered (95.38%)

76.4 hits per line

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

91.64
/src/report_reader.cpp
1
#include <bbp/sonata/report_reader.h>
2
#include <fmt/format.h>
3

4
#include <algorithm>  // std::copy, std::find, std::lower_bound, std::upper_bound
5
#include <iterator>   // std::advance, std::next
6

7
constexpr double EPSILON = 1e-6;
8

9
H5::EnumType<bbp::sonata::SpikeReader::Population::Sorting> create_enum_sorting() {
16✔
10
    using bbp::sonata::SpikeReader;
11
    return H5::EnumType<SpikeReader::Population::Sorting>(
12
        {{"none", SpikeReader::Population::Sorting::none},
13
         {"by_id", SpikeReader::Population::Sorting::by_id},
14
         {"by_time", SpikeReader::Population::Sorting::by_time}});
64✔
15
}
16

17
HIGHFIVE_REGISTER_TYPE(bbp::sonata::SpikeReader::Population::Sorting, create_enum_sorting)
16✔
18

19
namespace {
20

21
using bbp::sonata::CompartmentID;
22
using bbp::sonata::ElementID;
23
using bbp::sonata::NodeID;
24
using bbp::sonata::Selection;
25
using bbp::sonata::Spike;
26
using bbp::sonata::Spikes;
27

28
void filterNodeIDUnsorted(Spikes& spikes, const Selection& node_ids) {
6✔
29
    const auto values = node_ids.flatten();
6✔
30
    const auto new_end =
31
        std::remove_if(spikes.begin(), spikes.end(), [&values](const Spike& spike) {
66✔
32
            return std::find(values.cbegin(), values.cend(), spike.first) == values.cend();
22✔
33
        });
6✔
34
    spikes.erase(new_end, spikes.end());
6✔
35
}
6✔
36

37
void filterNodeIDSorted(Spikes& spikes, const Selection& node_ids) {
2✔
38
    Spikes _spikes;
4✔
39
    for (const auto& range : node_ids.ranges()) {
4✔
40
        const auto begin = std::lower_bound(spikes.begin(),
41
                                            spikes.end(),
42
                                            std::make_pair(range.first, 0.),
2✔
43
                                            [](const Spike& spike1, const Spike& spike2) {
4✔
44
                                                return spike1.first < spike2.first;
4✔
45
                                            });
2✔
46
        const auto end = std::upper_bound(spikes.begin(),
47
                                          spikes.end(),
48
                                          std::make_pair(range.second - 1, 0.),
2✔
49
                                          [](const Spike& spike1, const Spike& spike2) {
6✔
50
                                              return spike1.first < spike2.first;
6✔
51
                                          });
2✔
52

53
        std::move(begin, end, std::back_inserter(_spikes));
2✔
54
        spikes.erase(begin, end);  // have to erase, because otherwise it is no more sorted
2✔
55
    }
56
    spikes = std::move(_spikes);
2✔
57
}
2✔
58

59
void filterTimestampUnsorted(Spikes& spikes, double tstart, double tstop) {
6✔
60
    auto new_end =
61
        std::remove_if(spikes.begin(), spikes.end(), [&tstart, &tstop](const Spike& spike) {
40✔
62
            return (spike.second < tstart - EPSILON) || (spike.second > tstop + EPSILON);
20✔
63
        });
6✔
64
    spikes.erase(new_end, spikes.end());
6✔
65
}
6✔
66

67
void filterTimestampSorted(Spikes& spikes, double tstart, double tstop) {
4✔
68
    const auto end = std::upper_bound(spikes.begin(),
69
                                      spikes.end(),
70
                                      std::make_pair(0UL, tstop + EPSILON),
4✔
71
                                      [](const Spike& spike1, const Spike& spike2) {
10✔
72
                                          return spike1.second < spike2.second;
10✔
73
                                      });
4✔
74
    spikes.erase(end, spikes.end());
4✔
75
    const auto begin = std::lower_bound(spikes.begin(),
76
                                        spikes.end(),
77
                                        std::make_pair(0UL, tstart - EPSILON),
4✔
78
                                        [](const Spike& spike1, const Spike& spike2) {
8✔
79
                                            return spike1.second < spike2.second;
8✔
80
                                        });
4✔
81
    spikes.erase(spikes.begin(), begin);
4✔
82
}
4✔
83

84
inline void emplace_ids(NodeID& key, NodeID node_id, ElementID /* element_id */) {
92✔
85
    key = node_id;
92✔
86
}
92✔
87

88
inline void emplace_ids(CompartmentID& key, NodeID node_id, ElementID element_id) {
70✔
89
    key[0] = node_id;
70✔
90
    key[1] = element_id;
70✔
91
}
70✔
92

93
}  // anonymous namespace
94

95
namespace bbp {
96
namespace sonata {
97

98
SpikeReader::SpikeReader(const std::string& filename)
2✔
99
    : filename_(filename) {}
2✔
100

101
std::vector<std::string> SpikeReader::getPopulationNames() const {
2✔
102
    H5::File file(filename_, H5::File::ReadOnly);
2✔
103
    return file.getGroup("/spikes").listObjectNames();
6✔
104
}
105

106
auto SpikeReader::openPopulation(const std::string& populationName) const -> const Population& {
18✔
107
    if (populations_.find(populationName) == populations_.end()) {
18✔
108
        populations_.emplace(populationName, Population{filename_, populationName});
8✔
109
    }
110

111
    return populations_.at(populationName);
18✔
112
}
113

114
std::tuple<double, double> SpikeReader::Population::getTimes() const {
2✔
115
    return std::tie(tstart_, tstop_);
4✔
116
}
117

118
Spikes SpikeReader::Population::createSpikes() const {
10✔
119
    Spikes spikes;
10✔
120
    std::transform(spike_times_.node_ids.begin(),
121
                   spike_times_.node_ids.end(),
122
                   spike_times_.timestamps.begin(),
123
                   std::back_inserter(spikes),
124
                   [](Spike::first_type node_id, Spike::second_type timestamp) {
40✔
125
                       return std::make_pair(node_id, timestamp);
40✔
126
                   });
10✔
127
    return spikes;
10✔
128
}
129

130
Spikes SpikeReader::Population::get(const nonstd::optional<Selection>& node_ids,
10✔
131
                                    const nonstd::optional<double>& tstart,
132
                                    const nonstd::optional<double>& tstop) const {
133
    const double start = tstart.value_or(tstart_);
10✔
134
    const double stop = tstop.value_or(tstop_);
10✔
135

136
    if (start < 0 - EPSILON || stop < 0 - EPSILON) {
10✔
137
        throw SonataError("Times cannot be negative");
×
138
    }
139

140
    if (start > stop) {
10✔
141
        throw SonataError("tstart should be <= to tstop");
×
142
    }
143

144
    if (node_ids and node_ids->empty()) {
10✔
145
        return Spikes{};
×
146
    }
147

148
    auto spikes = createSpikes();
20✔
149
    filterTimestamp(spikes, start, stop);
10✔
150

151
    if (node_ids) {
10✔
152
        filterNode(spikes, node_ids.value());
8✔
153
    }
154

155
    return spikes;
10✔
156
}
157

158
const SpikeTimes& SpikeReader::Population::getRawArrays() const {
×
159
    return spike_times_;
×
160
}
161

162
SpikeTimes SpikeReader::Population::getArrays(const nonstd::optional<Selection>& node_ids,
×
163
                                              const nonstd::optional<double>& tstart,
164
                                              const nonstd::optional<double>& tstop) const {
165
    SpikeTimes filtered_spikes;
×
166
    const auto& node_ids_selection = node_ids ? node_ids.value().flatten() : std::vector<NodeID>{};
×
167
    // Create arrays directly for required data based on conditions
168
    for (size_t i = 0; i < spike_times_.node_ids.size(); ++i) {
×
169
        const auto& node_id = spike_times_.node_ids[i];
×
170
        const auto& timestamp = spike_times_.timestamps[i];
×
171

172
        // Check if node_id is found in node_ids_selection
173
        bool node_ids_found = true;
×
174
        if (node_ids) {
×
175
            node_ids_found = std::find(node_ids_selection.begin(),
×
176
                                       node_ids_selection.end(),
177
                                       node_id) != node_ids_selection.end();
×
178
        }
179

180
        // Check if timestamp is within valid range
181
        bool valid_timestamp = (!tstart || timestamp >= tstart.value()) &&
×
182
                               (!tstop || timestamp <= tstop.value());
×
183

184
        // Include data if both conditions are satisfied
185
        if (node_ids_found && valid_timestamp) {
×
186
            filtered_spikes.node_ids.emplace_back(node_id);
×
187
            filtered_spikes.timestamps.emplace_back(timestamp);
×
188
        }
189
    }
190
    return filtered_spikes;
×
191
}
192

193
SpikeReader::Population::Sorting SpikeReader::Population::getSorting() const {
6✔
194
    return sorting_;
6✔
195
}
196

197
SpikeReader::Population::Population(const std::string& filename,
8✔
198
                                    const std::string& populationName) {
8✔
199
    H5::File file(filename, H5::File::ReadOnly);
16✔
200
    const auto pop_path = std::string("/spikes/") + populationName;
24✔
201
    const auto pop = file.getGroup(pop_path);
16✔
202
    auto& node_ids = spike_times_.node_ids;
8✔
203
    auto& timestamps = spike_times_.timestamps;
8✔
204

205
    pop.getDataSet("node_ids").read(node_ids);
8✔
206
    pop.getDataSet("timestamps").read(timestamps);
8✔
207

208
    if (node_ids.size() != timestamps.size()) {
8✔
209
        throw SonataError(
210
            "In spikes file, 'node_ids' and 'timestamps' does not have the same size.");
×
211
    }
212

213
    if (pop.hasAttribute("sorting")) {
8✔
214
        pop.getAttribute("sorting").read(sorting_);
8✔
215
    }
216

217
    if (sorting_ == Sorting::by_time) {
8✔
218
        tstart_ = timestamps.empty() ? 0 : timestamps.front();
2✔
219
        tstop_ = timestamps.empty() ? 0 : timestamps.back();
2✔
220
    } else {
221
        tstart_ = timestamps.empty() ? 0 : *min_element(timestamps.cbegin(), timestamps.cend());
6✔
222
        tstop_ = timestamps.empty() ? 0 : *max_element(timestamps.cbegin(), timestamps.cend());
6✔
223
    }
224
}
8✔
225

226
void SpikeReader::Population::filterNode(Spikes& spikes, const Selection& node_ids) const {
8✔
227
    if (sorting_ == Sorting::by_id) {
8✔
228
        filterNodeIDSorted(spikes, node_ids);
2✔
229
    } else {
230
        filterNodeIDUnsorted(spikes, node_ids);
6✔
231
    }
232
}
8✔
233

234
void SpikeReader::Population::filterTimestamp(Spikes& spikes, double tstart, double tstop) const {
10✔
235
    if (sorting_ == Sorting::by_time) {
10✔
236
        filterTimestampSorted(spikes, tstart, tstop);
4✔
237
    } else {
238
        filterTimestampUnsorted(spikes, tstart, tstop);
6✔
239
    }
240
}
10✔
241

242
template <typename T>
243
ReportReader<T>::ReportReader(const std::string& filename)
8✔
244
    : file_(filename, H5::File::ReadOnly) {}
8✔
245

246
template <typename T>
247
std::vector<std::string> ReportReader<T>::getPopulationNames() const {
4✔
248
    return file_.getGroup("/report").listObjectNames();
4✔
249
}
250

251
template <typename T>
252
auto ReportReader<T>::openPopulation(const std::string& populationName) const -> const Population& {
10✔
253
    if (populations_.find(populationName) == populations_.end()) {
10✔
254
        populations_.emplace(populationName, Population{file_, populationName});
10✔
255
    }
256

257
    return populations_.at(populationName);
10✔
258
}
259

260
template <typename T>
261
ReportReader<T>::Population::Population(const H5::File& file, const std::string& populationName)
10✔
262
    : pop_group_(file.getGroup(std::string("/report/") + populationName))
263
    , is_node_ids_sorted_(false) {
10✔
264
    const auto mapping_group = pop_group_.getGroup("mapping");
30✔
265
    mapping_group.getDataSet("node_ids").read(node_ids_);
10✔
266

267
    std::vector<uint64_t> index_pointers;
10✔
268
    mapping_group.getDataSet("index_pointers").read(index_pointers);
10✔
269

270
    if (index_pointers.size() != (node_ids_.size() + 1)) {
10✔
271
        throw SonataError("'index_pointers' dataset size must be 'node_ids' size plus one");
×
272
    }
273

274
    // Expand the pointers into tuples that define the range of each GID
275
    size_t element_ids_count = 0;
10✔
276
    for (size_t i = 0; i < node_ids_.size(); ++i) {
210✔
277
        node_ranges_.emplace_back(index_pointers[i], index_pointers[i + 1]);  // Range of GID
200✔
278
        node_offsets_.emplace_back(element_ids_count);                        // Offset in output
200✔
279
        node_index_.emplace_back(i);                                          // Index of previous
200✔
280

281
        element_ids_count += (index_pointers[i + 1] - index_pointers[i]);
200✔
282
    }
283
    node_offsets_.emplace_back(element_ids_count);
10✔
284

285
    {  // Sort the index according to the GIDs, if not sorted in file
286
        if (mapping_group.getDataSet("node_ids").hasAttribute("sorted")) {
10✔
287
            uint8_t sorted = 0;
4✔
288
            mapping_group.getDataSet("node_ids").getAttribute("sorted").read(sorted);
4✔
289
            is_node_ids_sorted_ = (sorted != 0);
4✔
290
        }
291

292
        if (!is_node_ids_sorted_) {
10✔
293
            // Note: The idea is to sort the positions to access the values, allowing us to
294
            //       maintain all vectors intact, while still being able to index the data
295
            std::sort(node_index_.begin(), node_index_.end(), [&](const size_t i, const size_t j) {
1,014✔
296
                return node_ids_[i] < node_ids_[j];
504✔
297
            });
298
        }
299
    }
300

301
    {  // Get times
302
        std::vector<double> times;
20✔
303
        mapping_group.getDataSet("time").read(times);
10✔
304
        tstart_ = times[0];
10✔
305
        tstop_ = times[1];
10✔
306
        tstep_ = times[2];
10✔
307
        mapping_group.getDataSet("time").getAttribute("units").read(time_units_);
10✔
308
        size_t i = 0;
10✔
309
        for (double t = tstart_; t < tstop_ - EPSILON; t += tstep_, ++i) {
150✔
310
            times_index_.emplace_back(i, t);
140✔
311
        }
312
    }
313

314
    pop_group_.getDataSet("data").getAttribute("units").read(data_units_);
10✔
315
}
10✔
316

317
template <typename T>
318
std::tuple<double, double, double> ReportReader<T>::Population::getTimes() const {
4✔
319
    return std::tie(tstart_, tstop_, tstep_);
4✔
320
}
321

322
template <typename T>
323
std::string ReportReader<T>::Population::getTimeUnits() const {
4✔
324
    return time_units_;
4✔
325
}
326

327
template <typename T>
328
std::string ReportReader<T>::Population::getDataUnits() const {
4✔
329
    return data_units_;
4✔
330
}
331

332
template <typename T>
333
bool ReportReader<T>::Population::getSorted() const {
4✔
334
    return is_node_ids_sorted_;
4✔
335
}
336

337
template <typename T>
338
std::vector<NodeID> ReportReader<T>::Population::getNodeIds() const {
4✔
339
    return node_ids_;
4✔
340
}
341

342
template <typename T>
343
typename ReportReader<T>::Population::NodeIdElementLayout
344
ReportReader<T>::Population::getNodeIdElementLayout(
28✔
345
    const nonstd::optional<Selection>& node_ids,
346
    const nonstd::optional<size_t>& _block_gap_limit) const {
347
    NodeIdElementLayout result;
28✔
348
    std::vector<NodeID> concrete_node_ids;
56✔
349
    size_t element_ids_count = 0;
28✔
350

351
    // Set the gap between IO blocks while fetching data (Default: 64MB / 4 x GPFS blocks)
352
    const size_t block_gap_limit = _block_gap_limit.value_or(16777216);
28✔
353

354
    if (block_gap_limit < 4194304) {
28✔
355
        throw SonataError("block_gap_limit must be at least 4194304 (16MB / 1 x GPFS block)");
4✔
356
    }
357

358
    // Take all nodes if no selection is provided
359
    if (!node_ids) {
24✔
360
        concrete_node_ids = node_ids_;
4✔
361
        result.node_ranges = node_ranges_;
4✔
362
        result.node_offsets = node_offsets_;
4✔
363
        result.node_index = node_index_;
4✔
364
        element_ids_count = node_offsets_.back();
4✔
365
    } else if (!node_ids->empty()) {
20✔
366
        const auto selected_node_ids = node_ids->flatten();
36✔
367

368
        for (const auto node_id : selected_node_ids) {
60✔
369
            const auto it = std::lower_bound(node_index_.begin(),
42✔
370
                                             node_index_.end(),
371
                                             node_id,
372
                                             [&](const size_t i, const NodeID node_id) {
182✔
373
                                                 return node_ids_[i] < node_id;
182✔
374
                                             });
375

376
            if (it != node_index_.end() && node_ids_[*it] == node_id) {
42✔
377
                const auto& range = node_ranges_[*it];
26✔
378

379
                concrete_node_ids.emplace_back(node_id);
26✔
380
                result.node_ranges.emplace_back(range);
26✔
381
                result.node_offsets.emplace_back(element_ids_count);
26✔
382
                result.node_index.emplace_back(result.node_index.size());
26✔
383

384
                element_ids_count += (range.second - range.first);
26✔
385
            }
386
        }
387
    } else {
388
        // node_ids Selection exists, but is empty
389
    }
390

391
    // Extract the ElementIDs from the GIDs
392
    if (!concrete_node_ids.empty()) {
24✔
393
        // Sort the index by the selected ranges
394
        std::sort(result.node_index.begin(),
18✔
395
                  result.node_index.end(),
396
                  [&](const size_t i, const size_t j) {
800✔
397
                      return result.node_ranges[i].first < result.node_ranges[j].first;
400✔
398
                  });
399

400
        // Generate the {min,max} IO blocks for the requests
401
        size_t offset = 0;
18✔
402
        for (size_t i = 0; (i + 1) < result.node_index.size(); i++) {
106✔
403
            const auto index = result.node_index[i];
88✔
404
            const auto index_next = result.node_index[i + 1];
88✔
405
            const auto max = result.node_ranges[index].second;
88✔
406
            const auto min_next = result.node_ranges[index_next].first;
88✔
407

408
            if ((min_next - max) > block_gap_limit) {
88✔
409
                result.min_max_blocks.emplace_back(std::make_pair(offset, (i + 1)));
×
410
                offset = (i + 1);
×
411
            }
412
        }
413
        result.min_max_blocks.emplace_back(std::make_pair(offset, result.node_index.size()));
18✔
414

415
        // Fill the GID-ElementID mapping in blocks to reduce the file system overhead
416
        std::vector<ElementID> element_ids;
36✔
417
        auto dataset_elem_ids = pop_group_.getGroup("mapping").getDataSet("element_ids");
36✔
418

419
        result.ids.resize(element_ids_count);
18✔
420

421
        for (const auto& min_max_block : result.min_max_blocks) {
36✔
422
            const auto first_index = result.node_index[min_max_block.first];
18✔
423
            const auto last_index = result.node_index[min_max_block.second - 1];
18✔
424
            const auto min = result.node_ranges[first_index].first;
18✔
425
            const auto max = result.node_ranges[last_index].second;
18✔
426

427
            dataset_elem_ids.select({min}, {max - min}).read(element_ids);
18✔
428

429
            // Copy the values for each of the GIDs assigned into this block
430
            for (size_t i = min_max_block.first; i < min_max_block.second; ++i) {
124✔
431
                const auto index = result.node_index[i];
106✔
432
                const auto node_id = concrete_node_ids[index];
106✔
433
                const auto range = Selection::Range(result.node_ranges[index].first - min,
106✔
434
                                                    result.node_ranges[index].second - min);
106✔
435

436
                auto offset = result.node_offsets[index];
106✔
437
                for (auto i = range.first; i < range.second; i++, offset++) {
268✔
438
                    emplace_ids(result.ids[offset], node_id, element_ids[i]);
162✔
439
                }
440
            }
441
        }
442

443
        // Temp. fix: When you ask for a large hyperslab in a dataset and then move
444
        //            to another dataset in the same file where you also ask for
445
        //            another large range, the next IOps take an extra few seconds.
446
        //            We observed that fooling HDF5 hides the issue, but we should
447
        //            verify this behaviour once new releases of HDF5 are available.
448
        const auto min_max_block = result.min_max_blocks.back();
18✔
449
        const auto index = result.node_index[min_max_block.first];
18✔
450
        dataset_elem_ids.select({result.node_ranges[index].first}, {1}).read(element_ids);
18✔
451
    }
452

453
    return result;
48✔
454
}
455

456
template <typename T>
457
std::pair<size_t, size_t> ReportReader<T>::Population::getIndex(
30✔
458
    const nonstd::optional<double>& tstart, const nonstd::optional<double>& tstop) const {
459
    std::pair<size_t, size_t> indexes;
30✔
460

461
    const double start = tstart.value_or(tstart_);
30✔
462
    const double stop = tstop.value_or(tstop_);
30✔
463

464
    if (start < 0 - EPSILON || stop < 0 - EPSILON) {
30✔
465
        throw SonataError("Times cannot be negative");
4✔
466
    }
467

468
    const auto it_start = std::find_if(times_index_.cbegin(),
26✔
469
                                       times_index_.cend(),
470
                                       [&](const std::pair<size_t, double>& v) {
102✔
471
                                           return start < v.second + EPSILON;
102✔
472
                                       });
473
    if (it_start == times_index_.end()) {
26✔
474
        throw SonataError("tstart is after the end of the range");
4✔
475
    }
476
    indexes.first = it_start->first;
22✔
477

478
    const auto it_stop =
22✔
479
        std::find_if(times_index_.crbegin(),
480
                     times_index_.crend(),
481
                     [&](const std::pair<size_t, double>& v) { return stop > v.second - EPSILON; });
186✔
482
    if (it_stop == times_index_.rend()) {
22✔
483
        throw SonataError("tstop is before the beginning of the range");
×
484
    }
485
    indexes.second = it_stop->first;
22✔
486

487
    return indexes;
22✔
488
}
489

490

491
template <typename T>
492
typename DataFrame<T>::DataType ReportReader<T>::Population::getNodeIdElementIdMapping(
12✔
493
    const nonstd::optional<Selection>& node_ids,
494
    const nonstd::optional<size_t>& block_gap_limit) const {
495
    return getNodeIdElementLayout(node_ids, block_gap_limit).ids;
12✔
496
}
497

498
template <typename T>
499
DataFrame<T> ReportReader<T>::Population::get(
30✔
500
    const nonstd::optional<Selection>& node_ids,
501
    const nonstd::optional<double>& tstart,
502
    const nonstd::optional<double>& tstop,
503
    const nonstd::optional<size_t>& tstride,
504
    const nonstd::optional<size_t>& block_gap_limit) const {
505
    size_t index_start = 0;
30✔
506
    size_t index_stop = 0;
30✔
507
    std::tie(index_start, index_stop) = getIndex(tstart, tstop);
30✔
508
    const size_t stride = tstride.value_or(1);
22✔
509
    if (stride == 0) {
22✔
510
        throw SonataError("tstride should be > 0");
2✔
511
    }
512
    if (index_start > index_stop) {
20✔
513
        throw SonataError("tstart should be <= to tstop");
4✔
514
    }
515

516
    // Retrieve the GID-ElementID layout, alongside the {min,max} blocks
517
    auto node_id_element_layout = getNodeIdElementLayout(node_ids, block_gap_limit);
32✔
518
    const auto& node_ranges = node_id_element_layout.node_ranges;
16✔
519
    const auto& node_offsets = node_id_element_layout.node_offsets;
16✔
520
    const auto& node_index = node_id_element_layout.node_index;
16✔
521
    const auto& min_max_blocks = node_id_element_layout.min_max_blocks;
16✔
522

523
    if (node_id_element_layout.ids.empty()) {  // At the end no data available (wrong node_ids?)
16✔
524
        return DataFrame<T>{{}, {}, {}};
6✔
525
    }
526

527
    // Fill times
528
    DataFrame<T> data_frame;
20✔
529
    for (size_t i = index_start; i <= index_stop; i += stride) {
64✔
530
        data_frame.times.emplace_back(times_index_[i].second);
54✔
531
    }
532

533
    // Fill ids
534
    data_frame.ids.swap(node_id_element_layout.ids);
10✔
535

536
    // Fill .data member
537
    const size_t n_time_entries = ((index_stop - index_start) / stride) + 1;
10✔
538
    const size_t element_ids_count = data_frame.ids.size();
10✔
539
    data_frame.data.resize(n_time_entries * element_ids_count);
10✔
540

541
    auto dataset = pop_group_.getDataSet("data");
30✔
542
    auto dataset_type = dataset.getDataType();
20✔
543
    if (dataset_type.getClass() != HighFive::DataTypeClass::Float || dataset_type.getSize() != 4) {
10✔
544
        throw SonataError(
545
            fmt::format("DataType of dataset 'data' should be Float32 ('{}' was found)",
546
                        dataset_type.string()));
4✔
547
    }
548

549
    std::vector<float> buffer;
16✔
550
    auto data_start = data_frame.data.begin();
8✔
551
    for (size_t timer_index = index_start; timer_index <= index_stop; timer_index += stride) {
42✔
552
        // Access the data in blocks to reduce the file system overhead
553
        for (const auto& min_max_block : min_max_blocks) {
68✔
554
            const auto first_index = node_index[min_max_block.first];
34✔
555
            const auto last_index = node_index[min_max_block.second - 1];
34✔
556
            const auto min = node_ranges[first_index].first;
34✔
557
            const auto max = node_ranges[last_index].second;
34✔
558

559
            dataset.select({timer_index, min}, {1, max - min}).read(buffer);
34✔
560

561
            // Copy the values for each of the GIDs assigned into this block
562
            const auto buffer_start = buffer.begin();
34✔
563
            for (size_t i = min_max_block.first; i < min_max_block.second; ++i) {
460✔
564
                const auto index = node_index[i];
426✔
565
                const auto range = Selection::Range(node_ranges[index].first - min,
426✔
566
                                                    node_ranges[index].second - min);
426✔
567
                const auto elements_per_gid = (range.second - range.first);
426✔
568
                const auto offset = node_offsets[index];
426✔
569

570
                // Soma report
571
                if (elements_per_gid == 1) {
426✔
572
                    data_start[offset] = buffer_start[range.first];
416✔
573
                } else {  // Elements report
574
                    std::copy(std::next(buffer_start, range.first),
20✔
575
                              std::next(buffer_start, range.second),
10✔
576
                              std::next(data_start, offset));
577
                }
578
            }
579
        }
580

581
        std::advance(data_start, element_ids_count);
34✔
582
    }
583

584
    return data_frame;
8✔
585
}
586

587
template class ReportReader<NodeID>;
588
template class ReportReader<CompartmentID>;
589

590
}  // namespace sonata
591
}  // namespace bbp
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

© 2025 Coveralls, Inc