-
Notifications
You must be signed in to change notification settings - Fork 38
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
Conversation
Trying to fix WIndows failure.
On |
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 |
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.
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.
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. **/ |
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.
Correct me if I'm mistaken. You are using an std::set
because you are sure that the Contact
key is unique.
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.
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. **/ |
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.
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?
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.
Yes, it is used here: https://github.com/S-Dafarra/bipedal-locomotion-framework/blob/ab4e9d7a5c4a101ca8630b96af1052bf8a3c95e5/src/Planners/include/BipedalLocomotion/Planners/ContactList.h#L92
In addition, the defaultContactName
is used as a key in the ContactListMap
when using this method: https://github.com/S-Dafarra/bipedal-locomotion-framework/blob/ab4e9d7a5c4a101ca8630b96af1052bf8a3c95e5/src/Planners/include/BipedalLocomotion/Planners/ContactPhaseList.h#L58
* @brief Set the default name. | ||
* @param defaultName the default name. | ||
*/ | ||
void setDefaultName(const std::string& defaultName); |
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.
Only to have symmetry with the const std::string& defaultName() const
you may change the signature of the function in
std::string& defaultName();
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.
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); |
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 same of const std::string& defaultName() const
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.
As above, I would avoid this.
struct ContactReference | ||
{ | ||
std::string listLabel; /** Label to indicate the corresponding list. **/ | ||
ContactList::const_iterator contact_it; /** Const iterator to a contact. **/ | ||
}; |
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.
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;
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.
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>; |
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.
Could you please give me an example of key
? Thanks 😄
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 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) |
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.
Why this?
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.
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.
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; | ||
} | ||
} |
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 comment is just for me to recap. Before editing the contact you have to check:
- the previous contact has an activation time smaller than the new one;
- the next contact has an activation time greater than the new one.
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.
Almost
- the previous contact has a deactivation time smaller than the new activation time;
- 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() |
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 reminds me the unicycle-planner
👍
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 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
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.
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)); |
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.
Ok, I think I found an example of key
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.
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. **/ |
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.
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. **/ |
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.
Yes, it is used here: https://github.com/S-Dafarra/bipedal-locomotion-framework/blob/ab4e9d7a5c4a101ca8630b96af1052bf8a3c95e5/src/Planners/include/BipedalLocomotion/Planners/ContactList.h#L92
In addition, the defaultContactName
is used as a key in the ContactListMap
when using this method: https://github.com/S-Dafarra/bipedal-locomotion-framework/blob/ab4e9d7a5c4a101ca8630b96af1052bf8a3c95e5/src/Planners/include/BipedalLocomotion/Planners/ContactPhaseList.h#L58
* @brief Set the default name. | ||
* @param defaultName the default name. | ||
*/ | ||
void setDefaultName(const std::string& defaultName); |
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.
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); |
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.
As above, I would avoid this.
/** | ||
* @brief Utility alias to a map of ContacLists. | ||
*/ | ||
using ContactListMap = std::unordered_map<std::string, ContactList>; |
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 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) |
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.
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.
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; | ||
} | ||
} |
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.
Almost
- the previous contact has a deactivation time smaller than the new activation time;
- 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() |
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.
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.
@GiulioRomualdi let me know if I answered your questions and I can merge 😁 |
@S-Dafarra thanks for the explanation. You can merge it |
No description provided.