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 ContactList and utilities for defining contact phases. #45

Merged
merged 10 commits into from
May 22, 2020

Conversation

S-Dafarra
Copy link
Member

No description provided.

@S-Dafarra S-Dafarra changed the title Add ContactList and C Add ContactList and utilities for defining contact phases. May 19, 2020
@S-Dafarra S-Dafarra requested a review from GiulioRomualdi May 19, 2020 09:09
@S-Dafarra S-Dafarra self-assigned this May 19, 2020
@GiulioRomualdi
Copy link
Member

On macOS is failing. Do you have any hints? I think it is not related to this PR.

@S-Dafarra
Copy link
Member Author

On macOS is failing. Do you have any hints? I think it is not related to this PR.

To be honest I don't know. It seems the same cache problem we had back in time after renaming the project. The weird thing is that it was not failing on my fork https://github.com/S-Dafarra/bipedal-locomotion-framework/runs/688385965

Rather than trying to run again the jobs, I added a method on the ContactList and pushed a new commit.

@traversaro
Copy link
Collaborator

On macOS is failing. Do you have any hints? I think it is not related to this PR.

To be honest I don't know. It seems the same cache problem we had back in time after renaming the project. The weird thing is that it was not failing on my fork https://github.com/S-Dafarra/bipedal-locomotion-framework/runs/688385965

Rather than trying to run again the jobs, I added a method on the ContactList and pushed a new commit.

On macOS is failing. Do you have any hints? I think it is not related to this PR.

To be honest I don't know. It seems the same cache problem we had back in time after renaming the project. The weird thing is that it was not failing on my fork https://github.com/S-Dafarra/bipedal-locomotion-framework/runs/688385965

Rather than trying to run again the jobs, I added a method on the ContactList and pushed a new commit.

It is actualy iDynTree's fault, that has bugged CMake config ^^'' . See robotology/whole-body-estimators#60 for the issue and a workaround.

Using a fancy way to perform the ceiling of a division of integers.
Adding the links to the PRs in the changelog.
Copy link
Member

@GiulioRomualdi GiulioRomualdi left a comment

Choose a reason for hiding this comment

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

Really nice machinery! I add some minor comments. Fell free to merge it

bool operator()(const Contact& lhs, const Contact& rhs) const;
};

std::set<Contact, ContactCompare> m_contacts; /** Data structure for inserting and ordering contacts. **/
Copy link
Member

Choose a reason for hiding this comment

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

Correct me if I'm mistaken. You are using an std::set because you are sure that the Contact key is unique.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, indeed I make sure of that with ContactCompare. I basically needed a container to store all the contacts in a custom order. In addition, the insertion of a new contact fails if the new one is "intersecting" some contact already added. For example, see here for an attempt of invalid insertion. p3 is "intersecting" p2.


std::set<Contact, ContactCompare> m_contacts; /** Data structure for inserting and ordering contacts. **/
std::string m_defaultName{"ContactList"}; /** Default name for the contact list. **/
ContactType m_defaultContactType{ContactType::FULL}; /** Default contact type. **/
Copy link
Member

Choose a reason for hiding this comment

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

Why do you need this?
In the end, the Contact class contains

/**
 * Type of contact.	   
 */	   
ContactType type {ContactType::FULL};

Is it only an utility required to avoid passing the ContactType for each new contact that you define?

Copy link
Member Author

Choose a reason for hiding this comment

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

* @brief Set the default name.
* @param defaultName the default name.
*/
void setDefaultName(const std::string& defaultName);
Copy link
Member

Choose a reason for hiding this comment

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

Only to have symmetry with the const std::string& defaultName() const you may change the signature of the function in

std::string& defaultName();

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, I would avoid this to prevent stupid mistakes like

if (list.defaultName() = "a")

In addition, even if it is not the case now, in the future I may need to do some changes when this name or the contact type change.

* @brief Set the default contact type.
* @param The default contact type.
*/
void setDefaultContactType(const ContactType& type);
Copy link
Member

Choose a reason for hiding this comment

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

The same of const std::string& defaultName() const

Copy link
Member Author

Choose a reason for hiding this comment

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

As above, I would avoid this.

