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

BlueBrain / libsonata / 5556237834

pending completion
5556237834

push

github

mgeplf
initial test of NodeSet extend

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

1787 of 1887 relevant lines covered (94.7%)

74.36 hits per line

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

89.24
/src/node_sets.cpp
1
#include <algorithm>  // std::find, std::transform
2
#include <cassert>
3
#include <cmath>
4
#include <fmt/format.h>
5
#include <fstream>
6
#include <sstream>
7
#include <type_traits>
8

9
#include "../extlib/filesystem.hpp"
10

11
#include <nlohmann/json.hpp>
12
#include <utility>
13

14
#include "utils.h"  // readFile
15

16
#include <bbp/sonata/node_sets.h>
17

18
namespace bbp {
19
namespace sonata {
20

21
namespace fs = ghc::filesystem;
22

23
namespace detail {
24

25
const size_t MAX_COMPOUND_RECURSION = 10;
26

27
using json = nlohmann::json;
28

29
void replace_trailing_coma(std::string& s, char c) {
40✔
30
    s.pop_back();
40✔
31
    s.pop_back();
40✔
32
    s.push_back(c);
40✔
33
}
40✔
34

35
template <typename T>
36
std::string toString(const std::string& key, const std::vector<T>& values) {
10✔
37
    return fmt::format(R"("{}": [{}])", key, fmt::join(values, ", "));
20✔
38
}
39

40
template <>
41
std::string toString(const std::string& key, const std::vector<std::string>& values) {
50✔
42
    // strings need to be wrapped in quotes
43
    return fmt::format(R"("{}": ["{}"])", key, fmt::join(values, "\", \""));
100✔
44
}
45

46
class NodeSets;
47

48
class NodeSetRule
49
{
50
  public:
51
    virtual ~NodeSetRule() = default;
194✔
52

53
    virtual Selection materialize(const NodeSets&, const NodePopulation&) const = 0;
54
    virtual std::string toJSON() const = 0;
55
    virtual bool is_compound() const {
194✔
56
        return false;
194✔
57
    }
58
    virtual std::unique_ptr<NodeSetRule> clone() const = 0;
59
};
60

61
using NodeSetRulePtr = std::unique_ptr<NodeSetRule>;
62
void parse_basic(const json& j, std::map<std::string, NodeSetRulePtr>& node_sets);
63
void parse_compound(const json& j, std::map<std::string, NodeSetRulePtr>& node_sets);
64

65
class NodeSets
66
{
67
    std::map<std::string, NodeSetRulePtr> node_sets_;
68

69
  public:
70
    explicit NodeSets(const json& j) {
122✔
71
        if (!j.is_object()) {
86✔
72
            throw SonataError("Top level node_set must be an object");
4✔
73
        }
74

75
        // Need to two pass parsing the json so that compound lookup can rely
76
        // on all the basic rules existing
77
        parse_basic(j, node_sets_);
82✔
78
        parse_compound(j, node_sets_);
58✔
79
    }
50✔
80

81
    static const fs::path& validate_path(const fs::path& path) {
2✔
82
        if (!fs::exists(path)) {
2✔
83
            throw SonataError(fmt::format("Path does not exist: {}", std::string(path)));
×
84
        }
85
        return path;
2✔
86
    }
87

88
    explicit NodeSets(const fs::path& path)
2✔
89
        : NodeSets(json::parse(std::ifstream(validate_path(path)))) {}
2✔
90

91
    static std::unique_ptr<NodeSets> fromFile(const std::string& path_) {
2✔
92
        fs::path path(path_);
4✔
93
        return std::make_unique<detail::NodeSets>(path);
4✔
94
    }
95

96
    explicit NodeSets(const std::string& content)
84✔
97
        : NodeSets(json::parse(content)) {}
120✔
98

99
    Selection materialize(const std::string& name, const NodePopulation& population) const;
100

101
    std::set<std::string> names() const {
2✔
102
        return getMapKeys(node_sets_);
2✔
103
    }
104

105
    std::set<std::string> extend(const NodeSets& other){
×
106
        if(&other == this){
×
107
            return names();
×
108
        }
109
        std::set<std::string> duplicates;
×
110
        for(const auto& ns : other.node_sets_){
×
111
            if(node_sets_.count(ns.first) > 0){
×
112
                duplicates.insert(ns.first);
×
113
            }
114
            node_sets_[ns.first] = ns.second->clone();
×
115
        }
116
        return duplicates;
×
117
    }
118

119
    std::string toJSON() const {
10✔
120
        std::string ret{"{\n"};
10✔
121
        for (const auto& pair : node_sets_) {
60✔
122
            ret += fmt::format(R"(  "{}": {{)", pair.first);
100✔
123
            ret += pair.second->toJSON();
50✔
124
            ret += "},\n";
50✔
125
        }
126
        replace_trailing_coma(ret, '\n');
10✔
127
        ret += "}";
10✔
128

129
        return ret;
10✔
130
    }
131
};
132

133
class NodeSetNullRule: public NodeSetRule
134
{
135
    Selection materialize(const detail::NodeSets& /* unused */,
2✔
136
                          const NodePopulation& /* unused */) const final {
137
        return Selection({});
2✔
138
    }
139

140
    std::string toJSON() const final {
×
141
        return {};
×
142
    }
143

144
    std::unique_ptr<NodeSetRule> clone() const final
×
145
    {
146
        return std::make_unique<detail::NodeSetNullRule>();
×
147
    }
148
};
149

150
// { 'region': ['region1', 'region2', ...] }
151
template <typename T>
152
class NodeSetBasicRule: public NodeSetRule
153
{
154
  public:
155
    NodeSetBasicRule(const std::string attribute, const std::vector<T>& values)
44✔
156
        : attribute_(attribute)
157
        , values_(values) {}
44✔
158

159
    Selection materialize(const detail::NodeSets& /* unused */,
10✔
160
                          const NodePopulation& np) const final {
161
        return np.matchAttributeValues(attribute_, values_);
10✔
162
    }
163

164
    void add_attribute2rule(std::map<std::string, std::set<T>>& attribute2rule) const {
20✔
165
        auto& s = attribute2rule[attribute_];
20✔
166
        for (const auto& v : values_) {
50✔
167
            s.insert(v);
30✔
168
        }
169
    }
20✔
170

171
    std::string toJSON() const final {
38✔
172
        return toString(attribute_, values_);
38✔
173
    }
174

175
    std::unique_ptr<NodeSetRule> clone() const final
×
176
    {
177
        return std::make_unique<detail::NodeSetBasicRule<T>>(attribute_, values_);
×
178
    }
179

180
  private:
181
    std::string attribute_;
182
    std::vector<T> values_;
183
};
184

185
// { 'population': ['popA', 'popB', ] }
186
class NodeSetBasicPopulation: public NodeSetRule
187
{
188
  public:
189
    explicit NodeSetBasicPopulation(const std::vector<std::string>& values)
14✔
190
        : values_(values) {}
14✔
191

192
    Selection materialize(const detail::NodeSets& /* unused */,
6✔
193
                          const NodePopulation& np) const final {
194
        if (std::find(values_.begin(), values_.end(), np.name()) != values_.end()) {
6✔
195
            return np.selectAll();
4✔
196
        }
197

198
        return Selection{{}};
2✔
199
    }
200

201
    std::string toJSON() const final {
10✔
202
        return toString("population", values_);
10✔
203
    }
204

205
    std::unique_ptr<NodeSetRule> clone() const final
×
206
    {
207
        return std::make_unique<detail::NodeSetBasicPopulation>(values_);
×
208
    }
209

210
  private:
211
    std::vector<std::string> values_;
212
};
213

214
// { 'node_id': [1, 2, 3, 4] }
215
class NodeSetBasicNodeIds: public NodeSetRule
216
{
217
  public:
218
    explicit NodeSetBasicNodeIds(const Selection::Values& values)
18✔
219
        : values_(values) {}
18✔
220

221
    Selection materialize(const detail::NodeSets& /* unused */,
96✔
222
                          const NodePopulation& np) const final {
223
        return np.selectAll() & Selection::fromValues(values_.begin(), values_.end());
192✔
224
    }
225

226
    std::string toJSON() const final {
6✔
227
        return toString("node_ids", values_);
6✔
228
    }
229

230
    std::unique_ptr<NodeSetRule> clone() const final
×
231
    {
232
        return std::make_unique<detail::NodeSetBasicNodeIds>(values_);
×
233
    }
234

235
  private:
236
    Selection::Values values_;
237
};
238

239
//  {
240
//      "population": "biophysical",
241
//      "model_type": "point",
242
//      "node_id": [1, 2, 3, 5, 7, 9, ...]
243
//  }
244
class NodeSetBasicMultiClause: public NodeSetRule
245
{
246
  public:
247
    explicit NodeSetBasicMultiClause(std::vector<NodeSetRulePtr>&& clauses)
26✔
248
        : clauses_(std::move(clauses)) {}
26✔
249

250
    Selection materialize(const detail::NodeSets& ns, const NodePopulation& np) const final {
2✔
251
        Selection ret = np.selectAll();
2✔
252
        for (const auto& clause : clauses_) {
6✔
253
            ret = ret & clause->materialize(ns, np);
4✔
254
        }
255
        return ret;
2✔
256
    }
257

258
    std::string toJSON() const final {
30✔
259
        std::string ret;
30✔
260
        for (const auto& clause : clauses_) {
120✔
261
            ret += clause->toJSON();
90✔
262
            ret += ", ";
90✔
263
        }
264
        replace_trailing_coma(ret, ' ');
30✔
265
        return ret;
30✔
266
    }
267

268
    std::unique_ptr<NodeSetRule> clone() const final
×
269
    {
270
        std::vector<NodeSetRulePtr> clauses;
×
271
        clauses.reserve(clauses_.size());
×
272
        for(const auto& clause : clauses_){
×
273
            clauses.push_back(clause->clone());
×
274
        }
275
        return std::make_unique<detail::NodeSetBasicMultiClause>(std::move(clauses));
×
276
    }
277

278
  private:
279
    std::vector<NodeSetRulePtr> clauses_;
280
};
281

282
// "string_attr": { "$regex": "^[s][o]me value$" }
283
class NodeSetBasicOperatorString: public NodeSetRule
284
{
285
  public:
286
    explicit NodeSetBasicOperatorString(const std::string& attribute,
14✔
287
                                        const std::string& op,
288
                                        const std::string& value)
289
        : op_(string2op(op))
40✔
290
        , attribute_(attribute)
291
        , value_(value) {}
14✔
292

293
    Selection materialize(const detail::NodeSets& /* unused */,
4✔
294
                          const NodePopulation& np) const final {
295
        switch (op_) {
4✔
296
        case Op::regex:
4✔
297
            return np.regexMatch(attribute_, value_);
4✔
298
        default:              // LCOV_EXCL_LINE
299
            LIBSONATA_THROW_IF_REACHED  // LCOV_EXCL_LINE
300
        }
301
    }
302

303
    std::string toJSON() const final {
10✔
304
        return fmt::format(R"("{}": {{ "{}": "{}" }})", attribute_, op2string(op_), value_);
20✔
305
    }
306

307
    enum class Op {
308
        regex = 1,
309
    };
310

311
    static Op string2op(const std::string& s) {
14✔
312
        if (s == "$regex") {
14✔
313
            return Op::regex;
12✔
314
        }
315
        throw SonataError(fmt::format("Operator '{}' not available for strings", s));
4✔
316
    }
317

318
    static std::string op2string(const Op op) {
10✔
319
        switch (op) {
10✔
320
        case Op::regex:
10✔
321
            return "$regex";
10✔
322
        default:              // LCOV_EXCL_LINE
323
            LIBSONATA_THROW_IF_REACHED  // LCOV_EXCL_LINE
324
        }
325
    }
326

327
    std::unique_ptr<NodeSetRule> clone() const final
×
328
    {
329
        return std::make_unique<detail::NodeSetBasicOperatorString>(attribute_, op2string(op_), value_);
×
330
    }
331

332
  private:
333
    Op op_;
334
    std::string attribute_;
335
    std::string value_;
336
};
337

338
// "numeric_attribute_gt": { "$gt": 3 },
339
class NodeSetBasicOperatorNumeric: public NodeSetRule
340
{
341
  public:
342
    explicit NodeSetBasicOperatorNumeric(std::string name, const std::string& op, double value)
42✔
343
        : name_(std::move(name))
84✔
344
        , value_(value)
345
        , op_(string2op(op)) {}
44✔
346

347
    Selection materialize(const detail::NodeSets& /* unused */,
8✔
348
                          const NodePopulation& np) const final {
349
        switch (op_) {
8✔
350
        case Op::gt:
2✔
351
            return np.filterAttribute<double>(name_, [=](const double v) { return v > value_; });
14✔
352
        case Op::lt:
2✔
353
            return np.filterAttribute<double>(name_, [=](const double v) { return v < value_; });
14✔
354
        case Op::gte:
2✔
355
            return np.filterAttribute<double>(name_, [=](const double v) { return v >= value_; });
14✔
356
        case Op::lte:
2✔
357
            return np.filterAttribute<double>(name_, [=](const double v) { return v <= value_; });
14✔
358
        default:              // LCOV_EXCL_LINE
359
            LIBSONATA_THROW_IF_REACHED  // LCOV_EXCL_LINE
360
        }
361
    }
362

363
    std::string toJSON() const final {
40✔
364
        return fmt::format(R"("{}": {{ "{}": {} }})", name_, op2string(op_), value_);
80✔
365
    }
366

367
    enum class Op {
368
        gt = 1,
369
        lt = 2,
370
        gte = 3,
371
        lte = 4,
372
    };
373

374
    static Op string2op(const std::string& s) {
42✔
375
        if (s == "$gt") {
42✔
376
            return Op::gt;
10✔
377
        } else if (s == "$lt") {
32✔
378
            return Op::lt;
10✔
379
        } else if (s == "$gte") {
22✔
380
            return Op::gte;
10✔
381
        } else if (s == "$lte") {
12✔
382
            return Op::lte;
10✔
383
        }
384
        throw SonataError(fmt::format("Operator '{}' not available for numeric", s));
4✔
385
    }
386

387
    static std::string op2string(const Op op) {
40✔
388
        switch (op) {
40✔
389
        case Op::gt:
10✔
390
            return "$gt";
10✔
391
        case Op::lt:
10✔
392
            return "$lt";
10✔
393
        case Op::gte:
10✔
394
            return "$gte";
10✔
395
        case Op::lte:
10✔
396
            return "$lte";
10✔
397
        default:              // LCOV_EXCL_LINE
398
            LIBSONATA_THROW_IF_REACHED  // LCOV_EXCL_LINE
399
        }
400
    }
401

402
    std::unique_ptr<NodeSetRule> clone() const final
×
403
    {
404
        return std::make_unique<detail::NodeSetBasicOperatorNumeric>(name_, op2string(op_), value_);
×
405
    }
406

407
  private:
408
    std::string name_;
409
    double value_;
410
    Op op_;
411
};
412

413
using CompoundTargets = std::vector<std::string>;
414
class NodeSetCompoundRule: public NodeSetRule
415
{
416
  public:
417
    NodeSetCompoundRule(std::string name, CompoundTargets targets)
32✔
418
        : name_(std::move(name))
64✔
419
        , targets_(std::move(targets)) {}
32✔
420

421
    Selection materialize(const detail::NodeSets& ns, const NodePopulation& np) const final {
50✔
422
        Selection ret{{}};
50✔
423
        for (const auto& target : targets_) {
150✔
424
            ret = ret | ns.materialize(target, np);
100✔
425
        }
426
        return ret;
50✔
427
    }
428

429
    std::string toJSON() const final {
6✔
430
        return toString("node_ids", targets_);
6✔
431
    }
432

433
    bool is_compound() const override {
184✔
434
        return true;
184✔
435
    }
436
    const CompoundTargets& getTargets() const {
92✔
437
        return targets_;
92✔
438
    }
439

440
    std::unique_ptr<NodeSetRule> clone() const final
×
441
    {
442
        return std::make_unique<detail::NodeSetCompoundRule>(name_, targets_);
×
443
    }
444

445
  private:
446
    std::string name_;
447
    CompoundTargets targets_;
448
};
449

450
int64_t get_int64_or_throw(const json& el) {
78✔
451
    auto v = el.get<double>();
78✔
452
    if (std::floor(v) != v) {
78✔
453
        throw SonataError(fmt::format("expected integer, got float {}", v));
×
454
    }
455
    return static_cast<int64_t>(v);
78✔
456
}
457

458
uint64_t get_uint64_or_throw(const json& el) {
6✔
459
    auto v = el.get<double>();
6✔
460
    if (v < 0) {
6✔
461
        throw SonataError(fmt::format("expected unsigned integer, got {}", v));
4✔
462
    }
463

464
    if (std::floor(v) != v) {
4✔
465
        throw SonataError(fmt::format("expected integer, got float {}", v));
4✔
466
    }
467
    return static_cast<uint64_t>(v);
2✔
468
}
469

470
NodeSetRulePtr _dispatch_node(const std::string& attribute, const json& value) {
156✔
471
    if (value.is_number()) {
156✔
472
        if (attribute == "population") {
14✔
473
            throw SonataError("'population' must be a string");
2✔
474
        }
475

476
        if (attribute == "node_id") {
12✔
477
            Selection::Values node_ids{get_uint64_or_throw(value)};
6✔
478
            return std::make_unique<NodeSetBasicNodeIds>(std::move(node_ids));
2✔
479
        } else {
480
            std::vector<int64_t> f = {get_int64_or_throw(value)};
6✔
481
            return std::make_unique<NodeSetBasicRule<int64_t>>(attribute, f);
6✔
482
        }
483
    } else if (value.is_string()) {
142✔
484
        if (attribute == "node_id") {
28✔
485
            throw SonataError("'node_id' must be numeric or a list of numbers");
2✔
486
        }
487

488
        if (attribute == "population") {
26✔
489
            std::vector<std::string> v{value.get<std::string>()};
30✔
490
            return std::make_unique<NodeSetBasicPopulation>(v);
10✔
491
        } else {
492
            std::vector<std::string> f = {value.get<std::string>()};
48✔
493
            return std::make_unique<NodeSetBasicRule<std::string>>(attribute, f);
16✔
494
        }
495
    } else if (value.is_array()) {
114✔
496
        const auto& array = value;
54✔
497

498
        if (array.empty()) {
54✔
499
            return std::make_unique<NodeSetNullRule>();
4✔
500
        }
501

502
        if (array[0].is_number()) {
50✔
503
            if (attribute == "population") {
24✔
504
                throw SonataError("'population' must be a string");
2✔
505
            }
506

507
            std::vector<int64_t> values;
44✔
508
            for (auto& inner_el : array.items()) {
94✔
509
                values.emplace_back(get_int64_or_throw(inner_el.value()));
72✔
510
            }
511

512
            if (attribute == "node_id") {
22✔
513
                Selection::Values node_ids;
20✔
514
                std::transform(begin(values),
515
                               end(values),
516
                               back_inserter(node_ids),
517
                               [](int64_t integer) {
56✔
518
                                   if (integer < 0) {
56✔
519
                                       throw SonataError("'node_id' must be positive");
2✔
520
                                   }
521
                                   return static_cast<Selection::Value>(integer);
54✔
522
                               });
18✔
523
                return std::make_unique<NodeSetBasicNodeIds>(std::move(node_ids));
16✔
524
            } else {
525
                return std::make_unique<NodeSetBasicRule<int64_t>>(attribute, values);
4✔
526
            }
527
        } else if (array[0].is_string()) {
26✔
528
            if (attribute == "node_id") {
24✔
529
                throw SonataError("'node_id' must be numeric or a list of numbers");
2✔
530
            }
531

532
            std::vector<std::string> values;
44✔
533
            for (auto& inner_el : array.items()) {
60✔
534
                values.emplace_back(inner_el.value().get<std::string>());
38✔
535
            }
536

537
            if (attribute == "population") {
22✔
538
                return std::make_unique<NodeSetBasicPopulation>(values);
4✔
539
            } else {
540
                return std::make_unique<NodeSetBasicRule<std::string>>(attribute, values);
18✔
541
            }
542
        } else {
543
            throw SonataError("Unknown array type");
2✔
544
        }
545
    } else if (value.is_object()) {
60✔
546
        const auto& definition = value;
60✔
547
        if (definition.size() != 1) {
60✔
548
            throw SonataError(
549
                fmt::format("Operator '{}' must have object with one key value pair", attribute));
4✔
550
        }
551
        const auto& key = definition.begin().key();
58✔
552
        const auto& value = definition.begin().value();
58✔
553

554
        if (value.is_number()) {
58✔
555
            return std::make_unique<NodeSetBasicOperatorNumeric>(attribute,
40✔
556
                                                                 key,
557
                                                                 value.get<double>());
82✔
558
        } else if (value.is_string()) {
16✔
559
            return std::make_unique<NodeSetBasicOperatorString>(attribute,
26✔
560
                                                                key,
561
                                                                value.get<std::string>());
42✔
562
        } else {
563
            throw SonataError("Unknown operator");
2✔
564
        }
565
    } else {
566
        LIBSONATA_THROW_IF_REACHED  // LCOV_EXCL_LINE
567
    }
568
}
569

570
void parse_basic(const json& j, std::map<std::string, NodeSetRulePtr>& node_sets) {
82✔
571
    for (const auto& el : j.items()) {
254✔
572
        const auto& value = el.value();
148✔
573
        if (value.is_object()) {
148✔
574
            if (value.empty()) {
106✔
575
                // ignore
576
            } else if (value.size() == 1) {
106✔
577
                const auto& inner_el = value.items().begin();
104✔
578
                node_sets[el.key()] = _dispatch_node(inner_el.key(), inner_el.value());
80✔
579
            } else {
580
                std::vector<NodeSetRulePtr> clauses;
26✔
581
                for (const auto& inner_el : value.items()) {
102✔
582
                    clauses.push_back(_dispatch_node(inner_el.key(), inner_el.value()));
76✔
583
                }
584
                node_sets[el.key()] = std::make_unique<NodeSetBasicMultiClause>(std::move(clauses));
26✔
585
            }
586
        } else if (value.is_array()) {
42✔
587
            // will be parsed by the parse_compound
588
        } else {
589
            // null/boolean/number/string ?
590
            throw SonataError(fmt::format("Expected an array or an object, got: {}", value.dump()));
×
591
        }
592
    }
593
}
58✔
594

595
void check_compound(const std::map<std::string, NodeSetRulePtr>& node_sets,
104✔
596
                    const std::map<std::string, CompoundTargets>& compound_rules,
597
                    const std::string& name,
598
                    size_t depth) {
599
    if (node_sets.count(name) > 0) {
104✔
600
        return;
42✔
601
    }
602

603
    if (depth > MAX_COMPOUND_RECURSION) {
62✔
604
        throw SonataError("Compound node_set recursion depth exceeded");
2✔
605
    }
606

607
    const auto it = compound_rules.find(name);
60✔
608
    assert(it != compound_rules.end());
60✔
609

610
    for (auto const& target : it->second) {
102✔
611
        if (node_sets.count(target) == 0 && compound_rules.count(target) == 0) {
70✔
612
            throw SonataError(fmt::format("Missing '{}' from node_sets", target));
8✔
613
        }
614
        check_compound(node_sets, compound_rules, target, depth + 1);
66✔
615
    }
616
}
617

618
void parse_compound(const json& j, std::map<std::string, NodeSetRulePtr>& node_sets) {
58✔
619
    std::map<std::string, CompoundTargets> compound_rules;
116✔
620
    for (auto& el : j.items()) {
184✔
621
        if (el.value().is_array()) {
124✔
622
            CompoundTargets targets;
84✔
623
            for (const auto& name : el.value()) {
94✔
624
                if (!name.is_string()) {
54✔
625
                    throw SonataError("All compound elements must be strings");
2✔
626
                }
627

628
                targets.emplace_back(name);
52✔
629
            }
630
            compound_rules[el.key()] = targets;
40✔
631
        }
632
    }
633

634

635
    for (const auto& rule : compound_rules) {
88✔
636
        check_compound(node_sets, compound_rules, rule.first, 0);
38✔
637

638
        NodeSetRulePtr rules = std::make_unique<NodeSetCompoundRule>(rule.first, rule.second);
64✔
639
        node_sets.emplace(rule.first, std::move(rules));
32✔
640
    }
641
}
50✔
642

643
Selection NodeSets::materialize(const std::string& name, const NodePopulation& population) const {
158✔
644
    const auto& node_set = node_sets_.find(name);
158✔
645
    if (node_set == node_sets_.end()) {
158✔
646
        throw SonataError(fmt::format("Unknown node_set {}", name));
4✔
647
    }
648
    const auto& ns = node_set->second;
156✔
649
    if (!ns->is_compound()) {
156✔
650
        return population.selectAll() & ns->materialize(*this, population);
248✔
651
    }
652

653
    // it's common to have a deep structure of compound statements
654
    // (ie: a whole hierarchy of regions), all checking the same attribute
655
    // rather than `materializing` them separately, we group them, and materialize
656
    // them all at once
657
    Selection ret{{}};
64✔
658

659
    std::vector<NodeSetRule*> queue{ns.get()};
64✔
660
    std::map<std::string, std::set<std::string>> attribute2rule_strings;
64✔
661
    std::map<std::string, std::set<int64_t>> attribute2rule_int64;
64✔
662
    while (!queue.empty()) {
124✔
663
        const auto* ns = queue.back();
92✔
664
        queue.pop_back();
92✔
665

666
        if (ns->is_compound()) {
92✔
667
            const auto* targets = dynamic_cast<const NodeSetCompoundRule*>(ns);
92✔
668
            for (const auto& target : targets->getTargets()) {
222✔
669
                const auto& node_set = node_sets_.find(target)->second;
130✔
670
                if (node_set->is_compound()) {
130✔
671
                    queue.push_back(node_set.get());
60✔
672
                    continue;
60✔
673
                }
674

675
                {
676
                    const auto* basic_int = dynamic_cast<const NodeSetBasicRule<int64_t>*>(
70✔
677
                        node_set.get());
140✔
678
                    if (basic_int != nullptr) {
70✔
679
                        basic_int->add_attribute2rule(attribute2rule_int64);
10✔
680
                        continue;
10✔
681
                    }
682
                }
683

684
                {
685
                    const auto* basic_string = dynamic_cast<const NodeSetBasicRule<std::string>*>(
60✔
686
                        node_set.get());
120✔
687
                    if (basic_string != nullptr) {
60✔
688
                        basic_string->add_attribute2rule(attribute2rule_strings);
10✔
689
                        continue;
10✔
690
                    }
691
                }
692

693
                ret = ret | ns->materialize(*this, population);
50✔
694
            }
695
        } else {
696
            ret = ret | ns->materialize(*this, population);
×
697
        }
698
    }
699

700
    for (const auto& it : attribute2rule_strings) {
42✔
701
        std::vector<std::string> values(it.second.begin(), it.second.end());
10✔
702
        ret = ret | population.matchAttributeValues(it.first, values);
10✔
703
    }
704

705
    for (const auto& it : attribute2rule_int64) {
42✔
706
        std::vector<int64_t> values(it.second.begin(), it.second.end());
10✔
707
        ret = ret | population.matchAttributeValues(it.first, values);
10✔
708
    }
709

710
    return ret;
32✔
711
}
712
}  // namespace detail
713

714
NodeSets::NodeSets(const std::string& content)
84✔
715
    : impl_(new detail::NodeSets(content)) {}
84✔
716

717
NodeSets::NodeSets(std::unique_ptr<detail::NodeSets>&& impl)
2✔
718
    : impl_(std::move(impl)) {}
2✔
719

720
NodeSets::NodeSets(NodeSets&&) noexcept = default;
721
NodeSets& NodeSets::operator=(NodeSets&&) noexcept = default;
722
NodeSets::~NodeSets() = default;
723

724
NodeSets NodeSets::fromFile(const std::string& path) {
2✔
725
    return NodeSets(detail::NodeSets::fromFile(path));
4✔
726
}
727

728
Selection NodeSets::materialize(const std::string& name, const NodePopulation& population) const {
58✔
729
    return impl_->materialize(name, population);
58✔
730
}
731

732
std::set<std::string> NodeSets::names() const {
2✔
733
    return impl_->names();
2✔
734
}
735

736
std::set<std::string> NodeSets::extend(const NodeSets& other) const {
×
737
    return impl_->extend(*other.impl_);
×
738
}
739

740
std::string NodeSets::toJSON() const {
10✔
741
    return impl_->toJSON();
10✔
742
}
743

744
}  // namespace sonata
745
}  // 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