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

Add World::State #265

Merged
merged 18 commits into from
Dec 7, 2017
Merged

Add World::State #265

merged 18 commits into from
Dec 7, 2017

Conversation

gilwoolee
Copy link
Contributor

@gilwoolee gilwoolee commented Nov 23, 2017

This PR addresses #261 . Based on discussion on #263, this PR creates WorldState struct and provides equals method to compare with other WorldStates.


Before creating a pull request

  • Document new methods and classes
  • Format code with make format

Before merging a pull request

  • Set version target by selecting a milestone on the right side
  • Summarize this change in CHANGELOG.md
  • Add unit test(s) for this change

@gilwoolee gilwoolee mentioned this pull request Nov 23, 2017
5 tasks
@codecov
Copy link

codecov bot commented Nov 23, 2017

Codecov Report

Merging #265 into master will decrease coverage by 0.07%.
The diff coverage is 55.55%.

@@            Coverage Diff             @@
##           master     #265      +/-   ##
==========================================
- Coverage   70.63%   70.56%   -0.08%     
==========================================
  Files         186      188       +2     
  Lines        5510     5537      +27     
  Branches      863      869       +6     
==========================================
+ Hits         3892     3907      +15     
- Misses       1089     1097       +8     
- Partials      529      533       +4
Impacted Files Coverage Δ
src/planner/WorldStateSaver.cpp 0% <0%> (ø)
include/aikido/planner/World.hpp 100% <100%> (ø)
src/planner/World.cpp 76.74% <70%> (-2.05%) ⬇️

@mxgrey
Copy link

mxgrey commented Nov 23, 2017

Just to clarify some things about the semantics in DART:

Skeleton::Configuration follows the classical concept of a robot configuration, which is basically a glorified vector that describes only the relevant components of the robot state using as few parameters as possible. This is usually all that's needed for kinematic planning.

Skeleton::State contains a perfect and complete description of the state that the robot is in, making it suitable for perfect recreations of fully dynamic simulations. The Skeleton::State class is extensible, so if users add their own "Aspects" to the Skeleton or any of its BodyNodes or Joints, then the state information of those Aspects will also be captured by the Skeleton::State. The most concrete example of this can be seen in SoftBodyNode: The deformation of the soft body is captured in a Skeleton::State, but it is not captured in a Skeleton::Configuration.

Since the World::State implementation here is building off of Skeleton::Configuration, you might want to consider naming it World::Configuration. That would leave you with an opportunity to create a World::State later on which could build off of Skeleton::State and capture more than what this current implementation is able to.

@gilwoolee
Copy link
Contributor Author

Thanks for the comments @mxgrey .

Indeed, what you described is precisely why I chose to use Skeleton::Configuration instead of Skeleton::State. Skeleton::Configuration has everything we need from Skeleton for kinematic planning. I initially thought of calling ours as World::Configuration, but the reason why I decided to go with World::State is because, although the current version only contains skeleton configurations, World::State should contain everything we need for kinematic planning in aikido, which might include a set of collision groups(dart::collision::CollisionGroup) or collision constraints(aikido::constraint::CollisionFree), or joint limits, etc.

In fact, now might be a good time to discuss whether to put in collision groups or anything else, to make World::State contain everything we need for making planning calls. Any thoughts @mkoval @jslee02 @brianhou @mxgrey ?

Additionally, I think I'll add WorldStateSaver, just like aikido::statespace::dart::MetaSkeletonStateSpaceSaver, but it seems like once we have the former we might not need the latter too much. In general, shouldn't planners take World to restore after planning as well as MetaSkeletonStateSpace to plan with? Restoring World should include restoring MetaSksletonState .

@mxgrey
Copy link

mxgrey commented Nov 26, 2017

You may want to consider putting information like collision groups and constraints into a World::Properties data structure, assuming those are parameters that describe the scenario and don't change over time. That way, if you're recording or comparing states for a scenario, you can be a little leaner in terms of memory usage and the reduce the number of operations that are performed in a comparison.

Note that the Skeleton::Properties class records time-independent information like joint limits.

@gilwoolee
Copy link
Contributor Author

@brianhou @mkoval Could you review this?

Copy link
Contributor

@brianhou brianhou left a comment

Choose a reason for hiding this comment

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

Looks mostly fine to me!

