-
Notifications
You must be signed in to change notification settings - Fork 89
Multiparameter persistence, C++ part. #976
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
base: master
Are you sure you want to change the base?
Changes from 13 commits
d19ecb8
02a7ef4
36dd35b
7d3b0dd
5d3d766
a0799a0
bfe8e26
610e028
9a71cd1
cb71e10
7ea7097
ec91ec3
15ed6e0
2fa3caf
8fa1ff6
b938f79
0d4ad0c
d405253
74680ce
b697d15
9cc104d
a10d98b
b53998a
11e123d
c70741c
1527695
364df58
b8ec063
dd4014a
1f98cb6
f8fa2ca
e5ae91b
ed4472b
42bc7e3
561c4d3
6f7e3ca
7f0c243
6f77ee4
30e4e78
36c576f
b9aec35
5faefc8
a4ba64b
2f65c42
91238b5
cdbb855
bb9a0d0
cb5d804
e4a90b2
31377c6
f1f7667
fa9f0b1
8cc4b39
cd8dab0
d527806
6105d26
dc22edd
f260f71
1b487b4
e241468
e1a980f
e0c7188
e88f9bc
2ca51e1
40366e9
a45b5dd
a89f038
7c0dd5d
6af0a0a
3f128e6
0b9bbed
ef747b6
4dc66bd
c1cd900
2ad6605
b7bc7bf
2fdcc13
8fcf844
371c612
7e40d31
f9aaee0
c8f49da
bf02e13
a4c67cf
9639b67
8e811a9
8cc7ccf
41784e1
100d7ae
165029e
e4a25d1
07ab8d0
4a0b8d4
a53e8ab
7c89d6a
1fcfcb7
9897e63
a993729
0692eb3
ef01751
0fb5492
9303a96
b22efef
8cd1e7e
8d54fd2
83d2690
0bd504b
6841e2f
087b692
877f580
a6692c5
d756463
7875f69
befb1f5
993c8a1
8b4f630
78aad6b
153a887
6c09cb3
26378a4
6ffe07a
6cbffc3
2815ff9
b1d9d16
0037371
a6c52e9
b04f1b8
dffda0b
c4e99d1
5d2c187
6efad53
beb8ff7
20db201
13a4d94
59e2340
8e2a7a4
ed13684
ffd488a
3659477
0ec6796
1df9491
1daac9c
0aad5d0
35ea22c
5123657
1cfc9e3
6141777
eb7483e
f56a1c2
09a3743
f9296c9
b4df673
443f0c4
729d4a7
d295a24
ec15df2
d9456df
cc251c6
7ec70b3
83b1230
2319d36
6fa2c0a
0316e53
c17740c
49f275c
e9238d5
e2fa822
7263561
c7ad323
26d7365
0760324
754874d
120eb12
9b346af
5e88fd8
d94beb5
9b5f3d7
041a4ea
50ad475
f9d20ea
376e9d2
cfc4c45
48a75ef
b8ce1cc
785c385
9f4ba44
9fe27c4
5304aab
8880d85
895f4b1
98d39ad
b2fcd07
6ae25bd
5a30d56
5b84c8d
553ad34
1d0a44e
2609b9a
7020150
227cbff
5776f65
c68cc8f
4b6c8de
26b5995
c68c406
e320574
86cd753
8c34771
5ae5690
4f22b02
bc09882
48322d6
8d70f9d
373c525
5122dd6
cdbe4e3
8e77c35
525228d
8d435db
916728b
6420fc0
f3a2573
f18cf37
a31e611
5811b04
3ac4f1f
3bc4f80
307d51d
cf127b2
3a3657c
eeb0845
3147d53
96d28bd
b5ccb03
880dd7b
f0674af
fa2c845
8d97c89
4ae8c4e
b29c412
64bbb44
cfc2d53
47e5eba
bb096fa
973f85a
01131c6
2ecb281
e965513
b5c1fd5
e76dd00
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| /* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. | ||
| * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. | ||
| * Author(s): David Loiseaux | ||
| * | ||
| * Copyright (C) 2023 Inria | ||
| * | ||
| * Modification(s): | ||
| * - YYYY/MM Author: Description of the modification | ||
| */ | ||
|
|
||
| #include <gudhi/Simplex_tree.h> | ||
| #include <gudhi/Simplex_tree/Simplex_tree_multi.h> | ||
|
|
||
| #include <iostream> | ||
| #include <initializer_list> | ||
|
|
||
| struct ST_MULTI { | ||
| public: | ||
|
DavidLapous marked this conversation as resolved.
Outdated
|
||
| typedef Gudhi::linear_indexing_tag Indexing_tag; | ||
| typedef int Vertex_handle; | ||
| typedef float value_type; | ||
| using Filtration_value = Gudhi::multiparameter::multi_filtrations::Finitely_critical_multi_filtration<value_type>; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The nested multiparameter::multi_filtrations feel a bit redundant, but I guess it isn't that bad. |
||
| typedef std::uint32_t Simplex_key; | ||
| static const bool store_key = true; | ||
| static const bool store_filtration = true; | ||
| static const bool contiguous_vertices = false; | ||
| static const bool link_nodes_by_label = true; | ||
| static const bool stable_simplex_handles = false; | ||
| static const bool is_multi_parameter = true; | ||
| }; | ||
|
|
||
| using ST = Gudhi::Simplex_tree<ST_MULTI>; | ||
|
|
||
|
|
||
| int main() { | ||
| ST st; | ||
|
|
||
| /* Complex to build. */ | ||
| /* 1 */ | ||
| /* o */ | ||
| /* /X\ */ | ||
| /* o---o---o */ | ||
| /* 2 0 3 */ | ||
|
|
||
| auto triangle012 = {0, 1, 2}; | ||
| auto edge03 = {0, 3}; | ||
| st.insert_simplex_and_subfaces(triangle012, {1,2,3}); // {1,2,3} can be any array-like vector-like | ||
| st.insert_simplex_and_subfaces(edge03, {4,5,6}); | ||
|
|
||
| auto edge02 = {0, 2}; | ||
| ST::Simplex_handle e = st.find(edge02); | ||
| // Finitely_critical_multi_filtration has an operator<< | ||
| std::cout << st.filtration(e) << std::endl; | ||
| assert(st.filtration(st.find(edge03)) == std::vector<float>({4,5,6})); | ||
|
DavidLapous marked this conversation as resolved.
Outdated
|
||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -151,16 +151,18 @@ class Simplex_tree { | |
| Key_simplex_base; | ||
|
|
||
| struct Filtration_simplex_base_real { | ||
| Filtration_simplex_base_real() : filt_(0) {} | ||
| void assign_filtration(Filtration_value f) { filt_ = f; } | ||
| Filtration_value filtration() const { return filt_; } | ||
| Filtration_simplex_base_real() : filt_{} {} | ||
| void assign_filtration(const Filtration_value& f) { filt_ = f; } | ||
| const Filtration_value& filtration() const { return filt_; } | ||
| Filtration_value& filtration() { return filt_; } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be discussed, but I am not sure we should add this method. |
||
| private: | ||
| Filtration_value filt_; | ||
| }; | ||
| struct Filtration_simplex_base_dummy { | ||
| Filtration_simplex_base_dummy() {} | ||
| void assign_filtration(Filtration_value GUDHI_CHECK_code(f)) { GUDHI_CHECK(f == 0, "filtration value specified for a complex that does not store them"); } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this test |
||
| Filtration_value filtration() const { return 0; } | ||
| const Filtration_value& filtration() const { return null_value; } | ||
| static constexpr Filtration_value null_value={}; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we say: struct MyOptions : Simplex_tree_options_multidimensional_filtration {
// is_multi_parameter but not store_filtration
static const bool store_filtration = false;
};
Simplex_tree<MyOptions> stree;We are facing the error: But maybe this error is normal ? I was wondering to see if we should
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A simple fix is to do a
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess you meant |
||
| }; | ||
| typedef typename std::conditional<Options::store_filtration, Filtration_simplex_base_real, | ||
| Filtration_simplex_base_dummy>::type Filtration_simplex_base; | ||
|
|
@@ -576,7 +578,7 @@ class Simplex_tree { | |
| * | ||
| * Same as `filtration()`, but does not handle `null_simplex()`. | ||
| */ | ||
| static Filtration_value filtration_(Simplex_handle sh) { | ||
| static const Filtration_value& filtration_(Simplex_handle sh) { | ||
| GUDHI_CHECK (sh != null_simplex(), "null simplex"); | ||
| return sh->second.filtration(); | ||
| } | ||
|
|
@@ -604,18 +606,25 @@ class Simplex_tree { | |
| * Called on the null_simplex, it returns infinity. | ||
| * If SimplexTreeOptions::store_filtration is false, returns 0. | ||
| */ | ||
| static Filtration_value filtration(Simplex_handle sh) { | ||
| static const Filtration_value& filtration(Simplex_handle sh){ | ||
| if (sh != null_simplex()) { | ||
| return sh->second.filtration(); | ||
| } else { | ||
| return std::numeric_limits<Filtration_value>::infinity(); | ||
| return inf_; | ||
| } | ||
| } | ||
| static Filtration_value& filtration_mutable(Simplex_handle sh){ | ||
| if (sh != null_simplex()) { | ||
| return sh->second.filtration(); | ||
| } else { | ||
| return inf_; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Already from the previous PR:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't have a fixed point of view on this. So its only a matter of what's the gudhi style of accessing a filtration value by reference.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If they really do the same thing, I am ok with |
||
| } | ||
| } | ||
|
|
||
| /** \brief Sets the filtration value of a simplex. | ||
| * \exception std::invalid_argument In debug mode, if sh is a null_simplex. | ||
| */ | ||
| void assign_filtration(Simplex_handle sh, Filtration_value fv) { | ||
| void assign_filtration(Simplex_handle sh, const Filtration_value& fv) { | ||
| GUDHI_CHECK(sh != null_simplex(), | ||
| std::invalid_argument("Simplex_tree::assign_filtration - cannot assign filtration on null_simplex")); | ||
| sh->second.assign_filtration(fv); | ||
|
|
@@ -822,14 +831,16 @@ class Simplex_tree { | |
| * to the new simplex. | ||
| * If the insertion fails (the simplex is already there), the bool is set to false. If the insertion | ||
| * fails and the simplex already in the complex has a filtration value strictly bigger than 'filtration', | ||
| * and the simplex tree is not multi parameter (`SimplexTreeOptions::is_multi_parameter == false`), | ||
| * we assign this simplex with the new value 'filtration', and set the Simplex_handle field of the | ||
| * output pair to the Simplex_handle of the simplex. Otherwise, we set the Simplex_handle part to | ||
| * null_simplex. | ||
| * output pair to the Simplex_handle of the simplex. When the simplex tree is multi parameter, | ||
| * the existing filtration values are not updated. If the insertion fails for other reasons, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| * we set the Simplex_handle part to `null_simplex`. | ||
| * | ||
| */ | ||
| template <class RandomVertexHandleRange = std::initializer_list<Vertex_handle>> | ||
| std::pair<Simplex_handle, bool> insert_simplex_raw(const RandomVertexHandleRange& simplex, | ||
| Filtration_value filtration) { | ||
| const Filtration_value& filtration) { | ||
| Siblings * curr_sib = &root_; | ||
| std::pair<Simplex_handle, bool> res_insert; | ||
| auto vi = simplex.begin(); | ||
|
|
@@ -895,7 +906,7 @@ class Simplex_tree { | |
| * .end() return input iterators, with 'value_type' Vertex_handle. */ | ||
| template<class InputVertexRange = std::initializer_list<Vertex_handle>> | ||
| std::pair<Simplex_handle, bool> insert_simplex(const InputVertexRange & simplex, | ||
| Filtration_value filtration = 0) { | ||
| const Filtration_value& filtration = {}) { | ||
| auto first = std::begin(simplex); | ||
| auto last = std::end(simplex); | ||
|
|
||
|
|
@@ -924,7 +935,7 @@ class Simplex_tree { | |
| */ | ||
| template<class InputVertexRange = std::initializer_list<Vertex_handle>> | ||
| std::pair<Simplex_handle, bool> insert_simplex_and_subfaces(const InputVertexRange& Nsimplex, | ||
| Filtration_value filtration = 0) { | ||
|
VincentRouvreau marked this conversation as resolved.
Outdated
|
||
| const Filtration_value& filtration = {}) { | ||
| auto first = std::begin(Nsimplex); | ||
| auto last = std::end(Nsimplex); | ||
|
|
||
|
|
@@ -953,7 +964,7 @@ class Simplex_tree { | |
| std::pair<Simplex_handle, bool> rec_insert_simplex_and_subfaces_sorted(Siblings* sib, | ||
| ForwardVertexIterator first, | ||
| ForwardVertexIterator last, | ||
| Filtration_value filt) { | ||
| const Filtration_value& filt) { | ||
| // An alternative strategy would be: | ||
| // - try to find the complete simplex, if found (and low filtration) exit | ||
| // - insert all the vertices at once in sib | ||
|
|
@@ -969,14 +980,17 @@ class Simplex_tree { | |
|
|
||
| Simplex_handle simplex_one = insertion_result.first; | ||
| bool one_is_new = insertion_result.second; | ||
| if (!one_is_new) { | ||
| if (filtration(simplex_one) > filt) { | ||
| assign_filtration(simplex_one, filt); | ||
| } else { | ||
| // FIXME: this interface makes no sense, and it doesn't seem to be tested. | ||
| insertion_result.first = null_simplex(); | ||
| if constexpr (!SimplexTreeOptions::is_multi_parameter){ // Ignores the assign part for multiparameter filtrations. | ||
| if (!one_is_new) { | ||
| if (filtration(simplex_one) > filt){ | ||
| assign_filtration(simplex_one, filt); | ||
| } else { | ||
| // FIXME: this interface makes no sense, and it doesn't seem to be tested. | ||
| insertion_result.first = null_simplex(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (++first == last) return insertion_result; | ||
| if (!has_children(simplex_one)) | ||
| // TODO: have special code here, we know we are building the whole subtree from scratch. | ||
|
|
@@ -1316,7 +1330,7 @@ class Simplex_tree { | |
| * The complex does not need to be empty before calling this function. However, if a vertex is | ||
| * already present, its filtration value is not modified, unlike with other insertion functions. */ | ||
| template <class VertexRange> | ||
| void insert_batch_vertices(VertexRange const& vertices, Filtration_value filt = 0) { | ||
| void insert_batch_vertices(VertexRange const& vertices, const Filtration_value& filt ={}) { | ||
| auto verts = vertices | boost::adaptors::transformed([&](auto v){ | ||
| return Dit_value_t(v, Node(&root_, filt)); }); | ||
| root_.members_.insert(boost::begin(verts), boost::end(verts)); | ||
|
|
@@ -1403,7 +1417,7 @@ class Simplex_tree { | |
| static void intersection(std::vector<std::pair<Vertex_handle, Node> >& intersection, | ||
| Dictionary_it begin1, Dictionary_it end1, | ||
| Dictionary_it begin2, Dictionary_it end2, | ||
| Filtration_value filtration_) { | ||
| const Filtration_value& filtration_) { | ||
| if (begin1 == end1 || begin2 == end2) | ||
| return; // ----->> | ||
| while (true) { | ||
|
|
@@ -1610,12 +1624,21 @@ class Simplex_tree { | |
| if (dim == 0) return; | ||
| // Find the maximum filtration value in the border | ||
| Boundary_simplex_range&& boundary = boundary_simplex_range(sh); | ||
| Boundary_simplex_iterator max_border = std::max_element(std::begin(boundary), std::end(boundary), | ||
| [](Simplex_handle sh1, Simplex_handle sh2) { | ||
| return filtration(sh1) < filtration(sh2); | ||
| }); | ||
| Filtration_value max_filt_border_value; | ||
| if constexpr (SimplexTreeOptions::is_multi_parameter) { | ||
| // in that case, we assume that Filtration_value has a `push_to` member to handle this. | ||
|
VincentRouvreau marked this conversation as resolved.
Outdated
|
||
| max_filt_border_value = Filtration_value(this->number_of_parameters_); | ||
| for (auto& face_sh : boundary) { | ||
| max_filt_border_value.push_to( | ||
| filtration(face_sh)); // pushes the value of max_filt_border_value to reach simplex' filtration | ||
| } | ||
| } else { | ||
| Boundary_simplex_iterator max_border = | ||
| std::max_element(std::begin(boundary), std::end(boundary), | ||
| [](Simplex_handle sh1, Simplex_handle sh2) { return filtration(sh1) < filtration(sh2); }); | ||
| max_filt_border_value = filtration(*max_border); | ||
| } | ||
|
|
||
| Filtration_value max_filt_border_value = filtration(*max_border); | ||
| // Replacing if(f<max) with if(!(f>=max)) would mean that if f is NaN, we replace it with the max of the children. | ||
| // That seems more useful than keeping NaN. | ||
| if (!(sh->second.filtration() >= max_filt_border_value)) { | ||
|
|
@@ -1650,7 +1673,7 @@ class Simplex_tree { | |
| * than it was before. However, `upper_bound_dimension()` will return the old value, which remains a valid upper | ||
| * bound. If you care, you can call `dimension()` to recompute the exact dimension. | ||
| */ | ||
| bool prune_above_filtration(Filtration_value filtration) { | ||
| bool prune_above_filtration(const Filtration_value& filtration) { | ||
| if (std::numeric_limits<Filtration_value>::has_infinity && filtration == std::numeric_limits<Filtration_value>::infinity()) | ||
| return false; // ---->> | ||
| bool modified = rec_prune_above_filtration(root(), filtration); | ||
|
|
@@ -1660,7 +1683,7 @@ class Simplex_tree { | |
| } | ||
|
|
||
| private: | ||
| bool rec_prune_above_filtration(Siblings* sib, Filtration_value filt) { | ||
| bool rec_prune_above_filtration(Siblings* sib, const Filtration_value& filt) { | ||
| auto&& list = sib->members(); | ||
| auto last = std::remove_if(list.begin(), list.end(), [this,filt](Dit_value_t& simplex) { | ||
| if (simplex.second.filtration() <= filt) return false; | ||
|
|
@@ -2070,7 +2093,7 @@ class Simplex_tree { | |
| * @param[in] filt_value The new filtration value. | ||
| * @param[in] min_dim The minimal dimension. Default value is 0. | ||
| */ | ||
| void reset_filtration(Filtration_value filt_value, int min_dim = 0) { | ||
| void reset_filtration(const Filtration_value& filt_value, int min_dim = 0) { | ||
| rec_reset_filtration(&root_, filt_value, min_dim); | ||
| clear_filtration(); // Drop the cache. | ||
| } | ||
|
|
@@ -2081,7 +2104,7 @@ class Simplex_tree { | |
| * @param[in] filt_value The new filtration value. | ||
| * @param[in] min_depth The minimal depth. | ||
| */ | ||
| void rec_reset_filtration(Siblings * sib, Filtration_value filt_value, int min_depth) { | ||
| void rec_reset_filtration(Siblings * sib, const Filtration_value& filt_value, int min_depth) { | ||
| for (auto sh = sib->members().begin(); sh != sib->members().end(); ++sh) { | ||
| if (min_depth <= 0) { | ||
| sh->second.assign_filtration(filt_value); | ||
|
|
@@ -2246,6 +2269,24 @@ class Simplex_tree { | |
| /** \brief Upper bound on the dimension of the simplicial complex.*/ | ||
| int dimension_; | ||
| bool dimension_to_be_lowered_ = false; | ||
|
|
||
| // MULTIPERS STUFF | ||
| public: | ||
| /** | ||
| * \brief Sets the number of parameters of the filtrations if SimplexTreeOptions::is_multi_parameter. | ||
| * */ | ||
| void set_number_of_parameters(int num) { number_of_parameters_ = num; } | ||
|
VincentRouvreau marked this conversation as resolved.
Outdated
|
||
| /** | ||
| * \brief Gets the number of parameters of the filtrations if SimplexTreeOptions::is_multi_parameter. | ||
| * */ | ||
| int get_number_of_parameters() const { return number_of_parameters_; } | ||
|
|
||
| inline static Filtration_value inf_ = std::numeric_limits<Filtration_value>::has_infinity() ? | ||
| std::numeric_limits<Filtration_value>::infinity() | ||
| : std::numeric_limits<Filtration_value>::max(); /**< Default infinite value. */ | ||
|
|
||
| private: | ||
| int number_of_parameters_; /**< Number of parameters of the multi-filtrations when SimplexTreeOptions::is_multi_parameter.-*/ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, why not, as the get number of parameters will always be 1 for non-multiparam simplextrees, this default value seems good.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OTOH, a "broken" value like 0 (or even -1) forces the user to be explicit (which can be seen as good or bad). |
||
| }; | ||
|
|
||
| // Print a Simplex_tree in os. | ||
|
|
@@ -2297,6 +2338,7 @@ struct Simplex_tree_options_full_featured { | |
| static const bool contiguous_vertices = false; | ||
| static const bool link_nodes_by_label = false; | ||
| static const bool stable_simplex_handles = false; | ||
| static const bool is_multi_parameter = false; | ||
| }; | ||
|
|
||
| /** Model of SimplexTreeOptions, faster than `Simplex_tree_options_full_featured` but note the unsafe | ||
|
|
@@ -2314,6 +2356,7 @@ struct Simplex_tree_options_fast_persistence { | |
| static const bool contiguous_vertices = true; | ||
| static const bool link_nodes_by_label = false; | ||
| static const bool stable_simplex_handles = false; | ||
| static const bool is_multi_parameter = false; | ||
| }; | ||
|
|
||
| /** Model of SimplexTreeOptions, faster cofaces than `Simplex_tree_options_full_featured`, note the | ||
|
|
@@ -2331,6 +2374,8 @@ struct Simplex_tree_options_fast_cofaces { | |
| static const bool contiguous_vertices = false; | ||
| static const bool link_nodes_by_label = true; | ||
| static const bool stable_simplex_handles = false; | ||
| static const bool is_multi_parameter = false; | ||
|
|
||
| }; | ||
|
|
||
| /** @}*/ // end addtogroup simplex_tree | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I already commented somewhere that we should remove this line (and the same in several places) because is_multi_parameter is already false in the base class.