Skip to content

Commit 81e2fa3

Browse files
committed
fix description and add methods for testing
1 parent f8cfd91 commit 81e2fa3

File tree

6 files changed

+114
-6
lines changed

6 files changed

+114
-6
lines changed

dynamic_payload_analysis_core/dynamic_payload_analysis_core/core.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ def compute_inverse_dynamics(self, q : np.ndarray , qdot : np.ndarray, qddot : n
230230
return tau
231231

232232

233-
def create_ext_force(self, masses : Union[float, np.ndarray] , frame_name : Union[str | np.ndarray], q : np.ndarray) -> np.ndarray:
233+
def create_ext_force(self, masses : Union[float, np.ndarray] , frame_name : Union[str | np.ndarray], q : np.ndarray) -> list:
234234
"""
235235
Create external forces vector based on the masses and frame ID.
236236
The resulting vector will contain the force applied to the specified frame and with the local orientation of the parent joint.
@@ -338,9 +338,10 @@ def compute_inverse_kinematics_ikpy(self, q : np.ndarray, end_effector_position:
338338
def compute_inverse_kinematics(self, q : np.ndarray, end_effector_position: np.ndarray, joint_id : str) -> np.ndarray:
339339
"""
340340
Compute the inverse kinematics for the robot model with joint limits consideration.
341-
:param q: current joint configuration vector.
341+
342+
:param q: Current joint configuration vector.
342343
:param end_effector_position: Position of the end effector in the world frame [rotation matrix , translation vector].
343-
:param end_effector_name: Name of the end effector joint.
344+
:param joint_id: Id of the end effector joint.
344345
:return: Joint configuration vector that achieves the desired end effector position.
345346
"""
346347

@@ -450,7 +451,7 @@ def verify_configurations(self, configurations: np.ndarray, masses : np.ndarray,
450451
:param selected_joint_id (int): Identifier of the selected joint in the tree to verify the configurations for.
451452
:return: Array of valid configurations with related torques in format: [{"config", "end_effector_pos, "tau", "tree_id","selected_joint_id" }].
452453
"""
453-
454+
# TODO : get the tree_id from the sub tree array instead of passing it as parameter
454455
valid_configurations = []
455456

456457
# check valid configurations for left arm
@@ -467,7 +468,7 @@ def verify_configurations(self, configurations: np.ndarray, masses : np.ndarray,
467468
# Compute the inverse dynamics for the current configuration without external forces
468469
tau = self.compute_inverse_dynamics(q["config"], self.get_zero_velocity(), self.get_zero_acceleration())
469470

470-
# Check if the torques are within the effort limits
471+
# Check if the torques are within the effort limits
471472
if self.check_effort_limits(tau= tau, tree_id= tree_id).all():
472473
valid = True
473474
# Compute all the collisions
@@ -654,6 +655,17 @@ def get_links_from_tree(self, joint_ids: np.ndarray | int) -> np.ndarray:
654655
return np.array(frames, dtype=object)
655656

656657

658+
def get_end_effector_position_array(self, x: float, y: float, z: float) -> np.ndarray:
659+
"""
660+
Get the end effector position as a array.
661+
662+
:param x: X position of the end effector.
663+
:param y: Y position of the end effector.
664+
:param z: Z position of the end effector.
665+
:return: Numpy array representing the end effector position.
666+
"""
667+
return pin.SE3(np.eye(3), np.array([x, y, z]))
668+
657669
def get_maximum_torques(self, valid_configs : np.ndarray) -> np.ndarray | np.ndarray:
658670
"""
659671
Get the maximum torques for each joint in all valid configurations.

dynamic_payload_analysis_core/test/test_core.py

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from dynamic_payload_analysis_core.core import TorqueCalculator
22
import unittest
33
import os
4+
import numpy as np
45

56

67
class TestTorqueCalculator(unittest.TestCase):
@@ -24,13 +25,15 @@ def test_init_torque_calculator(self):
2425

2526
def test_get_root_name(self):
2627
root_name = self.robot_handler.get_root_joint_name(self.xml_string)
28+
self.assertIsInstance(root_name, str, "Root name should be a string")
2729
self.assertEqual(self.robot_handler.get_root_name(), "base_footprint")
2830
self.assertEqual(root_name, self.robot_handler.get_root_name())
2931
print("✅ Root name assertion passed")
3032

3133
def test_subtree(self):
3234
subtrees = self.robot_handler.get_subtrees()
3335

36+
self.assertIsInstance(subtrees, np.ndarray, "Subtrees should be a list")
3437
self.assertEqual(len(subtrees), 7)
3538
self.assertEqual(subtrees[0]["tip_link_name"], "wheel_front_left_link")
3639
self.assertEqual(subtrees[1]["tip_link_name"], "wheel_front_right_link")
@@ -49,8 +52,97 @@ def test_compute_mimic_joints(self):
4952
self.assertNotIn("arm_1_joint", mimic_joints)
5053
print("✅ Mimic joints assertion passed")
5154

52-
def
55+
def test_create_external_force(self):
56+
ext_forces = self.robot_handler.create_ext_force(masses = 1, frame_name = "arm_left_3_link", q = self.robot_handler.get_zero_configuration())
57+
self.assertIsInstance(ext_forces, list, "External forces should be a numpy array")
58+
self.assertNotEqual(ext_forces[8], [0,0,0], "External force should not be zero")
59+
print("✅ External force assertion passed")
5360

61+
def test_compute_inverse_dynamics(self):
62+
standard_config = self.robot_handler.get_zero_configuration()
63+
64+
torques = self.robot_handler.compute_inverse_dynamics(standard_config, self.robot_handler.get_zero_velocity(), self.robot_handler.get_zero_acceleration())
65+
66+
self.assertIsNotNone(torques, "Torques should not be None")
67+
self.assertIsInstance(torques, np.ndarray, "Torques should be a list")
68+
self.assertTrue(all(isinstance(torque, float) for torque in torques), "All torques should be floats")
69+
70+
# add external forces to the end-effector
71+
external_forces = self.robot_handler.create_ext_force(masses=1, frame_name="arm_left_3_link", q=standard_config)
72+
torques_with_ext = self.robot_handler.compute_inverse_dynamics(standard_config, self.robot_handler.get_zero_velocity(), self.robot_handler.get_zero_acceleration(), external_forces)
73+
74+
np.testing.assert_raises(AssertionError, np.testing.assert_array_equal, torques, torques_with_ext)
75+
print("✅ Inverse dynamics assertion passed")
76+
77+
def test_update_configuration(self):
78+
zero_config = self.robot_handler.get_zero_configuration()
79+
new_config = zero_config + 5
80+
81+
self.robot_handler.update_configuration(new_config)
82+
joint_placement = self.robot_handler.get_joint_placement(joint_id=20,q = new_config) # just to ensure the configuration is updated internally
83+
84+
self.assertIsInstance(joint_placement, dict, "Joint placement should be a dict")
85+
86+
self.robot_handler.update_configuration(zero_config)
87+
updated_joint_placement = self.robot_handler.get_joint_placement(joint_id=20,q = zero_config)
88+
89+
self.assertNotEqual(joint_placement["x"], updated_joint_placement["x"], "Joint placement should differ after configuration update")
90+
self.assertNotEqual(joint_placement["y"], updated_joint_placement["y"], "Joint placement should differ after configuration update")
91+
self.assertNotEqual(joint_placement["z"], updated_joint_placement["z"], "Joint placement should differ after configuration update")
92+
93+
print("✅ Update configuration assertion passed")
94+
95+
def test_compute_inverse_kinematics(self):
96+
initial_config = self.robot_handler.get_zero_configuration()
97+
98+
end_effector_pos = self.robot_handler.get_end_effector_position_array(-0.4, -0.6, 0.6) # reachable position
99+
computed_config = self.robot_handler.compute_inverse_kinematics(q = initial_config, end_effector_position = end_effector_pos, joint_id= 25)
100+
101+
self.assertIsNotNone(computed_config, "Computed configuration should not be None if IK is successful")
102+
self.assertIsInstance(computed_config, np.ndarray, "Computed configuration should be a numpy array")
103+
104+
end_effector_pos_not_reachable = self.robot_handler.get_end_effector_position_array(4, 6, 6) # not reachable position
105+
computed_config_not_reachable = self.robot_handler.compute_inverse_kinematics(q = initial_config, end_effector_position = end_effector_pos_not_reachable, joint_id= 25)
106+
107+
self.assertIsNone(computed_config_not_reachable, "Computed configuration should be None if IK is not successful")
108+
109+
print("✅ Inverse kinematics assertion passed")
110+
111+
112+
def test_compute_valid_workspace(self):
113+
computed_configurations = self.robot_handler.compute_all_configurations(range = 1, resolution = 0.3, end_joint_id= 25) # used 1 meter range and 0.3 resolution to speed up the test
114+
len_configs = len(computed_configurations)
115+
self.assertIsInstance(computed_configurations, np.ndarray, "Computed configuration should be a numpy array")
116+
117+
self.assertTrue(np.all(computed_configurations[round(len_configs/2)]["end_effector_pos"] <= np.array([1, 1, 1])), "End effector positions should be within the specified range")
118+
self.assertTrue(np.all(computed_configurations[round(len_configs/3)]["end_effector_pos"] <= np.array([1, 1, 1])), "End effector positions should be within the specified range")
119+
120+
verified_configs = self.robot_handler.verify_configurations(computed_configurations, masses= None, checked_frames= None, tree_id=5, selected_joint_id=25)
121+
122+
self.assertIsInstance(verified_configs, np.ndarray, "Verified configuration should be a numpy array")
123+
124+
self.assertEqual(verified_configs[0]["tree_id"], 5, "Tree ID should match the provided tree ID")
125+
self.assertEqual(verified_configs[0]["selected_joint_id"], 25, "Selected joint ID should match the provided joint ID")
126+
127+
128+
# should be None because there is no selected joint id inside the object
129+
valid_workspace = self.robot_handler.get_valid_workspace(range = 1, resolution= 0.3, masses = None, checked_frames = None)
130+
131+
self.assertIsInstance(valid_workspace, np.ndarray, "Valid workspace should be a numpy array")
132+
self.assertEqual(len(valid_workspace), 0, "Valid workspace length should be zero if there is no selected joint id")
133+
134+
# modify selected joint id to test valid workspace
135+
self.robot_handler.subtrees[5]["selected_joint_id"] = 25
136+
valid_workspace = self.robot_handler.get_valid_workspace(range = 1, resolution= 0.3, masses = None, checked_frames = None)
137+
138+
self.assertEqual(len(valid_workspace), len(verified_configs), "Valid workspace length should match the verified configurations length")
139+
140+
print("✅ Compute valid workspace assertion passed")
141+
142+
143+
144+
145+
54146

55147
if __name__ == "__main__":
56148
unittest.main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit 17f88f5513ef817ea3c65b07f33dbea3f3340e68
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit 4b8948e061c2ef8b2ea15d658cf35981c683f864

talos_robot

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit 6152887e3f258cca58231391ae0f380baede43ff

tiago_pro_robot

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Subproject commit c62d9f885a5a48a145228c13acb16e027691993d

0 commit comments

Comments
 (0)