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

BlueBrain / libsonata / 5821438321

pending completion
5821438321

Pull #285

github

jorblancoa
Avoid calling get() in the getArrays() method
Pull Request #285: Return a structured array for the spikes in python

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

1796 of 1881 relevant lines covered (95.48%)

76.55 hits per line

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

92.18
/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(node_ids_.begin(),
121
                   node_ids_.end(),
122
                   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
SpikesArrays SpikeReader::Population::getArrays(const nonstd::optional<Selection>& node_ids,
×
159
                                                const nonstd::optional<double>& tstart,
160
                                                const nonstd::optional<double>& tstop) const {
161
    if (!node_ids && !tstart && !tstop) {
×
162
        return std::make_pair(node_ids_, timestamps_);
×
163
    }
164

165
    SpikesArrays arrays;
×
166
    const auto& node_ids_selection = node_ids.value().flatten();
×
167
    // Create arrays directly for required data based on conditions
168
    for (size_t i = 0; i < node_ids_.size(); ++i) {
×
169
        const auto& node_id = node_ids_[i];
×
170
        const auto& timestamp = timestamps_[i];
×
171

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

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

181
        // Include data if both conditions are satisfied
182
        if (node_ids && node_ids_found && valid_timestamp) {
×
183
            arrays.first.push_back(node_id);
×
184
            arrays.second.push_back(timestamp);
×
185
        }
186
    }
187
    return arrays;
×
188
}
189

190
SpikeReader::Population::Sorting SpikeReader::Population::getSorting() const {
6✔
191
    return sorting_;
6✔
192
}
193

194
SpikeReader::Population::Population(const std::string& filename,
8✔
195
                                    const std::string& populationName) {
8✔
196
    H5::File file(filename, H5::File::ReadOnly);
16✔
197
    const auto pop_path = std::string("/spikes/") + populationName;
24✔
198
    const auto pop = file.getGroup(pop_path);
16✔
199

200
    pop.getDataSet("node_ids").read(node_ids_);
8✔
201
    pop.getDataSet("timestamps").read(timestamps_);
8✔
202

203
    if (node_ids_.size() != timestamps_.size()) {
8✔
204
        throw SonataError(
205
            "In spikes file, 'node_ids' and 'timestamps' does not have the same size.");
×
206
    }
207

208
    if (pop.hasAttribute("sorting")) {
8✔
209
        pop.getAttribute("sorting").read(sorting_);
8✔
210
    }
211

212
    if (sorting_ == Sorting::by_time) {
8✔
213
        tstart_ = timestamps_.empty() ? 0 : timestamps_.front();
2✔
214
        tstop_ = timestamps_.empty() ? 0 : timestamps_.back();
2✔
215
    } else {
216
        tstart_ = timestamps_.empty() ? 0 : *min_element(timestamps_.cbegin(), timestamps_.cend());
6✔
217
        tstop_ = timestamps_.empty() ? 0 : *max_element(timestamps_.cbegin(), timestamps_.cend());
6✔
218
    }
219
}
8✔
220

221
void SpikeReader::Population::filterNode(Spikes& spikes, const Selection& node_ids) const {
8✔
222
    if (sorting_ == Sorting::by_id) {
8✔
223
        filterNodeIDSorted(spikes, node_ids);
2✔
224
    } else {
225
        filterNodeIDUnsorted(spikes, node_ids);
6✔
226
    }
227
}
8✔
228

229
void SpikeReader::Population::filterTimestamp(Spikes& spikes, double tstart, double tstop) const {
10✔
230
    if (sorting_ == Sorting::by_time) {
10✔
231
        filterTimestampSorted(spikes, tstart, tstop);
4✔
232
    } else {
233
        filterTimestampUnsorted(spikes, tstart, tstop);
6✔
234
    }
235
}
10✔
236

237
template <typename T>
238
ReportReader<T>::ReportReader(const std::string& filename)
8✔
239
    : file_(filename, H5::File::ReadOnly) {}
8✔
240

241
template <typename T>
242
std::vector<std::string> ReportReader<T>::getPopulationNames() const {
4✔
243
    return file_.getGroup("/report").listObjectNames();
4✔
244
}
245

246
template <typename T>
247
auto ReportReader<T>::openPopulation(const std::string& populationName) const -> const Population& {
10✔
248
    if (populations_.find(populationName) == populations_.end()) {
10✔
249
        populations_.emplace(populationName, Population{file_, populationName});
10✔
250
    }
251

252
    return populations_.at(populationName);
10✔
253
}
254

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

262
    std::vector<uint64_t> index_pointers;
10✔
263
    mapping_group.getDataSet("index_pointers").read(index_pointers);
10✔
264

265
    if (index_pointers.size() != (node_ids_.size() + 1)) {
10✔
266
        throw SonataError("'index_pointers' dataset size must be 'node_ids' size plus one");
×
267
    }
268

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

276
        element_ids_count += (index_pointers[i + 1] - index_pointers[i]);
200✔
277
    }
