19 Changes from previous version:
20 1- added function js_create()
21 2- added settings in ik_settings:
22 respect_error_reduction, respect_limits_in_trajectories
23 3- property ik_settings.joint_limits_respected renamed to ik_settings.continue_until_inrange
24 4- method DLS(MADF) added, property manipulability_threshold added to the ik_settings
25 5- property run_log added to the object. Functions run(), run_dls_vdf(), js_project() and js_create() generate log
26 The log is plottable and can be saved as a .csv file
31 import math, time, sys, copy, numpy
as np
33 import general_python
as genpy
35 from math_tools
import general_math
as genmath
38 from math_tools.discrete
import discrete
43 all_algorithms = [
'JI',
'JT',
'JPI',
'DLS(CDF)',
'DLS(ADF)',
'DLS(MADF)']
48 'JI' :
'Jacobian Inverse',
49 'JT' :
'Jacobian Transpose',
50 'JPI' :
'Jacobian Pseudo Inverse',
51 'DLS(CDF)' :
'Damped Least Squares Method(Constant Damping Factor)',
52 'DLS(ADF)' :
'Damped Least Squares Method(Adaptive Damping Factor)',
53 'DLS(MADF)' :
'Damped Least Squares Method(Manipulability Adjusted Damping Factor)'
56 all_figures = [
'Time',
'Duration',
'Phase',
'Manipulability',
'Damping Factor',
'In Range',
'Target Reached',
'Error Norm']
60 self.current_time = 0.0
61 self.time_elapsed = 0.0
63 self.configuration =
None
65 self.pose_error =
None
66 self.error_norm =
None
67 self.manipulability =
None
68 self.damping_factor =
None
69 self.is_in_target =
None
70 self.joints_in_range =
None
72 def value(self, figure):
74 return self.current_time
75 elif figure ==
'Duration':
76 return self.time_elapsed
77 elif figure ==
'Phase':
79 elif figure ==
'Manipulability':
80 return self.manipulability
81 elif figure ==
'Damping Factor':
82 return self.damping_factor
83 elif figure ==
'In Range':
84 return self.joints_in_range
85 elif figure ==
'Target Reached':
86 return self.is_in_target
87 elif figure ==
'Error Norm':
88 return self.error_norm
90 ers = figure +
' is not a valid value for argument figure'
91 assert False, genpy.err_str(__name__, self.__class__.__name__, sys._getframe().f_code.co_name, ers)
93 def csv_str(self, figures = all_figures):
97 s +=
',' + str(self.value(fig))
104 def initial_config(self):
105 return step[0].configuration
107 def final_config(self):
108 lsi = len(self.step) - 1
109 return step[lsi].configuration
112 lsi = len(self.step) - 1
113 return step[lsi].is_in_target
117 self.time_elapsed = 0.0
118 self.time_start = time.time()
120 def csv_str(self, figures = all_figures, header = True):
128 for stp
in self.step:
129 s += stp.csv_str(figures = figures)
132 def write_csv(self, filename = 'iklog.csv', figures = all_figures, header = True):
133 FILE_HANDLE = open(filename,
"w")
134 FILE_HANDLE.write(self.csv_str(figures = figures, header = header))
136 def plot(self, axis_x = 'Time', axis_y = 'Error Norm', ltype = '-', plt = None):
139 import matplotlib.pyplot
as plt
144 for stp
in self.step:
145 x.append(stp.value(axis_x))
146 y.append(stp.value(axis_y))
156 class Inverse_Kinematics_Settings():
160 all_run_modes = [
'normal_run',
'binary_run' ]
161 all_algorithms = [
'JI',
'JT',
'JPI',
'DLS(CDF)',
'DLS(ADF)' ,
'DLS(MADF)']
164 def __init__(self, algorithm = 'JPI', run_mode = 'normal_run', num_steps = 100):
166 assert run_mode
in self.__class__.all_run_modes, self.__class__.err_head + func_name +
": The given run_mode is not known!"
167 assert algorithm
in self.__class__.all_algorithms, self.__class__.err_head + func_name +
": The given algorithm is not known!"
169 self.ngp_active =
True
170 self.continue_until_inrange =
True
171 self.return_min_config =
True
172 self.respect_error_reduction =
True
173 self.respect_limits =
True
174 self.respect_limits_in_trajectories =
True
175 self.generate_log =
True
178 self.algorithm = algorithm
179 self.run_mode = run_mode
180 self.number_of_steps = num_steps
181 self.initial_damping_factor = 1.0
182 self.manipulability_threshold= 0.0001
183 self.real_time =
False
184 self.time_step = 0.010
188 self.representation_of_orientation_for_binary_run =
'vectorial_identity'
189 self.include_current_config =
True
191 def __str__( self ) :
193 returns the algorithmic setting for the inverse kinematic solver
195 s =
'Running Mode : ' + self.run_mode
196 if self.run_mode ==
'binary_run':
197 s +=
'(Representation of Orientation for Interpolation : ' + self.representation_of_orientation_for_binary_run +
'\n'
201 s +=
'Algorithm : ' + key_dic[self.algorithm] +
'\n'
202 s +=
'Number of Steps : ' + str(self.number_of_steps) +
'\n'
205 def algorithm_key(self):
206 assert self.algorithm
in self.__class__.all_algorithms
207 return self.__class__.alg_dic[self.algorithm]
211 Inherits all properties and methods of a "Forward_Kinematics" class
212 and has some additional properties and methods for inverse kinematic calculations
213 Method "update" changes the "configuration" property inherited from "Forward_Kinematics"
216 # goto_target (call from outside) -- runs the suff on the grid already, calls stepwise_run or run
218 # run() -- run() # single run()
219 # step_forward (SINGLE STEP) -- one update_step()
223 def __init__(self, config_settings, geo_settings, end_settings, ik_settings):
227 super(Inverse_Kinematics, self).
__init__(copy.copy(config_settings), copy.copy(geo_settings), copy.copy(end_settings))
233 self.end_settings.last_link_number = geo_settings.nlink - 1
247 generate and return the text key for the settings
250 def ik_direction(self, u = None):
252 Returns the joint direction (joint update correction) expected to lead the endeffector closer to the target.
253 This direction should be multiplied by a proper stepsize to ensure that the pose error norm is reduced.
255 genpy.check_valid(self.ik_settings.algorithm, all_algorithms, __name__, self.__class__.__name__, sys._getframe().f_code.co_name,
'ik_settings.algorithm')
257 err = self.pose_error()
258 Je = self.error_jacobian()
259 W = self.joint_damping_weights()
263 if self.ik_settings.algorithm ==
"JI":
264 Je_dag = np.linalg.inv(Je)
265 elif self.ik_settings.algorithm ==
"JPI":
266 Je_dag = vecmat.weighted_pseudo_inverse(Je, W)
267 elif self.ik_settings.algorithm ==
"JT":
269 elif self.ik_settings.algorithm
in [
"DLS(CDF)",
"DLS(ADF)"]:
271 Je_dag = vecmat.weighted_dls_inverse(Je, W, k = self.damping_factor)
272 elif self.ik_settings.algorithm
in [
'DLS(MADF)']:
273 mu = self.manipulability()
274 if mu < self.ik_settings.manipulability_threshold:
275 self.damping_factor = self.ik_settings.initial_damping_factor*(1.0 - mu/self.ik_settings.manipulability_threshold)
276 Je_dag = vecmat.weighted_dls_inverse(Je, W, k = self.damping_factor)
281 delta_qs = - np.dot(Je_dag, err)
285 assert len(u) == self.config_settings.DOF
286 J_dag_J = np.dot(Je_dag, Je)
287 In_minus_J_dag_J = np.eye(self.config_settings.DOF) - J_dag_J
288 dq = np.dot(In_minus_J_dag_J, u)
294 def optimum_stepsize(self, direction):
295 if self.ik_settings.algorithm
in [
'JPI',
'JI']:
297 elif self.ik_settings.algorithm
in [
'JT',
'DLS(CDF)',
'DLS(ADF)',
'DLS(MADF)']:
298 J_delta_q = np.dot(self.error_jacobian(), direction)
299 k = - np.sum(J_delta_q*J_delta_q)/np.sum(J_delta_q*self.pose_error())
301 assert False, genpy.err_str(__name__, self.__class__.__name__, sys._getframe().f_code.co_name, self.ik_settings.algorithm +
" is an unknown value for algorithm")
306 def update_step(self):
308 Implements an update rule for the joint configuration. This function performs one iteration of the algorithm.
311 print 'US Start: **************************'
312 print 'US: qqs', np.sum(self.q), np.sum(self.qvr)
313 print 'US: per', np.sum(self.pose_error())
314 print 'US: ejt', np.sum(self.task_point[0].error_jacobian()), np.sum(self.task_frame[0].error_jacobian())
315 print 'US: EJ ', np.sum(self.error_jacobian()[0,:])
319 if self.ik_settings.ngp_active:
320 u = self.objective_function_gradient(k = 1.0)
325 jdir = self.ik_direction(u)
326 k = self.optimum_stepsize(jdir)
329 assert self.set_config_virtual(self.qvr + delta_qs)
331 if self.ik_settings.generate_log:
332 telap = time.time() - ts
333 step_log = self.report_status()
334 step_log.time_elapsed = telap
335 self.run_log.step.append(step_log)
337 def report_status(self):
339 sl.current_time = time.time() - self.run_log.time_start
340 sl.configuration = np.copy(self.free_config(self.q))
341 sl.end_pose = copy.copy(self.pose())
342 sl.pose_error = copy.copy(self.pose_error())
343 sl.error_norm = copy.copy(self.pose_error_norm())
344 sl.manipulability = self.manipulability()
345 sl.damping_factor = self.damping_factor
346 sl.is_in_target = self.in_target()
347 sl.joints_in_range = self.joints_in_range(sl.configuration)
350 def copy_from(self, ik):
352 Transfers all properties from ik to self
354 self.config_settings = copy.copy(ik.config_settings)
355 self.geo_settings = copy.copy(ik.geo_settings)
356 self.end_settings = copy.copy(ik.end_settings)
357 self.ik_settings = copy.copy(ik.ik_settings)
359 self.damping_factor = ik.damping_factor
361 self.q = copy.copy(ik.q)
362 self.qvr = copy.copy(ik.qvr)
363 self.jmc_a = copy.copy(ik.jmc_a)
364 self.jmc_b = copy.copy(ik.jmc_b)
365 self.jmc_c = copy.copy(ik.jmc_c)
366 self.jmc_f = copy.copy(ik.jmc_f)
367 self.jmc_g = copy.copy(ik.jmc_g)
369 self.T = copy.copy(ik.T)
370 self.H = copy.copy(ik.H)
372 self.ajac = copy.deepcopy(ik.ajac)
377 for i
in range(len(ik.task_point)):
378 self.task_point[i] = copy.deepcopy(ik.task_point[i])
380 for i
in range(len(ik.task_frame)):
381 self.task_frame[i] = copy.deepcopy(ik.task_frame[i])
383 self.current_pose = copy.copy(ik.current_pose)
384 self.current_pose_error = copy.copy(ik.current_pose_error)
385 self.current_error_norm = copy.copy(ik.current_error_norm)
387 self.all_task_points_in_target = ik.all_task_points_in_target
388 self.all_task_frames_in_target = ik.all_task_frames_in_target
389 self.is_in_target = ik.is_in_target
391 self.geo_jac = copy.copy(ik.geo_jac)
392 self.err_jac = copy.copy(ik.err_jac)
393 self.
run_log = copy.deepcopy(ik.run_log)
395 def js_create(self, phi_end = None, traj_capacity = 2, traj_type = 'regular'):
399 if self.ik_settings.generate_log:
401 keep_q = np.copy(self.q)
402 H = self.transfer_matrices()
405 phi_end = self.ik_settings.number_of_steps*self.ik_settings.time_step
407 if traj_type ==
'regular':
408 jt = trajlib.Trajectory(dimension = self.config_settings.DOF, capacity = traj_capacity)
409 elif traj_type ==
'polynomial':
410 jt = trajlib.Trajectory_Polynomial(dimension = self.config_settings.DOF, capacity = traj_capacity)
412 assert False,
"\n Unknown Trajectory Type \n"
414 jt.plot_settings.axis_label = self.config_settings.joint_label
415 if self.ik_settings.respect_limits_in_trajectories:
416 jt.vel_max = self.ik_settings.max_js
417 jt.acc_max = self.ik_settings.max_ja
418 jt.pos_max = self.config_settings.qh
419 jt.pos_min = self.config_settings.ql
423 jt.add_position(0.0, pos = np.copy(self.q))
425 stay =
not self.in_target()
428 stay =
not self.in_target()
429 phi = phi + self.ik_settings.time_step
431 if (phi > phi_end)
or genmath.equal(phi, phi_end, epsilon = 0.1*self.ik_settings.time_step):
437 if err_reduced
or (
not self.ik_settings.respect_error_reduction):
438 jt.add_position(phi = phi, pos = np.copy(self.q))
441 if traj_type ==
'polynomial':
444 self.set_config(keep_q)
445 if self.ik_settings.generate_log:
446 self.run_log.time_elapsed = time.time() - self.run_log.time_start
452 runs the IK iterations following Damped Least Squares method with Variable Damping Factor
453 The damping factor starts from the initial value.
454 At each time step, if the error norm is reduced, the joint correction is applied and the damping factor is reduced to half and
455 if it is increased, the algorithm returns to previous configuration and the DF is multiplied by 2.
456 This will continue until the error norm is reduced or maximum number of iterations achieved.
461 not_arrived =
not self.in_target()
462 have_time = (counter < self.ik_settings.number_of_steps)
465 while not_arrived
and have_time:
466 previous_pose_error_norm = self.pose_error_norm()
467 previous_configuration = self.free_config(self.q)
473 except np.linalg.linalg.LinAlgError:
475 print "******** Singular Matrix ********";
478 err_reduced = (self.pose_error_norm() < previous_pose_error_norm)
480 if err_reduced
or self.in_target():
487 self.set_config(previous_configuration)
491 rs = self.ik_settings.continue_until_inrange
492 ir = self.joints_in_range(self.free_config(self.q))
494 not_arrived = (
not self.in_target() )
or (rs
and (
not ir))
495 have_time = (counter < self.ik_settings.number_of_steps)
497 if self.ik_settings.generate_log:
498 self.run_log.time_elapsed = time.time() - self.run_log.time_start
504 Runs the inverse kinematic algorithm from the given starting point.
506 The stop criteria is the occurance of one of the following:
507 1- The counter exceeds property "number_of_steps" of the class and the norm of error is more than its value in the previous iteration
508 2- The solution is found according to the defined precision
509 when the stop criteria is met, the configuration corresponding to the minimum pose error norm is returned as the solution
511 The running time and number of iteration is returned in the "pps_log" property.
512 This method should only be used for "Pose Projection" or "PP" application scenario.
517 if self.ik_settings.return_min_config:
518 min_config = self.free_config(self.q)
519 min_error = self.pose_error_norm()
523 not_yet =
not self.in_target()
524 have_time = (counter < self.ik_settings.number_of_steps)
528 while not_yet
and have_time:
534 except np.linalg.linalg.LinAlgError:
536 print "******** Singular Matrix ********";
539 rs = self.ik_settings.continue_until_inrange
540 ir = self.joints_in_range(self.free_config(self.q))
541 it = self.in_target()
543 not_yet = (
not it)
or ((
not ir)
and rs)
544 have_time = (counter < self.ik_settings.number_of_steps)
547 print "Current config : ", self.q
548 print "Current config v : ", self.qvr
550 print "Current Error : ", self.pose_error_norm()
551 print "Current Ofun : ", self.objective_function_value()
553 ra = self.task_point[0].position(self.H)
554 rd = self.task_point[0].rd
556 print "Position Error : ", self.task_point[0].error.value(ra, rd)
557 print "Position in target: ", self.task_point[0].error.in_target(ra, rd)
559 ra = self.task_frame[0].orientation(self.H)
560 rd = self.task_frame[0].rd
562 print "Rotation Error : ", self.task_frame[0].error.value(ra, rd)
563 print "Rotation in target: ", self.task_frame[0].error.in_target(ra, rd)
566 if self.ik_settings.return_min_config:
568 print "Min Error Achieved: ", min_config.pose_error_norm()
569 print "Min Error Ofun : ", min_config.objective_function_value()
571 er = self.pose_error_norm() < min_error
572 err_reduced = er
and (ir
or (
not rs))
574 min_config = self.free_config(self.q)
575 min_error = self.pose_error_norm()
577 if not_yet
and self.ik_settings.return_min_config:
578 self.set_config(min_config)
580 if self.ik_settings.generate_log:
581 self.run_log.time_elapsed = time.time() - self.run_log.time_start
583 def goto_target(self):
586 if self.ik_settings.generate_log:
589 if self.ik_settings.include_current_config:
594 if self.ik_settings.run_mode ==
'binary_run':
595 self.run_binary(
True)
596 elif self.ik_settings.run_mode ==
'normal_run':
597 if self.ik_settings.algorithm
in [
"JT",
"JPI",
"DLS(CDF)",
"JI",
"DLS(MADF)"]:
599 elif self.ik_settings.algorithm ==
"DLS(ADF)":
602 assert False, genpy.err_str(__name__, self.__class__.__name__, sys._getframe().f_code.co_name, self.ik_settings.algorithm +
" is not a valid value for algorithm")
607 while (
not self.in_target())
and (self.start_node < p):
615 if self.ik_settings.run_mode ==
'binary_run':
616 self.run_binary(
True)
617 elif self.ik_settings.run_mode ==
'normal_run':
618 if self.ik_settings.algorithm
in [
"JT",
"JPI",
"DLS(CDF)",
"JI",
"DLS(MADF)"]:
620 elif self.ik_settings.algorithm ==
"DLS(ADF)":
623 assert False, genpy.err_str(__name__, self.__class__.__name__, sys._getframe().f_code.co_name, self.ik_settings.algorithm +
' is not a valid value for algorithm')
627 return self.in_target()
632 q0 = self.free_config(self.q)
633 err = self.pose_error_norm()
635 if self.ik_settings.ngp_active:
636 u = self.objective_function_gradient(k = 1.0)
641 jdir = self.ik_direction(u)
643 elt = time.time() - ts
644 if np.linalg.norm(jdir) > 0.00001:
645 max_eta = self.optimum_stepsize(jdir)
646 if self.ik_settings.respect_limits:
647 (el, eh) = self.joint_stepsize_interval(direction = jdir, max_speed = self.ik_settings.max_js, delta_t = self.ik_settings.time_step)
654 self.set_config_virtual(self.qvr + eh*jdir)
656 if self.pose_error_norm() < err:
658 if self.ik_settings.generate_log:
659 step_log = self.report_status()
660 step_log.time_elapsed = elt
661 self.run_log.step.append(step_log)
665 if self.ik_settings.generate_log:
666 step_log = self.report_status()
667 step_log.time_elapsed = elt
668 self.run_log.step.append(step_log)
669 if self.ik_settings.respect_error_reduction:
670 assert self.set_config(q0)
673 if self.ik_settings.generate_log:
674 step_log = self.report_status()
675 step_log.time_elapsed = elt
676 self.run_log.step.append(step_log)
679 def js_project(self, pos_traj, ori_traj = None, phi_start = 0.0, phi_end = None, relative = True, traj_capacity = 2, traj_type = 'regular'):
681 projects the given taskspace pose trajectory into the jointspace using the numeric inverse kinematics.
682 The phase starts from phi_start and added by self.ik_settings.time_step in each step.
683 at any time, if a solution is not found, the target is ignored and no configuration is added to the trajectory
686 keep_q = np.copy(self.q)
687 H = self.transfer_matrices()
690 ori_traj = trajlib.Orientation_Path()
691 ori_traj.add_point(0.0, self.task_frame[0].orientation(H))
692 ori_traj.add_point(pos_traj.phi_end, self.task_frame[0].orientation(H))
695 phi_end = min(pos_traj.phi_end, ori_traj.phi_end)
697 if (phi_end > pos_traj.phi_end)
or (phi_end > ori_traj.phi_end):
698 phi_end = min(pos_traj.phi_end, ori_traj.phi_end)
700 if traj_type ==
'regular':
701 jt = trajlib.Trajectory(dimension = self.config_settings.DOF, capacity = traj_capacity)
702 elif traj_type ==
'polynomial':
703 jt = trajlib.Trajectory_Polynomial(dimension = self.config_settings.DOF, capacity = traj_capacity)
705 assert False,
"\n Unknown Trajectory Type \n"
707 if self.ik_settings.respect_limits_in_trajectories:
708 jt.vel_max = self.ik_settings.max_js
709 jt.acc_max = self.ik_settings.max_ja
710 jt.pos_max = self.config_settings.qh
711 jt.pos_min = self.config_settings.ql
714 pos_traj.set_phi(phi)
715 ori_traj.set_phi(phi)
717 p0 = self.task_point[0].position(H) - pos_traj.current_position
718 self.set_target([self.task_point[0].position(H)], [self.task_frame[0].orientation(H)])
721 self.set_target([pos_traj.current_position], [ori_traj.current_orientation])
724 jt.add_position(0.0, pos = np.copy(self.q))
726 if self.ik_settings.generate_log:
728 self.run_log.step.append(self.report_status())
733 phi = phi + self.ik_settings.time_step
735 if (phi > phi_end)
or genmath.equal(phi, phi_end, epsilon = 0.1*self.ik_settings.time_step):
739 pos_traj.set_phi(phi)
740 ori_traj.set_phi(phi)
741 p = p0 + pos_traj.current_position
742 self.set_target([p], [ori_traj.current_orientation])
745 if err_reduced
or (
not self.ik_settings.respect_error_reduction):
746 jt.add_position(phi = phi - phi_start, pos = np.copy(self.q))
748 if traj_type ==
'polynomial':
751 self.set_config(keep_q)
753 if self.ik_settings.generate_log:
754 self.run_log.time_elapsed = time.time() - self.run_log.time_start
758 def ts_project(self, js_traj, phi_start = 0.0, phi_end = None):
760 projects the given jointspace trajectory into the taskspace
761 The phase starts from phi_start and added by delta_phi in each step.
762 if at any time the joint values are not feasible, the process is stopped.
766 phi_end = js_traj.phi_end
768 pt = trajlib.Trajectory_Polynomial(dimension = 3*len(self.task_point) + 9*len(self.task_frame))
769 if phi_end > js_traj.phi_end:
770 phi_end = js_traj.phi_end
775 if (phi > phi_end)
or genmath.equal(phi, phi_end, epsilon = 0.1*self.ik_settings.time_step):
779 if self.set_config(js_traj.current_position):
780 pt.add_point(phi - phi_start, self.pose())
781 phi = phi + self.ik_settings.time_step
785 def ts_project(self, js_traj, phi_start = 0.0, phi_end = None):
787 projects the given jointspace trajectory into the taskspace
788 The phase starts from phi_start and added by delta_phi in each step.
789 if at any time the joint values are not feasible, the process is stopped.
793 phi_end = js_traj.phi_end
795 ori_traj = trajlib.Orientation_Trajectory()
796 pos_traj = trajlib.Trajectory_Polynomial()
797 if phi_end > js_traj.phi_end:
798 phi_end = js_traj.phi_end
803 while stay and (self.set_config(js_traj.current_position)):
808 H = self.transfer_matrices()
810 pos_traj.add_point(phi - phi_start, self.task_point[0].position(H))
811 ori_traj.add_point(phi - phi_start, self.task_frame[0].orientation(H))
812 phi = phi + self.ik_settings.time_step
815 pos_traj.add_point(phi - phi_start, self.task_point[0].position(self.transfer_matrices()))
816 ori_traj.add_point(phi - phi_start, self.task_frame[0].orientation(self.transfer_matrices()))
817 return (pos_traj, ori_traj)
run_log
fklib.Forward_Kinematics.__init__(self, DEEPCOPY(??geometry??) ) # njoint)
ik_settings
def init( self, geometry ) : # njoint):