Skip to content

Commit

Permalink
Call the matching predicate over multiple rounds
Browse files Browse the repository at this point in the history
This allows for hierarchical matching, where in the first round only
very refined matching is performed and then the condition becomes
increasingly looser.
  • Loading branch information
vimpostor committed Sep 28, 2023
1 parent 978a011 commit e425313
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 22 deletions.
34 changes: 28 additions & 6 deletions include/transactions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,21 @@

namespace fair::graph {

static auto nullMatchPred = [](auto, auto) { return false; };
static auto nullMatchPred = [](auto, auto, auto) { return std::nullopt; };

template<typename Node>
class ctx_settings : public settings_base {
using MatchPredicate = std::function<bool(const property_map &, const property_map &)>;
/**
* A predicate for matching two contexts
* The third "attempt" parameter indicates the current round of matching being done.
* This is useful for hierarchical matching schemes,
* e.g. in the first round the predicate could look for almost exact matches only,
* then in a a second round (attempt=1) it could be more forgiving, given that there are no exact matches available.
*
* The predicate will be called until it returns "true" (a match is found), or until it returns std::nullopt,
* which indicates that no matches were found and there is no chance of matching anything in a further round.
*/
using MatchPredicate = std::function<std::optional<bool>(const property_map &, const property_map &, std::size_t)>;

Node *_node = nullptr;
mutable std::mutex _lock{};
Expand Down Expand Up @@ -249,10 +259,8 @@ class ctx_settings : public settings_base {
ret = exact_match;
} else {
// try the match predicate instead
auto match = std::find_if(_settings.begin(), _settings.end(), [&](const auto &i) { return _match_pred(i.first.context, ctx.context); });
if (match != _settings.end()) {
ret = match->second;
}
const auto &match = bestMatch(ctx.context);
ret = match.value_or(ret);
}

// return only the needed values
Expand Down Expand Up @@ -402,6 +410,20 @@ class ctx_settings : public settings_base {
}

private:
std::optional<property_map>
bestMatch(const property_map &context) const {
// retry until we either get a match or std::nullopt
for (std::size_t attempt = 0;; ++attempt) {
for (const auto &i : _settings) {
const auto matchres = _match_pred(i.first.context, context, attempt);
if (!matchres) {
return std::nullopt;
} else if (*matchres) {
return i.second;
}
}
}
}
void
store_default_settings(property_map &oldSettings) {
// take a copy of the field -> map value of the old settings
Expand Down
43 changes: 27 additions & 16 deletions test/qa_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -565,29 +565,40 @@ const boost::ut::suite TransactionTests = [] {
expect(eq(std::get<float>(*s.get("scaling_factor", ctx0)), 42.f)); // get value with an older timestamp
};

auto matchPred = [](const auto &lhs, const auto &rhs) {
return !lhs.empty() && std::all_of(lhs.begin(), lhs.end(), [&](const auto &v) { return rhs.contains(v.first) && rhs.at(v.first) == v.second; });
auto matchPred = [](const auto &lhs, const auto &rhs, const auto attempt) -> std::optional<bool> {
if (attempt >= 4) {
return std::nullopt;
}

constexpr std::array fields = { "BPCID", "SID", "BPID", "GID" };
// require increasingly less fields to match for each attempt
return std::ranges::all_of(fields | std::ranges::views::take(4 - attempt), [&](const auto &f) { return lhs.contains(f) && rhs.at(f) == lhs.at(f); });
};

"CtxSettings Parsing"_test = [&] {
graph flow_graph;
auto &block = flow_graph.make_node<TestBlock<float>>({ { "name", "TestName" }, { "scaling_factor", 2.f } });
auto &block = flow_graph.make_node<TestBlock<int>>({ { "scaling_factor", 42 } });
auto s = ctx_settings(block, matchPred);
const auto ctx0 = SettingsCtx(std::chrono::system_clock::now(), { { "BPCID", 1 } });
std::ignore = s.set({ { "name", "TestNameAlt" }, { "scaling_factor", 42.f } }, ctx0);
const auto ctx1 = SettingsCtx(std::chrono::system_clock::now(), { { "BPCID", 1 }, { "SID", 2 } });
std::ignore = s.set({ { "name", "TestNameNew" }, { "scaling_factor", 43.f } }, ctx1);
const auto ctx0 = SettingsCtx(std::chrono::system_clock::now(), { { "BPCID", 1 }, { "SID", 1 }, { "BPID", 1 }, { "GID", 1 } });
std::ignore = s.set({ { "scaling_factor", 101 } }, ctx0);
const auto ctx1 = SettingsCtx(std::chrono::system_clock::now(), { { "BPCID", 1 }, { "SID", 1 }, { "BPID", 1 } });
std::ignore = s.set({ { "scaling_factor", 102 } }, ctx1);
const auto ctx2 = SettingsCtx(std::chrono::system_clock::now(), { { "BPCID", 1 }, { "SID", 1 } });
std::ignore = s.set({ { "scaling_factor", 103 } }, ctx2);
const auto ctx3 = SettingsCtx(std::chrono::system_clock::now(), { { "BPCID", 1 } });
std::ignore = s.set({ { "scaling_factor", 104 } }, ctx3);

// exact matches for contexts work
expect(eq(std::get<float>(*s.get("scaling_factor", ctx0)), 42.f));
expect(eq(std::get<float>(*s.get("scaling_factor", ctx1)), 43.f));

// matching by using the custom predicate
auto ctx3 = SettingsCtx(std::chrono::system_clock::now(), { { "BPCID", 1 }, { "SID", 2 }, { "BPID", 1 } });
expect(eq(std::get<float>(*s.get("scaling_factor", ctx3)), 43.f));
auto ctx4 = SettingsCtx(std::chrono::system_clock::now(), { { "BPCID", 1 }, { "SID", 2 }, { "BPID", 3 } });
std::ignore = s.set({ { "scaling_factor", 44.f } }, ctx4);
expect(eq(std::get<float>(*s.get("scaling_factor", ctx4)), 44.f));
expect(eq(std::get<int>(*s.get("scaling_factor", ctx0)), 101));
expect(eq(std::get<int>(*s.get("scaling_factor", ctx1)), 102));
expect(eq(std::get<int>(*s.get("scaling_factor", ctx2)), 103));
expect(eq(std::get<int>(*s.get("scaling_factor", ctx3)), 104));

// matching by using the custom predicate (no exact matching possible anymore)
const auto ctx4 = SettingsCtx(std::chrono::system_clock::now(), { { "BPCID", 1 }, { "SID", 1 }, { "BPID", 1 }, { "GID", 2 } });
expect(eq(std::get<int>(*s.get("scaling_factor", ctx4)), 102)); // no setting for 'gid=2' -> fall back to 'gid=-1'
const auto ctx5 = SettingsCtx(std::chrono::system_clock::now(), { { "BPCID", 1 }, { "SID", 1 }, { "BPID", 2 }, { "GID", 2 } });
expect(eq(std::get<int>(*s.get("scaling_factor", ctx5)), 103)); // no setting for 'pid=2' and 'gid=2' -> fall back to 'pid=gid=-1'

// doesn't exist
auto ctx6 = SettingsCtx(std::chrono::system_clock::now(), { { "BPCID", 9 }, { "SID", 9 }, { "BPID", 9 }, { "GID", 9 } });
Expand Down

0 comments on commit e425313

Please sign in to comment.