278
    node_offsets_.emplace_back(element_ids_count);
10✔
279

280
    {  // Sort the index according to the GIDs, if not sorted in file
281
        if (mapping_group.getDataSet("node_ids").hasAttribute("sorted")) {
10✔
282
            uint8_t sorted = 0;
4✔
283
            mapping_group.getDataSet("node_ids").getAttribute("sorted").read(sorted);
4✔
284
            is_node_ids_sorted_ = (sorted != 0);
4✔
285
        }
286

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

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

309
    pop_group_.getDataSet("data").getAttribute("units").read(data_units_);
10✔
310
}
10✔
311

312
template <typename T>
313
std::tuple<double, double, double> ReportReader<T>::Population::getTimes() const {
4✔
314
    return std::tie(tstart_, tstop_, tstep_);
4✔
315
}
316

317
template <typename T>
318
std::string ReportReader<T>::Population::getTimeUnits() const {
4✔
319
    return time_units_;
4✔
320
}
321

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

327
template <typename T>
328
bool ReportReader<T>::Population::getSorted() const {
4✔
329
    return is_node_ids_sorted_;
4✔
330
}
331

332
template <typename T>
333
std::vector<NodeID> ReportReader<T>::Population::getNodeIds() const {
4✔
334
    return node_ids_;
4✔
335
}
336

337
template <typename T>
338
typename ReportReader<T>::Population::NodeIdElementLayout
339
ReportReader<T>::Population::getNodeIdElementLayout(
28✔
340
    const nonstd::optional<Selection>& node_ids,
341
    const nonstd::optional<size_t>& _block_gap_limit) const {
342
    NodeIdElementLayout result;
28✔
343
    std::vector<NodeID> concrete_node_ids;
56✔
344
    size_t element_ids_count = 0;
28✔
345

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

349
    if (block_gap_limit < 4194304) {
28✔
350
        throw SonataError("block_gap_limit must be at least 4194304 (16MB / 1 x GPFS block)");
4✔
351
    }
352

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

363
        for (const auto node_id : selected_node_ids) {
60✔
364
            const auto it = std::lower_bound(node_index_.begin(),
42✔
365
                                             node_index_.end(),
366
                                             node_id,
367
                                             [&](const size_t i, const NodeID node_id) {
182✔
368
                                                 return node_ids_[i] < node_id;
182✔
369
                                             });
370

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

374
                concrete_node_ids.emplace_back(node_id);
26✔
375
                result.node_ranges.emplace_back(range);
26✔
376
                result.node_offsets.emplace_back(element_ids_count);
26✔
377
                result.node_index.emplace_back(result.node_index.size());
26✔
378

379
                element_ids_count += (range.second - range.first);
26✔
380
            }
381
        }
382
    } else {
383
        // node_ids Selection exists, but is empty
384
    }
385

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

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

403
            if ((min_next - max) > block_gap_limit) {
88✔
404
                result.min_max_blocks.emplace_back(std::make_pair(offset, (i + 1)));
×
405
                offset = (i + 1);
×
406
            }
