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

BlueBrain / libsonata / 6219514691

18 Sep 2023 07:32AM UTC coverage: 93.737% (-1.6%) from 95.385%
6219514691

push

github

web-flow
initial test of NodeSet update (#283)

Can now do the following:

  ns0 = libsonata.NodeSets.from_file('path/to/config_json defined NodeSets')
  ns1 = libsonata.NodeSets.from_file('....')
  duplicates = ns0.update(ns1)

This adds the node sets from ns1 to ns0, and returns the duplicate names.

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

1796 of 1916 relevant lines covered (93.74%)

75.19 hits per line

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

88.73
/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> update(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
        return std::make_unique<detail::NodeSetNullRule>();
×
146
    }
147
};
148

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

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

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

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

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

178
  private:
179
    std::string attribute_;
180
    std::vector<T> values_;
181
};
182

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

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

196
        return Selection{{}};
2✔
197
    }
198

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

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

207
  private:
208
    std::vector<std::string> values_;
209
};
210

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

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

223
    std::string toJSON() const final {
6✔
224
        return toString("node_ids", values_);
6✔
225
    }
226

227
    std::unique_ptr<NodeSetRule> clone() const final {
×
228
        return std::make_unique<detail::NodeSetBasicNodeIds>(values_);
×
229
    }
230

231
  private:
232
    Selection::Values values_;
233
};
234

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

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

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

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

273
  private:
274
    std::vector<NodeSetRulePtr> clauses_;
275
};
276

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

288
    Selection materialize(const detail::NodeSets& /* unused */,
4✔
289
                          const NodePopulation& np) const final {
290
        switch (op_) {
4✔
291
        case Op::regex:
4✔
292
            return np.regexMatch(attribute_, value_);
4✔
293
        default:              // LCOV_EXCL_LINE
294
            LIBSONATA_THROW_IF_REACHED  // LCOV_EXCL_LINE
295
        }
296
    }
297

298
    std::string toJSON() const final {
10✔
299
        return fmt::format(R"("{}": {{ "{}": "{}" }})", attribute_, op2string(op_), value_);
20✔
300
    }
301

302
    enum class Op {
303
        regex = 1,
304
    };
305

306
    static Op string2op(const std::string& s) {
14✔
307
        if (s == "$regex") {
14✔
308
            return Op::regex;
12✔
309
        }
310
        throw SonataError(fmt::format("Operator '{}' not available for strings", s));
4✔
311
    }
312

313
    static std::string op2string(const Op op) {
10✔
314
        switch (op) {
10✔
315
        case Op::regex:
10✔
316
            return "$regex";
10✔
317
        default:              // LCOV_EXCL_LINE
318
            LIBSONATA_THROW_IF_REACHED  // LCOV_EXCL_LINE
319
        }
320
    }
321

322
    std::unique_ptr<NodeSetRule> clone() const final {
×
323
        return std::make_unique<detail::NodeSetBasicOperatorString>(attribute_,
×
324
                                                                    op2string(op_),
×
325
                                                                    value_);
×
326
    }
327

328
  private:
329
    Op op_;
330
    std::string attribute_;
331
    std::string value_;
332
};
333

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

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

359
    std::string toJSON() const final {
40✔
360
        return fmt::format(R"("{}": {{ "{}": {} }})", name_, op2string(op_), value_);
80✔
361
    }
362

363
    enum class Op {
364
        gt = 1,
365
        lt = 2,
366
        gte = 3,
367
        lte = 4,
368
    };
369

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

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

398
    std::unique_ptr<NodeSetRule> clone() const final {
×
399
        return std::make_unique<detail::NodeSetBasicOperatorNumeric>(name_, op2string(op_), value_);
×
400
    }
401

402
  private:
403
    std::string name_;
404
    double value_;
405
    Op op_;
406
};
407

408
using CompoundTargets = std::vector<std::string>;
409
class NodeSetCompoundRule: public NodeSetRule
410
{
411
  public:
412
    NodeSetCompoundRule(std::string name, CompoundTargets targets)
32✔
413
        : name_(std::move(name))
64✔
414
        , targets_(std::move(targets)) {}
32✔
415

416
    Selection materialize(const detail::NodeSets& ns, const NodePopulation& np) const final {
50✔
417
        Selection ret{{}};
50✔
418
        for (const auto& target : targets_) {
150✔
419
            ret = ret | ns.materialize(target, np);
100✔
420
        }
421
        return ret;
50✔
422
    }
423

424
    std::string toJSON() const final {
6✔
425
        return toString("node_ids", targets_);
6✔
426
    }
427

428
    bool is_compound() const override {
184✔
429
        return true;
184✔
430
    }
431
    const CompoundTargets& getTargets() const {
92✔
432
        return targets_;
92✔
433
    }
434

435
    std::unique_ptr<NodeSetRule> clone() const final {
×
436
        return std::make_unique<detail::NodeSetCompoundRule>(name_, targets_);
×
437
    }
438

439
  private:
440
    std::string name_;
441
    CompoundTargets targets_;
442
};
443

444
int64_t get_int64_or_throw(const json& el) {
78✔
445
    auto v = el.get<double>();
78✔
446
    if (std::floor(v) != v) {
78✔
447
        throw SonataError(fmt::format("expected integer, got float {}", v));
×
448
    }
449
    return static_cast<int64_t>(v);
78✔
450
}
451

452
uint64_t get_uint64_or_throw(const json& el) {
6✔
453
    auto v = el.get<double>();
6✔
454
    if (v < 0) {
6✔
455
        throw SonataError(fmt::format("expected unsigned integer, got {}", v));
4✔
456
    }
457

458
    if (std::floor(v) != v) {
4✔
459
        throw SonataError(fmt::format("expected integer, got float {}", v));
4✔
460
    }
461
    return static_cast<uint64_t>(v);
2✔
462
}
463

464
NodeSetRulePtr _dispatch_node(const std::string& attribute, const json& value) {
156✔
465
    if (value.is_number()) {
156✔
466
        if (attribute == "population") {
14✔
467
            throw SonataError("'population' must be a string");
2✔
468
        }
469

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

482
        if (attribute == "population") {
26✔
483
            std::vector<std::string> v{value.get<std::string>()};
30✔
484
            return std::make_unique<NodeSetBasicPopulation>(v);
10✔
485
        } else {
486
            std::vector<std::string> f = {value.get<std::string>()};
48✔
487
            return std::make_unique<NodeSetBasicRule<std::string>>(attribute, f);
16✔
488
        }
489
    } else if (value.is_array()) {
114✔
490
        const auto& array = value;
54✔
491

492
        if (array.empty()) {
54✔
493
            return std::make_unique<NodeSetNullRule>();
4✔
494
        }
495

496
        if (array[0].is_number()) {
50✔
497
            if (attribute == "population") {
24✔
498
                throw SonataError("'population' must be a string");
2✔
499
            }
500

501
            std::vector<int64_t> values;
44✔
502
            for (auto& inner_el : array.items()) {
94✔
503
                values.emplace_back(get_int64_or_throw(inner_el.value()));
72✔
504
            }
505

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

526
            std::vector<std::string> values;
44✔
527
            for (auto& inner_el : array.items()) {
60✔
528
                values.emplace_back(inner_el.value().get<std::string>());
38✔
529
            }
530

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

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

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

589
void check_compound(const std::map<std::string, NodeSetRulePtr>& node_sets,
104✔
590
                    const std::map<std::string, CompoundTargets>& compound_rules,
591
                    const std::string& name,
592
                    size_t depth) {
593
    if (node_sets.count(name) > 0) {
104✔
594
        return;
42✔
595
    }
596

597
    if (depth > MAX_COMPOUND_RECURSION) {
62✔
598
        throw SonataError("Compound node_set recursion depth exceeded");
2✔
599
    }
600

601
    const auto it = compound_rules.find(name);
60✔
602
    assert(it != compound_rules.end());
60✔
603

604
    for (auto const& target : it->second) {
102✔
605
        if (node_sets.count(target) == 0 && compound_rules.count(target) == 0) {
70✔
606
            throw SonataError(fmt::format("Missing '{}' from node_sets", target));
8✔
607
        }
608
        check_compound(node_sets, compound_rules, target, depth + 1);
66✔
609
    }
610
}
611

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

622
                targets.emplace_back(name);
52✔
623
            }
624
            compound_rules[el.key()] = targets;
40✔
625
        }
626
    }
627

628

629
    for (const auto& rule : compound_rules) {
88✔
630
        check_compound(node_sets, compound_rules, rule.first, 0);
38✔
631

632
        NodeSetRulePtr rules = std::make_unique<NodeSetCompoundRule>(rule.first, rule.second);
64✔
633
        node_sets.emplace(rule.first, std::move(rules));
32✔
634
    }
635
}
50✔
636

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

647
    // it's common to have a deep structure of compound statements
648
    // (ie: a whole hierarchy of regions), all checking the same attribute
649
    // rather than `materializing` them separately, we group them, and materialize
650
    // them all at once
651
    Selection ret{{}};
64✔
652

653
    std::vector<NodeSetRule*> queue{ns.get()};
64✔
654
    std::map<std::string, std::set<std::string>> attribute2rule_strings;
64✔
655
    std::map<std::string, std::set<int64_t>> attribute2rule_int64;
64✔
656
    while (!queue.empty()) {
124✔
657
        const auto* ns = queue.back();
92✔
658
        queue.pop_back();
92✔
659

660
        if (ns->is_compound()) {
92✔
661
            const auto* targets = dynamic_cast<const NodeSetCompoundRule*>(ns);
92✔
662
            for (const auto& target : targets->getTargets()) {
222✔
663
                const auto& node_set = node_sets_.find(target)->second;
130✔
664
                if (node_set->is_compound()) {
130✔
665
                    queue.push_back(node_set.get());
60✔
666
                    continue;
60✔
667
                }
668

669
                {
670
                    const auto* basic_int = dynamic_cast<const NodeSetBasicRule<int64_t>*>(
70✔
671
                        node_set.get());
140✔
672
                    if (basic_int != nullptr) {
70✔
673
                        basic_int->add_attribute2rule(attribute2rule_int64);
10✔
674
                        continue;
10✔
675
                    }
676
                }
677

678
                {
679
                    const auto* basic_string = dynamic_cast<const NodeSetBasicRule<std::string>*>(
60✔
680
                        node_set.get());
120✔
681
                    if (basic_string != nullptr) {
60✔
682
                        basic_string->add_attribute2rule(attribute2rule_strings);
10✔
683
                        continue;
10✔
684
                    }
685
                }
686

687
                ret = ret | ns->materialize(*this, population);
50✔
688
            }
689
        } else {
690
            ret = ret | ns->materialize(*this, population);
×
691
        }
692
    }
693

694
    for (const auto& it : attribute2rule_strings) {
42✔
695
        std::vector<std::string> values(it.second.begin(), it.second.end());
10✔
696
        ret = ret | population.matchAttributeValues(it.first, values);
10✔
697
    }
698

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

704
    return ret;
32✔
705
}
706
}  // namespace detail
707

708
NodeSets::NodeSets(const std::string& content)
84✔
709
    : impl_(new detail::NodeSets(content)) {}
84✔
710

711
NodeSets::NodeSets(std::unique_ptr<detail::NodeSets>&& impl)
2✔
712
    : impl_(std::move(impl)) {}
2✔
713

714
NodeSets::NodeSets(NodeSets&&) noexcept = default;
715
NodeSets& NodeSets::operator=(NodeSets&&) noexcept = default;
716
NodeSets::~NodeSets() = default;
717

718
NodeSets NodeSets::fromFile(const std::string& path) {
2✔
719
    return NodeSets(detail::NodeSets::fromFile(path));
4✔
720
}
721

722
Selection NodeSets::materialize(const std::string& name, const NodePopulation& population) const {
58✔
723
    return impl_->materialize(name, population);
58✔
724
}
725

726
std::set<std::string> NodeSets::names() const {
2✔
727
    return impl_->names();
2✔
728
}
729

730
std::set<std::string> NodeSets::update(const NodeSets& other) const {
×
731
    return impl_->update(*other.impl_);
×
732
}
733

734
std::string NodeSets::toJSON() const {
10✔
735
    return impl_->toJSON();
10✔
736
}
737

738
}  // namespace sonata
739
}  // 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