class World
{
public:
// Encapsulates the state of the World.
struct State
Copy link
Contributor

Choose a reason for hiding this comment

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

Should State also have some notion of the Skeletons it contains? Maybe the Skeleton names?

Copy link
Member

Choose a reason for hiding this comment

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

One option is to index configurations by Skeleton name, i.e. switch from an std::vector to an std::unordered_map.

{
std::vector<dart::dynamics::Skeleton::Configuration> configurations;

/// Returns true if two world states are the same.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any reason to use equals rather than operator==?

Copy link
Member

Choose a reason for hiding this comment

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

I don't see any reason why we couldn't use operator == here. If we define that, then we should also remember to define operator !=. 🙃

Also, this likely should be flagged as const.

{
if (state.configurations.size() != mSkeletons.size())
throw std::invalid_argument(
"World::State and this World does not have the same number of "
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: does -> do


WorldStateSaver::WorldStateSaver(
WorldPtr world)
: mWorld{world}
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make sense to initialize mWorldState here instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

WorldState does not keep a pointer to a specific world, but when WorldStateSaver is destroyed, we typically want to restore the state of a World to a previous state. So I think we need to keep a pointer to the world?

namespace aikido {
namespace planner {

WorldStateSaver::WorldStateSaver(
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: can we also add basic tests for WorldStateSaver?


EXPECT_FALSE(mWorld->getState().equals(otherWorld->getState()));
EXPECT_FALSE(otherWorld->getState().equals(mWorld->getState()));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: can we add a couple tests for setState to verify that it throws errors?

Copy link
Member

@mkoval mkoval left a comment

Choose a reason for hiding this comment

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

This generally looks okay to me.

I don't think this pull request really solves the tricky question of what it means for two Worlds to be equivalent. The current logic would treat two entirely different worlds as equivalent if they happen to contain the same number of Skeletons with the same dimension Configurations.

I also am not convinced that WorldStateSaver is a good idea. When we were writing Magi we discovered that it is generally safer, more efficient, and easier to implement more granular state saving. I.e. only save the state over the parts of the environment that you modify in an ExecutableSolution. I am fine with keeping this functionality for now, but generally think we should avoid using it in routine operation.

These are hard problems and I don't have a great solution. Maybe @psigen or @siddhss5 have better ideas.

class World
{
public:
// Encapsulates the state of the World.
struct State
Copy link
Member

Choose a reason for hiding this comment

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

One option is to index configurations by Skeleton name, i.e. switch from an std::vector to an std::unordered_map.

{
std::vector<dart::dynamics::Skeleton::Configuration> configurations;

/// Returns true if two world states are the same.
Copy link
Member

Choose a reason for hiding this comment

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

I don't see any reason why we couldn't use operator == here. If we define that, then we should also remember to define operator !=. 🙃

Also, this likely should be flagged as const.

/// WorldStateSaver is destructed.
///
/// \param _space WorldState to save/restore
explicit WorldStateSaver(WorldPtr world);
Copy link
Member

Choose a reason for hiding this comment

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

We should be able to flag this argument as const.

Also, I don't see a strong reason to make this accept a smart pointer rather than a reference or raw pointer.


virtual ~WorldStateSaver();

// WorldStateSaver is uncopyable, must use std::move
Copy link
Member

Choose a reason for hiding this comment

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

I don't see any harm in making this class copyable. What's the concern here?

}

return true;
}
Copy link
Member

Choose a reason for hiding this comment

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

:neckbeard: I think this whole function could be replaced with:

return configurations == other.configurations;

Something is also a bit funny about this. Shouldn't we verify that the two Configurations contain the same Skeletons somehow?

Copy link
Contributor Author

@gilwoolee gilwoolee Dec 6, 2017

Choose a reason for hiding this comment

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

We're not checking whether the skeletons are the same because the skeletons may be cloned ones. Shouldn't we want to be able to say that the two cloned worlds are in the same state if they have the same configurations?

for (size_t idx = 0; idx < state.configurations.size(); ++idx)
{
std::lock_guard<std::mutex> lock(mSkeletons[idx]->getMutex());
mSkeletons[idx]->setConfiguration(state.configurations[idx]);
Copy link
Member

Choose a reason for hiding this comment

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

Just a thought, outside the scope of this pull request: At some point we may want to abandon using per-Skeleton locks and instead having a single lock for the entire World. This wasn't an option before because we didn't have a World object. 😉

@gilwoolee
Copy link
Contributor Author

@brianhou could you give it a (hopefully) final pass? :)

@gilwoolee
Copy link
Contributor Author

@mkoval Thanks for the feedback! I agree that saving/restoring a smaller subset of the world which gets modified during planning is a better idea, which we're not currently doing in magi. I think it'd still be useful to be able to take a snapshot of the world's current state, for example, in serializing/deserializing, etc. We might want to support selective state saving/restoring in the near future, so I'll leave it to #276.

@gilwoolee
Copy link
Contributor Author

There also seems to be a bit of detailed comparison that needs to be done in order to say whether two skeletons have the same "state space", which is necessary when we are doing == operation or setState operation. I am leaving that for later and just comparing skeleton names for now.

@mkoval
Copy link
Member

mkoval commented Dec 6, 2017

I think comparing Skeleton names is fine for now. I agree that comparing StateSpaces is a giant can of worms - and I don't have a good solution in mind. Let's revisit that issue once we have some solid demos running in Aikido on HERB. Then we'll have some more context for the discussion.

{
World::State state;

for (auto skeleton : mSkeletons)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this can be const auto& skeleton?

"Skeleton " + name + " does not exist in state.");
}

for (const auto& skeleton : mSkeletons)
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this loop be merged into the previous one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't want to merge these two because I don't want this to throw an error after modifying some of the skeletons.

for (const auto& skeleton : mSkeletons)
{
std::lock_guard<std::mutex> lock(skeleton->getMutex());
skeleton->setConfiguration(state.configurations.at(skeleton->getName()));
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use state.configurations[skeleton->getName()] instead of at? Since we've already checked that the keys should all exist, at seems unnecessary.

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, maybe we could merge the two loops and then just use at instead of explicitly calling find and then indexing? @jslee02 is it inefficient to catch the (possible) out_of_range exception from at and then trigger our own more helpful exception?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm. Can't do [] because [] is a non-const operator and I'm taking in const State&.
https://stackoverflow.com/questions/15660838/error-no-match-for-operator-in-near-match

I'll keep it as is.

@brianhou brianhou merged commit 257ccdb into master Dec 7, 2017
@brianhou brianhou deleted the WorldState branch December 7, 2017 06:26
@yskim041 yskim041 added this to the Aikido 0.2.0 milestone Dec 7, 2017
@jslee02 jslee02 mentioned this pull request Jan 8, 2018
gilwoolee added a commit that referenced this pull request Jan 21, 2019
* Add World.equalConfiguration method.

* Code format

* Add WorldState

* Add WorldStateSaver

* Move MetaSkeletonStateSpaceSaver-impl to cpp file

* Add tests for MetaSkeletonStateSpaceSaver

* Remove unused header from test_MetaSkeletonStateSpaceSaver

* Address Mike and Brian's comments

* Modify format, docstring

* Address Brian's comments
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.

5 participants