407
        }
408
        result.min_max_blocks.emplace_back(std::make_pair(offset, result.node_index.size()));
18✔
409

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

414
        result.ids.resize(element_ids_count);
18✔
415

416
        for (const auto& min_max_block : result.min_max_blocks) {
36✔
417
            const auto first_index = result.node_index[min_max_block.first];
18✔
418
            const auto last_index = result.node_index[min_max_block.second - 1];
18✔
419
            const auto min = result.node_ranges[first_index].first;
18✔
420
            const auto max = result.node_ranges[last_index].second;
18✔
421

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

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

431
                auto offset = result.node_offsets[index];
106✔
432
                for (auto i = range.first; i < range.second; i++, offset++) {
268✔
433
                    emplace_ids(result.ids[offset], node_id, element_ids[i]);
162✔
434
                }
435
            }
436
        }
437

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

448
    return result;
48✔
449
}
450

451
template <typename T>
452
std::pair<size_t, size_t> ReportReader<T>::Population::getIndex(
30✔
453
    const nonstd::optional<double>& tstart, const nonstd::optional<double>& tstop) const {
454
    std::pair<size_t, size_t> indexes;
30✔
455

456
    const double start = tstart.value_or(tstart_);
30✔
457
    const double stop = tstop.value_or(tstop_);
30✔
458

459
    if (start < 0 - EPSILON || stop < 0 - EPSILON) {
30✔
460
        throw SonataError("Times cannot be negative");
4✔
461
    }
462

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

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

482
    return indexes;
22✔
483
}
484

485

486
template <typename T>
487
typename DataFrame<T>::DataType ReportReader<T>::Population::getNodeIdElementIdMapping(
12✔
488
    const nonstd::optional<Selection>& node_ids,
489
    const nonstd::optional<size_t>& block_gap_limit) const {
490
    return getNodeIdElementLayout(node_ids, block_gap_limit).ids;
12✔
491
}
492

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

511
    // Retrieve the GID-ElementID layout, alongside the {min,max} blocks
512
    auto node_id_element_layout = getNodeIdElementLayout(node_ids, block_gap_limit);
32✔
513
    const auto& node_ranges = node_id_element_layout.node_ranges;
16✔
514
    const auto& node_offsets = node_id_element_layout.node_offsets;
16✔
515
    const auto& node_index = node_id_element_layout.node_index;
16✔
516
    const auto& min_max_blocks = node_id_element_layout.min_max_blocks;
16✔
517

518
    if (node_id_element_layout.ids.empty()) {  // At the end no data available (wrong node_ids?)
16✔
519
        return DataFrame<T>{{}, {}, {}};
6✔
520
    }
521

522
    // Fill times
523
    DataFrame<T> data_frame;
20✔
524
    for (size_t i = index_start; i <= index_stop; i += stride) {
64✔
525
        data_frame.times.emplace_back(times_index_[i].second);
54✔
526
    }
527

528
    // Fill ids
529
    data_frame.ids.swap(node_id_element_layout.ids);
10✔
530

531
    // Fill .data member
532
    const size_t n_time_entries = ((index_stop - index_start) / stride) + 1;
10✔
533
    const size_t element_ids_count = data_frame.ids.size();
10✔
534
    data_frame.data.resize(n_time_entries * element_ids_count);
10✔
535

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

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

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

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

565
                // Soma report
566
                if (elements_per_gid == 1) {
426✔
567
                    data_start[offset] = buffer_start[range.first];
416✔
568
                } else {  // Elements report
569
                    std::copy(std::next(buffer_start, range.first),
20✔
570
                              std::next(buffer_start, range.second),
10✔
571
                              std::next(data_start, offset));
572
                }
573
            }
574
        }
575

576
        std::advance(data_start, element_ids_count);
34✔
577
    }
578

579
    return data_frame;
8✔
580
}
581

582
template class ReportReader<NodeID>;
583
template class ReportReader<CompartmentID>;
584

585
}  // namespace sonata
586
}  // 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