Skip to content

Commit 18eee57

Browse files
author
DavidB-CMU
committed
Changes for VectorFieldPlanner
1 parent 823594f commit 18eee57

File tree

2 files changed

+109
-40
lines changed

2 files changed

+109
-40
lines changed

src/prpy/planning/vectorfield.py

Lines changed: 69 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ def PlanToEndEffectorPose(self, robot, goal_pose, timelimit=5.0,
9696

9797
def vf_geodesic():
9898
"""
99-
Function defining a joint-space vector field.
99+
Define a joint-space vector field, that moves along the
100+
geodesic (shortest path) from the start pose to the goal pose.
100101
"""
101102
twist = util.GeodesicTwist(manip.GetEndEffectorTransform(),
102103
goal_pose)
@@ -109,9 +110,10 @@ def vf_geodesic():
109110

110111
def CloseEnough():
111112
"""
112-
Function defining the termination condition.
113-
114-
Succeed if end-effector pose is close to the goal pose.
113+
The termination condition.
114+
At each integration step, the geodesic error between the
115+
start and goal poses is compared. If within threshold,
116+
the integration will terminate.
115117
"""
116118
pose_error = util.GetGeodesicDistanceBetweenTransforms(
117119
manip.GetEndEffectorTransform(),
@@ -120,9 +122,9 @@ def CloseEnough():
120122
return Status.TERMINATE
121123
return Status.CONTINUE
122124

123-
integration_timelimit = 10.0
125+
integration_interval = 10.0
124126
traj = self.FollowVectorField(robot, vf_geodesic, CloseEnough,
125-
integration_timelimit,
127+
integration_interval,
126128
timelimit,
127129
**kw_args)
128130

@@ -163,7 +165,7 @@ def PlanToEndEffectorOffset(self, robot, direction, distance,
163165

164166
# Normalize the direction vector.
165167
direction = numpy.array(direction, dtype='float')
166-
direction /= numpy.linalg.norm(direction)
168+
direction = util.NormalizeVector(direction)
167169

168170
manip = robot.GetActiveManipulator()
169171
Tstart = manip.GetEndEffectorTransform()
@@ -217,9 +219,9 @@ def TerminateMove():
217219

218220
return Status.CONTINUE
219221

220-
integration_timelimit = 10.0
222+
integration_interval = 10.0
221223
return self.FollowVectorField(robot, vf_straightline, TerminateMove,
222-
integration_timelimit,
224+
integration_interval,
223225
timelimit,
224226
**kw_args)
225227

@@ -230,6 +232,8 @@ def PlanWorkspacePath(self, robot, traj,
230232
position_tolerance=0.01,
231233
angular_tolerance=0.15,
232234
goal_pose_tol=0.01,
235+
Kp_ff=None,
236+
Kp_e=None,
233237
**kw_args):
234238
"""
235239
Plan a configuration space path given a workspace path.
@@ -243,13 +247,47 @@ def PlanWorkspacePath(self, robot, traj,
243247
@param float position_tolerance: Constraint tolerance (meters).
244248
@param float angular_tolerance: Constraint tolerance (radians).
245249
@param float goal_pose_tol: Goal tolerance (meters).
250+
@param numpy.array Kp_ff: Feed-forward gain.
251+
A 1x6 vector, where first 3 elements
252+
affect the translational velocity,
253+
the last 3 elements affect the
254+
rotational velocity.
255+
@param numpy.array Kp_e: Error gain.
256+
A 1x6 vector, where first 3 elements
257+
affect the translational velocity,
258+
the last 3 elements affect the
259+
rotational velocity.
246260
247261
@return openravepy.Trajectory qtraj: Configuration space path.
248262
"""
263+
264+
if not util.IsTrajectoryTypeIkParameterization(traj):
265+
raise ValueError("Trajectory is not a workspace trajectory, it "
266+
"must have configuration specification of "
267+
"openravepy.IkParameterizationType.Transform6D")
268+
269+
#if IsTimedTrajectory(traj):
270+
# raise ValueError("PlanWorkspacePath expected an un-timed "
271+
# "trajectory.")
272+
249273
if position_tolerance < 0.0:
250274
raise ValueError('Position tolerance must be non-negative.')
251275
elif angular_tolerance < 0.0:
252276
raise ValueError('Angular tolerance must be non-negative.')
277+
elif goal_pose_tol < 0.0:
278+
raise ValueError('Goal-pose tolerance must be non-negative.')
279+
280+
#
281+
#
282+
# ADD ComputeGeodesicUnitTiming()
283+
#
284+
#
285+
286+
# Set the default gains
287+
if Kp_ff == None:
288+
Kp_ff = numpy.array([1.0,1.0,1.0,1.0,1.0,1.0])
289+
if Kp_e == None:
290+
Kp_e = numpy.array([1.0,1.0,1.0,1.0,1.0,1.0])
253291

254292
manip = robot.GetActiveManipulator()
255293
Tstart = manip.GetEndEffectorTransform()
@@ -266,7 +304,7 @@ def vf_path():
266304

267305
# Find where we are on the goal trajectory by finding
268306
# the the closest point
269-
(_,t) = util.GetMinDistanceBetweenTransformAndWorkspaceTraj(
307+
(_,t,_) = util.GetMinDistanceBetweenTransformAndWorkspaceTraj(
270308
T_ee_actual,
271309
traj)
272310
# Get the desired end-effector transform from
@@ -278,27 +316,23 @@ def vf_path():
278316

279317
# The twist between the actual end-effector position and
280318
# where it should be on the goal trajectory
319+
# (the error term)
281320
twist_perpendicular = util.GeodesicTwist(T_ee_actual,
282321
desired_T_ee)
283-
direction_perpendicular = desired_T_ee[0:3,3] - T_ee_actual[0:3,3]
284322

285323
# The twist tangent to where the end-effector should be
286324
# on the goal trajectory
325+
# (the feed-forward term)
287326
twist_parallel = util.GeodesicTwist(desired_T_ee,
288327
desired_T_ee_next)
289-
direction_parallel = desired_T_ee_next[0:3,3] - desired_T_ee[0:3,3]
290-
291-
twist = 0.0*twist_perpendicular + 1.0*twist_parallel
292328

293-
K_p = 0.5
294-
direction = (1.0-K_p)*direction_perpendicular + \
295-
K_p*direction_parallel
296-
# Normalize the direction vector
297-
direction = numpy.array(direction, dtype='float')
298-
direction /= numpy.linalg.norm(direction)
329+
# Normalize the translational and angular velocity of
330+
# the feed-forward twist
331+
twist_parallel[0:3] = util.NormalizeVector(twist_parallel[0:3])
332+
twist_parallel[3:6] = util.NormalizeVector(twist_parallel[3:6])
299333

300-
# Modify the twist
301-
twist[0:3] = direction
334+
# Apply gains
335+
twist = Kp_e*twist_perpendicular + Kp_ff*twist_parallel
302336

303337
# Calculate joint velocities using an optimized jacobian
304338
dqout, _ = util.ComputeJointVelocityFromTwist(robot, twist,
@@ -319,7 +353,7 @@ def TerminateMove():
319353

320354
# Find where we are on the goal trajectory by finding
321355
# the the closest point
322-
(_, t) = util.GetMinDistanceBetweenTransformAndWorkspaceTraj(
356+
(_,t,_) = util.GetMinDistanceBetweenTransformAndWorkspaceTraj(
323357
T_ee_curr,
324358
traj)
325359
# Get the desired end-effector transform from
@@ -349,15 +383,15 @@ def TerminateMove():
349383

350384
return Status.CONTINUE
351385

352-
integration_timelimit = 10.0
386+
integration_interval = 10.0
353387
return self.FollowVectorField(robot, vf_path, TerminateMove,
354-
integration_timelimit,
388+
integration_interval,
355389
timelimit, **kw_args)
356390

357391

358392
@PlanningMethod
359393
def FollowVectorField(self, robot, fn_vectorfield, fn_terminate,
360-
integration_timelimit=10.,
394+
integration_time_interval=10.0,
361395
timelimit=5.0,
362396
**kw_args):
363397
"""
@@ -366,8 +400,8 @@ def FollowVectorField(self, robot, fn_vectorfield, fn_terminate,
366400
@param robot
367401
@param fn_vectorfield a vectorfield of joint velocities
368402
@param fn_terminate custom termination condition
369-
@param integration_timelimit The initial time step to be taken
370-
by the integrator.
403+
@param integration_time_interval The time interval to integrate
404+
over.
371405
@param timelimit time limit before giving up
372406
@param kw_args keyword arguments to be passed to fn_vectorfield
373407
@return traj
@@ -420,7 +454,12 @@ def fn_wrapper(t, q):
420454

421455
def fn_status_callback(t, q):
422456
"""
423-
This is called for each collision check.
457+
Check joint-limits and collisions for a specific joint
458+
configuration. This is called multiple times at DOF
459+
resolution in order to check along the entire length of the
460+
trajectory.
461+
Note: Currently this is called after each integration time
462+
step, which means we are doing more checks than required.
424463
"""
425464
if time.time() - time_start >= timelimit:
426465
raise TimeLimitError()
@@ -496,7 +535,7 @@ def fn_callback(t, q):
496535
integrator.set_solout(fn_callback)
497536
# Initial conditions
498537
integrator.set_initial_value(y=robot.GetActiveDOFValues(), t=0.)
499-
integrator.integrate(t=integration_timelimit)
538+
integrator.integrate(t=integration_time_interval)
500539

501540
t_cache = nonlocals['t_cache']
502541
exception = nonlocals['exception']

src/prpy/util.py

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,19 @@ def ComputeAinv(N,dof):
196196
invA[i*dof+k,j*dof+k] = invA_small[i-1,j-1]
197197
return invA
198198

199+
def NormalizeVector(vec):
200+
"""
201+
Normalize a vector.
202+
This is faster than doing: vec/numpy.linalg.norm(vec)
203+
204+
@param numpy.array vec: A 1-dimensional vector.
205+
@returns numpy.array result: A vector of the same size, where the
206+
L2 norm of the elements equals 1.
207+
"""
208+
magnitude = numpy.sqrt(vec.dot(vec))
209+
vec2 = (vec / magnitude)
210+
return numpy.nan_to_num(vec2) # convert NaN to zero
211+
199212
def MatrixToTraj(traj_matrix,cs,dof,robot):
200213
env = robot.GetEnv()
201214
traj = openravepy.RaveCreateTrajectory(env,'')
@@ -730,12 +743,14 @@ def GetGeodesicDistanceBetweenTransforms(T0, T1):
730743
"""
731744
Wrapper, to match GetGeodesicDistanceBetweenQuaternions()
732745
"""
746+
733747
return GeodesicDistance(T0, T1, r=1.0)
734748

735749

736750
def GetEuclideanDistanceBetweenTransforms(T0, T1):
737751
"""
738-
Calculate the Euclidean distance between two 4x4 transforms.
752+
Calculate the Euclidean distance between the translational
753+
component of two 4x4 transforms.
739754
(also called L2 or Pythagorean distance)
740755
"""
741756
p0 = T0[0:3,3] # Get the x,y,z translation from the 4x4 matrix
@@ -752,29 +767,44 @@ def GetMinDistanceBetweenTransformAndWorkspaceTraj(T, traj, dt=0.01):
752767
@param openravepy.Trajectory traj: A timed workspace trajectory.
753768
@param float dt: Resolution at which to sample along the trajectory.
754769
755-
@return (float,float) (min_dist, t_loc) The minimum distance and the
756-
time value along the timed
757-
trajectory.
770+
@return (float,float) (min_dist, t_loc, T_loc) The minimum distance,
771+
the time value along the timed
772+
trajectory, and the transform.
758773
"""
759774
if not IsTimedTrajectory(traj):
760775
raise ValueError("Trajectory must have timing information.")
761776

762-
if not IsWorkspaceTrajectory(traj):
777+
if not IsTrajectoryTypeIkParameterization(traj):
763778
raise ValueError("Trajectory is not a workspace trajectory, it "
764779
"must have configuration specification of "
765780
"openravepy.IkParameterizationType.Transform6D")
766781

782+
def _GetError(t):
783+
T_curr = openravepy.matrixFromPose(traj.Sample(t)[0:7])
784+
error = GetEuclideanDistanceBetweenTransforms(T, T_curr)
785+
767786
min_dist = numpy.inf
768787
t_loc = 0.0
788+
T_loc = None
789+
790+
# Iterate over the trajectory
769791
t = 0.0
770-
while t < traj.GetDuration():
771-
T_curr = openravepy.matrixFromPose(traj.Sample(t)[0:7])
772-
error = GetEuclideanDistanceBetweenTransforms(T, T_curr)
792+
duration = traj.GetDuration()
793+
while t < duration:
794+
error = _GetError(t)
773795
if error < min_dist:
774796
min_dist = error
775797
t_loc = t
776798
t = t + dt
777-
return (min_dist, t_loc)
799+
# Also check the end-point
800+
error = _GetError(duration)
801+
if error < min_dist:
802+
min_dist = error
803+
t_loc = t
804+
805+
T_loc = openravepy.matrixFromPose(traj.Sample(t_loc)[0:7])
806+
807+
return (min_dist, t_loc, T_loc)
778808

779809

780810
def FindCatkinResource(package, relative_path):
@@ -965,7 +995,7 @@ def IsJointSpaceTrajectory(traj):
965995
return False
966996

967997

968-
def IsWorkspaceTrajectory(traj):
998+
def IsTrajectoryTypeIkParameterization(traj):
969999
"""
9701000
Check if trajectory is a workspace trajectory.
9711001

0 commit comments

Comments
 (0)