Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement set based aod verifier, support aod mining in fastod #468

Open
wants to merge 25 commits into
base: main
Choose a base branch
from

Conversation

polyntsov
Copy link
Collaborator

No description provided.

@polyntsov polyntsov force-pushed the aod branch 2 times, most recently from d7ece66 to 7794d3a Compare September 30, 2024 20:07
Copy link
Collaborator

@ol-imorozko ol-imorozko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First three commits:

Comment on lines 5 to 29
namespace config {

ColumnIndexOption::ColumnIndexOption(
std::string_view name, std::string_view description,
typename Option<config::IndexType>::DefaultFunc get_default_value)
: common_option_(name, description, std::move(get_default_value)) {}

std::string_view ColumnIndexOption::GetName() const {
return common_option_.GetName();
}

Option<config::IndexType> ColumnIndexOption::operator()(
config::IndexType* value_ptr, std::function<config::IndexType()> get_col_count,
typename Option<config::IndexType>::ValueCheckFunc value_check_func) const {
Option<config::IndexType> option = common_option_(value_ptr);
option.SetValueCheck(
[get_col_count = std::move(get_col_count),
value_check_func = std::move(value_check_func)](config::IndexType const index) {
config::ValidateIndex(index, get_col_count());
if (value_check_func) {
value_check_func(index);
}
});
return option;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're already in config namespace so things like config::IndexType or config::ValidateIndex are redundant, just IndexType/ValidateIndex is enough


ColumnIndexOption::ColumnIndexOption(
std::string_view name, std::string_view description,
typename Option<config::IndexType>::DefaultFunc get_default_value)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the point of typename here? It's already a fully resolved type due to <config::IndexType>


Option<config::IndexType> ColumnIndexOption::operator()(
config::IndexType* value_ptr, std::function<config::IndexType()> get_col_count,
typename Option<config::IndexType>::ValueCheckFunc value_check_func) const {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same, typename not needed

Comment on lines 8 to 24
namespace config {

// Simplifies creating of options that represent a single index of the attribute in the table
class ColumnIndexOption {
public:
ColumnIndexOption(std::string_view name, std::string_view description,
typename Option<config::IndexType>::DefaultFunc get_default_value = nullptr);

[[nodiscard]] std::string_view GetName() const;
// Creates an option which performs a check that index is not greater than column count
[[nodiscard]] Option<config::IndexType> operator()(
config::IndexType* value_ptr, std::function<config::IndexType()> get_col_count,
typename Option<config::IndexType>::ValueCheckFunc value_check_func = nullptr) const;

private:
CommonOption<config::IndexType> const common_option_;
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same with redundant config:: and typename Option<..>

Comment on lines 17 to 20
typename Option<config::IndicesType>::DefaultFunc calculate_default,
bool allow_empty)
: common_option_(name, description, std::move(calculate_default), NormalizeIndices, nullptr),
allow_empty_(allow_empty) {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant typename and config

Comment on lines 43 to 45
if (!indices.empty()) {
config::ValidateIndex(indices.back(), get_col_count());
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant config

Comment on lines 109 to 125
if (is_stripped_partition_) {
values.reserve(group_end - group_begin);

for (size_t i = group_begin; i < group_end; i) {
size_t const index = (*sp_indexes_)[i];

values.emplace_back(data_->GetValue(index, left), data_->GetValue(index, right));
}
} else {
for (size_t i = group_begin; i < group_end; i) {
DataFrame::Range const range = (*rb_indexes_)[i];

for (size_t j = range.first; j <= range.second; j) {
values.emplace_back(data_->GetValue(j, left), data_->GetValue(j, right));
}
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDK if I should comment on this code since you've just moved it to another place, but the else branch does not have the values.reserve

Comment on lines 135 to 153
size_t prev_group_max_index = 0;
size_t current_group_max_index = 0;
bool is_first_group = true;

for (size_t i = 0; i < values.size(); i ) {
auto const& [first, second] = values[i];

if (i != 0 && values[i - 1].first != first) {
is_first_group = false;
prev_group_max_index = current_group_max_index;
current_group_max_index = i;
} else if (values[current_group_max_index].second <= second) {
current_group_max_index = i;
}

if (!is_first_group && values[prev_group_max_index].second > second) {
return true;
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This loop constantly checking for i != 0.
Moreover, the first iteration of this loop will always look like

if (values[0].second <= values[0].second) //current_group_max_index is zero
    current_group_max_index = 0;

since i == 0 and !is_first_group is false.

So we can just start from i = 1 and remove that check

Comment on lines 127 to 133
if constexpr (Ascending) {
std::sort(values.begin(), values.end(),
[](auto const& p1, auto const& p2) { return p1.first < p2.first; });
} else {
std::sort(values.begin(), values.end(),
[](auto const& p1, auto const& p2) { return p2.first < p1.first; });
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lambdas looks identical, I would do

std::sort(values.begin(), values.end(),
          [](auto const& p1, auto const& p2) {
              return Ascending ? p1.first < p2.first : p1.first > p2.first;
          });

Though I'm not sure whether we can use constexpr values with ternary operator, but I think that with lambdas we can

Comment on lines -73 to 100
if constexpr (Ascending) {
if constexpr (Ordering == od::Ordering::ascending) {
cs_asc_[key].emplace(value);
} else {
cs_desc_[key].emplace(value);
}
}

template <bool Ascending>
template <od::Ordering Ordering>
void CSPut(AttributeSet const& key, AttributePair&& value) {
if constexpr (Ascending) {
if constexpr (Ordering == od::Ordering::ascending) {
cs_asc_[key].emplace(std::move(value));
} else {
cs_desc_[key].emplace(std::move(value));
}
}

template <bool Ascending>
template <od::Ordering Ordering>
std::unordered_set<AttributePair>& CSGet(AttributeSet const& key) {
if constexpr (Ascending) {
if constexpr (Ordering == od::Ordering::ascending) {
return cs_asc_[key];
} else {
return cs_desc_[key];
}
}

template <bool Ascending>
void AddToResult(fastod::CanonicalOD<Ascending>&& od) {
if constexpr (Ascending) {
template <od::Ordering Ordering>
void AddToResult(fastod::CanonicalOD<Ordering>&& od) {
if constexpr (Ordering == od::Ordering::ascending) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO it is better to define

constexpr bool is_ascending(od::Ordering order) {
    return order == od::Ordering::ascending;
}

and then use if constexpr (is_ascending(Ordering))
Or maybe even

template <od::Ordering Ordering>
concept Ascending = (Ordering == od::Ordering::ascending);
template <od::Ordering Ordering>
concept Descending = (Ordering == od::Ordering::descending);

and do

    if constexpr (Ascending<Ordering>) {
        cs_asc_[key].emplace(value);
    } else {
        cs_desc_[key].emplace(value);
    }

Comment on lines 44 to 74
bool operator==(CanonicalOD<od::Ordering::ascending> const& x,
CanonicalOD<od::Ordering::ascending> const& y) {
return x.context_ == y.context_ && x.ap_ == y.ap_;
}

bool operator!=(CanonicalOD<true> const& x, CanonicalOD<true> const& y) {
bool operator!=(CanonicalOD<od::Ordering::ascending> const& x,
CanonicalOD<od::Ordering::ascending> const& y) {
return !(x == y);
}

bool operator<(CanonicalOD<true> const& x, CanonicalOD<true> const& y) {
bool operator<(CanonicalOD<od::Ordering::ascending> const& x,
CanonicalOD<od::Ordering::ascending> const& y) {
if (x.ap_ != y.ap_) {
return x.ap_ < y.ap_;
}

return x.context_ < y.context_;
}

bool operator==(CanonicalOD<false> const& x, CanonicalOD<false> const& y) {
bool operator==(CanonicalOD<od::Ordering::descending> const& x,
CanonicalOD<od::Ordering::descending> const& y) {
return x.context_ == y.context_ && x.ap_ == y.ap_;
}

bool operator!=(CanonicalOD<false> const& x, CanonicalOD<false> const& y) {
bool operator!=(CanonicalOD<od::Ordering::descending> const& x,
CanonicalOD<od::Ordering::descending> const& y) {
return !(x == y);
}

bool operator<(CanonicalOD<false> const& x, CanonicalOD<false> const& y) {
bool operator<(CanonicalOD<od::Ordering::descending> const& x,
CanonicalOD<od::Ordering::descending> const& y) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use AscCanonicalOD and DescCanonicalOD here

Comment on lines 133 to 137
using Comp = std::conditional_t<Ordering == od::Ordering::ascending, std::less<int>,
std::greater<int>>;
std::sort(values.begin(), values.end(), [](auto const& p1, auto const& p2) {
return Comp()(p1.left_value, p2.left_value);
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest

template <Ordering Ordering, std::totally_ordered T>
bool Comp(T const& lhs, T const& rhs) {
    if constexpr (Ordering ==  Ordering::ascending) {
        return std::less<>{}(lhs, rhs);
    } else {
        return std::greater<>{}(lhs, rhs);
    }
}

And then we won't need to define Comp each time (I saw that the same Comp gets defined second time in Implement aod verifier and cover it with tests)

        std::sort(values.begin(), values.end(), [](auto const& p1, auto const& p2) {
            return od::Comp<Ordering>(p1.left_value, p2.left_value);
        });

Comment on lines 163 to 164
inline AttributeSet CreateAttributeSet(std::ranges::input_range auto const& attributes,
model::ColumnIndex size) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe std::span<const model::ColumnIndex>?
We're only need a view of a sequence and this way we're explicitly saying that we want model::ColumnIndex objects there

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Store DataFrame in ComplexStrippedPartition as raw pointer:

A commit description explaining why will come in handy

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, some commit description will be nice

namespace util {

template <typename T>
concept Printable = requires(std::stringstream& sstream, T& t) { sstream << t; };
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
concept Printable = requires(std::stringstream& sstream, T& t) { sstream << t; };
concept Printable = requires(std::stringstream& sstream, const T& t) { sstream << t; };

will allow more types

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also maybe just std::ostream instead of stringstream? It seems like we can safely accept any output stream here

template <std::ranges::input_range Range>
requires Printable<std::ranges::range_value_t<Range>>
std::string RangeToString(Range const& range) {
std::stringstream sstream;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::stringstream sstream;
std::ostringstream oss;

since we're only performing output operations

Comment on lines 17 to 22
for (auto pt{range.begin()}; pt != range.end(); pt) {
if (pt != range.begin()) {
sstream << ", ";
}
sstream << *pt;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use range-based for for example like so

Suggested change
for (auto pt{range.begin()}; pt != range.end(); pt) {
if (pt != range.begin()) {
sstream << ", ";
}
sstream << *pt;
}
bool first = true;
for (const auto& elem : range) {
if (!first) {
oss << ", ";
} else {
first = false;
}
oss << elem;
}

Comment on lines 190 to 195
using Comp = std::conditional_t<Ordering == od::Ordering::ascending, std::less<int>,
std::greater<int>>;
std::sort(values.begin(), values.end(), [](auto const& t1, auto const& t2) {
return Comp()(t1.left_value, t2.left_value) ||
(t1.left_value == t2.left_value && t1.right_value < t2.right_value);
});
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Comp defined as a result of fixing previous issue

Comment on lines 5 to 9
RemovalSet UnionRemovalSets(RemovalSetAsVec const& vec1, RemovalSetAsVec const& vec2) {
RemovalSet removal_set{vec1.begin(), vec1.end()};
removal_set.insert(vec2.begin(), vec2.end());
return removal_set;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this function gets used? It seems like we don't need it

Comment on lines 205 to 207
Tuple const& value = values[i];
auto it = std::upper_bound(subseq_len_to_last.begin(), subseq_len_to_last.end(),
value.right_value);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we use value only as value.right_value, so let's just define it as int value_right = values[i].right_value;

Comment on lines 197 to 201
std::vector<int> subseq_len_to_last(values.size() 1, std::numeric_limits<int>::max());
subseq_len_to_last.front() = std::numeric_limits<int>::min();
std::vector<size_t> predecessors(values.size(), std::numeric_limits<size_t>::max());
std::vector<size_t> subseq_len_to_index(values.size() 1,
std::numeric_limits<size_t>::max());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would extract values.size() to like and n variable since it gets used a lot

Comment on lines 220 to 230
std::unordered_set<model::TupleIndex> longest_subseq_elements;
for (size_t i = subseq_len_to_index[longest_subseq];
i != std::numeric_limits<model::TupleIndex>::max(); i = predecessors[i]) {
longest_subseq_elements.insert(values[i].tuple_index);
}

for (auto const& [index, left_value, right_value] : values) {
if (!longest_subseq_elements.contains(index)) {
removal_set.push_back(index);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is longest_subseq_elements is an unordered_set? Can't we just define like vector<bool> of boost::dynamic_bitset with values.size() = n size and do a faster lookup this way?
Well, maybe not if n is much more that the size of longest subseq elements..
Like

Suggested change
std::unordered_set<model::TupleIndex> longest_subseq_elements;
for (size_t i = subseq_len_to_index[longest_subseq];
i != std::numeric_limits<model::TupleIndex>::max(); i = predecessors[i]) {
longest_subseq_elements.insert(values[i].tuple_index);
}
for (auto const& [index, left_value, right_value] : values) {
if (!longest_subseq_elements.contains(index)) {
removal_set.push_back(index);
}
}
for (size_t i = subseq_len_to_index[longest_subseq];
i != std::numeric_limits<size_t>::max(); i = predecessors[i]) {
is_in_lis[i] = true;
}
for (size_t i = 0; i < n; i) {
if (!is_in_lis[i]) {
removal_set.push_back(values[i].tuple_index);
}
}

Comment on lines 241 to 279
od::RemovalSetAsVec ComplexStrippedPartition::CommonSplitRemovalSet(
model::ColumnIndex right) const {
od::RemovalSetAsVec removal_set;

for (size_t begin_pointer = 0; begin_pointer < sp_begins_->size() - 1; begin_pointer ) {
size_t const group_begin = (*sp_begins_)[begin_pointer];
size_t const group_end = (*sp_begins_)[begin_pointer 1];

std::vector<ValueAndIndex> values;
for (size_t i = group_begin; i < group_end; i ) {
model::TupleIndex const tuple_index = (*sp_indexes_)[i];
values.emplace_back(data_->GetValue(tuple_index, right), tuple_index);
}
od::RemovalSetAsVec cur_removal_set = SplitRemovalSet(values);
removal_set.insert(removal_set.end(), cur_removal_set.begin(), cur_removal_set.end());
}

return removal_set;
}

od::RemovalSetAsVec ComplexStrippedPartition::RangeBasedSplitRemovalSet(
model::ColumnIndex right) const {
od::RemovalSetAsVec removal_set;

for (size_t begin_pointer = 0; begin_pointer < rb_begins_->size() - 1; begin_pointer) {
size_t const group_begin = (*rb_begins_)[begin_pointer];
size_t const group_end = (*rb_begins_)[begin_pointer 1];

std::vector<ValueAndIndex> values;
for (size_t i = group_begin; i < group_end; i) {
DataFrame::Range const range = (*rb_indexes_)[i];

for (size_t j = range.first; j <= range.second; j) {
values.emplace_back(data_->GetValue(j, right), j);
}
}
od::RemovalSetAsVec cur_removal_set = SplitRemovalSet(values);
removal_set.insert(removal_set.end(), cur_removal_set.begin(), cur_removal_set.end());
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, a lot of code duplication in these two methods. The lines:

    od::RemovalSetAsVec removal_set;

    for (size_t begin_pointer = 0; begin_pointer < sp_begins_->size() - 1; begin_pointer  ) {
        size_t const group_begin = (*sp_begins_)[begin_pointer];
        size_t const group_end = (*sp_begins_)[begin_pointer   1];

        od::RemovalSetAsVec cur_removal_set = SplitRemovalSet(values);
        removal_set.insert(removal_set.end(), cur_removal_set.begin(), cur_removal_set.end());

are virtually the same in both cases.

I think we can create a helper function like this:

void ProcessGroups(const std::vector<size_t>& begins, std::function</*type*/> collect_values, od::RemovalSetAsVec& removal_set)
{
    for (size_t begin_pointer = 0; begin_pointer < begins.size() - 1;   begin_pointer) {
        size_t const group_begin = begins[begin_pointer];
        size_t const group_end = begins[begin_pointer   1];

        std::vector<ValueAndIndex> values;
        collect_values(group_begin, group_end, values);

        od::RemovalSetAsVec cur_removal_set = SplitRemovalSet(values);
        removal_set.insert(
            removal_set.end(),
            std::make_move_iterator(cur_removal_set.begin()),
            std::make_move_iterator(cur_removal_set.end())
        );
    }
}

And use it like so:

od::RemovalSetAsVec ComplexStrippedPartition::CommonSplitRemovalSet(model::ColumnIndex right) const {
    od::RemovalSetAsVec removal_set;

    ProcessGroups( *sp_begins_,
        [this, right](size_t group_begin, size_t group_end, std::vector<ValueAndIndex>& values) {
            values.reserve(group_end - group_begin);
            for (size_t i = group_begin; i < group_end;   i) {
                model::TupleIndex const tuple_index = (*sp_indexes_)[i];
                values.emplace_back(data_->GetValue(tuple_index, right), tuple_index);
            }
        },
        removal_set
    );

    return removal_set;
}

Comment on lines 254 to 255
od::RemovalSetAsVec cur_removal_set = SplitRemovalSet(values);
removal_set.insert(removal_set.end(), cur_removal_set.begin(), cur_removal_set.end());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need cur_removal_set object anymore - might as well move elements to a removal_set:

removal_set.insert(
            removal_set.end(),
            std::make_move_iterator(cur_removal_set.begin()),
            std::make_move_iterator(cur_removal_set.end())
        );

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, nevermind, there are integers there

Comment on lines 249 to 253
std::vector<ValueAndIndex> values;
for (size_t i = group_begin; i < group_end; i ) {
model::TupleIndex const tuple_index = (*sp_indexes_)[i];
values.emplace_back(data_->GetValue(tuple_index, right), tuple_index);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we know how much time we will call emplace_back, so we should reserve memory to avoid reallocations:
values.reserve(group_end - group_begin);

Comment on lines 277 to 278
od::RemovalSetAsVec cur_removal_set = SplitRemovalSet(values);
removal_set.insert(removal_set.end(), cur_removal_set.begin(), cur_removal_set.end());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same thing here with make_move_iterator

Comment on lines 269 to 276
std::vector<ValueAndIndex> values;
for (size_t i = group_begin; i < group_end; i) {
DataFrame::Range const range = (*rb_indexes_)[i];

for (size_t j = range.first; j <= range.second; j) {
values.emplace_back(data_->GetValue(j, right), j);
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, we can avoid reallocations here, but this might increase the constant factor in time complexity. I'm not sure if it's worth it, as it depends on how frequently this function is called and the number of reallocations. However, we can do something like this:

            size_t total_values = 0;
            for (size_t i = group_begin; i < group_end;   i) {
                DataFrame::Range const& range = (*rb_indexes_)[i];
                total_values  = range.second - range.first   1;
            }
            values.reserve(total_values);

Comment on lines 14 to 15
using namespace algos::od;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add using namespace config::names; and do :%s/config::names:://g

Comment on lines 67 to 79
::testing::Values(/* {}: Col5 -> Col1 */
SetBasedAodVerifierParams(kTestFD, /*oc_context=*/{},
/*oc_left=*/5, /*oc_right=*/1,
/*ordering=*/Ordering::ascending,
/*ofd_context=*/{5}, /*ofd_right=*/1,
/*removal_set=*/{}),
/* {}: Col5 -> Col2 */
SetBasedAodVerifierParams(kTestFD, /*oc_context=*/{},
/*oc_left=*/5, /*oc_right=*/2,
/*ordering=*/Ordering::ascending,
/*ofd_context=*/{5}, /*ofd_right=*/2,
/*removal_set=*/{2, 5, 8}),
/* {Col3}: Col4 -> Col1 */
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idk if this is possible, but can we add some descriptive names to individual tests in this test suite? Like make those comments {}: Col5 -> Col1 into names? If it's hard, then feel free to ignore

Comment on lines 59 to 61
SetBasedAodVerifier::Error expected_error =
static_cast<SetBasedAodVerifier::Error>(expected_removal_set_vec.size()) /
verifier->GetTupleCount();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we're specifying type in static_cast, we can just use auto expected_error

Comment on lines 140 to 141
{config::names::kOcLeftOrdering, Ordering}};
algos::ConfigureFromMap(verifier, execute_params);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

separate with empty line

Comment on lines 162 to 163
{config::names::kOFDRightIndex, ofd_right}};
algos::ConfigureFromMap(verifier, execute_params);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

separate with empty line

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants