Skip to content

Commit c75e5f3

Browse files
committed
working version
1 parent 41c755a commit c75e5f3

File tree

1 file changed

+103
-64
lines changed

1 file changed

+103
-64
lines changed

roboticstoolbox/mobile/Vehicle.py

Lines changed: 103 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515

1616
from spatialmath import SE2, base
1717
from roboticstoolbox.mobile.drivers import VehicleDriver
18-
from roboticstoolbox.mobile.animations import VehiclePolygon
18+
from roboticstoolbox.mobile.Animations import VehiclePolygon
1919

2020

21-
class Vehicle(ABC):
22-
def __init__(self, covar=None, speed_max=np.inf, accel_max=np.inf, x0=None, dt=0.1,
23-
control=None, seed=0, animation=None, verbose=False, plot=False, workspace=None):
21+
class VehicleBase(ABC):
22+
def __init__(self, covar=None, speed_max=np.inf, accel_max=np.inf, x0=[0, 0, 0], dt=0.1,
23+
control=None, seed=0, animation=None, verbose=False, plot=False, workspace=None,
24+
polygon=None):
2425
r"""
2526
Superclass for vehicle kinematic models
2627
@@ -62,13 +63,14 @@ def __init__(self, covar=None, speed_max=np.inf, accel_max=np.inf, x0=None, dt=0
6263
if len(x0) not in (2,3):
6364
raise ValueError('x0 must be length 2 or 3')
6465
self._x0 = x0
65-
self._x = x0
66+
self._x = x0.copy()
6667

6768
self._random = np.random.default_rng(seed)
6869
self._seed = seed
6970
self._speed_max = speed_max
7071
self._accel_max = accel_max
7172
self._v_prev = 0
73+
self._polygon = polygon
7274

7375
if isinstance(animation, str):
7476
animation = VehiclePolygon(animation)
@@ -85,6 +87,9 @@ def __init__(self, covar=None, speed_max=np.inf, accel_max=np.inf, x0=None, dt=0
8587
self._verbose = verbose
8688
self._plot = False
8789

90+
self._control = None
91+
self._x_hist = []
92+
8893
if workspace:
8994
self._workspace = base.expand_dims(workspace)
9095
else:
@@ -111,7 +116,12 @@ def workspace(self):
111116
Returns the bounds of the workspace as specified by constructor
112117
option ``workspace``
113118
"""
114-
return self._workspace
119+
120+
# get workspace specified for Vehicle or from its driver
121+
if self._workspace is not None:
122+
return self._workspace
123+
if self._control is not None:
124+
return self._control._workspace
115125

116126
@property
117127
def x(self):
@@ -123,6 +133,16 @@ def x(self):
123133
"""
124134
return self._x
125135

136+
@property
137+
def q(self):
138+
"""
139+
Get vehicle state/configuration (superclass method)
140+
141+
:return: Vehicle state :math:`(x, y, \theta)`
142+
:rtype: ndarray(3)
143+
"""
144+
return self._x
145+
126146
@property
127147
def x0(self):
128148
"""
@@ -140,7 +160,7 @@ def x0(self):
140160
"""
141161
return self._x0
142162

143-
@property
163+
@x0.setter
144164
def x0(self, x0):
145165
"""
146166
Set vehicle initial state/configuration (superclass method)
@@ -288,9 +308,7 @@ def control(self, control):
288308
Control can be:
289309
290310
* a constant tuple as the control inputs to the vehicle
291-
* a function called as ``f(vehicle, t, x)`` that returns a tuple
292-
* an interpolator called as f(t) that returns a tuple, see
293-
SciPy interp1d
311+
294312
* a driver agent, subclass of :func:`VehicleDriver`
295313
296314
Example:
@@ -303,11 +321,17 @@ def control(self, control):
303321
304322
:seealso: :func:`eval_control`, :func:`RandomPath`, :func:`PurePursuit`
305323
"""
324+
# * a function called as ``f(vehicle, t, x)`` that returns a tuple
325+
# * an interpolator called as f(t) that returns a tuple, see
326+
# SciPy interp1d
327+
306328
self._control = control
307329
if isinstance(control, VehicleDriver):
308330
# if this is a driver agent, connect it to the vehicle
309331
control.vehicle = self
310332

333+
def polygon(self, q):
334+
return self._polygon.transformed(SE2(q))
311335

312336
# This function is overridden by the child class
313337
@abstractmethod
@@ -399,14 +423,15 @@ def init(self, x0=None, control=None):
399423
if x0 is not None:
400424
self._x = base.getvector(x0, 3)
401425
else:
402-
self._x = self._x0
426+
self._x = self._x0.copy()
403427

404428
self._x_hist = []
405429

406430
if self._seed is not None:
407431
self._random = np.random.default_rng(self._seed)
408432

409433
if control is not None:
434+
# override control
410435
self._control = control
411436

412437
if self._control is not None:
@@ -418,12 +443,12 @@ def init(self, x0=None, control=None):
418443
if self._animation is not None:
419444

420445
# setup the plot
421-
self._ax = base.plotvol2(self._workspace)
446+
self._ax = base.plotvol2(self.workspace)
422447

423448
self._ax.set_xlabel('x')
424449
self._ax.set_ylabel('y')
425450
self._ax.set_aspect('equal')
426-
self._ax.figure.canvas.set_window_title(
451+
self._ax.figure.canvas.manager.set_window_title(
427452
f"Robotics Toolbox for Python (Figure {self._ax.figure.number})")
428453

429454
self._animation.add(ax=self._ax) # add vehicle animation to axis
@@ -433,16 +458,15 @@ def init(self, x0=None, control=None):
433458
if isinstance(self._control, VehicleDriver):
434459
self._control.init(ax=self._ax)
435460

436-
def step(self, u1=None, u2=None):
461+
def step(self, u=None, animate=False):
437462
"""
438463
Step simulator by one time step (superclass method)
439464
440465
:return: odometry :math:`(\delta_d, \delta_\theta)`
441466
:rtype: ndarray(2)
442467
443-
- ``veh.step(vel, steer)`` for a Bicycle vehicle model
444-
- ``veh.step((vel, steer))`` as above but control is a tuple
445-
- ``veh.step(vel, vel_diff)`` for a Unicycle vehicle model
468+
- ``veh.step((vel, steer))`` for a Bicycle vehicle model
469+
- ``veh.step((vel, vel_diff))`` for a Unicycle vehicle model
446470
- ``veh.step()`` as above but control is taken from the ``control``
447471
attribute which might be a function or driver agent.
448472
@@ -466,11 +490,8 @@ def step(self, u1=None, u2=None):
466490
:seealso: :func:`control`, :func:`update`, :func:`run`
467491
"""
468492
# determine vehicle control
469-
if u1 is not None:
470-
if u2 is not None:
471-
u = self.eval_control((u1, u2), self._x)
472-
else:
473-
u = self.eval_control(u1, self._x)
493+
if u is not None:
494+
u = self.eval_control(u, self._x)
474495
else:
475496
u = self.eval_control(self._control, self._x)
476497

@@ -490,7 +511,7 @@ def step(self, u1=None, u2=None):
490511
odo += self.random.multivariate_normal((0, 0), self._V)
491512

492513
# do the graphics
493-
if self._animation:
514+
if animate and self._animation:
494515
self._animation.update(self._x)
495516
if self._timer is not None:
496517
self._timer.set_text(f"t = {self._t:.2f}")
@@ -502,7 +523,6 @@ def step(self, u1=None, u2=None):
502523
if self._verbose:
503524
print(f"{self._t:8.2f}: u=({u[0]:8.2f}, {u[1]:8.2f}), x=({self._x[0]:8.2f}, {self._x[1]:8.2f}, {self._x[2]:8.2f})")
504525

505-
506526
return odo
507527

508528

@@ -595,6 +615,8 @@ def plot(self, x=None, shape='box', block=False, size=True, **kwargs):
595615
base.plot_poly(SE2(x) * vertices, close=True, **kwargs)
596616

597617
def plot_xy(self, *args, block=False, **kwargs):
618+
if args is None and 'color' not in kwargs:
619+
kwargs['color'] = 'b'
598620
xyt = self.x_hist
599621
plt.plot(xyt[:, 0], xyt[:, 1], *args, **kwargs)
600622
plt.show(block=block)
@@ -620,14 +642,17 @@ def limits_va(self, v):
620642
is reset at the start of each simulation.
621643
"""
622644
# acceleration limit
623-
if (v - self._v_prev) / self._dt > self._accel_max:
624-
v = self._v_prev + self._accelmax * self._dt;
625-
elif (v - self._v_prev) / self._dt < -self._accel_max:
626-
v = self._v_prev - self._accel_max * self._dt;
645+
if self._accel_max is not None:
646+
if (v - self._v_prev) / self._dt > self._accel_max:
647+
v = self._v_prev + self._accelmax * self._dt;
648+
elif (v - self._v_prev) / self._dt < -self._accel_max:
649+
v = self._v_prev - self._accel_max * self._dt;
627650
self._v_prev = v
628651

629652
# speed limit
630-
return min(self._speed_max, max(v, -self._speed_max));
653+
if self._speed_max is not None:
654+
v = np.clip(v, -self._speed_max, self._speed_max)
655+
return v
631656

632657

633658
def path(self, t=10, u=None, x0=None):
@@ -681,18 +706,18 @@ def dynamics(t, x, vehicle, u):
681706
return (sol.t, sol.y.T)
682707
# ========================================================================= #
683708

684-
class Bicycle(Vehicle):
709+
class Bicycle(VehicleBase):
685710

686711
def __init__(self,
687-
l=1,
712+
L=1,
688713
steer_max=0.45 * pi,
689714
**kwargs
690715
):
691716
r"""
692717
Create new bicycle kinematic model
693718
694-
:param l: wheel base, defaults to 1
695-
:type l: float, optional
719+
:param L: wheel base, defaults to 1
720+
:type L: float, optional
696721
:param steer_max: [description], defaults to :math:`0.45\pi`
697722
:type steer_max: float, optional
698723
:param **kwargs: additional arguments passed to :class:`Vehicle`
@@ -702,7 +727,7 @@ def __init__(self,
702727
"""
703728
super().__init__(**kwargs)
704729