Comment on lines 24 to 28
struct ContactReference
{
std::string listLabel; /** Label to indicate the corresponding list. **/
ContactList::const_iterator contact_it; /** Const iterator to a contact. **/
};
Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason why you prefer using a struct rather than an unordered_map? i
It may also simplify the implementation of

std::vector<ContactReference>::const_iterator getContactGivenList(const std::string& key) const;

Copy link
Member Author

Choose a reason for hiding this comment

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

Indeed, it was easier as you suggested. I modified the container in the ContactPhase to use an unordered_map in 546052a, thanks!

/**
* @brief Utility alias to a map of ContacLists.
*/
using ContactListMap = std::unordered_map<std::string, ContactList>;
Copy link
Member

Choose a reason for hiding this comment

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

Could you please give me an example of key? Thanks 😄

Copy link
Member Author

Choose a reason for hiding this comment

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

It is an identifier of the ContactList, like "left_foot", "right hand"..

{
assert(index < size());

if (index > (size() / 2 + (size() % 2 != 0))) //fancy way for doing std::ceil(size() / 2.0)
Copy link
Member

Choose a reason for hiding this comment

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

Why this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Premature optimization 😁 I needed the ceil, but the division between integers gives the floor so I add one in case the remainder is not zero. The reason is that I wanted to avoid casting this division with a double.
This if was meant to make this call slightly faster when used in a for loop. In fact, an std::set does not provide a random access operator. Hence, I used std::advance, which basically increase by one the iterator for index times. I thought that if index is greater than half of the size, it is faster to reach the desired position by starting from the end. But this advantage would have been eaten up by the division itself.

Comment on lines +165 to +182
if (element != begin())
{
--previousElement;
if (newContact.activationTime < previousElement->deactivationTime)
{
std::cerr << "[ContactList::addContact] The new contact cannot have an activation time smaller than the previous contact." << std::endl;
return false;
}
}

if (nextElement != end())
{
if (newContact.deactivationTime > nextElement->activationTime)
{
std::cerr << "[ContactList::addContact] The new contact cannot have a deactivation time greater than the next contact." << std::endl;
return false;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

This comment is just for me to recap. Before editing the contact you have to check:

  1. the previous contact has an activation time smaller than the new one;
  2. the next contact has an activation time greater than the new one.

Copy link
Member Author

Choose a reason for hiding this comment

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

Almost

  1. the previous contact has a deactivation time smaller than the new activation time;
  2. the next contact has an activation time greater than the new deactivation time.

It is done to make sure that the new step remains in the same position.


using namespace BipedalLocomotion::Planners;

void BipedalLocomotion::Planners::ContactPhaseList::createPhases()
Copy link
Member

Choose a reason for hiding this comment

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

It reminds me the unicycle-planner 👍

Copy link
Member

Choose a reason for hiding this comment

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

This method is a bit obscure (I think I have to go deeper into the Planners library before understanding the details).

Could you give me a short recap of what the function is aim to do? Thanks

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, after your comment on using the unordered_map instead of the vector it got simplified. It works as follows. It is used to determine the phases given the contact lists. A phase is characterized by a set of contacts which remains constant (the set) for the entirety of its duration.

To compute them, I use two auxiliary maps. The items are ordered according to the time (hence the key is the time). I have one for the activations and one for the deactivations. Basically, an element contains all the contacts that got activated or deactivated for a given instant (the key). Then I start iterating.

The first phase will have the activation time equal to the first key in the activations map. The active contacts correspond to the first element. Then I remove the first element in the activations map. Subsequently, I check if the new first key on the activations map is smaller than the first key in the deactivations map. If this is the case, I have found the end of the previous phase and the begin of the new phase. The new active contacts are the previous ones plus those in the new first element in the activations map. Otherwise, if the first element of the deactivations map was occurring before, I have found again the phase end time and begin time of the new phase. The active contacts instead are the previous ones, minus those contained in the first deactivation element. After this passage, I remove the first deactivation element and I iterate until the two maps are empty.

SECTION("Set from map")
{
ContactListMap contactListMap;
REQUIRE(contactListMap["left"].addContact(iDynTree::Transform::Identity(), 0.0, 1.0));
Copy link
Member

Choose a reason for hiding this comment

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

Ok, I think I found an example of key

Copy link
Member Author

@S-Dafarra S-Dafarra left a comment

Choose a reason for hiding this comment

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

Answers to @GiulioRomualdi questions

bool operator()(const Contact& lhs, const Contact& rhs) const;
};

std::set<Contact, ContactCompare> m_contacts; /** Data structure for inserting and ordering contacts. **/
Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, indeed I make sure of that with ContactCompare. I basically needed a container to store all the contacts in a custom order. In addition, the insertion of a new contact fails if the new one is "intersecting" some contact already added. For example, see here for an attempt of invalid insertion. p3 is "intersecting" p2.


std::set<Contact, ContactCompare> m_contacts; /** Data structure for inserting and ordering contacts. **/
std::string m_defaultName{"ContactList"}; /** Default name for the contact list. **/
ContactType m_defaultContactType{ContactType::FULL}; /** Default contact type. **/
Copy link
Member Author

Choose a reason for hiding this comment

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

* @brief Set the default name.
* @param defaultName the default name.
*/
void setDefaultName(const std::string& defaultName);
Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, I would avoid this to prevent stupid mistakes like

if (list.defaultName() = "a")

In addition, even if it is not the case now, in the future I may need to do some changes when this name or the contact type change.

* @brief Set the default contact type.
* @param The default contact type.
*/
void setDefaultContactType(const ContactType& type);
Copy link
Member Author

Choose a reason for hiding this comment

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

As above, I would avoid this.

/**
* @brief Utility alias to a map of ContacLists.
*/
using ContactListMap = std::unordered_map<std::string, ContactList>;
Copy link
Member Author

Choose a reason for hiding this comment

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

It is an identifier of the ContactList, like "left_foot", "right hand"..

{
assert(index < size());

if (index > (size() / 2 + (size() % 2 != 0))) //fancy way for doing std::ceil(size() / 2.0)
Copy link
Member Author

Choose a reason for hiding this comment

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

Premature optimization 😁 I needed the ceil, but the division between integers gives the floor so I add one in case the remainder is not zero. The reason is that I wanted to avoid casting this division with a double.
This if was meant to make this call slightly faster when used in a for loop. In fact, an std::set does not provide a random access operator. Hence, I used std::advance, which basically increase by one the iterator for index times. I thought that if index is greater than half of the size, it is faster to reach the desired position by starting from the end. But this advantage would have been eaten up by the division itself.

Comment on lines +165 to +182
if (element != begin())
{
--previousElement;
if (newContact.activationTime < previousElement->deactivationTime)
{
std::cerr << "[ContactList::addContact] The new contact cannot have an activation time smaller than the previous contact." << std::endl;
return false;
}
}

if (nextElement != end())
{
if (newContact.deactivationTime > nextElement->activationTime)
{
std::cerr << "[ContactList::addContact] The new contact cannot have a deactivation time greater than the next contact." << std::endl;
return false;
}
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Almost

  1. the previous contact has a deactivation time smaller than the new activation time;
  2. the next contact has an activation time greater than the new deactivation time.

It is done to make sure that the new step remains in the same position.


using namespace BipedalLocomotion::Planners;

void BipedalLocomotion::Planners::ContactPhaseList::createPhases()
Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, after your comment on using the unordered_map instead of the vector it got simplified. It works as follows. It is used to determine the phases given the contact lists. A phase is characterized by a set of contacts which remains constant (the set) for the entirety of its duration.

To compute them, I use two auxiliary maps. The items are ordered according to the time (hence the key is the time). I have one for the activations and one for the deactivations. Basically, an element contains all the contacts that got activated or deactivated for a given instant (the key). Then I start iterating.

The first phase will have the activation time equal to the first key in the activations map. The active contacts correspond to the first element. Then I remove the first element in the activations map. Subsequently, I check if the new first key on the activations map is smaller than the first key in the deactivations map. If this is the case, I have found the end of the previous phase and the begin of the new phase. The new active contacts are the previous ones plus those in the new first element in the activations map. Otherwise, if the first element of the deactivations map was occurring before, I have found again the phase end time and begin time of the new phase. The active contacts instead are the previous ones, minus those contained in the first deactivation element. After this passage, I remove the first deactivation element and I iterate until the two maps are empty.

@S-Dafarra
Copy link
Member Author

@GiulioRomualdi let me know if I answered your questions and I can merge 😁

@GiulioRomualdi
Copy link
Member

@S-Dafarra thanks for the explanation. You can merge it

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.

3 participants