-
Notifications
You must be signed in to change notification settings - Fork 30
Create a Sequential Sampleable #393
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
Conversation
Codecov Report
@@ Coverage Diff @@
## master #393 +/- ##
==========================================
- Coverage 80.96% 80.94% -0.02%
==========================================
Files 201 203 +2
Lines 5705 5774 +69
==========================================
+ Hits 4619 4674 +55
- Misses 1086 1100 +14
|
#ifndef AIKIDO_CONSTRAINT_SEQUENTIALSAMPLEABLE_HPP_ | ||
#define AIKIDO_CONSTRAINT_SEQUENTIALSAMPLEABLE_HPP_ | ||
|
||
#include "Sampleable.hpp" |
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.
Nit: Use full path (e.g., "aikido/constraint/Sampleable.hpp"
)
|
||
private: | ||
statespace::StateSpacePtr mStateSpace; | ||
std::vector<SampleablePtr> mSampleables; |
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.
Please add docstring
@@ -0,0 +1,117 @@ | |||
#include <aikido/constraint/SequentialSampleable.hpp> |
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.
Nit: #include "aikido/constraint/SequentialSampleable.hpp"
namespace constraint { | ||
|
||
// TODO (avk): Should I declare the class first and then define? | ||
// What is "good" C++ convention? Don't tell me it's a preference JS! -.- |
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 don't think that's necessary when defining in a source file.
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.
In that case, shall we add docstring during definitions, which we generally do not do right?
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.
shall we add docstring during definitions
I think so. Although they are not exposed to the users, it would be very helpful for us (i.e., developers).
/// \param[in] sampleables Set of sampleables. | ||
SequentialSampleable( | ||
statespace::StateSpacePtr stateSpace, | ||
std::vector<SampleablePtr> sampleables); |
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.
Pass sampleables
by const reference to avoid the unnecessary copy. Looking at the implementation, you used std::move(...)
. (a) Passing by const reference and (b) passing by value + std::move()
are technically the same, but we only use (b) for smart pointers to indicate the ownership is being shared or transferred.
{ | ||
// Do nothing | ||
// TODO (avk): Should we again check equivalence of statespaces between | ||
// that of each generator and mStateSpace? I don't think it's necessary. |
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.
It seems this class is vulnerable in the case of empty generators
.
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.
getNumSamples()
returns 0 after checking for empty vector.
|
||
int getNumSamples() const override | ||
{ | ||
return mGenerators[mIndex]->getNumSamples(); |
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'm not sure if this logic is following the description of getNumSamples()
:
Gets an upper bound on the number of samples remaining or NO_LIMIT.
if (mIndex >= mGenerators.size()) | ||
return false; | ||
|
||
return true; |
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 this function should return true when:
- the number of remaining generators is not zero and
- one of the remaining generators is able to sample.
It seems the current implementation is missing 2.
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.
The current implementation with the iterating through generators until sampling is possible, (2) is implicitly taken care of
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.
It sounds like canSample()
works correctly only when it's used in sample()
, which I don't think that's a good idea. Also, this doesn't agree with the docstrion of canSample()
:
/// Returns whether getNumSamples() > 0.
I propose changing this function to return getNumSamples() > 0
; and changing canSample()
not to call this function and to check the size of mGenerators
directly in canSample()
.
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.
Would it be possible for one of the remaining generators to not be sampleable? Then even though you have generators remaining (this implementation would return true
), it still seems like you wouldn't be sample (logically, maybe it should return false
). What if we just do something like
for (std::size_t i = mIndex; i < mGenerators.size(); ++i)
if (mGenerators[i].canSample())
return true;
return false;
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.
private: | ||
statespace::StateSpacePtr mStateSpace; | ||
std::vector<std::unique_ptr<SampleGenerator>> mGenerators; | ||
std::size_t mIndex; |
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.
Please add docstrings.
bool sample(statespace::StateSpace::State* state) | ||
{ | ||
if (!mGenerators[mIndex]->canSample()) | ||
mIndex++; |
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 this function should keep iterating until it finds a sampleable generator instead of just check only one generator.
/// \param[in] sampleables Set of sampleables. | ||
SequentialSampleable( | ||
statespace::StateSpacePtr stateSpace, | ||
std::vector<SampleablePtr>& sampleables); |
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 meant passing by const reference rather than just reference. 😄
private: | ||
/// StateSpace in which the constraint operates. | ||
statespace::StateSpacePtr mStateSpace; | ||
/// Set of sampleables. |
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.
Nit: Add empty line between members.
public: | ||
SequentialSampleGenerator( | ||
statespace::StateSpacePtr stateSpace, | ||
std::vector<std::unique_ptr<SampleGenerator>>& generators) |
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 we should pass by value for generators
(and move) because it's transferring the onwerships.
, mIndex(0) | ||
{ | ||
for (std::size_t i = 0; i < mGenerators.size(); ++i) | ||
assert(mGenerators[i]->getStateSpace() == mStateSpace); |
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.
Please note that assert
is ignored in release mode. This code will not be built in release mode since the for-statement is empty.
We could either:
#ifndef NDEBUG // debug mode
for (const auto& generator : mGenerators)
assert(generator->getStateSpace() == mStateSpace);
#endif
or
for (const auto& generator : mGenerators)
{
assert(generator->getStateSpace() == mStateSpace);
}
I slightly prefer former version. 😄
if (mIndex >= mGenerators.size()) | ||
return false; | ||
|
||
return true; |
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.
It sounds like canSample()
works correctly only when it's used in sample()
, which I don't think that's a good idea. Also, this doesn't agree with the docstrion of canSample()
:
/// Returns whether getNumSamples() > 0.
I propose changing this function to return getNumSamples() > 0
; and changing canSample()
not to call this function and to check the size of mGenerators
directly in canSample()
.
|
||
mIndex++; | ||
} | ||
return false; |
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.
This logic (i.e., "try the current generator to sample and return the result") is possible to return false even when one of the remaining generators can sample, which I don't think that's the expected behavior of sample
.
I propose changing this logic to something like:
index2 = index
for generator in generators[index2:]:
result = generator.sample(state);
index += 1
if result:
return True
return False
so that it tries to sample until it can sample and returns false when all the remaining generators are not able to draw a sample.
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.
Ah. Got it.
Your proposal will account for sample() may transiently fail, i.e. return false, at any point before then
if (mIndex >= mGenerators.size()) | ||
return false; | ||
|
||
return true; |
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.
Would it be possible for one of the remaining generators to not be sampleable? Then even though you have generators remaining (this implementation would return true
), it still seems like you wouldn't be sample (logically, maybe it should return false
). What if we just do something like
for (std::size_t i = mIndex; i < mGenerators.size(); ++i)
if (mGenerators[i].canSample())
return true;
return false;
// Documentation inherited | ||
int getNumSamples() const override | ||
{ | ||
if (mGenerators.empty()) |
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 this might be unnecessary. If this happens, the for loop wouldn't execute and numSamples
would be 0 anyway.
, mGenerators(std::move(generators)) | ||
, mIndex(0) | ||
{ | ||
#ifndef NDEBUG |
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.
@jslee02 what's the rule with indentation and preprocessor commands? Should this be indented or not? Should the for loop be indented more?
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.
We haven't explicitly decided yet, but most people don't indent due to preprocessors. So my preference is like:
{
#ifndef NDEBUG // debug mode
for (...)
assert(...);
#endif
}
or
{
#ifndef NDEBUG // debug mode
for (...)
assert(...);
#endif
}
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.
Looks like our current settings for formatting prefer the former.
return true; | ||
} | ||
mIndex++; | ||
} |
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.
This logic is still inefficient because of calling canSample()
; canSample()
iterates over the generators remaining while canSample()
is called repeatidly in this logic.
It seems that calling both of canSample()
is not necessary. What would be the problem if we remove calling canSample()
and just calling sample(...)
iterating the generators?
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.
Yeah that was my concern as well. I was wondering if changing mIndex
within canSample()
would make it any better but I guess that wouldn't be as clean.
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 don't think that's a desirable behavior.
Could you point me out what's your concern with the following proposal?:
for (auto i = mIndex; i < mGenerators.size(); ++i)
{
if (mGenerators[mIndex]->sample(state))
return true;
mIndex++;
}
return false;
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.
The current [updated] implementation has the same behavior as your proposal.
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.
Oh wait, actually I think it is beneficial to get rid of canSample()
, for instance if a SequentialSampler
is composed of more SequentialSampler
s, the current implementation would be still inefficient.
{ | ||
#ifndef NDEBUG | ||
for (const auto& generator : mGenerators) | ||
assert(generator->getStateSpace() == mStateSpace); |
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.
Nit: Could we add an assertion for checking nullity of the generators?
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.
@jslee02 Should it be an assertion? 🤔
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.
It seems this PR is almost there. :) Two minor nitpicks though.
} | ||
|
||
// Documentation inherited | ||
bool sample(statespace::StateSpace::State* state) |
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.
Nit: Missing override specifier.
@@ -0,0 +1,135 @@ | |||
#include "aikido/constraint/SequentialSampleable.hpp" | |||
|
|||
#include <dart/common/StlHelpers.hpp> |
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.
Nit: I guess you meant dart/common/Memory.hpp
for [dart::common::make_unique
].
/// StateSpace in which the constraint operates. | ||
statespace::StateSpacePtr mStateSpace; | ||
|
||
/// Set of sampleables. |
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.
Nit: set
-> sequence
namespace aikido { | ||
namespace constraint { | ||
|
||
/// Sampleable supporting a composition of multiple sampleables which |
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 this is confusing. Here's my stab at it:
Sampleable
that wraps a sequence of Sampleable
s. Exhausts the current Sampleable
before sampling from the next Sampleable
in the sequence.
#endif | ||
} | ||
|
||
// Documentation inherited |
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.
Nit: //==============================================================================
, not // Documentation inherited
.
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.
In that case, I prefer the current formatting where the declaration happens before definition.
return mStateSpace; | ||
} | ||
|
||
// Documentation inherited |
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.
Nit: //==============================================================================
, not // Documentation inherited
.
return false; | ||
} | ||
|
||
// Documentation inherited |
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.
Nit: //==============================================================================
, not // Documentation inherited
.
return numSamples; | ||
} | ||
|
||
// Documentation inherited |
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.
Nit: //==============================================================================
, not // Documentation inherited
.
throw std::invalid_argument(msg.str()); | ||
} | ||
} | ||
// TODO (avk): Somewhere we need to give warning if initial samplers are |
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.
This is a good idea. I think something like "the i'th sampleable is infinite, will ignore N subsequent sampleables" would be helpful (if N > 0).
if (mGenerators[i]->getNumSamples() == NO_LIMIT) | ||
dtwarn << "Sampleable " << i << " is infinite. " | ||
<< "Remaining " << mGenerators.size() - i - 1 | ||
<< " sampleable(s) will potentially be ignored."; |
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.
This warning seems very informative! It would be slightly better:
- to
break
once print a warning - not to warn if
i
is equal tomGenerators.size() - 1
; otherwise,mGenerators.size() - i - 1
would be the greatest number ofstd::size_t
.
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.
Agree with the first point, but I think the second point should never happen since the for loop condition is i < mGenerators.size() - 1
?
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.
Oh, I missed that. Please ignore the second point.
/// sequence of sampleables | ||
SequentialSampleGenerator( | ||
statespace::StateSpacePtr stateSpace, | ||
std::vector<std::unique_ptr<SampleGenerator>> generators); |
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.
Should this be passed by reference? (https://app.codacy.com/app/personalrobotics/aikido/pullRequest?prid=1615713)
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.
Passing by const reference is not allowed because std::unique_ptr
is not copiable (see this).
The codacy reports regarding smart pointers are somewhat inaccurate. 😞
//============================================================================== | ||
SequentialSampleGenerator::SequentialSampleGenerator( | ||
statespace::StateSpacePtr stateSpace, | ||
std::vector<std::unique_ptr<SampleGenerator>> generators) |
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.
Should this be passed by reference? (https://app.codacy.com/app/personalrobotics/aikido/pullRequest?prid=1615713)
* sequential sampleable init * implement the functions * make format * add docstring * address JS comments * clean up getNumSamples() * make format * codacy issue resolved * format + fix sample() logic * const correctness * make format * iron out sample() * simplify the logic for sample() * remove unnecessary variables * add nullity check * address JS comments * rename test file for cartesianProductSampleable * init test files * add some initial tests * add more tests * make format * update change log * make format * warning in the gen. constructor * make format * nit warning update * break point
This PR adds a Sequential Sampleable. It samples in the order given, moving to the next one upon exhaustion of the current sampleable. This is useful, for instance, in solving IK, in sampling the current configuration first before other random/informed sampling.
Before creating a pull request
make format
Before merging a pull request
CHANGELOG.md