705-
self._l = l
730+
self._l = L
706731
self._steer_max = steer_max
707732

708733
def __str__(self):
@@ -867,7 +892,7 @@ def Fv(self, x, odo):
867892
# fmt: on
868893
return J
869894

870-
def deriv(self, x, u):
895+
def deriv(self, x, u, limits=True):
871896
r"""
872897
Time derivative of state
873898
@@ -880,12 +905,13 @@ def deriv(self, x, u):
880905
881906
Returns the time derivative of state (3x1) at the state ``x`` with
882907
inputs ``u``.
883-
884-
.. note:: Vehicle speed and steering limits are not applied here
885908
"""
886909

887910
# unpack some variables
888911
theta = x[2]
912+
913+
if limits:
914+
u = self.u_limited(u)
889915
v = u[0]
890916
gamma = u[1]
891917

@@ -900,29 +926,29 @@ def u_limited(self, u):
900926
# limit speed and steer angle
901927
ulim = np.array(u)
902928
ulim[0] = self.limits_va(u[0])
903-
ulim[1] = np.maximum(-self._steer_max, np.minimum(self._steer_max, u[1]))
929+
ulim[1] = np.clip(u[1], -self._steer_max, self._steer_max)
904930

905931
return ulim
906932

907933
# ========================================================================= #
908934

909-
class Unicycle(Vehicle):
935+
class Unicycle(VehicleBase):
910936

911937
def __init__(self,
912-
w=1,
938+
W=1,
913939
**kwargs):
914940
r"""
915941
Create new unicycle kinematic model
916942
917-
:param w: vehicle width, defaults to 1
918-
:type w: float, optional
943+
:param W: vehicle width, defaults to 1
944+
:type W: float, optional
919945
:param **kwargs: additional arguments passed to :class:`Vehicle`
920946
constructor
921947
922948
:seealso: :class:`.Vehicle`
923949
"""
924950
super().__init__(**kwargs)
925-
self._w = w
951+
self._w = W
926952

927953
def __str__(self):
928954

@@ -1070,39 +1096,52 @@ def u_limited(self, u):
10701096

10711097
return ulim
10721098

1099+
class DiffSteer(Unicycle):
1100+
pass
10731101

10741102
if __name__ == "__main__":
1075-
from math import pi
10761103

1077-
V = np.diag(np.r_[0.02, 0.5 * pi / 180] ** 2)
1104+
from roboticstoolbox import RandomPath
1105+
1106+
V = np.eye(2) * 0.001
1107+
robot = Bicycle(covar=V, animation="car")
1108+
odo = robot.step((1, 0.3), animate=False)
1109+
1110+
robot.control = RandomPath(workspace=10)
1111+
1112+
robot.run(T=10)
1113+
1114+
# from math import pi
1115+
1116+
# V = np.diag(np.r_[0.02, 0.5 * pi / 180] ** 2)
10781117

1079-
v = VehiclePolygon()
1080-
# v = VehicleIcon('greycar2', scale=2, rotation=90)
1118+
# v = VehiclePolygon()
1119+
# # v = VehicleIcon('greycar2', scale=2, rotation=90)
10811120

1082-
veh = Bicycle(covar=V, animation=v, control=RandomPath(10), verbose=False)
1083-
print(veh)
1121+
# veh = Bicycle(covar=V, animation=v, control=RandomPath(10), verbose=False)
1122+
# print(veh)
10841123

1085-
odo = veh.step(1, 0.3)
1086-
print(odo)
1124+
# odo = veh.step(1, 0.3)
1125+
# print(odo)
10871126

1088-
print(veh.x)
1127+
# print(veh.x)
10891128

1090-
print(veh.f([0, 0, 0], odo))
1129+
# print(veh.f([0, 0, 0], odo))
10911130

1092-
def control(v, t, x):
1093-
goal = (6,6)
1094-
goal_heading = atan2(goal[1]-x[1], goal[0]-x[0])
1095-
d_heading = base.angdiff(goal_heading, x[2])
1096-
v.stopif(base.norm(x[0:2] - goal) < 0.1)
1131+
# def control(v, t, x):
1132+
# goal = (6,6)
1133+
# goal_heading = atan2(goal[1]-x[1], goal[0]-x[0])
1134+
# d_heading = base.angdiff(goal_heading, x[2])
1135+
# v.stopif(base.norm(x[0:2] - goal) < 0.1)
10971136

1098-
return (1, d_heading)
1137+
# return (1, d_heading)
10991138

1100-
veh.control=RandomPath(10)
1101-
p = veh.run(1000, plot=True)
1102-
# plt.show()
1103-
print(p)
1139+
# veh.control=RandomPath(10)
1140+
# p = veh.run(1000, plot=True)
1141+
# # plt.show()
1142+
# print(p)
11041143

1105-
veh.plot_xyt_t()
1144+
# veh.plot_xyt_t()
11061145
# veh.plot(p)
11071146

11081147
# t, x = veh.path(5, u=control)

0 commit comments

Comments
 (0)