5
5
import numpy as np
6
6
import matplotlib .pyplot as plt
7
7
from collections import namedtuple
8
- from roboticstoolbox .mobile . PlannerBase import PlannerBase
8
+ from roboticstoolbox .mobile import *
9
9
10
-
11
- def solvepath ( poly , s_f , x0 = [ 0 , 0 , 0 ], ** kwargs ):
12
- # poly is 4 coeffs of curvature polynomial
13
- # x0 is initial state of the vehicle
10
+ def solvepath ( x , q0 = [ 0 , 0 , 0 ], ** kwargs ):
11
+ # x[:4] is 4 coeffs of curvature polynomial
12
+ # x[4] is total path length
13
+ # q0 is initial state of the vehicle
14
14
maxcurvature = 0
15
15
16
- def dotfunc (s , x , poly ):
17
- # x = (x, y, θ)
18
- # xdot = (cosθ, sinθ, ϰ)
16
+ def dotfunc (s , q , poly ):
17
+ # q = (x, y, θ)
18
+ # qdot = (cosθ, sinθ, ϰ)
19
19
k = poly [0 ] * s ** 3 + poly [1 ] * s ** 2 + poly [2 ] * s + poly [3 ]
20
+ # k = ((poly[0] * s + poly[1]) * s + poly[2]) * s + poly[3]
20
21
21
22
# save maximum curvature for this path solution
22
23
nonlocal maxcurvature
23
24
maxcurvature = max (maxcurvature , abs (k ))
24
25
25
- theta = x [2 ]
26
+ theta = q [2 ]
26
27
return math .cos (theta ), math .sin (theta ), k
27
28
28
- sol = scipy .integrate .solve_ivp (dotfunc , [0 , s_f ], x0 , args = (poly ,), ** kwargs )
29
+ cpoly = x [:4 ]
30
+ s_f = x [4 ]
31
+ sol = scipy .integrate .solve_ivp (dotfunc , [0 , s_f ], q0 , args = (cpoly ,), ** kwargs )
29
32
return sol .y , maxcurvature
30
33
31
- def costfunc (unknowns , start , goal , curvature ):
34
+ def xcurvature (x ):
35
+ # inequality constraint function, must be non-negative
36
+ _ , maxcurvature = solvepath (x , q0 = (0 , 0 , 0 ))
37
+ return maxcurvature
38
+
39
+ def costfunc (x , start , goal ):
32
40
# final cost of path from start with params
33
- # p[0:4] is polynomial
41
+ # p[0:4] is polynomial: k0, a, b, c
34
42
# p[4] is s_f
35
43
36
44
# integrate the path for this curvature polynomial and length
37
- path , maxcurvature = solvepath (poly = unknowns [:4 ], s_f = unknowns [4 ], x0 = start )
45
+ # path is 3xN
46
+ path , _ = solvepath (x , q0 = start )
38
47
39
48
# cost is configuration error at end of path
40
49
e = np .linalg .norm (path [:, - 1 ] - np .r_ [goal ])
41
50
42
- if curvature is not None and maxcurvature > curvature :
43
- # e *= np.exp(maxcurvature - curvature)
44
- e += 0.1 * (maxcurvature - curvature )
45
- # print(e, path[:, -1], np.r_[goal])
46
51
return e
47
52
class CurvaturePolyPlanner (PlannerBase ):
48
53
49
- r"""
50
- Curvature polynomial planner
51
-
52
- :return: curvature polynomial path planner
53
- :rtype: CurvaturePolyPlanner instance
54
-
55
- ================== ========================
56
- Feature Capability
57
- ================== ========================
58
- Plan Configuration space
59
- Obstacle avoidance No
60
- Curvature Continuous
61
- Motion Forwards only
62
- ================== ========================
63
-
64
- Creates a planner that finds the path between two configurations in the
65
- plane using forward motion only. The path is a continuous cubic polynomial
66
- in curvature:
67
-
68
- .. math::
69
-
70
- \kappa(s) = \kappa_0 + as + b s^2 + c s^3, 0 \le s \le s_f
71
-
72
- where :math:`\kappa_0` is the initial path curvature. This is integrated along the path
73
-
74
- .. math::
75
-
76
- \theta(s) &= \theta_0 + \kappa_0 s + \frac{a}{2}s^2 + \frac{b}{3}s^3 + \frac{c}{4}s^4 \\
77
- x(s) &= x_0 + \int_0^s \cos \theta(t) dt \\
78
- y(s) &= y_0 + \int_0^s \sin \theta(t) dt
79
-
80
- where the initial configuration is :math:`(x_0, y_0, \theta_0)` and the final
81
- configuration is :math:`(x_{s_f}, y_{s_f}, \theta_{s_f})`.
82
-
83
- Numerical optimization is used to find the parameters path length
84
- :math:`s_f` and the polynomial coefficients :math:`(\kappa_0, a, b, c)`.
85
-
86
- :reference: Mobile Robotics, Cambridge 2013. Alonzo Kelly
87
-
88
- :seealso: :class:`Planner`
89
- """
90
-
91
54
def __init__ (self , curvature = None ):
92
55
super ().__init__ (ndims = 3 )
93
56
self .curvature = curvature
94
57
95
58
def query (self , start , goal ):
96
- r"""
97
- Find a curvature polynomial path
98
-
99
- :param start: start configuration :math:`(x, y, \theta)`
100
- :type start: array_like(3), optional
101
- :param goal: goal configuration :math:`(x, y, \theta)`
102
- :type goal: array_like(3), optional
103
- :return: path and status
104
- :rtype: ndarray(N,3), namedtuple
105
-
106
- The returned status value has elements:
107
-
108
- ========== ===================================================
109
- Element Description
110
- ========== ===================================================
111
- ``length`` the length of the path, :math:`s_f`
112
- ``poly`` the polynomial coefficients :math:`(\kappa_0, a, b, c)`
113
-
114
- ========== ===================================================
115
-
116
- :seealso: :meth:`Planner.query`
117
- """
118
59
goal = np .r_ [goal ]
119
60
start = np .r_ [start ]
120
61
self ._start = start
121
62
self ._goal = goal
122
63
123
- delta = goal [:2 ] - start [:2 ]
124
- d = np .linalg .norm (delta )
125
- # theta = math.atan2(delta[1], delta[0])
126
- sol = scipy .optimize .minimize (costfunc , [0 , 0 , 1 , 0 , d ],
127
- bounds = [(None , None ), (None , None ), (None , None ), (None , None ), (d , None )],
128
- args = (start , goal , self .curvature ))
64
+ # initial estimate of path length is Euclidean distance
65
+ d = np .linalg .norm (goal [:2 ] - start [:2 ])
66
+ # state vector is kappa_0, a, b, c, s_f
129
67
130
- path , maxcurvature = solvepath (sol .x [:4 ], s_f = sol .x [4 ], x0 = start , dense_output = True , max_step = 1e-2 )
68
+ if self .curvature is not None :
69
+ nlcontraints = (scipy .optimize .NonlinearConstraint (xcurvature , 0 , self .curvature ),)
70
+ else :
71
+ nlcontraints = ()
131
72
132
- status = namedtuple ('CurvaturePolyStatus' ,
133
- ['length' , 'maxcurvature' , 'poly' , 'success' , 'iterations' , 'message' ])
73
+ sol = scipy .optimize .minimize (costfunc , [0 , 0 , 0 , 0 , d ],
74
+ constraints = nlcontraints ,
75
+ bounds = [(None , None ), (None , None ), (None , None ), (None , None ), (d , None )],
76
+ args = (start , goal ))
77
+ print (sol )
78
+ path , maxcurvature = solvepath (sol .x , q0 = start , dense_output = True , max_step = 1e-2 )
134
79
135
- return path , status (sol .x [4 ], maxcurvature , sol .x [:4 ], sol .success , sol .nit , sol .message )
80
+ status = namedtuple ('CurvaturePolyStatus' , ['length' , 'maxcurvature' , 'poly' ])(sol .x [4 ], maxcurvature , sol .x [:4 ])
81
+
82
+ return path .T , status
136
83
137
84
if __name__ == '__main__' :
138
85
from math import pi
@@ -142,13 +89,26 @@ def query(self, start, goal):
142
89
start = (0 , 0 , - pi / 4 )
143
90
goal = (1 , 2 , pi / 4 )
144
91
145
- # start = (0, 0, pi/2)
146
- # goal = (1, 0, pi/2)
92
+ start = (0 , 0 , pi / 2 )
93
+ goal = (1 , 0 , pi / 2 )
147
94
148
- planner = CurvaturePolyPlanner (curvature = 1 )
95
+ planner = CurvaturePolyPlanner ()
149
96
path , status = planner .query (start , goal )
150
- print ('start' , path [:, 0 ])
151
- print ('goal' , path [:, - 1 ])
97
+ print ('start' , path [0 ,: ])
98
+ print ('goal' , path [- 1 , : ])
152
99
153
100
print (status )
154
101
planner .plot (path , block = True )
102
+
103
+ ## attempt polynomial scaling, doesnt seem to work
104
+ # sf = status.s_f
105
+ # c = status.poly
106
+ # print(c)
107
+
108
+ # print(solvepath(np.r_[c, sf], start))
109
+
110
+ # for i in range(4):
111
+ # c[i] /= sf ** (3-i)
112
+
113
+ # print(solvepath(np.r_[c, 1], start))
114
+ # print(c)
0 commit comments