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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy