Skip to content

Commit

Permalink
add "power" matching (#147)
Browse files Browse the repository at this point in the history
* allow for simple expressions in node_sets as proposed to the offical SONATA spec in AllenInstitute/sonata#129
  • Loading branch information
mgeplf authored Jun 17, 2021
1 parent d6d9770 commit 4795217
Show file tree
Hide file tree
Showing 13 changed files with 399 additions and 84 deletions.
3 changes: 3 additions & 0 deletions include/bbp/sonata/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,8 @@ class SONATA_API SonataError: public std::runtime_error
public:
explicit SonataError(const std::string& what);
};

#define THROW_IF_REACHED throw SonataError("Should never be reached");

} // namespace sonata
} // namespace bbp
4 changes: 2 additions & 2 deletions include/bbp/sonata/node_sets.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ class SONATA_API NodeSets
* \throw if content cannot be parsed
*/
NodeSets(const std::string& content);
NodeSets(NodeSets&&);
NodeSets(NodeSets&&) noexcept;
NodeSets(const NodeSets& other) = delete;
NodeSets& operator=(NodeSets&&);
NodeSets& operator=(NodeSets&&) noexcept;
~NodeSets();

/** Open a SONATA `node sets` file from a path */
Expand Down
8 changes: 7 additions & 1 deletion include/bbp/sonata/nodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,20 @@ class SONATA_API NodePopulation: public Population
* Note: This does not match dynamics_params datasets
*/
template <typename T>
Selection matchAttributeValues(const std::string& attribute, const T value) const;
Selection matchAttributeValues(const std::string& attribute, const T values) const;

/**
* Like matchAttributeValues, but for vectors of values to match
*/
template <typename T>
Selection matchAttributeValues(const std::string& attribute,
const std::vector<T>& values) const;


/**
* For named attribute, return a selection where the passed regular expression matches
*/
Selection regexMatch(const std::string& attribute, const std::string& re) const;
};

//--------------------------------------------------------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions include/bbp/sonata/population.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "common.h"

#include <cstdint>
#include <functional>
#include <memory> // std::shared_ptr, std::unique_ptr
#include <set>
#include <string>
Expand Down Expand Up @@ -232,6 +233,9 @@ class SONATA_API Population
*/
std::string _dynamicsAttributeDataType(const std::string& name) const;

template <typename T>
Selection filterAttribute(const std::string& name, std::function<bool(const T)> pred) const;

protected:
Population(const std::string& h5FilePath,
const std::string& csvFilePath,
Expand Down
6 changes: 6 additions & 0 deletions python/generated/docstrings.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ Note: This does not match dynamics_params datasets)doc";

static const char *__doc_bbp_sonata_NodePopulation_matchAttributeValues_2 = R"doc(Like matchAttributeValues, but for vectors of values to match)doc";

static const char *__doc_bbp_sonata_NodePopulation_regexMatch =
R"doc(For named attribute, return a selection where the passed regular
expression matches)doc";

static const char *__doc_bbp_sonata_NodeSets = R"doc()doc";

static const char *__doc_bbp_sonata_NodeSets_NodeSets =
Expand Down Expand Up @@ -182,6 +186,8 @@ Parameter ``name``:
Throws:
if there is no such attribute for the population)doc";

static const char *__doc_bbp_sonata_Population_filterAttribute = R"doc()doc";

static const char *__doc_bbp_sonata_Population_getAttribute =
R"doc(Get attribute values for given {element} Selection
Expand Down
9 changes: 9 additions & 0 deletions python/tests/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,15 @@ def test_NodeSet_toJSON(self):
"model_type": "point",
"node_id": [1, 2, 3, 5, 7, 9]
},
"power_number_test": {
"numeric_attribute_gt": { "$gt": 3 },
"numeric_attribute_lt": { "$lt": 3 },
"numeric_attribute_gte": { "$gte": 3 },
"numeric_attribute_lte": { "$lte": 3 }
},
"power_regex_test": {
"string_attr": { "$regex": "^[s][o]me value$" }
},
"combined": ["bio_layer45", "V1_point_prime"]
}'''
new = NodeSets(j).toJSON()
Expand Down
153 changes: 147 additions & 6 deletions src/node_sets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class NodeSets;
class NodeSetRule
{
public:
virtual ~NodeSetRule(){};
virtual ~NodeSetRule() = default;

virtual Selection materialize(const NodeSets&, const NodePopulation&) const = 0;
virtual std::string toJSON() const = 0;
Expand All @@ -51,7 +51,7 @@ class NodeSets
std::map<std::string, NodeSetRules> node_sets_;

public:
NodeSets(const std::string& content) {
explicit NodeSets(const std::string& content) {
json j = json::parse(content);
if (!j.is_object()) {
throw SonataError("Top level node_set must be an object");
Expand Down Expand Up @@ -176,13 +176,134 @@ class NodeSetBasicNodeIds: public NodeSetRule
Selection::Values values_;
};

class NodeSetBasicOperatorString: public NodeSetRule
{
public:
explicit NodeSetBasicOperatorString(const std::string& attribute,
const std::string& op,
const std::string& value)
: op_(string2op(op))
, attribute_(attribute)
, value_(value) {}

Selection materialize(const detail::NodeSets& /* unused */,
const NodePopulation& np) const final {
switch (op_) {
case Op::regex:
return np.regexMatch(attribute_, value_);
default: // LCOV_EXCL_LINE
THROW_IF_REACHED // LCOV_EXCL_LINE
}
}

std::string toJSON() const final {
return fmt::format(R"("{}": {{ "{}": "{}" }})", attribute_, op2string(op_), value_);
}

enum class Op {
regex = 1,
};

static Op string2op(const std::string& s) {
if (s == "$regex") {
return Op::regex;
}
throw SonataError(fmt::format("Operator '{}' not available for strings", s));
}

static std::string op2string(const Op op) {
switch (op) {
case Op::regex:
return "$regex";
default: // LCOV_EXCL_LINE
THROW_IF_REACHED // LCOV_EXCL_LINE
}
}

private:
Op op_;
std::string attribute_;
std::string value_;
};

class NodeSetBasicOperatorNumeric: public NodeSetRule
{
public:
explicit NodeSetBasicOperatorNumeric(const std::string& name,
const std::string& op,
double value)
: name_(name)
, value_(value)
, op_(string2op(op)) {}

Selection materialize(const detail::NodeSets& /* unused */,
const NodePopulation& np) const final {
switch (op_) {
case Op::gt:
return np.filterAttribute<double>(name_, [=](const double v) { return v > value_; });
case Op::lt:
return np.filterAttribute<double>(name_, [=](const double v) { return v < value_; });
case Op::gte:
return np.filterAttribute<double>(name_, [=](const double v) { return v >= value_; });
case Op::lte:
return np.filterAttribute<double>(name_, [=](const double v) { return v <= value_; });
default: // LCOV_EXCL_LINE
THROW_IF_REACHED // LCOV_EXCL_LINE
}
}

std::string toJSON() const final {
return fmt::format(R"("{}": {{ "{}": {} }})", name_, op2string(op_), value_);
}

enum class Op {
gt = 1,
lt = 2,
gte = 3,
lte = 4,
};

static Op string2op(const std::string& s) {
if (s == "$gt") {
return Op::gt;
} else if (s == "$lt") {
return Op::lt;
} else if (s == "$gte") {
return Op::gte;
} else if (s == "$lte") {
return Op::lte;
}
throw SonataError(fmt::format("Operator '{}' not available for numeric", s));
}

static std::string op2string(const Op op) {
switch (op) {
case Op::gt:
return "$gt";
case Op::lt:
return "$lt";
case Op::gte:
return "$gte";
case Op::lte:
return "$lte";
default: // LCOV_EXCL_LINE
THROW_IF_REACHED // LCOV_EXCL_LINE
}
}

private:
std::string name_;
double value_;
Op op_;
};

using CompoundTargets = std::vector<std::string>;
class NodeSetCompoundRule: public NodeSetRule
{
public:
NodeSetCompoundRule(std::string name, const CompoundTargets& targets)
NodeSetCompoundRule(std::string name, CompoundTargets targets)
: name_(std::move(name))
, targets_(targets) {}
, targets_(std::move(targets)) {}

Selection materialize(const detail::NodeSets& ns, const NodePopulation& np) const final {
Selection ret{{}};
Expand Down Expand Up @@ -287,6 +408,26 @@ NodeSetRules _dispatch_node(const json& contents) {
} else {
throw SonataError("Unknown array type");
}
} else if (el.value().is_object()) {
const auto& definition = el.value();
if (definition.size() != 1) {
throw SonataError(
fmt::format("Operator '{}' must have object with one key value pair",
attribute));
}
const auto& key = definition.begin().key();
const auto& value = definition.begin().value();
if (value.is_number()) {
ret.emplace_back(
new NodeSetBasicOperatorNumeric(attribute, key, value.get<double>()));
} else if (value.is_string()) {
ret.emplace_back(
new NodeSetBasicOperatorString(attribute, key, value.get<std::string>()));
} else {
throw SonataError("Unknown operator");
}
} else {
THROW_IF_REACHED // LCOV_EXCL_LINE
}
}
return ret;
Expand Down Expand Up @@ -354,8 +495,8 @@ void parse_compound(const json& j, std::map<std::string, NodeSetRules>& node_set
NodeSets::NodeSets(const std::string& content)
: impl_(new detail::NodeSets(content)) {}

NodeSets::NodeSets(NodeSets&&) = default;
NodeSets& NodeSets::operator=(NodeSets&&) = default;
NodeSets::NodeSets(NodeSets&&) noexcept = default;
NodeSets& NodeSets::operator=(NodeSets&&) noexcept = default;
NodeSets::~NodeSets() = default;

NodeSets NodeSets::fromFile(const std::string& path) {
Expand Down
Loading

0 comments on commit 4795217

Please sign in to comment.