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

BlueBrain / libsonata / 5903290505

18 Aug 2023 01:34PM UTC coverage: 95.38% (-0.9%) from 96.3%
5903290505

Pull #285

github

jorblancoa
Allow getting only times or nodes in the getArrays function
Pull Request #285: Return a structured array for the spikes in python

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

1796 of 1883 relevant lines covered (95.38%)

76.47 hits per line

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

91.59
/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
    // Create arrays directly for required data based on conditions
167
    for (size_t i = 0; i < node_ids_.size(); ++i) {
×
168
        const auto& node_id = node_ids_[i];
×
169
        const auto& timestamp = timestamps_[i];
×
170

171
        // Check if node_id is found in node_ids_selection
172
        bool node_ids_found = true;
×
173
        if (node_ids) {
×
174
            const auto& node_ids_selection = node_ids.value().flatten();
×
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
            arrays.first.push_back(node_id);
×
187
            arrays.second.push_back(timestamp);
×
188
        }
189
    }
190
    return arrays;
×
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

203
    pop.getDataSet("node_ids").read(node_ids_);
8✔
204
    pop.getDataSet("timestamps").read(timestamps_);
8✔
205

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

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

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

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

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

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

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

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

255
    return populations_.at(populationName);
10✔
256
}
257

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

417
        result.ids.resize(element_ids_count);
18✔
418

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

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

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

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

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

451
    return result;
48✔
452
}
453

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

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

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

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

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

485
    return indexes;
22✔
486
}
487

488

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

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

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

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

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

531
    // Fill ids
532
    data_frame.ids.swap(node_id_element_layout.ids);
10✔
533

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

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

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

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

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

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

579
        std::advance(data_start, element_ids_count);
34✔
580
    }
581

582
    return data_frame;
8✔
583
}
584

585
template class ReportReader<NodeID>;
586
template class ReportReader<CompartmentID>;
587

588
}  // namespace sonata
589
}  // 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