From d645a63935fbd4166bdbca2fa02c8c24f57b1849 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Wed, 7 Apr 2021 22:43:16 +0200 Subject: [PATCH 01/93] created PythonRobotics library, added turtlebot simulator and install file --- .gitignore | 3 +++ README.md | 7 +++++-- environment.yml | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index c971b8f9c5..92b7aa398c 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,6 @@ target/ matplotrecorder/* .vscode/settings.json + +# scratchpad files +scratch/ \ No newline at end of file diff --git a/README.md b/README.md index 804966d121..df33aba691 100644 --- a/README.md +++ b/README.md @@ -147,10 +147,13 @@ using pip : > pip install -r requirements.txt +3. Add this library to the environment (in development mode) -3. Execute python script in each directory. +> pip install -e . -4. Add star to this repo if you like it :smiley:. +4. Execute python script in each directory. + +5. Add star to this repo if you like it :smiley:. # Localization diff --git a/environment.yml b/environment.yml index 1441b79080..3eb7663906 100644 --- a/environment.yml +++ b/environment.yml @@ -1,4 +1,4 @@ -name: python_robotics +name: robotics channels: - conda-forge dependencies: From 568960f0a557bb99bfe57071cee48df789864e63 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Wed, 7 Apr 2021 22:44:56 +0200 Subject: [PATCH 02/93] added library and setup file --- PythonRobotics/__init__.py | 0 PythonRobotics/turtlebot.py | 96 +++++++++++++++++++++++++++++++++++++ setup.py | 17 +++++++ 3 files changed, 113 insertions(+) create mode 100644 PythonRobotics/__init__.py create mode 100644 PythonRobotics/turtlebot.py create mode 100644 setup.py diff --git a/PythonRobotics/__init__.py b/PythonRobotics/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py new file mode 100644 index 0000000000..20d275ef24 --- /dev/null +++ b/PythonRobotics/turtlebot.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +""" + +Simulator of a differantial drive robot, Turtlebot3 - like + +Author - Jev Kuznetsov + +""" + +import numpy as np +from numpy import sin, cos, sign +import pandas as pd + +from collections import namedtuple + +# define state object +state_fields = ['x','y','phi','v','omega','t'] +State = namedtuple('State', state_fields, defaults = (0.,) * len(state_fields)) + + + +class Robot: + """ + Robot kinematics simulator + """ + # numbers for Robotis Turtlebot3 + _v_max = 1.0#0.22 # [m/s] + _omega_max = 2.84 # [rad/s] + _accel_angular = 0.1 # [rad/s2] + _accel_linear = 0.1 # [m/s2] + _wheel_diameter = 66e-3 # [m] + _wheel_distance = 160e-3 # [m] + + def __init__(self, + x=0., # x-position + y=0., + phi=0., + name='Turtlebot3' ): + + """ initiate robot at given location and orientation """ + self.name = name + self._states = [State(x,y,phi)] + + self._omega_target = 0. + self._v_target = 0. + + def set_velocity(self,v,omega): + """ set target velocities """ + self._omega_target = np.clip(omega, -self._omega_max, self._omega_max) + self._v_target = np.clip(v, 0, self._v_max) + + def step(self, dt = 0.1): + """ perform simulation step with timestep dt """ + + s = self.state # start state + + # update linear speed + dv = self._accel_linear * dt # dv is an absolute number + v_error = self._v_target - s.v + if abs(v_error) > dv: + v_new = s.v + sign(v_error) * dv + else: + v_new = s.v + v_error + + + s_new = State(x = s.x + s.v*cos(s.phi)*dt, + y = s.y + s.v*sin(s.phi)*dt, + phi = s.phi + s.omega*dt, + t = s.t + dt, + v = v_new) + + self._states.append(s_new) + + @property + def states(self) -> pd.DataFrame: + """ get history of all states as pandas DataFrame """ + + cols = state_fields[:-1] + data = {} + + for var_name in cols: + data[var_name] = [getattr(s,var_name) for s in self._states] + + t = pd.Index(name='time',data=[s.t for s in self._states]) + + df = pd.DataFrame(data, index=t) + + return df + + @property + def state(self): + """ last known state """ + return self._states[-1] + + def __repr__(self): + return f'{self.name} {self.state})' diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000..202cb63f59 --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + + +from setuptools import setup, find_packages + + +setup( + name='PythonRobotics', + version = '1.0.1', + description="Python implementation of robotics algoritms", + author= "Atsushi Sakai et al.", + packages=find_packages(include=['PythonRobotics']), + url = "https://github.com/AtsushiSakai/PythonRobotics", + install_requires=[], + license='MIT' + ) \ No newline at end of file From 505dbb2d7c46327c44f54c939fd862e7cf8c7dd5 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Wed, 7 Apr 2021 23:30:05 +0200 Subject: [PATCH 03/93] fixed speed calculations --- PythonRobotics/turtlebot.py | 78 ++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index 20d275ef24..3fe19fe02a 100644 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -14,9 +14,8 @@ from collections import namedtuple # define state object -state_fields = ['x','y','phi','v','omega','t'] -State = namedtuple('State', state_fields, defaults = (0.,) * len(state_fields)) - +state_fields = ['x', 'y', 'phi', 'v', 'omega', 't'] +State = namedtuple('State', state_fields, defaults=(0.,) * len(state_fields)) class Robot: @@ -24,67 +23,76 @@ class Robot: Robot kinematics simulator """ # numbers for Robotis Turtlebot3 - _v_max = 1.0#0.22 # [m/s] - _omega_max = 2.84 # [rad/s] - _accel_angular = 0.1 # [rad/s2] - _accel_linear = 0.1 # [m/s2] - _wheel_diameter = 66e-3 # [m] - _wheel_distance = 160e-3 # [m] - - def __init__(self, - x=0., # x-position - y=0., - phi=0., - name='Turtlebot3' ): - + _v_max = 0.22 # [m/s] + _omega_max = 2.84 # [rad/s] + _accel_angular = 0.1 # [rad/s2] + _accel_linear = 0.1 # [m/s2] + _wheel_diameter = 66e-3 # [m] + _wheel_distance = 160e-3 # [m] + + def __init__(self, + x=0., # x-position + y=0., + phi=0., + name='Turtlebot3'): """ initiate robot at given location and orientation """ self.name = name - self._states = [State(x,y,phi)] + self._states = [State(x, y, phi)] self._omega_target = 0. self._v_target = 0. - def set_velocity(self,v,omega): + def set_velocity(self, v, omega): """ set target velocities """ self._omega_target = np.clip(omega, -self._omega_max, self._omega_max) self._v_target = np.clip(v, 0, self._v_max) - def step(self, dt = 0.1): + def step(self, dt=0.1): """ perform simulation step with timestep dt """ - - s = self.state # start state + + s = self.state # start state # update linear speed - dv = self._accel_linear * dt # dv is an absolute number + delta_v = self._accel_linear * dt # dv is an absolute number v_error = self._v_target - s.v - if abs(v_error) > dv: - v_new = s.v + sign(v_error) * dv + if abs(v_error) > delta_v: + v_new = s.v + sign(v_error) * delta_v else: v_new = s.v + v_error + # update angular speed + delta_omega = self._accel_angular * dt + omega_error = self._omega_target - s.omega + if abs(omega_error) > delta_omega: + omega_new = s.omega + sign(omega_error) * delta_omega + else: + omega_new = s.omega + omega_error + - s_new = State(x = s.x + s.v*cos(s.phi)*dt, - y = s.y + s.v*sin(s.phi)*dt, - phi = s.phi + s.omega*dt, - t = s.t + dt, - v = v_new) + # create new state + s_new = State(x=s.x + s.v*cos(s.phi)*dt, + y=s.y + s.v*sin(s.phi)*dt, + phi=s.phi + s.omega*dt, + t=s.t + dt, + v=v_new, + omega=omega_new) self._states.append(s_new) @property def states(self) -> pd.DataFrame: """ get history of all states as pandas DataFrame """ - + cols = state_fields[:-1] - data = {} - + data = {} + for var_name in cols: - data[var_name] = [getattr(s,var_name) for s in self._states] + data[var_name] = [getattr(s, var_name) for s in self._states] - t = pd.Index(name='time',data=[s.t for s in self._states]) + t = pd.Index(name='time', data=[s.t for s in self._states]) df = pd.DataFrame(data, index=t) - + return df @property From e7166e74be8918cbd12127d58350512dec5c5f58 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Wed, 7 Apr 2021 23:35:40 +0200 Subject: [PATCH 04/93] minor tidying up --- .gitignore | 5 ++++- PythonRobotics/turtlebot.py | 5 ++--- setup.py | 18 +++++++++--------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 92b7aa398c..bc5469c3e8 100644 --- a/.gitignore +++ b/.gitignore @@ -73,4 +73,7 @@ matplotrecorder/* .vscode/settings.json # scratchpad files -scratch/ \ No newline at end of file +scratch/ + +# IDE files +.vscode/ diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index 3fe19fe02a..a4b0d5e866 100644 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -23,7 +23,7 @@ class Robot: Robot kinematics simulator """ # numbers for Robotis Turtlebot3 - _v_max = 0.22 # [m/s] + _v_max = 0.22 # [m/s] _omega_max = 2.84 # [rad/s] _accel_angular = 0.1 # [rad/s2] _accel_linear = 0.1 # [m/s2] @@ -61,14 +61,13 @@ def step(self, dt=0.1): v_new = s.v + v_error # update angular speed - delta_omega = self._accel_angular * dt + delta_omega = self._accel_angular * dt omega_error = self._omega_target - s.omega if abs(omega_error) > delta_omega: omega_new = s.omega + sign(omega_error) * delta_omega else: omega_new = s.omega + omega_error - # create new state s_new = State(x=s.x + s.v*cos(s.phi)*dt, y=s.y + s.v*sin(s.phi)*dt, diff --git a/setup.py b/setup.py index 202cb63f59..cfd77c1cfd 100644 --- a/setup.py +++ b/setup.py @@ -6,12 +6,12 @@ setup( - name='PythonRobotics', - version = '1.0.1', - description="Python implementation of robotics algoritms", - author= "Atsushi Sakai et al.", - packages=find_packages(include=['PythonRobotics']), - url = "https://github.com/AtsushiSakai/PythonRobotics", - install_requires=[], - license='MIT' - ) \ No newline at end of file + name='PythonRobotics', + version='1.0.1', + description="Python implementation of robotics algoritms", + author="Atsushi Sakai et al.", + packages=find_packages(include=['PythonRobotics']), + url="https://github.com/AtsushiSakai/PythonRobotics", + install_requires=[], + license='MIT' +) From b001108f557fbfbf8d3e5e023779565a2b0ae7be Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Thu, 8 Apr 2021 11:34:10 +0200 Subject: [PATCH 05/93] added main (demo) function to turtlebot simulator --- PythonRobotics/turtlebot.py | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index a4b0d5e866..1ce8c8bea9 100644 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -8,11 +8,14 @@ """ import numpy as np +import matplotlib.pyplot as plt from numpy import sin, cos, sign import pandas as pd from collections import namedtuple +show_animation = True + # define state object state_fields = ['x', 'y', 'phi', 'v', 'omega', 't'] State = namedtuple('State', state_fields, defaults=(0.,) * len(state_fields)) @@ -101,3 +104,65 @@ def state(self): def __repr__(self): return f'{self.name} {self.state})' + + +def main(): + """ demonstrate functionality """ + + bot = Robot() + + # high acceleration ~ instantaneous velocity + bot._accel_angular = 1000 # high angular acceleration + bot._accel_linear = 1 # constant linear acceleration + bot._v_max = 10 # max linear velocity + bot._omega_max = 2*np.pi # max angular velocity + + omega = 2*np.pi # rad/s + + # calculate path + t_sim = 1.0 # simulation time [sec] + dt = 0.01 # timestep + n_steps = int(t_sim / dt) + + bot.set_velocity(1.0, omega) + + for _ in range(n_steps): + bot.step(dt) + + print('Simulation result:\n', bot.states) + + # plot data + if show_animation: + plt.style.use('seaborn-whitegrid') + + plt.cla() + plt.tight_layout() + + plt.subplot(2, 2, 1) + bot.states.v.plot() + plt.title('v (linear velocity)') + + plt.subplot(2, 2, 2) + plt.plot(bot.states.x, bot.states.y, 'x-',) + plt.xlabel('x') + plt.ylabel('y') + plt.title('position') + + plt.subplot(2, 2, 3) + bot.states.omega.plot() + plt.title('omega (angular velocity)') + plt.ylabel('[rad/s]') + + plt.subplot(2, 2, 4) + bot.states.phi.plot() + plt.title('phi') + plt.ylabel('phi [rad]') + + plt.subplots_adjust(hspace=0.4) + + plt.show() + + +if __name__ == '__main__': + print('Turtlebot simulation demo starting...') + main() From e3c38b285db3aa23d3471e32a25d6d68e88cd5b1 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Thu, 8 Apr 2021 11:39:47 +0200 Subject: [PATCH 06/93] restored original name --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index 3eb7663906..1441b79080 100644 --- a/environment.yml +++ b/environment.yml @@ -1,4 +1,4 @@ -name: robotics +name: python_robotics channels: - conda-forge dependencies: From 10e043f20010d140cb06f93658d4ed79d828194a Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Thu, 8 Apr 2021 16:04:09 +0200 Subject: [PATCH 07/93] made executable --- PythonRobotics/turtlebot.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 PythonRobotics/turtlebot.py diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py old mode 100644 new mode 100755 From 344b35ddb4e3ad98bb1595982dab5423bcd876a4 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Thu, 8 Apr 2021 16:18:29 +0200 Subject: [PATCH 08/93] moved plt import --- PythonRobotics/turtlebot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index 1ce8c8bea9..bad155d2c1 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -8,7 +8,7 @@ """ import numpy as np -import matplotlib.pyplot as plt + from numpy import sin, cos, sign import pandas as pd @@ -108,6 +108,7 @@ def __repr__(self): def main(): """ demonstrate functionality """ + import matplotlib.pyplot as plt bot = Robot() From 1627064f56b5896eff3bd98b76a66af63ae63c7f Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Thu, 8 Apr 2021 16:41:19 +0200 Subject: [PATCH 09/93] added test --- tests/test_turtlebot.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/test_turtlebot.py diff --git a/tests/test_turtlebot.py b/tests/test_turtlebot.py new file mode 100644 index 0000000000..88ece2c2e8 --- /dev/null +++ b/tests/test_turtlebot.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +""" +Test turtlebot simulator + +to run this test first install the PythonRobotics as development version. +do this by running +>pip install -e . +from the main PythonRobotics directory. +This removes the need for the `conftest` hack ;-). + +author - Jev Kuznetsov +""" + +from PythonRobotics.PythonRobotics import turtlebot as m + +def test_1(): + m.show_animation = False + m.main() + + From 10a6e39c78a6a1a6c3faf1d00b80cdee880012d5 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Thu, 8 Apr 2021 17:12:51 +0200 Subject: [PATCH 10/93] deleted trailing spaces --- PythonRobotics/turtlebot.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index bad155d2c1..b862ac2ada 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ - -Simulator of a differantial drive robot, Turtlebot3 - like + +Simulator of a differantial drive robot, Turtlebot3 - like Author - Jev Kuznetsov @@ -22,8 +22,8 @@ class Robot: - """ - Robot kinematics simulator + """ + Robot kinematics simulator """ # numbers for Robotis Turtlebot3 _v_max = 0.22 # [m/s] From 5345501b69798c1d6ff4787b8bdeb6cf45c76304 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Thu, 8 Apr 2021 17:24:46 +0200 Subject: [PATCH 11/93] removed spaces --- tests/test_turtlebot.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_turtlebot.py b/tests/test_turtlebot.py index 88ece2c2e8..45dbfb2ded 100644 --- a/tests/test_turtlebot.py +++ b/tests/test_turtlebot.py @@ -3,7 +3,7 @@ Test turtlebot simulator to run this test first install the PythonRobotics as development version. -do this by running +do this by running >pip install -e . from the main PythonRobotics directory. This removes the need for the `conftest` hack ;-). @@ -13,8 +13,7 @@ from PythonRobotics.PythonRobotics import turtlebot as m + def test_1(): m.show_animation = False - m.main() - - + m.main() \ No newline at end of file From 5f9979a65bcb5b97d52e3ba206311e7d7883dcf4 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Thu, 8 Apr 2021 17:33:03 +0200 Subject: [PATCH 12/93] update style --- tests/test_turtlebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_turtlebot.py b/tests/test_turtlebot.py index 45dbfb2ded..df4ff5cb77 100644 --- a/tests/test_turtlebot.py +++ b/tests/test_turtlebot.py @@ -16,4 +16,4 @@ def test_1(): m.show_animation = False - m.main() \ No newline at end of file + m.main() From 3bcd041fe134952657bd164b277ccad26cca13fa Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Thu, 8 Apr 2021 18:09:47 +0200 Subject: [PATCH 13/93] made test compatible with CI again --- tests/test_turtlebot.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/test_turtlebot.py b/tests/test_turtlebot.py index df4ff5cb77..6023f96549 100644 --- a/tests/test_turtlebot.py +++ b/tests/test_turtlebot.py @@ -2,18 +2,16 @@ """ Test turtlebot simulator -to run this test first install the PythonRobotics as development version. -do this by running ->pip install -e . -from the main PythonRobotics directory. -This removes the need for the `conftest` hack ;-). - author - Jev Kuznetsov """ +import conftest from PythonRobotics.PythonRobotics import turtlebot as m def test_1(): m.show_animation = False m.main() + +if __name__ == '__main__': + conftest.run_this_test(__file__) \ No newline at end of file From e21bb0136f2709850be72e4ee84e022b66a9a9fc Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Thu, 8 Apr 2021 18:16:54 +0200 Subject: [PATCH 14/93] code style --- tests/test_turtlebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_turtlebot.py b/tests/test_turtlebot.py index 6023f96549..0ecca7c1e1 100644 --- a/tests/test_turtlebot.py +++ b/tests/test_turtlebot.py @@ -14,4 +14,4 @@ def test_1(): m.main() if __name__ == '__main__': - conftest.run_this_test(__file__) \ No newline at end of file + conftest.run_this_test(__file__) From d713053c407179e0261ab09986acbf1a79848f7d Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Thu, 8 Apr 2021 18:23:24 +0200 Subject: [PATCH 15/93] style fix --- tests/test_turtlebot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_turtlebot.py b/tests/test_turtlebot.py index 0ecca7c1e1..0f3e960d83 100644 --- a/tests/test_turtlebot.py +++ b/tests/test_turtlebot.py @@ -13,5 +13,6 @@ def test_1(): m.show_animation = False m.main() + if __name__ == '__main__': conftest.run_this_test(__file__) From af10bf820ecc08b7ecc71a59aadd7846a24e3d63 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Thu, 8 Apr 2021 18:34:30 +0200 Subject: [PATCH 16/93] removed annotation --- PythonRobotics/turtlebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index b862ac2ada..e2150b5c75 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -82,7 +82,7 @@ def step(self, dt=0.1): self._states.append(s_new) @property - def states(self) -> pd.DataFrame: + def states(self): """ get history of all states as pandas DataFrame """ cols = state_fields[:-1] From b87be345111359f1a3b19c776f96bd9daf3447c7 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Fri, 9 Apr 2021 13:35:23 +0200 Subject: [PATCH 17/93] removed init file as it should NOT be in tests folder. See https://stackoverflow.com/a/41752043 --- tests/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/__init__.py diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 From 1597a63ee3c768e75a973e264fdca983a0ed2b83 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Fri, 9 Apr 2021 15:38:00 +0200 Subject: [PATCH 18/93] added vectors class --- PythonRobotics/vectors.py | 71 +++++++++++++++++++++++++++++++++++++++ tests/test_vectors.py | 62 ++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 PythonRobotics/vectors.py create mode 100644 tests/test_vectors.py diff --git a/PythonRobotics/vectors.py b/PythonRobotics/vectors.py new file mode 100644 index 0000000000..f1744b2fab --- /dev/null +++ b/PythonRobotics/vectors.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +""" +Simple vector classes. (Python3) + +Why not use numpy instead? A couple of reasons: +1. performance, working with small numpy arrays introduces a lot of overhead +2. .x and .y class properties +3. clear implementation for readability and porting to other languages. + + Copyright (c) 2021 AIGRO B.V. - Jev Kuznetsov + License: BSD + +Special thanks for these information sources and references: + +* [vector.py](https://gist.github.com/mostley/3819375) +* [StackOverflow](https://stackoverflow.com/a/43542669) +* [vector.py](https://github.com/betados/vector_2d/blob/develop/vector_2d/vector.py) + +""" +import numpy as np +from math import atan2, hypot, pi, acos + + +class Vector2d: + """ 2d vector, compatible with numpy """ + + def __init__(self, *args): + """ create from x,y or xy """ + try: + self.x, self.y = float(args[0]), float(args[1]) + except: + self.x = float(args[0][0]) + self.y = float(args[0][1]) + + def __repr__(self): + return f"{self.__class__.__name__}(x={self.x}, y={self.y})" + + def __add__(self, other): + return Vector2d(self.x+other.x, self.y+other.y) + + def __sub__(self, other): + return Vector2d(self.x-other.x, self.y-other.y) + + def __eq__(self, other): + return (self.x == other.x) & (self.y == other.y) + + def cross(self, other): + return (self.x * other.y) - (self.y * other.x) + + def inner(self, other): + return self.x * other.x + self.y * other.y + + def __mul__(self, scalar): + """ scalar multiplification """ + return Vector2d(self.x * scalar, self.y * scalar) + + def __truediv__(self, scalar): # note __div__ was removed in Python3 + return Vector2d(self.x / scalar, self.y / scalar) + + def __neg__(self): + return Vector2d(-self.x, -self.y) + + def __abs__(self): + return hypot(self.x, self.y) + + def __array__(self, dtype=None) -> np.array: + """ make this class compatable with numpy operations """ + if dtype: + return np.array([self.x, self.y], dtype=dtype) + else: + return np.array([self.x, self.y]) diff --git a/tests/test_vectors.py b/tests/test_vectors.py new file mode 100644 index 0000000000..f882e4e5e3 --- /dev/null +++ b/tests/test_vectors.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +""" +Test vector module + +author - Jev Kuznetsov +""" + + +from PythonRobotics.vectors import Vector2d +import numpy as np + +# test tuples +ta = (1, 2) +tb = (3, 4) + +a = Vector2d(ta) +b = Vector2d(tb) +c = Vector2d(1, 0) +d = Vector2d(0, 1) +e = Vector2d(1, 1) + + +def test_creation(): + an = np.array(ta) + v = Vector2d(ta) + assert (an == v.__array__()).all() + + v = Vector2d(an) + assert (an == v.__array__()).all() + + +def test_add(): + assert a + b == Vector2d(4, 6) + + +def test_cross(): + assert np.cross(a, b) == a.cross(b) + assert np.cross(c, d) == c.cross(d) + + +def test_sub(): + assert a - b == Vector2d(-2, -2) + + +def test_inner(): + assert (a.inner(b) == np.inner(ta, tb)).all() + + +def test_mul(): + assert a * 3 == Vector2d(3, 6) + +def test_div(): + a = Vector2d(1,2) + assert a / 2 == Vector2d(0.5,1.) + +def test_neg(): + assert -a == Vector2d(-1,-2) + +def test_length(): + assert abs(c) == 1 + assert abs(d) == 1 + assert abs(e) == np.sqrt(2) \ No newline at end of file From 3c409bca99d00ddee196446cc60e27bf1f365b06 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Fri, 9 Apr 2021 16:00:53 +0200 Subject: [PATCH 19/93] added angle calculation --- PythonRobotics/vectors.py | 4 ++++ tests/test_vectors.py | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/PythonRobotics/vectors.py b/PythonRobotics/vectors.py index f1744b2fab..4dc5c77f01 100644 --- a/PythonRobotics/vectors.py +++ b/PythonRobotics/vectors.py @@ -50,6 +50,10 @@ def cross(self, other): def inner(self, other): return self.x * other.x + self.y * other.y + def angle(self, other): + """ angle between vector, *relative* to other, range (-pi..pi) """ + return -atan2(self.cross(other), self.inner(other)) + def __mul__(self, scalar): """ scalar multiplification """ return Vector2d(self.x * scalar, self.y * scalar) diff --git a/tests/test_vectors.py b/tests/test_vectors.py index f882e4e5e3..75adcfe79b 100644 --- a/tests/test_vectors.py +++ b/tests/test_vectors.py @@ -59,4 +59,11 @@ def test_neg(): def test_length(): assert abs(c) == 1 assert abs(d) == 1 - assert abs(e) == np.sqrt(2) \ No newline at end of file + assert abs(e) == np.sqrt(2) + +def test_angle(): + + assert c.angle(c) == 0 + assert d.angle(c) == np.pi/2 + assert d.angle(e) == np.pi/4 + assert -d.angle(c) == -np.pi/2 From 7bc219a9a3f312d5ff82ca34c4619c577e85d1f4 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Fri, 9 Apr 2021 20:15:56 +0200 Subject: [PATCH 20/93] renamed Vector2d to Veector --- PythonRobotics/vectors.py | 18 +++++++++++------- tests/test_vectors.py | 33 +++++++++++++++++++-------------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/PythonRobotics/vectors.py b/PythonRobotics/vectors.py index 4dc5c77f01..e990112d7d 100644 --- a/PythonRobotics/vectors.py +++ b/PythonRobotics/vectors.py @@ -18,10 +18,10 @@ """ import numpy as np -from math import atan2, hypot, pi, acos +from math import atan2, hypot, pi, acos, sin, cos -class Vector2d: +class Vector: """ 2d vector, compatible with numpy """ def __init__(self, *args): @@ -36,10 +36,10 @@ def __repr__(self): return f"{self.__class__.__name__}(x={self.x}, y={self.y})" def __add__(self, other): - return Vector2d(self.x+other.x, self.y+other.y) + return Vector(self.x+other.x, self.y+other.y) def __sub__(self, other): - return Vector2d(self.x-other.x, self.y-other.y) + return Vector(self.x-other.x, self.y-other.y) def __eq__(self, other): return (self.x == other.x) & (self.y == other.y) @@ -56,17 +56,21 @@ def angle(self, other): def __mul__(self, scalar): """ scalar multiplification """ - return Vector2d(self.x * scalar, self.y * scalar) + return Vector(self.x * scalar, self.y * scalar) def __truediv__(self, scalar): # note __div__ was removed in Python3 - return Vector2d(self.x / scalar, self.y / scalar) + return Vector(self.x / scalar, self.y / scalar) def __neg__(self): - return Vector2d(-self.x, -self.y) + return Vector(-self.x, -self.y) def __abs__(self): return hypot(self.x, self.y) + @classmethod + def from_polar(cls,r, theta): + return Vector(r * cos(theta), r * sin(theta)) + def __array__(self, dtype=None) -> np.array: """ make this class compatable with numpy operations """ if dtype: diff --git a/tests/test_vectors.py b/tests/test_vectors.py index 75adcfe79b..cec0b009c4 100644 --- a/tests/test_vectors.py +++ b/tests/test_vectors.py @@ -6,31 +6,32 @@ """ -from PythonRobotics.vectors import Vector2d +from PythonRobotics.vectors import Vector import numpy as np +import math # test tuples ta = (1, 2) tb = (3, 4) -a = Vector2d(ta) -b = Vector2d(tb) -c = Vector2d(1, 0) -d = Vector2d(0, 1) -e = Vector2d(1, 1) +a = Vector(ta) +b = Vector(tb) +c = Vector(1, 0) +d = Vector(0, 1) +e = Vector(1, 1) def test_creation(): an = np.array(ta) - v = Vector2d(ta) + v = Vector(ta) assert (an == v.__array__()).all() - v = Vector2d(an) + v = Vector(an) assert (an == v.__array__()).all() def test_add(): - assert a + b == Vector2d(4, 6) + assert a + b == Vector(4, 6) def test_cross(): @@ -39,7 +40,7 @@ def test_cross(): def test_sub(): - assert a - b == Vector2d(-2, -2) + assert a - b == Vector(-2, -2) def test_inner(): @@ -47,14 +48,14 @@ def test_inner(): def test_mul(): - assert a * 3 == Vector2d(3, 6) + assert a * 3 == Vector(3, 6) def test_div(): - a = Vector2d(1,2) - assert a / 2 == Vector2d(0.5,1.) + a = Vector(1,2) + assert a / 2 == Vector(0.5,1.) def test_neg(): - assert -a == Vector2d(-1,-2) + assert -a == Vector(-1,-2) def test_length(): assert abs(c) == 1 @@ -67,3 +68,7 @@ def test_angle(): assert d.angle(c) == np.pi/2 assert d.angle(e) == np.pi/4 assert -d.angle(c) == -np.pi/2 + +def test_from_polar(): + + assert Vector.from_polar(1,0) == Vector(1,0) From b28fcd7d126f12fd1186b42f621dbf8524275449 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Fri, 9 Apr 2021 20:50:49 +0200 Subject: [PATCH 21/93] added point to line projection --- PythonRobotics/vectors.py | 27 +++++++++++++++++++++++---- tests/test_vectors.py | 15 ++++++++++++++- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/PythonRobotics/vectors.py b/PythonRobotics/vectors.py index e990112d7d..6f392ae06a 100644 --- a/PythonRobotics/vectors.py +++ b/PythonRobotics/vectors.py @@ -36,10 +36,10 @@ def __repr__(self): return f"{self.__class__.__name__}(x={self.x}, y={self.y})" def __add__(self, other): - return Vector(self.x+other.x, self.y+other.y) + return Vector(self.x + other.x, self.y + other.y) def __sub__(self, other): - return Vector(self.x-other.x, self.y-other.y) + return Vector(self.x - other.x, self.y - other.y) def __eq__(self, other): return (self.x == other.x) & (self.y == other.y) @@ -47,9 +47,12 @@ def __eq__(self, other): def cross(self, other): return (self.x * other.y) - (self.y * other.x) - def inner(self, other): + def dot(self,other): return self.x * other.x + self.y * other.y + def inner(self, other): + return self.dot(other) + def angle(self, other): """ angle between vector, *relative* to other, range (-pi..pi) """ return -atan2(self.cross(other), self.inner(other)) @@ -58,6 +61,9 @@ def __mul__(self, scalar): """ scalar multiplification """ return Vector(self.x * scalar, self.y * scalar) + def __rmul__(self, scalar): + return self.__mul__(scalar) + def __truediv__(self, scalar): # note __div__ was removed in Python3 return Vector(self.x / scalar, self.y / scalar) @@ -68,12 +74,25 @@ def __abs__(self): return hypot(self.x, self.y) @classmethod - def from_polar(cls,r, theta): + def from_polar(cls, r, theta): return Vector(r * cos(theta), r * sin(theta)) + def __getitem__(self, item): + """ make subscriptable """ + return [self.x, self.y][item] + def __array__(self, dtype=None) -> np.array: """ make this class compatable with numpy operations """ if dtype: return np.array([self.x, self.y], dtype=dtype) else: return np.array([self.x, self.y]) + + +def point_on_line(a: Vector,b: Vector,p: Vector): + """ + project point p to line defined by a and b + """ + ap = p - a + ab = b - a + return a + ap.dot(ab) / ab.dot(ab) * ab \ No newline at end of file diff --git a/tests/test_vectors.py b/tests/test_vectors.py index cec0b009c4..09e4f85047 100644 --- a/tests/test_vectors.py +++ b/tests/test_vectors.py @@ -6,7 +6,7 @@ """ -from PythonRobotics.vectors import Vector +from PythonRobotics.vectors import Vector, point_on_line import numpy as np import math @@ -72,3 +72,16 @@ def test_angle(): def test_from_polar(): assert Vector.from_polar(1,0) == Vector(1,0) + +def test_subscriptable(): + + assert a[0] == 1 + assert a[1] == 2 + +def test_projection(): + + a = Vector(1,1) + b = Vector(3,3) + c = Vector(1,3) + + assert point_on_line(a,b,c) == Vector(2,2) \ No newline at end of file From 61015db877d6bbcf1db799fe92d6ba3036387efd Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Fri, 9 Apr 2021 23:39:08 +0200 Subject: [PATCH 22/93] added rotation --- PythonRobotics/vectors.py | 19 ++++++++++++++++--- tests/test_vectors.py | 30 ++++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/PythonRobotics/vectors.py b/PythonRobotics/vectors.py index 6f392ae06a..ff94a47780 100644 --- a/PythonRobotics/vectors.py +++ b/PythonRobotics/vectors.py @@ -21,6 +21,11 @@ from math import atan2, hypot, pi, acos, sin, cos +def rotation_matrix(theta): + """ create 2-d rotation matrix """ + return np.array([[cos(theta), sin(theta)], [-sin(theta), cos(theta)]]) + + class Vector: """ 2d vector, compatible with numpy """ @@ -47,12 +52,20 @@ def __eq__(self, other): def cross(self, other): return (self.x * other.y) - (self.y * other.x) - def dot(self,other): + def dot(self, other): return self.x * other.x + self.y * other.y def inner(self, other): return self.dot(other) + def rotate(self, angle): + a = np.dot(self, rotation_matrix(angle)) + return Vector(a[0], a[1]) + + def round(self, precision=5): + r = np.round(self, precision) + return Vector(r) + def angle(self, other): """ angle between vector, *relative* to other, range (-pi..pi) """ return -atan2(self.cross(other), self.inner(other)) @@ -89,10 +102,10 @@ def __array__(self, dtype=None) -> np.array: return np.array([self.x, self.y]) -def point_on_line(a: Vector,b: Vector,p: Vector): +def point_on_line(a: Vector, b: Vector, p: Vector): """ project point p to line defined by a and b """ ap = p - a ab = b - a - return a + ap.dot(ab) / ab.dot(ab) * ab \ No newline at end of file + return a + ap.dot(ab) / ab.dot(ab) * ab diff --git a/tests/test_vectors.py b/tests/test_vectors.py index 09e4f85047..17984799cc 100644 --- a/tests/test_vectors.py +++ b/tests/test_vectors.py @@ -50,18 +50,22 @@ def test_inner(): def test_mul(): assert a * 3 == Vector(3, 6) + def test_div(): - a = Vector(1,2) - assert a / 2 == Vector(0.5,1.) + a = Vector(1, 2) + assert a / 2 == Vector(0.5, 1.) + def test_neg(): - assert -a == Vector(-1,-2) + assert -a == Vector(-1, -2) + def test_length(): assert abs(c) == 1 assert abs(d) == 1 assert abs(e) == np.sqrt(2) + def test_angle(): assert c.angle(c) == 0 @@ -69,19 +73,29 @@ def test_angle(): assert d.angle(e) == np.pi/4 assert -d.angle(c) == -np.pi/2 + def test_from_polar(): - assert Vector.from_polar(1,0) == Vector(1,0) + assert Vector.from_polar(1, 0) == Vector(1, 0) + def test_subscriptable(): assert a[0] == 1 assert a[1] == 2 + def test_projection(): - a = Vector(1,1) - b = Vector(3,3) - c = Vector(1,3) + a = Vector(1, 1) + b = Vector(3, 3) + c = Vector(1, 3) + + assert point_on_line(a, b, c) == Vector(2, 2) + + +def test_rotation(): - assert point_on_line(a,b,c) == Vector(2,2) \ No newline at end of file + a = Vector(1, 0) + assert a.rotate(np.pi/2).round() == Vector(0, 1) + assert a.rotate(-np.pi/4) == Vector.from_polar(1, -np.pi/4) From 866c321572d9b7b3294d061f940c7a8f8d4d9c4a Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Fri, 9 Apr 2021 23:59:09 +0200 Subject: [PATCH 23/93] added notebook on pure pursuit --- PathTracking/pure_pursuit/pure_pursuit.ipynb | 184 +++++++++++++++++++ PathTracking/pure_pursuit/pure_pursuit.png | Bin 0 -> 170675 bytes 2 files changed, 184 insertions(+) create mode 100644 PathTracking/pure_pursuit/pure_pursuit.ipynb create mode 100644 PathTracking/pure_pursuit/pure_pursuit.png diff --git a/PathTracking/pure_pursuit/pure_pursuit.ipynb b/PathTracking/pure_pursuit/pure_pursuit.ipynb new file mode 100644 index 0000000000..6770adc026 --- /dev/null +++ b/PathTracking/pure_pursuit/pure_pursuit.ipynb @@ -0,0 +1,184 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Pure pursuit controller\n", + "\n", + "\n", + "References:\n", + "\n", + "* [TheNatureOfCode](https://natureofcode.com/book/chapter-6-autonomous-agents/#chapter06_section8)\n", + "\n", + "\"Drawing\"\n", + "\n", + "An interactive geometric model below is avaliable on [GeoGebra](https://www.geogebra.org/calculator/vh5d7jvy)\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "from PythonRobotics.vectors import Vector\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "plt.style.use('ggplot')\n", + "plt.rcParams['figure.figsize'] = (8,8)\n", + "\n", + "# support functions\n", + "\n", + "def plot_point(xy, txt = None, style = 'o'):\n", + " plt.plot(xy[0], xy[1], style) \n", + " if txt is not None:\n", + " plt.text(xy[0]+0.1,xy[1], txt)\n", + " \n", + "def plot_path(points, style = 'o-'):\n", + " x = [pt[0] for pt in points]\n", + " y = [pt[1] for pt in points]\n", + " plt.plot(x,y, style)\n", + " \n", + "def plot_vector(p1,p2):\n", + " plt.quiver(p1.x,p1.y,(p2-p1).x,(p2-p1).y,angles='xy', scale_units='xy', scale=1)\n", + " \n", + "def point_on_line(a: Vector,b: Vector,p: Vector):\n", + " \"\"\"\n", + " project point p to line defined by a and b\n", + " \"\"\"\n", + " ap = p - a\n", + " ab = b - a\n", + " return a + ap.dot(ab) / ab.dot(ab) * ab" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# reproduce geogebra setup\n", + "\n", + "look_ahead = 0.5 # [m]\n", + "phi = np.pi/2# robot pose [rad]\n", + "\n", + "a = Vector(1,0)\n", + "b = Vector(3,2)\n", + "pos = Vector(1,1)\n", + "v = Vector.from_polar(1,phi)\n", + "\n", + "d = pos + v # future position\n", + "e = point_on_line(a,b,d) # project onto path\n", + "f = look_ahead*(b - a)/abs(b -a) + e\n", + "\n", + "# calculate heading error\n", + "alpha = v.angle(f-pos)\n", + "print('Heading error = %.3f rad' % alpha)\n", + "print('Point E:', e)\n", + "\n", + "plot_point(pos,'position')\n", + "plot_point(d,'D')\n", + "plot_point(e,'E')\n", + "plot_point(f,'F')\n", + "plot_path((a,b),style='o-')\n", + "plot_path((pos,d))\n", + "plot_vector(pos,d)\n", + "\n", + "plot_path((pos,f),style='-r')\n", + "plot_path((d,e),style='--')\n", + "plt.axis('equal');" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + " \n", + "class Waypoints:\n", + " \"\"\" navigation waypoints \"\"\"\n", + " \n", + " def __init__(self, x, y):\n", + " \"\"\" create waypoints. \"\"\"\n", + " self.xy = np.column_stack((x,y))\n", + " \n", + " def find_nearest(self,pt):\n", + " \"\"\" get index of nearest waypoint \"\"\"\n", + "\n", + " dst = np.linalg.norm(self.xy - pt, axis=1) # distance between points\n", + " idx = np.argmin(dst)\n", + " \n", + " return idx\n", + "\n", + " @property\n", + " def x(self):\n", + " return self.xy[:,0]\n", + " \n", + " @property\n", + " def y(self):\n", + " return self.xy[:,1]\n", + " \n", + " \n", + "\n", + " \n", + "def point_on_line(a, b, p):\n", + " \"\"\" project point p to a line defined by points a and b \"\"\"\n", + " ap = p - a\n", + " ab = b - a\n", + " result = a + np.dot(ap, ab) / np.dot(ab, ab) * ab\n", + " return result\n", + " \n", + "# create waypoints \n", + "cx = np.arange(0, 50, 5)\n", + "cy = [np.sin(ix / 5.0) * ix / 2.0 for ix in cx]\n", + "\n", + "wp = Waypoints(cx,cy)\n", + "plot_path(wp.xy)\n", + "\n", + "# test nearst point search\n", + "pt = np.array((29,20))\n", + "plot_point(pt)\n", + "\n", + "idx = wp.find_nearest(pt)\n", + "# plot nearest segment\n", + "plot_path(wp.xy[idx:idx+2,:], style='ko-') \n", + "\n", + "#plot projection\n", + "px = point_on_line(wp.xy[idx],wp.xy[idx+1],pt)\n", + "plot_point(px)\n", + "\n", + "plot_path((pt,px),style='--')\n", + "\n", + "plt.axis('equal')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/PathTracking/pure_pursuit/pure_pursuit.png b/PathTracking/pure_pursuit/pure_pursuit.png new file mode 100644 index 0000000000000000000000000000000000000000..004230451c7baf32c5849a400a4ef48ba99f1e02 GIT binary patch literal 170675 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�EktI7p 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

x|{ zJ(wvGnPqWQ+5q@SjZSVbXg*{`>%3MS!sfc$`|X=N$~9mkN*Q6Ie2yXA1{4IJ>*HN= zx=7Z{9ZX0|hKc8xBl)D%wRQNYpFfUwT;Uh|M4>Er0pMAHrsr5Pbqu8Aas)Th^Lmaz zNo@JO^R>0Lq}USA#Yr4@`Jer`weG4jS9KcIAV`6$1?bvPbH8I6#uOLLqX`U5X;@gn+)+uUf zX}!g$khz&D;PjLcHp=~+68Q4q2i?v46264#@xcfXYLSsb-oBOHfZ(Kq-y{rIkd@&b zg82ZsebsrQzTGi!8{L5WtRqdSFj;##9;kj5yGU|IhMFlV8)Ca~J#yio%1cwd5uV?b zO*Ttd-g)bJ1KLCex@<(8cC5mA(glnFNC&e~g@g)GClv%SSpm=GJUl?~y__(1gwP9y zgpkRq=FN0%6k@)ut}ez}l8*dzpYfp&WXunOCA(spNqHROc|MCi#;=uUt{+Z37D6_^ z&Q){mM@JL9ln=Tsq@BB|QO)nN6JPiC&4;A1G6W`-02#<((Uwt(nsimd{IgUzlyq80 z4@0`!{2Hxb&vpO(bm%qa!Va9%-kDYm4-rmF^oNgt??xcpe7LI|rAp<=$;X;E@`4$< zeiPWOCBNMQi9AFms3MNG=-SE6RhVeeyBW>|YeV*SA~rew*4I2h{+ z%~_Axry|j+827iK30SrOtQ?pJn6wEGe!H@pp*>xOdSjUFIJqM*ND|&sl{Ggy{&8u( zQDOSv@9>7YG^)N0D8wnLoxc(NUx?qvb@sF0sKsz@{F{VU?CF(28q|p+#g$a>I+^-a z`r3l~6&fWr+Ihji3LgL{e>sQGUT)LgtmL5CWA++l>vtbLgIJp_LgdcZc{)ZS?4k3z zGIy-$)GCSEWBgx7FE_nb`}#*O>8aGY<|42np>h{z+7uo@+g~D|p*w3t?a@n`xxa@& zNrF6(gW43`pi7Hdv$_7^fJI#lT!|=)ffwk@?XsUY#AeXv7I}6HETX|MUWdEOiEa*H z<^`Jd8vfB;CV60J;>M-3o30clTdhrn)EH=pu$~|m zS+&z9-;AL-jcdm?h$nCi^BgO^rl4SJ|Ix0XUCK859(b({e<59cw+RNT3(4;3qzBw} zlfw2b!_J1`y_3@$)2)Llx9`VP%HGk-(CJ=~Wsv155b-A*nHbiRLFry}lN;>MX`zSm z29Le&l}dIKP#(KEi$I6cvrbYXp@Z*pxl)~yY2a1F{thK`!SN9%GR7Q`Bl8&!-xo&W<;}&zWaOiBS-E2 zd-vPTV%x7D!F>vruAx&m8A_)Q^ej&|OS^EPxfDitkMemqjcjI>e~)xtg_#4-7p44a zVkJY_IRoETI_iaM`Z-I{_PU$Jg2@FTwIJ;spBga?q(ueB_qt+ZA9pPH8n!YU>}{6M z-7v7ADOu;@Z2;FA!QkRWIvLP`4ZiE6Et0cd#vPj?lt#jc~=wncL6wmTY`5)h*h)A^_j? z`1u*x-T6f_=>tR8kK~yd8K&A{6aWt80c3H8CdpVu{I3(q4DpsrO^zJ|SA3nW+I7gR zQY*H-vw(Zip#Q3{A7#CINp5WsJtG~5P=zD3;oMd0G_$qBLK)NplBj|jF;&C6uSP=<9o8`vKB=h}F0L|t zP**clW;1Qs;uqb=jXlP;_j+=fTvcHT4h?N=;rP*<8bKJD`y0rg6h6c{2$HBh$KH*> zD|-Im^arg;Cd&O6Ap&QAeY2=>yZL30NR8K!+uIvke>QV z-}7c`PST&b$w9kfAx;)%-Z^-E^1mGi+63qZLnkCEDqqtI8Eb%XH=4IzVFhci!W-Co z5H;=R-`*qoUTk~1(N~x94Zr&Q4eiz~TXwc$dDex9KRp{vqiin-ak_N>j@H8{RKmi^Qwb%i;Y3tfW-9( z(G*#v8iAg*+|t`xGB~)Te8&e5=O`B>C9TTP#$h!h-w%QH5e@QP?JQ>ON zcYK2)e_y!bUZ-}suPlu5)*jx z+E&+o-l-?XDL~r{yx_af)XI^R#>J;FBv343r?#sU19_Sm*NI*s|v;xk9)EL-BTD_g=N82M$|h;=F`CW zz=>=Vt0~3ujci;}Tm0P*&gR`!O)sury7Vec=xgy{NJOmsysZTQ zA6Ocjexy^Mr;tDNfv^4ywbYV);7%D;rkUWP2BE z%*lWHbVJsU`-gVRo_WhT`Yn1s>*WuQf={~=Pg;eG6$l~qpac?_Nih({DR8<_eCqUP zB(O-SnNTR1G`62w!>Lk#5#5d>dT z+7;qfk&b{UrblNG_+{Xf6eFhkq?9Le>!x+I^S=09^Wv2It28z^Suo*e_Ik_o7Ys=6 za?(uG6|>*jG=wSGNH9n>66L<_0w)YNu1ndk*hNd0KLSPjnf1$0zc!byvV6Jy2PSIY z%0}zf*KFvE7aJ)$dV7=EjS4>Y3lmpK`cN!RvYZBs2kMrnh9W|>lWRDQZ$hOw)*_XK z$A*O`hEwY8Y-Igq*9onLNWpm7W_ywy^|uqQ9}Au`+!6Guj7uta*bmhN(qF)gT{cU= zmfZf2nphAcYpY>AlU>}X_DD|d$H2{66e;2Wy}x9*^Qy>%QMs{Fg6-wAcIljw1A1dJ zmoxL{@@^AYerXX&B-OuRq&Vu5NNp1g3#hQ`#HJ(%xfAtrLy2ANg^J~^f!y$0uU32e zR@RxGaneH*E%5R4j09diAtmMbH`K)7j41+UM0=NCLw1HsDX}{4vwk`5Yk7VPtPeL| z8UN$0+VAQ4?Gvk){+=9nLPbp^ys8U~_!m`W_?j>G1{6G5gJxbfkN^RA)Nc8Is$ykTIscCF1%9%vVB5anT)9F17Q>ByX zCuAq|-8I=P6`3N+rqP0$p9)RUTNNaDLC2H)-k#SqiPQvhm4N6(t9cP&CGBnmYt3%e zH0sojsKoDKpuF){E5r%1w2-+^RQfW#`YuSxaM=z|Crk-6#U~#^1lNRmon$3BDdWpOK|4e2~Ps8-saj&xbRGSUT zrYpM>tzLIugtP#{lifdMw|>!8K{IE(^*9?2HYX z43(EJ*!QCzn^bDa0RF8(o#rSYAu0L2jq5wlpxcw8XJ@$%qyq6GeGFW!z9oaIz5PA* zHiw8_CTMH@jf%dIqfvJ#a=I`RA@YQ=BK}VAl9U#@|EsfO^VVHQ!Cb?ihK~*Vpvtx?hs^w$v*0 zeB+M#Z?!sxpUw_G{q^wrib|g~Y7K781*6a8!p!cb0k zaH7qOeshP4Hp~R(-Ee(^JMB7@faJAN>+qnBBJ|fl+Qd1X-HhDw!|7?e1Mk6CB`3jxd`=X=54^k`^JCjT)rb18o5#p#tUfkSTZFdk35iThNs#r9qlZ?Z zKh}F)8Z+0AmM0zmE?b4NnF2KXzUan*&W_^P<2Qsdk){NtgOD#n624~@Z%<@gR6ZNG z0Kosr#^0``)E&H0%ik;U@lFq=Wx>M>ucs={F(aQfMf+_X?LYQ^%B|AYb@}?>JXJR5 zePivq?I`8NpKsS~@7eM`KKKEAe5^^4E;0drX0f;EDo%wVXJQi-$IJX*{r8VjS!R~y*B7mxTo?H2tZ0NV zD-%kwwQc{p$zO$-76&{IvxLBIVx*1aMS1w0c-!TR7r|sZ-wpX@}|zw|nJ zOPhJU0+b?yeeO{u3`Xlb(koC6*%ue;#edvCHK><@$d8dfed?YAozTf<{`;Q zM)DqV!2DkkxI`y1agz+|#mteYZn`RXlNP$BykvO$rWywsu-*5KF&Ye{1iBHpe15j* z>VrW0+(pkey*vwG+*-MH?QCPb;)=DmVq&CZU0(VEJr>xt7bkb#+9-^1QjIoWz)Dv# z==q}rbyIdjxxhDaEZ;qZT)?Y@%99tG=pMo%WlMF=$DUSXGSz=x;ZuJ(cm=OFr}R&t zlANRwJ7+KUUtQ$gMGnni@cYStTHq0s*MpSu!DwfMShzQ!l&XR@ce0B2a21*8EIgI_ z-7X%pbbZ~YCtF*}*`%AN%86zF#9!Qv2Jv1}EW;M~;D!e1-3tEM*=AWkZmxc)vj2_z z4+5$(P22xQoPZsZz)@s(LK#dLFJHHgaRV7(#qgyyxmbZi!^oY11-8h4{M#3K&Fjy{ zK2`qm6F&q$e2CdKZ-nKG~8Jl}RUTq)B4jc%#;<++yi>;iL5K9`{Q7j2dPI=_@)-#n(_hy9+PdQwqsOlhawSsXXd;sPb- z*tKhyRIf~B`ODgT+zCHOenWQM%uBn@z}Nv9*IZ8d(-*dK6BI;=Ap?S8%YYn-qzP_} zOtq!#m71SXmh=tXY%}dCzv(Fl*r)BrU|6B84v z`T5o@W{orVg~e}UKP1KHe@}r{0A(T0UyMEBh~G%3BrUUsSWzhF830-1CL3qJb-tY( z?y0>gUog(IOtzzS%O1Xv$nPkEi22pQOcs?fJddKVtBMUxpUShd-y5S=WE2cSU7Q@j zQVj+``u^_w?bzN~a>ddf;vemUnH*3DHJt8IM(fv94#qnIqESse;IM zJZ^0K3Fbj!7L{JDYyS9rhg6n(Nn7Emrq+Tlk^1_TBYjfVVVnmv3|ExMGoFqsdBoes zk$R&A37!v(dWFnO8#Xn!ROh(MZS{=NIrYW4|Ni~Wor_S!!?#7=IoM}>@YURGC3}=bYTSkRVFC)@Hnmn>B0~uN zvw$?{_k8;%YE&%ITuQ0CLNNSnPiVfJ%bPnJvc%$hEr}h4Qb!tB0|pZ zx5pl{sxQF_Sr2X{$o`o=;{{oRA|NvN0Cj1X0?sHrmb)7Gsw`T{&!KRc8h`EPCEy1TpYP6s^;>bUD{(MsLQLj*K9UjKxpwWQ#kKBBk z8hyYno>e_4Y8r-TptQne~{*|9JH9(u1 z^`l?xs3bago^&_-h6xalGR+hO1?nd{GY{ExjYz6Qm=?V zJ|HFczjf=hsP2O8r9o%ms)L_Or;!AMVAdy{5@azD5~XqxqDIx7UnsoZZCTxVLnT*o zP=E3BrM&yg~Gy}Gp#Y-}w%F&=y6e14LVq1zm8u8)w0N&*QLZCj}e zCQJ`9D>S6HNFk7_xLi;Tv`je0e-_CCCjzl)4#a6vw=fFdYNoyr{*}np>;Xr#^EUav zVyR#y_LZPYglm-Ig}{K>iGn7Y#30-%Yg8?nvTvFzO#>%4g zsO;+XrJs=4j5==^6Q1Z6I!D~SA_Z91-t(bYJLYT`Z^3g5;g{7_dyWQ59K9`mi|qtWMg6 z#Z~l$*V#-LB~-t;C9JszvC=(DGm&dn$|MuAzt~cOZaoq8_LIBJQDVYfde5A9lSs#+HPyN-7os|x##Y69ul{W3C!+Jm1=KUdgx9~*+*|3<|Z zm~mN_Yv%!+^{&{4jn((d9Pjnx$3j*`OXx@(p@Lof^V5`92?z}xnTV(ZO9wOx2o4i* zrvMe-!?KG~ij#T5`lvyI8@*8von^m4v5*o**Bgj4Vf4`WNtn5~x$dLeTDDf#XceiW zO>s!8sjHhqVcA{~$Vi?y-879#Y#n@(oV-&=NC-Bf?Bmu?if`Ur&Z{KOsWmN6)E;pb zK;Us#xR3AdPtO>@o9`5}iP2PuC2#8i@Bdl~sb1N9>YoY{MBBFQ+NCOTu-odWUBMn} zt+Q`fC-3bk^B0>);TnNzgd$gnZE;aO&Lwx(KB`W&!&`@NTJ;1mMt2 z#nazx_&VcJ9%%G{;qgeOr8WLvdksPPsM6nX4y5|Rt{~*PqwY?|u7C_z`M+J!vpc(< z8vaED;r}xyR2PIP9AbYF>V|GWbEp)wR@n9GEY8Kwl8%Xw>$V4WzPD{k#)FVuKf3g~ zV#Spu4W_tVoLN@+KheizS1SoQ!=&7T62lKeDZbd)-mv}oca`Wp8JG0H#*S-jh;>*K z@Twj8dBc87RlW`<_vu5vyxGVgMoFXF#W{cdIL$x<4D>EkVgSqH^&}vF zmW62&XcxX>s$Kpn%L32&tQ`0K_7ly`k%>aGJny0gBb^3!puGx65}SK-fe)|e-kT?a zKM*R25TWldi}s%&G?mx(i=!)~-#+-K@@$t%v=WWR701bPYn7Pn#n=_{6a9{=cjwbj z7iFUaS^)At#fVW*(r-;B(PE~yCrp`;_v@}e9bm+--d-S{dwa0n>c`aP@3YkB9!~S^u`^{aCmHyN%(^fBW?N;^V>2(cT>% z*<+@ZVf>fsTSxkG{O3JxIBe6FX^KV+69|a{KNa@m(}1-D#Jb75KPugfn(3vm%cdX(KGUuF@CuB^oljUY6mxhX0MGt?C#DL z7kQWVHQlblCtiVdJY`T9?>K1a2=N0xY=SKGlQ<9+xAAOE8DTFg4j(iZPd z_T{1-Qa?0YaMwWigr528lIChPYrTzmdWJ4aACnC-b=3FrM`r$5w3v7LT>jM`KZ5q1 z+#$Tl(LUt(r~2;h4I^)%8`MSan=HHZZ3`bCpY@sBUVgs5$&s0QB4p+RBpwjKGhBz6 zpW1xLCMEZ0J9p;x_VUV=&Yq8_y{*oEv-dVl{)SYvaN2;*BzMZx**Rq;C8gCtNe}Rk zCO18ia4^L{2PGQJgyjNu5nq^JzW3H*EJr)k55|GHP%Bkya4X_^(&@(zP`Ruf~n2L$S#4ArYaW)aqjtd z%HBUbwAT07sxcSxLOqcwlr0v_6R0gdm$nZQ~;JsAou$AUPYoUAd>({M2 zwc+`~hbV#>`CEByyhw$`zS*DGMYsRMVS_~Y{FSke^jN9!1a_Z=rRQGQK8G(!Z_!E3 z%ert#cb5#;_wSh>&JOoGWllcA2s{u3J?hu>KES)qlW?QKVg zh7SAkQnWsS1M_wIs%G>kDr?&>yz24eLqlzetN%_;zABTuYOQn7`uoCdYF03A(;!(4 zp559gNs5MsMmTUDTj6n;tNF4MwKc2GR{s1srro)Iy+rEP;I+R@$Y%eSbI}HTNaf$e zMh46EI!~BR{n-j(#1AkOrr*E+jAPouf=^J;mzzfA7mTPZWB0ev=BIlt+_1{R4GX}o z6vTFTbRJXVAmCjvK4Tc4f&p6$BLvPEw5$FkAUPjPakx+b<;B#v=xNKl#ewSGAh zK{kiT!d6|s8=iRdzNie^I%yAgeLU=BK)|r-zw_OA;QokI*E{xzrn_lFag=-Y zDjU-)45AIzH^YB4jK)04xxou}weG*W$~Jg{hQYWH%gJl-uMu7j6wSgbY``@8;2&u> zPARS-=fciILmyH!r|5QWkCRunp?@nd^Df(SjQ3b&#r88~rHCz&u5#@>ihFPoG{2HK z+ZpwwyV5j=SFY1K=lh8hXVQKZS)M$J%apoW=^uXh88-GkXr!OixXkXqYcVfes|Xi@ zH}mCy!^vyS7e``z-GlGmSjFMSCC8(6`z_rxOG~eLID1mi+5xUqynyNd`(%=@U%XJv ztJYw9^~IsziK-voyemgzilUX4sjj25qXtDeuBFDEA&aDARgZ`8AK%N#vWtDtxkQfO z@Qc*gUF^buPCziuC1_q^fQFBD$3rMPZvlxguUgf4X!cW%g+W0vS^@38wfdEgVEaF4 z05$OAkY(clE*)-9IE$iKH)u6L%_RHeACnD;v*xwgH$ys04LV?jn! z(%Kg<+zkD14U82&Y!!1~=R?&#m zl)T~^cHy~5`mF^_*gg)jJX+YTeXo)ij{QK|htcp>KvS z6svW!NI_>-<8Y%+(%;8f>h*dz6BrTZvH$JcG1#5R$kbw~y8hhJVqiovc&#j{}eb&3MlLn3p`^j(5n{+Vr-c->FoQ zQGrLs89}#hX{5eY_{ycaQCNhNW%=TE{RQ4097+82)*|Q6Vx^?U$difp?%n(O(?R=$otIA1PG&=HFYLv|Ol#Nb z&RQx&>#TS-IKG)>d1%!+44D(r8PpN9d^bQ*I(#_g>YZf?@A}7GB?ksyR8-Vj)ceIT8y-* zI42Q)&8yk8PfVbycrhzGd*aAoG8UKr`1`?ew9CksaO;w9vq30zmYG>1F(A%&b>l`dMP;oi62 zU~G)#g$gZS|FI(&hCgQB-&*SU_5TV$D{SCjK<>A2yK ziQzqGDC^h97?temERH{EG+csK- zsK)(%GGI}#J$dDuhY##OIQpFF6k!@h=F(aw%lChFud)gy}kicK}2*~|LyrmpRrjCE5osrXsgmjeqp;#8~~ zik#6W=llKlX?ZHT7qjeh?JW}LUarh(O>G@KRe9yu^yFmiaP%F@Y70LBYo+FIMXf{Yb5>`-IpxD^X(erxt_9pn2-Q6 zCn_Xl{k_GQ<#B5hZqPv9O~yE$k@T z**pWxneBTUdFkipL-R+17o*vm?2a9Yhc1NPT^7B1)25qmPx?2ev<{kzH(6D-wc!rV znn|V$oa#4aMCRJHjA;6>7;&DUR2dgLjN0++hMNDrvC(HWSK?H~)&q;9S5Hqow>x^= z&pzSdX^+a5C&4j8{_fphbJO@vS(+_eY5l^Lwt3&UL2w8bf^S*Ncc*&UU$<{g*{iOx zk!rKa>J@+r?UQ%{j(9-uPF0J^okc za*Es5cms(Mx`xl-{HCNtBV--} z*t@?~7%5)AlNf5VlK|YbR!1wdC9XL=UB{ySy{`8o)8cJKC7oT(;Te)P1wf-$4+k}6c z!;!MGGK(Ya(~QixU3Y8icISvmdbwBhk}TIQT(|&x=&ymcI8%OQl@%kC+?43Q6;>VY zzok6%6uNCXoZ#sqQ*=z!ok!LD`)h-;F&D|| z^#5IRxcT2}eod>Cs0&Rp1o|LivV9x|GCdU-rHPv9e|=U=N?y7`)H)Kqx!DJq^Gr8r zqN3V#rX%m-{QHE(|2}~TK4Bos_V3IQJta0~lksu!EqX?;IWBl{- z=!!b!cn_Nplrni?Y5JHQRD*~o2%^XDH~(u87?}| zOVql|Y*KJIeE7Fg2)h4P=|uzk8xj3aVhP9ATqR1sSB$Qd7PNz9X9TO_{CZ0blh|AE z5k%4py~c7>m0P@Zve^aUOga7l==RrOv*_&X`#znGo&8umUS2Uf zGFpO$##@Y~6Kp_E!7+7dxEQNMRZhnrHeujUiM&t^rhDTcRA6If5E<~OZn(= z=-8mO%%kcyGn8qouglT#49Kod>dC370F#5Bpd6T<%X(>%4$0s*3}#)*|9ud_tI1~1 zN<*7#(JV@r5qX-=tsCFGxu=-~OQP7Qj7=#z7YY$LMx8%QP=HCq-!ipwv{b*$BfL_- zY92&0rj>eyLo{*k7W-01kq>wK2Af8_TTOW{68GqW*6Qb@MkpjkeAck-hhNL@d|7e_ z?d6^&sY5wXVe}JUW6t-ZOZ&(GIAIM^014mq@vjx@3&&JqRm{VqG|I|6sNX*%$|HS?yggzvugC)=vQH4hYc8w0a!d( zEyG&^6{)s%Qj?;r$j>j!E0_;n27zl}>(O#)bS`4EJ}$$#$sTkKd;C&AjwHftOCL95 z%)Ok%jLLN>Vkl(RUkj2OWp>R$y#-}&vci06h`&j`<*V_07x$W&Dh)sA>e zWkCu4`~x{D=_|`BW}o0P`_iGJz={VvR^%?TB$ppiup@vr-(pW857;WqmJGWihY_GF zg&(xdlfadiq0yjX}B6$HdSzUnIXGhz><(vz%uTvVITh zp|0yz@7=)7tiBo3<5obiR8@ML%lsG3S2^~fvAP^?&z`DS!5cRW%9WwPa20o#$fPv; zggpkH;5>a?y80jjd~P%DQ0MUMOU!Th*HowZU7;lFT@cLZH}nf#hOl71)DPA|X0+Yf zfsj(q4N2O(yrQ&mx}~B3&%zfZC)PP5i6Ue*$&n})S-ZDQP)oz13{HA=;Ft`(4yGLa z+qT3hfvFs|*-)z(cl%e^?@OeJ+Br4VD@PO2I=`L@AW3KecHD zQyk#m?{%zEMVGIGgdFDP{ztnPpw_O^-|8s0H!D0+$~&ATiqfqItr~cR$5^c}Du{q` z2g?{$JvpEn>WP#2}8 zQNywnk+2`Gtt9(fs^Vt!<1<~mrZjB{wJkJmuzM z#gQ+`M6Cn@qL}PN(pA}!(!8_PbSQGA(1#JuuP)Et0USt}%)|H3ZIf08tdZw7 z28nE;9rs=hJe*{~oIiK(wo}LxgWaWynp!Jp>JU&a6dy}C3@_uliNq@8EzhXfPb@=C zRu?HRM@0qrH;qB+CaMBn%m12= zL@ANn*i7#n^TaYD?O&I%yizM^?*uK_QSSj9l?bT0{tSJ+`f2?SFT{d zJ~IxD|H|NJ7*LLs6l!}^z}>sIH@@$ch4)y#0Jo;vH4orOF@O3zQOf^_>6%h@67D5@elpFjA~7^QY$Ray zNSl8PzEm#mU#}A0Hg9PLlhW{o-q6YG%vefFa0N0UdWAa;#_u*a@%sYliF)UIVzRyF zVaDHl{S)hDQD5j6fIS!R9b4nPhLw}bW{~1Gp6p>%ukc9dGTF1V$kGaj$dYg487^(! z!0|y#$Hc?{9li=80pL^h3jVh%8Fv4N4zn)}UmJ63^JduI4jB`ay7J+}?@{6l7k&>Z z)*OwDf|ehM7C8536B83bEyiz9{n~M~B2Hg0_ufAku88t)MOXBp!4>PjqT=OCCXeAI zXb-V)x=Wl>8R@5R|F1j_zUFFS@rs(@6M0(E`rq1ybc7F=?SZD*zJJ7F*(wUfTr2*X}0Q zkh{TP7|lROHyMd!bvXL(vhY~N97w#qkMa~jf8MbeG8tzA=878%^2}nCnUbk!X)fjC zrpu(yY^$rEW;L!$lo+^U1}pUbO+L zu4-_UL+9VRtSFuu>wc*CV+g@KDd&7@^S*k6J+tBjolH)Z*FcShiYsR)kB=R$orhM` zOfeo{XKNeiOEfh}B;R!>RW4If73%Dz{@ zBs7Za8Qw8^gAwnZRxZj*)ALXu;1ImOJrCljnva&5E}vszDM7_hJP!(SD*40PCua0jg#7Kk`6PB}Lilhoko9V;>ky2;IMSjdx<4c$qZGb8wY=ypXq zUPC&(CnVsMgfXGCQsLLYrM>RRNJHufoTa6|I1sk2K+$vf6HMo^SavHFHaakrYiVi0 zY#AOVk?AZHJ2P-}@f*D*YzND35l|K&m-QaFfWru+5D$4SkK+;V}146W{8qR#e!cESce|=`X zUVnoA^|@<~6E!0J>doY(R)$2}P%NKTej<;?%^{7$B@V>zDH{Ld`Q_5V=IfT0wO()| zcq4gOZ_;C+p0PF2GoL)e>89k$H0p@EN=l8@=MSN(hSQ%SFo99J4f+Ss>6+SFZK)w3=G~QRe+6ci(djcDlMjK$ zLM_e;^Pa(E%v0At-nW}+>r6>Z{MhJf%5cvn>INxpj2!iCb)rX)-dLS;moTG*knbzB zs&eF4n(4)$&ADDE97#V}-`gJCX5ce+@J$2W6G-FWh|Ece+L6|-hTag0lo%8cy}j9L`OjxjpPnypFS_ zuKm@HtefyGfn9m0Lr!jS;AQZ?4PM-nFOpu39nKzIWK6=jSzf-BQ`G02BTzqOfk*|c zoe*H@2s1&-{H0$g0Ic)yzQK1;5*GwE(+{fzKw~omX5=1Vf(bV2Q;+(1kvzJUqeeWG zqTICYV5F-j4(GQCz$(Kn?n@zjCIWuq#(Y<9Mzboq{(h>T1}jb}WYUD0fby}N>pcO| zI;W-(Y*_$y7{aIH$v8qL8_iIFt$eb5$Tjx$#+7)$PawnoHrO-XH+Q&^9&qL;^rBWp zYR|{Njvxm@4g+yW7#XEu?w@KFFuvrDsb!G~{}63Jg!+?uLJqwI2`^Fc&AunSaw8;9 z@maQ679L%k0ImNH-eb%Kh}hV?8Gqx#xVpbt#*?WY(ZpZ#y;Ki?BI5>CU!7h=G`SZr z3+Tb#Dv66!G*;nLppC&92=`EsyaRPD1k2V#k`HJ$Ks46??(5vsLzY#C5eSeydP0-x z(R>OO0_L7Hxzm8<)x-K}O5N1tr~I1aHeF9H0l@+Rpt2tFXaimtH_#Iy!25)Rp57mm zu?!B@7!x6&$xP}Q-pykXyb;Q#gqawE$_-$88v$w%x{EqbH9)ghT+j+`;+vrD8g~M< zPYaxW0PHz{`z8SNWi~c%VQ}hqv{dNP+zOL4I}z~OA)*ob0Y((1HsyT~Y_kg7)f;Z} zVLg^nF)__x1{py}qt*8q^M2$AU>8v4m`K(-Ms$Q?9qLxi)Ds7}#Gh~do{AS58WL61 zc%K|VDa=^WSrIACSD~582WXKX>Dq+7u6&6C@If6MmZ7joU=;i*3uy`exB$}YGrt)L zrry6W^^KDlCk}9DELb4yW=geK(1Druub@PFw_docTZ^VZ!`6|HkUGzPvu2eyeHYH8x`}&kBVG=fDU+F zcx-7F!3sOtTY0U^M+b_QEKTl%WdZKqBL(f?9S*yzL9Lx8)N-uf;_edAi8<-Fc)8`r zU3!j&(*;Nvb8ykn<^k0UHX&8W$$|g^X^7MSlYw>M09{=n$oJsn0dZ3+7&Vyg59*caGjGp;w?jR24myR@d^|i; z)iwk{q^t^&65zOwT7n4x_=m#XfwTs_zOrtGye*Ih0v?$z<-HBeZ}1ETbaMwDa1`ER zz+R~tCb8#C(zo?HurdO{ml1(U&H$fWcuRo3;|F6HFh!{vcLGnQnL0;38JUnks)Sy! zCp!5FwsY75=Iud10{4Vc=*^o=j64oZTfoE$=D27uC_J?~h=C|MN_LuOZzji36Yvd z)6IgmghzXhQ(R=Ec)t$apk3o8@Q~cz2^|w;!B$eD7C=D7JEBr^r1`}`IT$lQ24>g5 z%g)I3XK(=K7Zm753(>Kob;xbN3YZ0b3eNLx;O2Scg0b*~$gAB?!k;0<08B;h2jc`l zy$v@T6X6Yil8E|=i9`X%4oGjJEFAg+>F}}Cf}IX^-?o3lVFEc!53CLZhtoQ^Oy2(W#w?HOpg0Db#Y=$|I;HVyB~N>Q!LO!)(#o13rPVhF zcR#nO)a{Ht_vdH3Zsh1(Rqy8TeK_p|1rbb2u8p(qohuXvLe@5Om4Q9);>115NV!!2 z`2RI9xAS@HtBTkB1=Y-GIaWulN$7`PgGnk%X>!nkR zKpF&Cp~IW(Qf5e6>{GAPv*dS2{P<#NU-B2J_U0ewJa$E%_OtaWj%VSW{oW0q~;s6RLK&v7A4jlP;CdLWSeAbQR&| z=7w>}%C`^fjiq75{tXO4yn$v_U9<0>5$Y8Id7+Cx?i7+!b-Q-AZsDZ!?s70BMjt7m$r0^P>1jnkBRysfmA+LEN zYXAqTiXe|Cq_^4DG%oX@;B@a{%=X_mP)mSRx6=*GGV+pw$=x3yc%1b0f=;mA+Y16u z=me*{AR7rnKvAubuwa?lgn2t)^AWwN4C6T<9kDKNxJ&eqcS&Tbt3CuboL|}QUf>%5 z#|Z*X8|}5{>Edp=hYP^NIdH**T?D{R19%cFW1Z=%f`X($lWT1xt`eM>3SMkgC8grl?P?wXPiw|`l%WU1&f$!nT%-I@ z9q0sl;m{IZhc7vIpb$)1Y_IC70~`CZPZNueqNKqo3~a9u;2fqH>r?R~-LaxrWeygX zyMaRCP_d;O_^Sc-;5ylsmtu=2_6vp^$fLH1^qxf1AeGAIMbx@)m6QfbGuYBfm zoA2#pUT$CW@NG2(Xv!_T*s>}FQUn0CDxnS7%;6BepPE_TH}DPbaZ$2*<_|2Kq;9#Z zw@mG4+D@LR^sD+6*lk_l?yL6j&m3JrHX1K8IH(zAM$;DqE(K{7z!Xx?zC}+)CJdzo zz?!@gSD%r6s9w#E(B?I)mq5l)$dv-xSduLbH$Ak2NhvY92cNP6AXNyMYppZ_%W*K< z#Nv$3<&6G)JpghXQ2^?fTyb^7mHTKf^ul2|6`nQ#az7#4=V*gJt9Y!mFTYOkH1uH) zAL68IM_zJx$({YV5|riT-{2(#7L}gltpg~(zzU$%bxy|1>ktaa9&Ed_UhpSY$kQAa zyMFxuHfkIJ2qJ+LAw8~NP{2?oRKtD*4j#v5yX_BX>FIgTp0a&d*m_^hV|oX@^4o>u z%q0!H0{CsiiawePz;6pp-3AFWCO{mcp#r}m7==n?4y3CSgJ`6O)nj@1+8axF3A=|( zO3LK8-tp@h8T#7+XTj}j5unP0`O&}Ufv=5WE-=pIR?7AqJTvT8vN~VaboN(Q~$) zbcmx6U^X=PdU)-d{PVAT*hDY0RVbo)QqQ$JD7Mf0=O3NpXmaoHKk)Y56GCqU`ncKh zKf%dR6`%&(qJHMx!HjRjrRO1xStQZr12dy`ZWYDyrLygdaZUbB-~2Ph$x9d?1U`D@ zTor;|xDUvDi~yguVdmMUg{;DpL1&G_Eyd@3;qUL_6{(C?);e50*xaSVKjWQJ! zWu?NJ{2_e;nC9?$#t>)^j@pxAl}?UE?ag46Hho>!*qE?g z`r1QtAmkU0t{Af|TmjYSvWqTL>q zqulW|fm)H5fF1kUZEL^MP#bp8ot+&xR<^-vA(mFOBxTgZ&rm5*d+0+fT50WjYx5B+ zQfc-%y!pVj3IKBs>>h8J=<^_|SkO7_^Z=V?dx$>n#=xKe2D`nA|=Hn1tdRTvuQz+4Bs)*(IHpMyG1 z;V%hEckPq$9vl9MAx}#CpPpqU7lFe8b-?J=AJiFk@n`mZdyX0zX=~Z0;BNg%yU`vz z_u@rUrsT5>0z@KiCXoW`uFp#;`+yZ*Yuk@jy)0s4MTxQ%BT;<}5K!IYdoM;eLgh5? zgLR3|={-in1LQoe{9dQLRgGc!GK2MK&169@710Xs?}bWTW7oJUnjSqq;K0O_x#sci#0HJSq(=Uyz_1*`sE}}=7w;yD6V61D^wGf{YGxOwmZd-y#6fZ;;p-MP^HSGv>U0 ziVa%J1)Sdi>h{;`d#c=o$fzbs_mqNu-xzbJ5>~^O_=HcRSr<~vDCD7PVa31H8F&_&DXFP7Z^ScT7bd>6e|h?3uim7tm!c5J^x-vb@5kO7s zu8e^s4;nqJ-Q^K(KE8^ABkOFW*c2w+ zU8`(bE`b90kIE)l%ul?_k73aB#VsF)FXeS9TiDV65G0$l2X~FN^AcPrj+-39bxR4z zxvF9tM2O0D3#ao1`RE^vXtdMLVjJW=SFro`cM7%aE!wd-8@c?>vZK6yKHc%nV~(>O zM$U^Zrgyde;C57^;`0>Uer2B5?+A@75|fwzA3hAUl5LBnO>Ri#l6g3}VZ(kY_RtUv zFld|kj92NQFgmd@q-S^PLx=iUUK89r zt~BK5fT7a$xn{R|87o`Efb232s7r2%sQg#tAYeBDZ;-Y_D}k}xEb;V$9uep>(D>`2 zp*6IOL0>3sD6GiRkMqKBC;r7-2MDUYeY|spar(=K=%wfzSUPVwa3WH zQox<_Z1Y^1XHw@s*0_m@O@$fg z1Y;{eE+8tHfwP27>0l-hb$Iq}Wv{{v#SqkE=?y?0|1mkl&9j_&fivmyQEllCfOpXb zqhLsS0CAhgz2uTt;xWb4Je-*eb@TOZ?cnAq{~liQ32-@zj;rQ@Kx8CzN_?q~&~RE> z%^G?DkQg@R2=oSs3pl7G03VJD=qQnE6FM=AmGHJ1Vs7JJ&ZyO{oz(VuODn9~DSZ(* z4{YZ610l9E&gc${gI%FIjWl{{ce^i5iYD+(SnOdZv2;$+?`=s#9p9C;56UozK`a_t0^Pk3GzEu;hej}vf-Y}qqHYDYq4Tql zP_N(S=WW7p3VRTQF<=G3U=3)}nwm+lcimdhaPu1^6X)wsz+ww_B_<*KB zPv_)p7T!^K3fJt8K+OL9LoFCigCiCP8{6GPuzZwW1L-qZ3*(2*{mgJVgG-0d79C_I zCh>d*eRhBe+ZCYR-JoUI zF}BY5-PZuhZiV*EIqrRU$I;;J4_i!~J`d;(p>zraw|Es56+i;SA=3l%8)rHTVHDiD zpTKpPg+)d)3C^ZfDB>F$4go&?126_A1Q*rVcbjRtI3N##`Fvb_ ze1*@0`bl1PZfeLrfU^L61!08pG&`FS8Y3zyy|oED1h^GKJDcL*@^#4_K;r|nFd*t< zh5N84#VqlJsb@;)_!K0v0okyxI&B~~MQ0Qfw+lB*;z~RifBYN70L%%qpng=e2~%jt z8?UW-kkh{|ETp2Od<)AG_J80Rok?PnH~=PwQ4*{OD|7Z+ZZJ3?1IM)o;3zX;S=HUs zgV{W$$j1_Z@@U%PRW+d(3`dp&99eg7j+(}-PgnU%?t{ZwJzX_n4NYUCelsP9#(YIH zZF97oHnC|XW=t6}9Zwq)x{O?T@|taTBcWSPAc%Dha3b&=hJXC|H33mlHC}Xi9*irs z(+%>u;0EJNao?vn7D4F>n!a_-Bo4NfCVpac8=DcY{a49~vFh@i_^!W=f1Pl#8B>0y~Qb7nrmrGM#c`jO!tE(5E%bXmt@|kwCCTteY$f-fL#os~w zss}kaZk3hwYjQ?@4h}keB01xiz^pEzD;;TqX9o2{v}hS_-XLedk=n38y60JG9WcC< z5?NSTOEbM**+ZY}TrbJP^9iN(k_$*y(fP38>qOuRY6tTP7<>uM{P6^nGoXp?irAWu zIr3e9cC>51%5_;&zEGpfcBiN9*8pBs-iNT)Y-}8?%y*VY`g9B(gkBt~5hF@Rmd)CK z3GJ`t~o%CH*6T*$9Ugv(36k%3X)cE}bk~mh!Ig)*=c$9nolI-(20>zEV-PSGPKBLn&hnM~FPS-) zJp`7=yUS#x6ybqqWDf>zC(b_VaKbk~`X!AdI=9|jS+$}#yq%QNX)FA?Fv7+5Kh^NTh>f?uC%1P^uKbnu@4_fHjo z(%{*C6SC{M#{62n+iZ}FgLx~1&&{?&>_2~i$@Q*+QImj4>CL!bY18=HQ?^zf?!>Sr z)$+0Yn13wlhou42XD1KJj3W9o1TAFLJOydvz@#Tyo7=rY<~01t^5*OGnlRj#(!>ag zRNiWR9%zrWJRhkdBr+@b9v=&#^Pl`jbW(yD^)OSJF4T&=M!>*8xI5Qdiga3cX5G|p z8or*uMp_J=zrqujtOyBpW$xOCF|ahQGBaakN`jaJ(H@BQ9dCwN4#)@{^uoa_AX%lX z>v@TEt7+v?wXJOp5OUtJh$UE~lsn6Kv%k zLrs-;|Hwndn9B+Ra9>mN@FyL@wb??X(+MJeq0Hbhz-FCEkaK{Y`md5fm-64ls}og1 z=d=5zEC6;+X718phEiLu1Z?N095AB>TtSBsRW`c9%EF?3H`uY@EY0yYa8~+%a!($> zP6viPk@DS%`zpRtf z!nb%%?aUiE8U5EkbiEM1vsyH|knRXitf$ve7qtwmoP(L(tpyUt0+*S10xE?0kGs1o zdI8rf!4xz&5KR|Boc9`6mwSF5+yX2Gc5sgGh9 z99;C2bU%+iq5_(HU-pYTSw=`=(e{>NP|l&I^RE| zzERz2{SoP>RY#IwWE>0lA+hxA)D1Z~^LB*PE{V@RVGCdy*FI~ZcNm=}pL9iNYKMI* zCuK~Sbm~%J{L65ae(Bef3--^eE zf(0{tuAlgN0}eh8=wrT>BV0#Kj@G9gecMFGP=RQ`XJ%c+q2j_!%0YToJ7W2k%t7!U zk4+S#wWXcf8*MhK7ppKKK$!9)s%~HT+26Ihlq^KVQlc4f%Smj8jkq@+uiix_Bq{Cp z^7N#*`E7l3v5BXv9#T81f23#p{GXe5{tp*`%%$xD!f`aEFu&Pz;Z+1o+ZO%N+f;1q z^V6Qs()2BkGhF5~J--xOV(=%2DWyrX-=vv$z7%$rB$Z0ap#-DFhs#r*jHZF~_V!SS z;p3d`7GCU0Nw!$lcAEN4;Bl&3E~9l>%tb*=_r2pN23(Wlw{#sZ1_#4kqx+o`S&?td?uzvo7aUmZ4pm=(^<`6kG9RnICF>BjS z=iakZP(S4<0;xq)v%2r9fnJ5PRE# z*&Q>&Z`jiX%E!wqe%lv3)?{=k72;$R6v89#^W4%kjPB2IMaCZF=&Ls*8>j@&nQ--puUfNTQ?&w2`4tlO`^_J()uoACaC772gf7IYk8;e3 z`qmYIa<~G=IgkbqKF@#PAf<`=m91E5%E{IF_Tk}?qnDJ|w$hZ}p-=fcJ3o4Ed2MFQMB0yoB=KE>Vk#dePdB`LnVwIx z6Sfw%`mZ<4J-G-Z0A@4b0PqJKNbasiwMaQlM_>Pbd*vI_-{A}=BRiqW>RD!7FC)B+ z?JGT>rLA^sy;`A|s-Fru-r%2EIo=QqXxY$XU3h0P%=vroaNh`e1taHa_Vo7`QQSw)K z&@<;!Dr^275za^vvk;W#{gs=gPfS9`U1l7mmf>f=ghOoJ8FtZAzlAzH9P!4L*|wi+ zCMz+dF#8>MB?$Lgj<{?Gcu-2J*VZ3d^wY*NChV<{(ijA+^u(k|Ga^`6Hm5xoci5zM z2q}5m3HX`V@ey3SZ{E5W^P@ARXzcA5>(yUIo@4ADtAZz$bZxA$MqOjM`se3fn8)WO z%$mJ&o)D2b6aQ8o+7}MHTP)3@XTYkU*;%vw{aJ*?$qd@!rz(#@$gVq&ab!Nm!M6K-J#1>;mb<|xyh*@qPb9qo^2O}9kR zR^_H~o35+VF}M6by0reWow+~OP$h$VvRcz7mYwpXm+|1C-?ox!dRUbejQ*1eh@Xr* zVT}AiV2XWz7K(4^ZZQPo`-SRy_t@)H|LV0O)5ViV2ibFHrM7UNT*8@E2@W@?;X0X3 zEwY(F&1{|>r%-UmK4-TWxYDviJ3j_8!7_a7>Ok;I zb+h->R1tb+vwVb=C3^H(E_182WI3WU_5 zdH9K*e(n3~2=D&%?e7%rFNCSZNvf*At@on_G+=%^cLI0R!c+F&(X~mZ@L)ax_N({L zFzfk6%+3xlnkc}-jg0+}<*@yi*P%cgngLOPYLh}!D8zM^8H}`8-?@CvRjbEh0*2zi zIz53Kw_+e(NAlkty9H21+}i%6+fM)O9&)dNmq%t}WZ_v0y;~nR0R2n275ytqxMeTJ z;xmJpgF*AADf_)@lv4Aht=9otYJH>#3NKu-i|kFKBKP{={=Vm>yZ(o_ajuG*yM;?cIi5nkmvHP1TnrqJC%AtXPAG zkE7+dVk1F$yeMb%7@z6auT*I_#OIq4*?|h`L<(^ww)}$i)srT3!5*s!L@7o6cf)?Z zU{;TnqQ?)WJd8vsC^dxniKFKy^^aBdcZURCgaqtz$z6P@?`FCLY!>+5h4Bjd{+WJ| zUlu3D)g%+JB{b&qL;L@UB{Aiv|&hX=1F1erXjX2d#V%;w63c?zhlJFD%WZN)JA$HWePeiMIigSq;x0RvV z*kaYepYV!)zd{TiJDC(fYIlYoZko?9N}p=`&l*LI8f9f16PI-b!_+-TBYS!0oAN^O zisfwkHfcw&+_y@=Y5he>`DucF{8-)qlb{iva4M;qaVfAm02?`GZtgx*RZ$Vf*8+M0 zC)PF>rV>kFAjVMgmi%Ywph*Ujz#{vG6~2b)v6^#Ff0y&sgC|sds@RMXwfDm-88$-_f;OClFJ@!f_4{&$XFZDRrjGanwQV=TGe0 z8DbPEiaP|viuKOoehr=P?icz6e#$nxwdQmcAIEw;_4zlm5$4Y!X|)~FQs2z3XA&*E z(?P}PPZJdtI5&I3IRX7zJbhPBjH|@lp03$l=ZcLxj_K3~7x6RBRm@Vomlh01+efDo z-u^W(vhlSrfeQ@wVtOzp58gXJZES6iu7j_%KS;QNu&KAmT=Zfbz*7f@l8X5Qn*SC! zJ1lUucP@bhsRT4MZER5Qz7Y1>(}7pp-&B^|(3lTH@ajuF!O!>g1PMTzzLnhA36z=5 z8x;agx|ND@xT@@F{o;XbZ+RNNCe?*R%9XY%*$ovu!ZkKtJ+?Yn?;Czw^jpaDUS?57 z?x8Jig}nK+F`sHyZ2k8V*NDjTa`K=%a1;Fak!$M5=Vv`{R!STuOk*4(5PE&+-=eJ! zCGnpDx&aau!4(2OKvtx|<|O~jfyAU5rgKmq{HJ~-6*W+JFzdp#(!b^bh7-uXGP%7@ z5XP}7fk;p9K&9IJBDf7J%KWca426fKLpGpX zt9BnBZ(4OS5vZ*G`YKeCxnl(84DARjC#OG`xaeu>C#NIJ2h0W19I|^x7CL?pww0ZX zqTUIAHvhr|k1~tFW6f|uu00jheS1D&3{okikXMx3pp#;g(2LV($)BS0NL2MH~GkG!W#E+cF7-#D8*q`>*o>icH~g>L0X$0hWxjsx`! zq#68q*DYig-N(?sQzzcCDZyO7OT|Ko$u7^DEhW&S*8^ku{HfxzP2^$)7= z>m(5K!YYYuyz1>>5%OjAb(Mu-W)s!>+$wJ=242%UZo$xpt^8rukAe|oVT_?Q-u!F9 zFhi3Z5PV_=p8lB%@LAvsA%k~2#Rk4WjsK}Ai1I2%iW%OY-&?&K#}(idNk9w(aS;ch z2K~Gf8TV#bKzmQm2lXvpR#TpKHW9hD#$v89tNh#Ho^_)XLRS)-a)WzBS{%=fvlcE` zTzhC#YBR|D)aNvctc`rkF08+x%w#2CS95sn2F&tL{NpHpr5n~t7%Mwi8|c<+j5bvc z6^;CPP5{)I6UJe(E{QS!S?c_1(^`N_rpSK|vMg?#fg0sqR`;HIHzhtMB%o zo$tM)rG-4ZYo%-zW#X$G3pHVk8tCP63VGKKKUx{-+FEdx*m}>pS#wSrc@a~g z^BP$mdmM}uCza`lG9A}l7*u7S_5R-d{Zo~@_w}%&F9r^e%ki3sw?s?AcM*paX%H{+UplYAAQj`YEkE9tW!*}kcBWLiOiOG(N_}Qnd+D9dFh!} zmXYZMLWD@$?{OnfI?C*soGk zClMbNE*5_1(`Ock2Pl(;-s7D=Z`4hDm0=3#qg}^f8sUg?zUlOBWF?KO(0i`TYN#cV z1{{mPgbOx{_aEnlgd1b1rYAq{$c|elE-d69dNsb?f=9N9t z+r(a4!!zRzvb0*jA|!$F?1k|AzMWnM@SowUu~7jPM5l}~5cALf>~cQcdQa;4!yvgq z+=iJo&-lWaqjJv0UblR;pA@*SY6nKm1vaJ3bVsjHfn1PTN!ii}1!&^c20t1;Dnk78Ay3mC zPWFwY&b%7KSAHNJjPiPkjfpF}>I}kw0XJ#6=e#mTaI>F4Zzk8?;E^+*me$iOzSpm3 ze<&Tes|ThJ9eRPbk`LPOq*m0p)3*g=>d}dzZ0q_*3Y$4Y1_4kNSDZr+Qx;G+q2pt^ zDpA-lZ9VKWYpM6ayKN4yIuGE553={FLU|T`Ysx9+eX1OZRj`t0jPIL4xtRIwC0k}_ zTo9LLROet}nQ~MfUPd~7k1gZbNXEypw0xQf<6+l*o^+9|b+@zQnMbQU^mj-;dW&dZ zJ$yLniay;eNtp?F{A&g0-gdkrRz}{B@k3CioJ%3qoNyT z(9PLd;ZtL1rQ|m~0is_?Pc`1nd34IIgqnFxtwZK$u`I>SPRn7x${o4npgk|d(1(>> zU%N8e${~-SGUfK84|wU)BGm~lA};$h%jY!edRC;0d8=b3M84&fTiLB6o$Sh(uAl%7KwXl4@*cRLd5G)TEe zp3a=QHab$-_G9}#Ex(slVbPToo*=#d-ThGD?2iUx1i%=RF6V+S(Y>*WNuY;G-ouHU zheoRti>3w>^7ehQ#Mj&|e-C8LBKxdk>Q50zm#4??DbaAbZu#fetFes4NUvFgs;&rb zf*2}aM&veI+u=O^+CfAxPV&c-W$yZbeonr3Gw6y1pumC)yg!h^LFi_>SIAzH3ehJR zVus@)42-X#>F$u=-XW1I&v7Phxsb~~)N+Ht8&`qVn#SSe+Xkxl8S97xr6z})_#01t zz@ww0sIL<}{s~DxPczp>t9T6o#sowxh98>L!xb}_Dqv%~zirt6QudX zx3$TgoBMk;(I*_ZSkm3I(vX?w`jbn+WyAQVz<^nk?soVU@MxC9T1Cj{WGbtc0 zWd}B0&%6-|kV9d%Od<(g%$IvM{xfaS@0+bJynT3ISNZa{N+|fjswSm8_Md8u8d!9833~7#U~8P~0{Q}isSmo3&~u=FCzdW7E%vX1 z!6p>)AOTTL7m3oIl?c~3^#lbP^f6_WzUM=j;AdpC%w>T)!{$I;Ny7*QqAz~xru7%{ zAq~4;TnruRP=Up&{>cKPe%(cT`%RANJ6%tpgRX3gzN)gh)$4U+zq=g1>3Ygr5$?Vg zeYA&VzGE8}&dhl?>OjbWyvg%iP!f5qeds{uU@;l3XlD#R3)jWIltx*R-L;rkveO~9 zG}MIpZwXh)&NQUKVe>q(qB#>_wpSu5QBOW={BUl*1V?x%mbW+cO!Wz zs%eM_oN_~p#g8lTylc54zee=7J_#dSXAw>AcicsM4iqNe+Kni`sFHML`W`sb8kncx zHHU}br!*{tItM^ta=4aO zgAIbtDEb>W^qaI_#Fohpm?6?`8j-GhuFH*9oWCe@j|498czhMx{xY*=J+{p`0GLh-SJI6IecgI1l&Oifx9NUsZn zoVcHs?W)C;IN&k>wFjPHmDZi$goZADZ{3=D4EU2us%0lGjP`&?Sl^IBMjQS4*8N>K zO*ywJmcfhI0VkvfGsIaD)A6UrZktS>Y*o{O)+PsYu(ARfSQQZMJuzdoA7&y+T74`w zOk)|!Rl5g1D|(5W`donKQ%qMWSyyQ4$5<6Cp*)2SCk1Cdk99di+UV#n4T7e=mzq7B zWxu8PYo>R(0gMje)bMP3=YDIhjs>$SGDSzm^+=giktmJ*>Cbwx%Y0GYJf+fh?VPpHrmY*$u-w?4T1rXcVtg)lWZ}3Fum@ z=hpl9Q_@kr>&aLn8Gmb-Lfq7YMs$GFO#C^hjZ&$a4g|VX|rIQOZe`~;HakPUYn;=nv=t1T5sKaT{ur+Zq?w51a33ET1Kw- zV;g@tT-}Q9#lE|0;a$P(YJjm14@=y+1H%vajvwKvrE`*6QSr$&&e(YV4%%n~$WJBJ zys9CT4WazWn7=>CZKeG1bKpJW*iWj)O<_4qA`hb}d6jh-RGN#vQVT2m`Ta`7Nvx8B zC-fpBmLYC1&uxn({z}J4sofVue>$_!SI-+EK{Udf-;Bgv5r6(fM&5ScMW#ymOMLes z_%-m#&C0950Jgz}cg(_MFei0V&`vtejc3@qrDFWj=%r3n>~7W9=U;{TMoJ@yZ?&Ie zWa1~zjeK%xI%8O%_tr*wfog`>Ly$ZeFwpD(SrLd#FO!mnTc(axhSima0>MrHK8G)_ z8fII@y)%5-u1uYzE|`?n^+;rUXU?_V606r@&!N_7iKsz|gko`C{)$x5`!9mJlS&v2xWPBAAdYE#W(t2il1+&?XBdQp|pqzFWkBNKJKL^NfM0F!G(+wKj#y2 zPsw~1DHzyS5F7IqJEE)O(IHo$qFh5wYjR8vkXx0f9mG-%F9-@oMiocCNY|>&I!GVZ zMhMUj))s%~sMqHKFqyv?n}eM_@uJ^-ttV$jy!-zyog_J!%{-*&l88gtGuExWQDq15 z8X7oJ_Z(UdEs;hF!|b@F$um_rAu&jKCS3 zn|su;6gC(+tX*ajqnTGSDU@r=8V##}hNeH@PmkA+NM;f8mKn|23{hDbYzd@H1ktSm z#>+a9LrYV2j;Z5L>jRIvr>WJ_G2-4^u`pHt90)s$Cv^n8d)$?Xpz+wktgr2sh}#fE z#D)o$Y(bmqO@lL5^E3kqiMh}V5&jb(s6zf!Ty(U9&O2>K{#XUDHbf*y$M?icKW|H5 z?8PB&U&OwbT#3U=*#}KLyGE9=7G9R{19i}omVCM_BYhWr_3!)H@Q3L_8k2(lv%^a%JFOA|J1} z>eb3CzN{0^rW7#URy`*UcpCntkdbHeZ$ zWm1?rFKlZ*#BTjyVbY~4;HJ->j2!O3%Z~juS8)Wek)#fpO03U>SOo;W6ke19MhP@^ z0;=eu?y!pgeJc0=9-ZJk6&BWRr+t=WLry+h(fr0sFgkkHm5B-R-^m*S{9F6&Cu*-L zwTou{KdRn4p6mYmAAi|tnUR#TvqSc#kS)rNke!vi_uea;kWI2TA^Uq? zUGMAq{C?f8KdMVSUytYGaUSQK`~BSS8y0Elw~MzIyU7H(xxA1941dGG)m*L(RbXNcrOY`EpMU+W*+F|ibO z`R*_8o*zLvliir#+1dH?=TBBaB^CP>;wW|$&zfzS%Zm}q#~AnAD6_HxHe3%aFbID+ zOCJV@%GNklkIQ}+fw7Nr%g!zsm>2Y*K~VBS6A|df)f_kFll3LfPG|qD+=R>FDl;qv zI}9PNOsKd$hT)vy)m$E&Yh%iD0<&-T!HNKZ3A4)JV5d4hjw4EU9{u;K{CUd{c_{U- z1!LpcKe-TfB{v_Fh`K`IFXl!};$81%d%|08^^V1RS)W_)$Vy7q)yQV=g6dD`{hfu- z&d$b>9Jrj7Nl%VC!suB9g2Sgntm|Qr|KwnI&c`c#5#@B_F4ITl>p_$%828q#edDPB zZ$L=Nj1uDf8DdIHV=3M)dPydvY&M&?JtW<=ruxq8iqZm1qD|R7a=h_C*%z$+;!-)f z?EtS*S!)PtYy@24l_JnfA>rbB<}##7xZ3bHE)oj}@Ln*#r$pW=+dVH4H%~vh5m9IVC<4^Y z*SADwAzdc8n&6sVq5AJTAL-t&!Sq;Hu@*-LAr4NAsH;AW6fzV?k`SLk z>qJVgk`LZykys3#LW?UgkzYJAe+%u&`K3uON&?wWGozN$# zlSisZ`9aL}_GTW7srP_bt`2j@fe|R=HngV|X73>V_ah*d#zsX)xgn79h(b!*mTDPx z*!U~5^P>;UI|Ux}j8Yo9T0vtvEzC)cRvmZyS@z$m6qnqwqAG`i3BKdnNM}FS`cS@> z5;+`g`=vih6jB;H{)9OGg!4b{7#e;lEJT&{^JklPd->9|nNNuxcv!E0U-Qo7g>iIX zyqkcuzvB&wPq_cS8%OkT&kxnUbacF=>24VPQtD6M%qXYp=_~)s1!$k-890>W-7Z(2s`OWC7Q=A_oc%}h0-K=OGntU@Qtdt>Ri zt@HDXY7(p1i9fVemV#mMtCW|2a;&+y`gm27JdokEy-mqP4YFTC_+Wp)n__Jw z2Wf7J93ic^=S%eOMUW}{9n;8{j@N6sVA$jN^CGiIp6+64ydcVVS8jfKtf3KLH&K$2 zEKBs#ghgtwru4>u2MW_ei3bnv)pYRy&;qn+PsH|dRU$U($wAAQu4ELXAxKKl$`D3T z(KkXYq7)rbttxMS+QU09VirJPV`vCLCwwY|qYl{9{rWQ!4$VJ~+H34{@(&w9nsCtU zXuAX1k9ZkMFG7{0ikwGX#J%;T-Lj&JsI$LgqU=W*9;~93tO<*O0kA+;W~LWN?xhZz z@Wgy-$WsN#xi`*s9<{k;Fd2=;IZeoDxa8Hx*lcuXy@;}N|1Qu|fB#{yLlA1P8_U2g zzJQfMz~nj@?|xLCqs0F2wd2BT7m|b>d?`<&<9>aASy$l3>p=!6U{v$G$AiX8n%eKz zy!Kg8n4j>~A|JLQK>O$Q^rBh}kU>6? zrlpCKMiRH*5U4gV0HN=3i}sa4sI)lMvId#GeIhXmh#F_#ptblNzmHU+%togoU)rW; znse4UCUjd?tlh&!;8VR%V|;D+!JpRYQ6o@I&#|U&0ltAYg#(V!#tkl)d?*pQ}scxgf}Nt9>NrnvinV zr5D=nN-E^?9ia<31>E|mw-hS%OU|3k+M0X_N=(tab=EhQGpm;$>&38 zs5P)jL8)Pc571^9cLwD%&}vrVqm<$PZ)tq~E{(!1X`JZ>T3Qc$1iWa=$*Jt0e|E3& zpy#N)+|tsj!$lm;beF%EmcA98!x{vM3q^|=2cLez6U}m|2KPUk0+m`8oZOe-MjaU8 z?fnfW?{pzly@C({6c=UXboL{BblX*u(cGqs^Ui)*NGmEj-d_RG4mCaN4jXW~?%eq^ zkF~8%faE5Lgu+<2+iCUKllb?-Ta8=OFw+Tj+)Ul{YTk^8?x07~TarME8Y1U;43Ps; z5#t^Aa()oHSTd;W`|x3l|l_xVL(XIHaZ48K_oT z)xV-G7sF#d{C1E&G};arL(+N@`^m6j@0#B}t(;7nIlTumP z+-y7U>-$Cbey&EUug{!dOB9DzfzAxQRYyph$Hl_M-CLLo?emM#(_R!-*Z7EAqjkL( z2I_ok+#W0sMBMFq)%xQrjN z*AV$xjW8nq;mZ@MY^wi0InUouo~ZV9STj$aztCw@j~CT*I19TtIT^w2Qp$jGwAM!W zFkms(-h5l+sfW`TM35@&tND^0>t%^A5$27Wge$*}7xsAcK$QoS(Y4 zZCWniDTG&Pt;?>pGOCYqwu?pcmUw*pM*~a@`N81YQ;YsYvb&N|)0z#-3Wj`V`D&dn zBl{BASW)b&sw9x=+p;p4iix?O6LxnYkAaUPz(r3_4=4egVJ4Nog8>?{3d%TLU0vJt zr}<;@|9vXLf8OVFhI_x3Se57ixp*9&))ODexFd3M@O)JF>K!Jqfv{}PhkPx|!1%L5 z;rN&5hV$;{VF?7D?|d3B&f$_0b*X&)nFnK`qs6Rkb@h^}ccJ*9F5@@phG+@^bSRG~~o`N3YGXXMEy<@P=R9MroG=EmqA!oB)k+0f8tbrmHhCVqKi&(3smG-0II;_!$CE;vm^b#?V;&)~#H zD7w^gz_ijdi1Xbq>t}K>hB*OP2^}`M|J8$;&;LfS&*)m}nP;nHCyk5V-580oKA0xo zv8$@GRapX4w!aV<-)(L=4<5;B{)(^0B9(jTu;OVo4c%X=o3>|yz$4$BNOnECao@4dB~z{7dtTm)g+bXOLma6q4QJk1w7)BU4E?l#{Pkk{ za;ljVk$R=6i94pzR!rg!DcNnj^mnx^H%a|^{XUVtR;}Ri7K~1Fwud&6V!}XCDo<}b z9d^pr6>@TZ&|UAZYJ?e+OBm2ec=EtH+xOS6Ur-;|jlJUgx4ucqhk{-y`YG9q0_RGz zH0+G4&d#2)+^UI3eHd7cCQ|Cx-q2(y-uAkA29AipA-)j z0^Be#yPRVcxP}Y51YiFU{ZrbYKSaH?hF+Z8^NRpSX*6@+Mk5H@=YbN=(WcKZ4xF6Z zlXcCMZ)h;)e%xWabz&}4VhlWJ1Ti{ov7M1#bfc}wIEfGDd`6P5>-OT=hr7ah2vjsB zt)S_eveFDjv(_No-jsu`sj7fm!ml}X+(uk;`k!{MxxJD*legP+y2(KxC)e3&BP(zS zc*x%uqK524>BA%qi~${h&e#;pQH@wW()QYGxbn{vvtkyNn%a`dMj1tSGj$jH8AVC$ zH-kbk0PxZu3sz*$$!=?f8XT}_WHgJmtnv^IX4_~)G0929gfyYlGTM53c0RloY$IfQ zR%s{L{|I4S#6tTpUn7;ue&e%UZqw7D^n3HM+*Fh<+J)1_rr#fyjW5~(>Eq&{eAL93 z&c^#`tnfDxuIu<=jN_}m>^{rKJ2ql0564|#`u{2MY{Pv7kp4tn0VD{I&@jO}#9I^$ zS#}P=8Q*yJd1q583GvBIo^q}zRP$;WBbbmofTWoPR^oDG(n#5f@=XiwN8fqeRNnoox>`;fLe%S4qi`{li%zQ#w*1i7QYW;VKMyM{X!#b|xp3pgF zr^K?p=vbe?bVwt}cI z6``fJ>aznejVPlkP$kblvluY{@yNZ+Lo}Waq*Jd4$y~#K|KWi=uAN<2T%b&_V8n;e z_fNl`kdulhP((zgv+vZltp2K9iqgz8P81L@@$^OCn9`s(XUZ6Mxu`A6d{5Axe_CV{ ze_Lc~s8E00-nXhz99jd#7X97!ON%i#8I;c{5ENAGMAyDx*PI^SRx5QEy*L#3^t)xl z!pLBQ@8c(&RHIAZdy0dNk*+R#jL_o9dcHDB0G*>)r?=Yl+2u1e0{LZRiG)iILe7b= z9G~q4fqc0MFKc_ran-?kj@(GTu-PC3EqO*^)3)GTt@_{+aYAV{RTI(DjLvgSY3~vgg5d7MBew(Z# zWkTY5+j~X})kh~Dg{L_)ck1b6INvt`orp2TiKrlR_|tr~3uve7U78`$Kp{UyIsQ24 zAX_dnf7+>rldH#<{e($RN6kvt>1Cl`pJp=RfxBMCWK83$o%`J;G3=mX24lw-8g;fX zB6R^%z@>YlN~9o2v$-ea_&u$|MB;wIuxVyW)tGa$i+Zm=>qgA%s>9kK+1fA16M``F z)Ui7rg_D~uQx6iVtw%@i@Mt({y)A2wd)Cqw9=$-X2sa5fbwAf z`j?jXG?7*h35*}x58tJh1CO2LR%lb-8H*a7fpoY#qP7P(}?y^5!2Ij7&xM9yoLhSb?NN**PAbN>19=d ztOq4xUevwBQq9#HpUFSvb340pxQzwv$ic4!FJ5t96X|cO%`d%*<2}d8&49YxM=wC( z?97gp;5uCE9MBoKZZwWbFp!RSxY?2{`DTTlT(;SAcD#fMO)8T|0C61kBJIzcdty3k z11RKpL_3~EU&I^ALq15wQuGP&TY5~eZiuESS|-4%NCqtkD(;sPj-8OE4rW>w_4GR) z2#pGwzW@SUIOFQ~mTa92|9Zg2i{|~CzU&T10}Dm(Ah)Wwa%NpMSG@>8PT-yjfG10M z8+_@z;F*=Nu;T0iLr;(Z7)ncH?59m&@0Ro@)Nc0PGxw##witK;tO`-nm5EW)7wkUg zjRz*{yuYi%nM!C>CzEl_qc9$1O6y=vL+F`vjD0=3aa$?F$tT(Qs2AusiAfrwV+;`G zXx~5xQhp#4S{{;IHMUCMnVA_8Y!*GX%Bws$x&HearJ5bD{y^bmn9;aZ#lA?vbG6oi zTAM>B>5My!uIXQ%2d3tQWjF8FGRpnK$q)_CHf2NUF1FyM?6x+SQc{vma>3nSYFcdt zx_;D)ojIR5XRW$`tmj20S7bt3J3qpO`jDjDs;%>5ZId1@A^)i|^Ck@AH5%-yl}0k= z7Y?v=bH|NWV8UM7boBDVCcx+b?C@E?6RK*!@+7J6lWq`o;CE#p(35jC+Jd~-{_;W#5lW*)d#&?EWq7Z^X?SUA z)KRtAL7(u)SX_;EJTAR15^@*RSABH1ao~&VjsP(S5`Cg53yZjFRQS+iqfO1f5c4>7 z(|8Gpws>#N{)Lf&A!yno+J8I;Oq0pHoiO zj5-a}Ld!s-iqHkY^IFBw=J6iv1Q+#@bqAN-FaBd$M3!aE>(~BTT$x$j6{)ov$p-Pt zJk%Ye_F^m_8;m>iAa_?sAeIX1XgU99-mI=`#fBFGAqz0z3nCM_$E8GvKaW30d_{-_&l zs{OR`PM&PKRM{6D15@jt=~7|ObH(t!4rRSYw?62$4ulZ=V7BqFXF`H0R&x##r24!J zLX>xn=Yj?FDi-GbcboA{eh_zrfHR~rX?VnRi7c2s1ySLI+%5DqWm3`8kWu=`{{SIK zD3i*-)SndEfB&ec-{Ugy@?z}#RVa;7>z6QX&XBIHS@*#CmZH~OAv74>jaIy@-hwwp zs4kC*DES>|Yn|`XcudJA!-%XGR{o2ZW!`1NL?RGkxGgEE2y0I{XWS@^@4lf~%P4;E z;LqM8V2xiNdrw4bTM;oiwBiBWt`y|8%Js^E+)m0lSG(pA2JiK{ zVQvIw^FUD$4EzqV$Fem8`p>N4Is*jka)^TTXh{U6hSKRmYZ-*k5{cY$S4=R{SEG z^O|7TZ(XRSvYS>NK(3_8{;`#nm4ShQj*gCFjI!%))@8`GI|d+}Fk(RTmoI~^l=?-Z zQ7WOy>|GNg>YnA4Ak1#{D8!2e&*_z#WA)?6Vi9Hr8a(jolwey4U~e&dH;(_HvDiqx zJQo!Ubxtr|c)4mQCds;|=NC00uQ_Jwm8-Y(n$e*GZ1InrBZF6D`C^uy9ZIWEQKy?~ zFggUfO15`~hHZ~|f@Avb^js68iZX@QzX#c+LI(yuqhTd)T#+4XvH`*=;7g$hmcx><1@; z(&|0X2SC1TK6CP3jKid%-?L!yUOEL-%755G+MbMhDSdR^@}pc|QPCi6Q&7gnlkmk)#SJ;^ARQM2TwN*mn)bW1?R*yw!Lg@U!89^87T_^|g_Ci{BW= z+kp>_;tuOhXdOkRgNv;}H8v=wrt)ue``>pl?Xo~k5wb9lj0V7P)>R>gOmQaYV-mZN zP*Q?7%?3!ZCV)VVw)5eJ#xt+;4E*zo|JVs;bjT7PM+4e|g_>7LecW40`M3hSYV0j9Desh&StRTY_7cICCUwypar zaWzhD+YK-=ndJ8M*DKepd#xR_pH1)Gy9e*a1e7d5VAwj>7kAEqflno7GcJ)ZqXe}> zMg}H;kkmj$d>k<9=AjHuyCi`M&d}S(2dVmMI>g9DCI|!mw{HV=Y3Sd|*?Z?IvK=18 zuBPxJ*AX2v#azu|0zDe97+($qJp|ZM)KIi!FhgimKIxK|wLEEVAsZJEqxtx8>mVhw z>La`pUmA2wO!)rxYu~dpG&I(}!Hg5QJ#nfRexW4DDf$;beej1!t`Xh#`EVIqxrNs- zjuPPQrM!@wWoEANcl&Hu7#d<{Wr2K!ADh}IVyX+hB3N|^RL!!XVJYM~vtr}p$BVh0 zfT#!vMvPO2EP(ckcAe^o!oQtbI#%?*KjG>XuY7Ho;nL!~7K{jsQavZxSsjKxnDJ?% z7=dR5W8dN)8#Ygbo3Nt5d6!cg+R8;HL}V%2!qUgV!SB2@IpB1~U61JE zg`r7kRm^~z7$`;MS=pOW{e3G)7J>lc!)8R0~KYm3{F-F z%~;oF>vtyO&!CC$h0bl`CSdz!47VZx`!*f1lz!}sj9f^(|7SZA?zeKn$AmMkT{CBQ z+x*#c$4{QxoM~SfZb~Y-Z#rEPMc&|-f-S3BadL1_9QKzwMgM;ff)DGagr!GP$X@gE z0X}X1?romCky-VttCcQgs91h}u(7gU%9_XVe^t#x50rk{0zdbCTp-v|5rO5ah)8WU zLDsYXdktuEjZ$R#CF2{aZ2h(({ZOCYC<|OMtC+kO0#-Ig+vL>L)1YMm(vRkjGZRwl zqu7Kp`pFC^;1`HTF;J*)6xiDZNJbF;uO-zv@rbU35e`;d&tEp3s^&(UNDh(}@bMEt z;j*CNq8jXc;JkgOCacU}Z4l|Vr&60%{mb!qFr^HAFJ%nSUKssp0CN$Z%g6EYR6cYj zDwV^l=wMpVJ+rwon2N+gQb}x{5>J%1(0YiIHkIz3pWb=1L-2_){{D89+mpoHA5FHl z0eh3lEd=qguy&c;S3i3DNQS(F*uD)YxH=1&j=Q4HnLM;mjk74DFxV1IXAu zUvvw;-qM2mFZh`k7$U_aBy3(@KwlMhE(X{FY^3}4jlH-Og)I%bJ5Yu`T5I5pUT^e5 zgJTaKBjnO|SXeTlbO1@OfS@4sMq4dCy)P~E1FiUo6qc=tGVnUZ#K3Ueo|!h}aTc4v zKt~7Q0jdoHh-U?Lhe)NeQX#H?t{Q3}5v7=YG}4FyhctFMO|mXyl-(sc4I49}<^r>f9*s~)pP8sAWDqwu~WAj!`!jbHy z<&4%xw}pi(RJrrg$W7uH1wuqz;FJuCB5Ses%YAjw5(mdDi@zYy*Kja&GibxTJOcYz z1CqkAI19461KpbG3m}L7BWZg7_kd(#9N0>WfjCrmG?y!NF&H6(H#n%cj2aYSdq3S0 zPLs2=W@EkOu0%@HNTe$r|l6J&gClYL6(F>m50FsxsFyM}PzV|NyYZ}|UDfjS^ zq)Ro!sSa)Ym<2z`8$5e*{mOil+f9bjDtu(nsPyYHj0FulkntvZLM8}OcaQ-n$jci% ze{PN~(tmoDvFWL?jxX!`%Nnq$fMo#>IzY)>DQiKi3s|K^(d%GuSpn<~adG4U;JJI3 zNp1qJMID`)^@4^z+xVjvnYNFNrZCnASL5z7{U%7?#DShTsKea{iD;awx0Lt3!wW#Z z10)=4rUy$D#nak~`wXny_uuO2(IG$*k&^P;{#(#(ht4C`91z4oLt`IAc7UP?W|rM7 z%mM7Z0J9!v!L_*I`$#4YOV45yq&vj8A zqmPs-x3eAx$7EEY1TRxUN(rRy7+_RgSPbY2BbJ2lP)g(q7>PoT*}+w`l>c%8UirJj zAOccH2<|#QS&aJ2i&KAs8%viLFwj;nCLWc2iHVNxu<_##z#QVB>x2|J&kO45>N?)6 zm_^`U=V>ij1KwXW$BSD?iW^8EZO%3{oJiTiy$Qw&;8Y5Fcw0Y;Qb8moQeh%0E-o%U z{_U^MD+oAnn(O{)Ei=vO%SNZ=~Gxn(Q$sv+q{Jd9B{uD5qS%n{DRhmm-1dncX#(Gz{4VA zSWC+twD+39CmY+FCAPv+_;9b>h3(q?KCo`z%tF}mlE9>G| zpM7xy8vLO81o1>YG$LQUNFr+S%a2q+R0;jk)R%SD`$Cb7VAh9Ki z0C8WWtpJmyI>;iySHr9<$asx`PXNT+XxC2a*HgP7Xr&Dv1dHDyAs%I?VgAFcRC2FAv>JmDTvdx&am`07>3aUN@e*Qu8}9Vs^o58c=F`K_7}YS?Oz9?3=! zQXvRYO%aFNmu^2>#h89+gO)XHN!Se$1V(E4S{nOIddNg&RG&Z2ExVDOvrtX3L{oNT zr2!H*kPAZ{sIb=UK2W*1LOJ6O&qV#kK( zCkN*uQpCO>!oaCpe+Ck2V0rxIL*qY(#vRCLO2hMliUj6#0Sut!)jDcJ#VkkCVQb{U zk1bA&)Pv%Kg#|mx^vz*lQGW;WCb^yAhh*|@Xz8!+J`+Jw5 z*(!Ue=0!pKFF8Nj=oobIA1Wx6j$447A50%_?=5z-S=@!EyPrB50QWmh8)V)9B|rB< z(tIN$G3dCtZ9ts>1bA5Cj=itZssi*vTFGywOdr{Fz^~p&qh@7QgfaeXuG3VFO;uU;tMWom;>u<48@% z8KY73XeuH;{tRxCDNppCt+J&O&v7>2Z_8}V@ZQYmKmqBW1RJbfT7ka`j(|CnDK}pI zgI228=R@IHSg}-8R757~1mndP$)G<*8K-i^;N{EVp&==yo^Rh${@wl;a5*a37pb#1 zH8oLnoP(kqC`%GzWB+NmJiU*AvkhuyqM*?_Vip_}1h&DmVf2!35|3i`gS$K+9|SD% z9O-fcl|4`m!O$J@36LK8X5SA=4bY=Pc^??K4p)_m_M+p%@b5Wl1>p9Q37O{Nwq=R_ zx+*Nw-_m8h<&S(gBN|WRb$pgAFH9^T22I%$0Y7+uX)$pPE-J|C$UR{YN0gJZZ(YjE z%L`)T1^9$MukTH7WN_80eX|Q*U#Q&BXa(y@IQdM>%t>%DgVf{Yy6$E67JI-?2y8G! zKL#3Z(9nm+&CJXc-l}}r`u>WJ8CU;)b}pz~T)YPT4k!*`LJ>RJ4`d5eAUbNRO)_NCx2NF&9lU3%D@w3jGp|wzBfpyNols#+%a8?G=mNP)3 zkNS>y<9Z>-bp>}`&{fRLKZDx|X)D{CAbiw>MXRg^24oj-uVR8T#+zFxFla0n9{p%v zq+iPjq-rm)n%9TY1KSMr8?LCLg)>SHx+(?Ja_s3;>RN0vnyd#a4lWe_6LE7OFm8>SKRdBE<47ILPz&d`0;NzNIwrON1_{=_J^4bAqQf@&9P^I`P_6ZKqVSHCPd!qwzi zZj+wLNOq)V$eN?e!w9h(&&sZ<9V0~@8!pcaHc}{gO+b}X3jxOKr*N3+%8?FEVD0v_ zW&*bQu)YgkU^79EH(OSjra>e^`eAW*gH*p=!{vGBkKh5`kr3C-C zAp`aQy0&GddBcwA=+u-$$X@Ml_UxpwRb{eBs>jy?0s=~Tq@#=g9t(FcRr#uRjNs+{ ztY4<7A%EvzII{Gdbb8+Auam8n;7j%E@H_vO$HAhIhl&^(iGfmfc_j^kjj|M>vqSQKcYkIJN){p2Y z2(^h+2PQUxc7^Bzdm7j5dt>=5SWXK(&I7gUhgS{n2!QFigM-8M`_39sfaQ&cuwqL< zLM?prh5fOP~DHlk{WS{_R82?o&EF*JPH7tJ7(*p;2Kn1 zuGm1My59_76@0#nkwY=S8S&-9p@^Zwj(w#$43p7Mf%x*~cyAFB2{RQz^Of2Z#TbRJ z;k5L}{h(3#%e>Ia2woSsm7&;^l#FX7vzIZ|IF62XAwi%KihPT!>pC!HteK}1%A5OK zb-ND+iorTUS6LZQKphxr``TEol@Ix(JpZYYuZ2PVvNMNBKNcn?t~W6il|_*{Jc|nl zXLJt4YYQ$F*~czHx0DNp%MDlfm5U}f6;%1Jm0q%ba!y#1B&(V3!yQXy`<|7DoK5F< zq29-;na=N^{+@M8F8%Rb{fzeLm}tG;{Nq^GV(MWh&xWlM;41+4KhA`cn^-TU^!<=f zwHKig>MwI%ODGhyqO|Jw{mVR;m!kyx^Fr0Xbc=CqkW+WTf2!fbiS1o(TvCA>OVO=Y zU{%U-R&DILfc}8xsAF#2DYL>DvUEr+(_+~%KxwJg1`Lsm4GrP7x`WbvVu{Ojq&v`6 z5)xQgaw@8xQ4y%qpemn$>kCFWkmU|Y))nF3fO~X)L5Fo(lf@^S>QiyGH4Lm^?P-`B`&BvFpeLr1M+km#P;uB z7HQ@YQ1Y$}rFVnz6GR~dXk_4c14LbyD58vIBS=+Hf5PesW={KHsEe0h_w(iJumc5| z)S^+Yi`<|MWGCcyE(+Yc&{8& zp4TLs1B7cbu98)VW9y>)o~Dgw(c6tCS-*(Y0zXTZh)phhMl`qli5kS;94t{yqJ;SC zAVo2>9i5T`GsvR=sOo}DF|_YAvl_*2429DOf)XNhG(VX$gI8sh>F}+nlSnc#_)B8_XQsQI6)c!G#xaIoy#E-$(;pT2L;v)NypjMX;nJ0L4Dv-88$pohwIV^!N z)Zk=c0e2J&ezi^su5WL04YuZwvFEGopFR!iHGbZ4pXoK{2X}%jxCijv#)AiH^R7yN z9M=kTQ9qA;x-vStdNiPp0mOo%yd&g3brMPtIw%~-I30D(MCP>t*|0NdC?Iq@>(tLmCu5j8*HF^zDYK?3%`N<0kplgg#KNt|`^_=q5HMkv-U|UW z2>9*9jO!t(Ns&71@sG``aMo?Lq+ca`BIw_zibKUmU}Iw!etLDU!<4NUoHSo7YrF; z-LGzJ6t^xA_MmAv_XCz57**OYS@XxCUmLj{py0_FGZG9_sXrHNPd9mD)Yn2@RLY78XwCh45!J4Nu&kXoO$uK26CF*b>6tZC47vF`t2X;Vi5Cx3h z{$9A>GmeUi`i&oCV!>9$crgm-Ey%R^9UeuoEEhD_LA)CD%~jha`uiiV<>r;Bvzwjv zEQMy$^nD#YVSgO82>CI{!O)09KSBre#`xUtXBVH@4=;a0{` zZzxyH*tuzunPT|@CUIKkp_TuAArNb1y_7nel!Bt9`C#S$mkk)_g@J(X0^!BWyd#)O?+c-MxlwPcCPZ~xw0?l z3R=_h@Ywv6>gufJL5wm+zHJ8SC<}I%!`3ekRtB2v@!~jMfLYqxW4pk%A&4v>d3^1)iN(YW5w_A8~MudRLh&YFJXtZmJ&^;k!OwbAAH<1KKGlAS~-7rhDcD{lTGjWa-UESWpppe0&UzI;1}J z81rKUa0;|09*qMZxX+^w*)?8t`eGLFj_2@-^e&_{2DFb3pqnvOY3tHjl7nwS6G2mQ z@DKmpN*)Rxv#Sa-D#MSncLzuMN%r^lY8mIk!iW*j!5}A(!e`EKwu`d^YOwHY20lI* z10^N35o`GqrG?X|Ctl+O7&IWCrJroa^qwaJaM5GDksX=Z%sUH>uWOBP0KbKKYM8wH z@#6=WUZFvk1-coyjB`AGE6`v^IvDqRT$Fe~>G-M-Y&_o?{obpx2PcP`HYPT&`F{Uu z_{371%?3qCqgEu6psE^GUF?_H2)?hUaEjH_^faRS0ipvZEJ)=F3CEZR2L~^heRr27 z)yS!VT=w&)L6S=mdp8tgMkm`)xo+2N*N303d|Db@CpEI*U}tYFGymWC_qTV@n&)r& ztrf*LXy8Nkm zt%;{6U&QEP4+aK?1$oJ`EO_f7kBZcFB$Zme*7xDA=Gj#C8I28iVyC-(z8)1l#=xAh zJ6>|Yse{kTjy^|i5N}#9vMqURIf{>ToX5H;J2UyU7b*+`Ny+mu50r=D$~?F1dW(6`vDa?{5&5hKKA0CTQaAwTfR$2+qygJrwu0Ir$hjfk&QY7F zb+#`MZ!}8?WMW{yo6SLj4G)=o7obcC8*l}3W})`S+|2;6e8eP9-UpKancr%^`yS3~ zIq``Dw!_yn^!T{A8nY2r1OO?nU`YCn#edoPpsIV`hDziOu{cam1U99!nY}L1!TYDT zq{I(>W)=UK<%GybG|;R6QEB@!ith%EM4`}l32Oru#H77=drjKPt1GCbSg}QjdQa?M zXb8A#-aL4Ut{W0jw?@x^YHPbPg4g2TGWIqeSD%;i{Fwhab@?%Au{P%z%(9_Oxvb|| zyj^h+!o$PMQ9{*$N!?)&*0!(fir z-)|*P0@h|?uR%DPL%k3oZfMB(i17s{1nX~IUAtN>edcAFaCfX%)kA>SNvL;`OifKa zzen-Eo*M~wGDY9rSNT@s&uTpy_NS_%p@>LL4W?p7J5EuL|72L8)_;x6XzhC!Oz(Td zKu;KsAXNuY0qv^3_b_HCCZIOLz@z)bc={uXrqvCn12g}W6qcN_qG>CZD;l5AIqa#J zv1jf&OG^Bz6olb+P%WtxBp`U6lfI#F8=4+3@x&x$Hf9@=x>8{q8=G%9xtiLW=J>&G zDSijp@2Bq`5CS5=bh7LjwOS6vI>zuPo&u8Y)EXBsd>jndzM~~R5A|Eedz(mMIakrd;g2iO{CPCcCn3yUk1jE<4bnAP* z8%6?5OSd2L*7%|HHIM#7U= z0uNA`EcvumENs?jgVgt)DLRF8s3fi4xGNY^4;Y^*r?JstdV4-&WGp3qDDmy91^>9% z-C0`yp!KiJcNv?Gj;5A{6zW^bGT zML6oC_sjEtYz3Y7TkQsI^~K94FKTXB>*UL~MFrN>UOG_r*vxa7FH<;^HcRtZmdf zZw)|YFnG9FjMfEQk%xz8N=7Dc;bB$t_P`J@LaMD`tE`Uls1`huKR)3 z_j;__=7z?@hn@`=-s@w@Q)sy+$^w2~Rna>dG(Ya|(e!^2G)Kik(sl3ivUZ7dF30uK2x0 z&h>1U2ugL4cH!lRk#KyFe6w<1Hd+_deoImS3cluM}Mgq z>gRMlO% z6QyU*iUCxDj{mzeMB%G`8lR@H_;~i#_h1ZN1h|TSOA$l?m_$Yz<@~yS3dSLT8(KmE z1;*>5H2zrhz5{g`!>dR}45WBS5a@`Jsj2n^yO``NU z%Bu3oO*lIclPC&e7lCii_=cK>MyF$&47rOXE?KXk$}q5;t!Cs_3ciX_$mLtCv`w{H zeYTmY6swf_O2 zvDJV&_4M>Wk^dw~Ogm>}V$(^#F$T5|0C%8U8eQY8s!ERF#=j{-uKL?}1uU_cYc%Wl##w|$O9=I)` z5JooZAD#(_h{UG}XP$Eg2Iw7aHHL~!pcJBgnN9L&-dzZb#Q60wsP(NWr3qzcrd7VE zM_pa=YiqcQL#Vu3rT(1Rzgf%DZ1s?qav{l5&IWHSqz}6U&un-&3CShwur9C`vjsw6 zg#&KB0=Qg5{-Cc|C-SA*;F>X*0s*d&YC2(D0ugcx-0|eo?Etz(rd5=O2u@ySu6PGjcbkLKo5jS?+?x2u5*Ya`8MY~KqS^0$WC zER_IPcG{jv05#XsL(7~IN>WnN>(_6yGg<#;BMp!RxB*la$(Ay{e${DkuglEr1O?iY z6V%3D^RX>VOJ)Vn$%-bcBg~cgiO?T>T|SdvEHyc#WF?Qbz#Q!pruzC86c72*bgsZT zg#YXlURF#l+TCm6=6wtkN3hLIF@5NnnA!|^Ov@$!Py`Pk*1+EGZu_|BgTmQmZ>-p1 ztjSLw5+PP1-qYjbwvG<)07mXLARq_|3i9wg%Tf+c*UUSF&kZhs0L=lQmoG<_gtReF zddmY@(#@NaS;{g}QrK?o)}M}Eq9H>rS?XXV*dZZ#U!t-*a0Nh5W`o}zATfyUnJ>Fi zZiu=#z;qert$B`(8X;{h=S42f&3y8nqH#t=oyqIVA` zpS616%$W5a??@Inu3rmY+Kx%zZ35s8D)NfQ7W_9>Yt~j*vuxxVT!C8v*;Tkz;^E5< zIzBQ^EzdZCCIj2qoUr)^E5F#J7#Ho{2Y_evT(bX{C$t|!o{>;>fL3pwD%&r-!~OlM z?ay-f_Ik?JtEq@rNlqW7v(2 z^)(}d3)-FV!%VF1p?Rl`C1O6h^unT~&uGPpy!ial=-C}@zdMwNx z9$+Q>z4s;1{XCU87OiJ>es^+r7QQ;{Tyx)kX9ACQ{rF!cHDg{e)DLSjVpO>?m?(x!jy3_ z`wL}dsjt+8{#}MCrkV>w8lR2EQ(y@9!-rx#3PTDmF=K87LYkpg;{I?zb3I}p4aZ>x zv_6_TKN|b{EIYr8R*S?6Tzmif2blo6f(z&Dg8t|g32Z(FKr!!qxFo^!@e0z_JTyxE z#@GIbYY2ktqxnojA6@L3DFrMwi50awHp(BLITmhZ00Qa%~x?n z5Kl0j*lrvj9QiwK!lnbA1RJ+CdqLLw3V(l~4qQ-A9zRB!+Ae`MMjbeM^R3}cy;*4Z zdkR)`Q6(EQ+xN%zjSkf*~V4VGK(3e5f>xyq*55tB4KX*{XZ z^z572fuM`B+iuEQJHajC8{b;rZTq<;*NQYGCW40B?+u(5f64ABl~Ho!J3G3bpm+q0 zdE~Wc<>p1fsfBdS`>1dNcpJp@jjjl0aCgH-Kw=F1C=bBZ9=baRT{0k_KG}~5p3d09 z)HE`~b0>+P!)B>xjpCuJv$NP~Zkgqa;mZrPL7_n=CfX-4;lpV-U5Aaf@f#Jh${A0( zBO+pg@MnzC==;&=Tb*y?vIGPf$dK>LGqtvgL%cJ@cG%K7cR~J`m3pu*1{CC$A^NR{ zq?Pbox^H*e;JLKNX5nN39*;5ybT%Y_3+|bZ!QTy6jnoUHHrwp`HM)Mc@5>g9vkGSKe$7~2rFRngO(nw6KKttp5 z_Y2V~V`XK{Cg+oa?>KP&t-CwNg7gja@4z(j5<$ny`@Mj)e;J_?gV{posmS?GPM28XWI@Sy(4#Wj`n_{SxD$X zezr0{Ys3tkl+>d{!37m^QRv0NS<)%M`tNOh6E*XD$|To+-yf`0oF>(ka2@**_!nTz07p zWuNdOXR+g&^4a(2krtP25gVq%6F*;+b_ ze3mk5Ba}uoaD@btAm6LQ?288XbAYv>Au$0TlhX1Hl$4O%9nH8FKu4HlBu|DVU~hN# zLqNbY7E)~;pq@|r* zTqZUTf!YNr&q@B@sIW!r;HHM)qg1xFl@5HvRq(CiVD9qD#v)4q*VRjor>y-a$ChT} zc5dBKWRVM9`wn?ejW9p{y{7H#7upFB1M3HT)7*lB>OX2{26?GTNQR&&cqf5^fDH+m zfmxYmr&TSujw46wv}>Zyp5KZX}u_)0)7Pu zFUqGgh`wku0yjqTz-L(wkMi_(_iJ>TZqd8D(?{x1ytLtVJYapIgrG}hk^X;Vy$3i~ z@BasU6s0IBSs5X+$%s%&M)uxYR`%X2A=#PPWsgwyRzx8rv4UrzPIx^&gY!_-1mFFUhnt92sc{-#l!^WNg!v))A^*RdD4WW(-IQ1iP@Xmc9c|l z)vY8F>K30#4Gky3_g1edB#2;$Jn~!6CjP zUpHiOx^vmKqP&A&864trM?c3mf==ECx#SGAd1zgCuax_C*H2&L@cB0vkl37?vu$5h+u5{EaPCida` zZNA06eJDUksvS7LihZNH+0fP5Y-;36!0NV27JG* z1jO$I8AiK+Xpe>5B!Is`f``ScGZNxlV0YUMzfLA$3|ChJvl#->&}u1%nF?Ls$L4}m zR7ISg?Q}*`>U2}v_d|jV8n)Z6*yBRe)(Bdz)1f$_g+^v>o95fYB~E0wc^+LH%8Oj0 z%9sZg_9&%vqhXy)o`CqD~L}L`Pg0Om}#Kq$qN&w3Y7zPNofc7-d(*y68 zo}T^`Avw3n2YigkML=hQ_=SdkGlJ(4z?lKF1Tg~0PH7=)kUV6lo4}QYOpp$HBE(6b z(s)4u=^N@yc`@CyV#j8HxrSH<(A;uza}(m@3x*cg*4DsoJWs)slXa)pt-%*OeL!%? zN=kZdUzo8mZ^U(1uwP(2U(E!|L+@D{7nZMCdmYhrMq_VKDLWpR4%a@^0=^Fsd!oGzkMq@5e#8<5M5QGC79$;k5g;YL0y$R&j z05@FltDsD;ZsQ*!1q>I(;vs-d{p(l!-fRH>2pTOz&|t`_3Jme<-T-@)b#ijLckeNT zr6BqO@nQDC$7UnZqDSP&r^k9gw+E~-5&$|2s8(v=Ez3SbHJO?rpCod=TCJ}i+Fr7<=I-Dgr-Eh zLoA}g!rqWIMczOX8?ydURLL0`EA_3Z_RSPzWCD<}t~m!;gUt_@6e020ZhHiAP>4ib zqNSb8A5`Dk6 z_QIk9vzT*Lq`&fCV`2hq&hGarYcov(A|f;f3*qh6o5zrthhW_Ud3o1#z{KQ1B&#P= z)W0*9kwl^nPaQn{Mw<~Hj1NI~A_p{6{@4s~9cW^0KUy6LJ}~q^)Y=2@Vxr!a?dHvD znCZ5sL;(K@dh-i_woqhaAA%3Cx{gLHG66Q%%|FnJ#u*^OURx_zBc#io2=P$cvD$tP zN=SGY*0P_Q}2LZlj$U{Mtx?bZ59^0VYv=idNfaJhOK$vgE0{Jm^ z@-m7{0c_a`b6nKSQc5?*Cm#wZwVxDTZPHw!AGU0~L`GI^BB9?U;InHcL1$jRQ%{18 ztf|$(-b3-muuNM3bp?)x8IQK9sVU=9CNNbP7*3+QMyW?1e{m*!6!(y29;z6M+yfVF^ad5(hT zC%i@ad!z7V5H*2aBMWcibO4n8hJ+a0E=4$^8Zyjz7T_ev*%}L z4*{_ZRq*v1+-xPJ=z=UE8hRTTIdPrWlDc7YjZ|4{($Z!g92~&+EsrVCsT{ zz6(MFpcFd!1L+VM0p!^Erw^e%0nGXD{_LkpDFX{scYOYWYk|Ck__gBgEj6$N0HO>1 z!~rVi2CO5vEdWRdrdiaS^#KGOTpDc20mu&=9v)VdmqWx1ES31UI9>psfrJRlGUXWo zxGY5Qfh*>W04C!QU?<2+$Px`%s$zGmdK=i;I1+J8i#KiAFaJ#7r4gJ05X(fi6sFX{ zT1IAgaVX(+KvMu}SYAtqWddE&k-J7lMi2(AzY3Idts(GU|$pD zLi)-93@pfDI0Eehmf9ZVRdDWtfCMc7&WX6#I|$6Y;CrMpk^*=_CPv2J5ZSEkPO5>K zzQe@SVe%I3{sT;~Lgw}oZj({i^zsm(^?~F?_VF&`8sH5eGXw2&APznW)ew@km6X;% zPzqC>2P+o9G@wuJY5BuF!1w^(AJ8TRAiE1MBMv|wyC}>av8?(IUDe|3DiDA&j{&)6 zKT=@g>H^7_*SR?Vj9bp95XkRB{z`EUOb%ftra8C^SmUCA7=Rc(EXMoB#eg&e8iO

(N8EcH4;N>jppfPvg0&0}rKbG}+(n$bxhl7z@0(pX zsP#8avt5wvZSUwf%Zo-^$Hw#!bS|t9$dEy+EeMxLf7v~m^w|isJ}W2ZsQ~z@GwX(E zSp~S?YwsNeu1#3+QIc`!T>IePX+Q(16=X_KfitOPr*CHzGD+fMH6U3onCp~isANKj z8EO&?atw=@dcmuxfw#!;z zIwhi_G6sg zti^4T0J*TEBQOhgcDA=U+1Z!jUPlJsRb8N=QDAxyp&0t-44o_{ssQd8a%A49o(DYu z9U@6dNg;KZXlAYdxSTrd3|;>{#`jWDGY^EOz)sl!C>S&x&@?xngE9!2o^#0n)F6UX zFK{m*cIqN)2&7TyqxQh=ec{TbG^uQM-jXy+#U=m>wu+VH^8fVE;V%@mytI%3pwzP8K2I}1@{Z`{W zVk5@<%CV-3aaq6%fV7>lIdFTyK+zLU{>oP}WHswlC-&-7Pfv*~doAgM)rY}i@3}vt zOzPL#U@f^X4Rb)RLx@ww#6T^hRa{)$nCD-D7#L4}#JTI|cLdg_-YzGaA;-N%L>ZZv z#SacQ>sg$no_9)zxhGzIH+86!!FMffH$WdJ=vpv%X0q?0`g5k>+pZyYY3anAYdpWt zA&7u=dD6?>O(@4QSnGsc+zCaKf4S7CC|rcnO~oFlE;$H-YoM?}M+hD(Y-{~`m-Y2^ zVi`+Fb8}q8!94FVe@Gr;Odgg6<`h5JAppi&0h%C~wqQM)YeUJtc}UejdUpx@kG9rU zz35-Sws`@oeRei3;sSE_RA}+F4vsC$M`Bf=$bQ3R)XU8!MmVr{zDNz*lB006@=V%o z;r;CBoYCO<`$W675wwBm=hwo#qK{iey(8*Fo&?{?7fT6*jCI^!dmEc&ST5ZiQNhm# zRv2UPTemXNfcr?rd@Q4Egyal-2;}6%*jOhJJ7AdsFP#=?Y?Agz);2a$K)dt1=L}iB z3Orve?&jw9n<$$y$Z7}9^FKa{T_Hf%{R61*OI39H`1s&si`~grYOT)i0@i8;pPiM0 zS^UzXaX<9<#5+)0pK#>|N`DMiR`T)CMGOJGvgP?RNan>y9s>oMl!WAg!8nf^YeLMI z;mD*Uj4D`PU`R+xNu7KLVno{DA;g10FWlXdtiKeK(+M$|Q%_zke}zQzbJO=$=ljqV){2=9hhNHOl<@F#P#|X@h*;2rGNpyxTm1YTIGrUTX zQ<`Ui&35v1_1N1s??0BxG6Mk90m`_a;%!D5H0^gmEHLEKF&*i?x55GPJtT$ zm-X`We7=jC3YP$dA_GEgfRT|Aj7bVXhbge2Av}K;Qs?ja--}sVGAD;dM_=y@l!(Ry zpP~uu5`-I^JMcBWSn{0>x4AhA4lI0f4$UtOEp3?|-6y&&UVn;K$i)(r7X-~(nSca$ zMZ{wb6az#7K!pVD!nx3wg+3KEEYfhEwz1)@lKzJUcnfkH`03ys;Gg^`J%6|}X+a(` zzYFdR7}~P<29RY2XU-DLMdWH+Cm0fHNlmzTc+Y#MUm(0>zE)>Qy5Us=#uCH2y`N%aCob zfPy90j3MWn4pI|=((()tYy$8S1UpRBGv+;!m`+ne->n3N{EE@v;5THowD_Y*TCgBM z1w%~yt1Q>L2rV)3I=I$#b`4;g0qN}m0+r_{AoU3*68y>ywPH}szqAhiM5)M!|{%Cm0gNpe>-DkvGf-#P0(F z0zhm4bO=^Y$H$MDw@f_AHh_CaVp37Oj4GhkE*iUE@%)!8CWUGJ@WI0?qs1JBiv4Hm#q=L{lTmuS;16K|5{4mwP zx3mFARljCyef^f#A4@$wJ#ad;5}Q2Mr~hH>0H>)y~uM<>Jss$OqWL-@a+(swBt7!He<${||@;ka&W!6tMGG z#d@dfwJOZzVRLp?cAC|>UAS0oQsG! z(5)d6O7~ZbADc9sTP?F1l2P6ZqemBS3<;(AK@zMdv@Qey9Knz%uc(0R0xZU$1ENw0 z6HL#{Y;JBI?=5jbha@U0Dhi51HP%j*5{-R;w*lV|0+mx!Ms&G0pwwW_ArcNIBlP$H z{MaWSj1MsixdcLIZ~`W6-ZQmF@F#pC60#0IaoUc1Z)C(jj&@t2WqGd zLS#?Nj#{V6$`$q|ED3VLQ=+1xUkP5Q(FmVF4e$a~f?oRo-u$0M39zmney?-BbLYX%WL!dZh%w&Sqc!=BvAZ|Na4epOp*53}BV@K+F(~lmgk?Y>#}g?+&fw!aq(7_}DK&tO26n!Y-L6}U>M40m zO-<86b7#Ig|a;Nx9rxy2wTwBBU8wa zqr#?FmGl9kiV8*iaeGST1I&U8DH)k?x&q5t0Hgr_#I8nGxmNrEyZR?@plABP%>GMV zE`c0&j})dAcC$gFn=jQ^Ek3_cPH)W>ybPs5>v?UebfmwVq1Sy?|4#e@R?Yrujs?RK zl6-YNHZRr<1I@Ib?ZuoxK01EA%t=kRBCR|KfKVC;qXEqTbP8m=-Iu>97Z2s-WbKPh{891&FckX@`%)$*(_p?&->jhf=&zgr(4SWPp-0P5Hzp?$M)#3qkfCX0^X0ofTy6GBM|K=(vtKUzQi zu|lVWy}O*fn>!-39o!cv0_`C9TBA6${USs}p@;ZyH%BR2P8Jp%uwqzNF2~moJ+}%k zy-_Xnm4=-({FncetJ=j6wTG>$cnD`41jX5_pH~a<_(gS^#+n#87OuM5QSXWO*XmHS zK!%d#2zdp0sENz{Ha9m#xE7ie{TYB zKWKY%^tQDtpFA7n&cx^ zbYr=5ma!fK*|@jxfIL=NBQ}ZCXJ!7Z%dbENQa+51HaL2<VNfZ+QM`_KgrTaeuYv|B`KkH`t&2i}(d8%}7qS*ztaZieqmEdZvCp4(yAAL~QP_Xz6CYFuyYEh>|FmP*r9U6(17lcbv2T)q_YU z{TJ=z&nN2H{SO@R82A1%?Fa>nz0xR_U{AbCI~iFpL>#$M=5XjX8#9RDewd5 z`rjUvneFh0H~jQp41z95&zRE)l_GS5f8(zl`$EP8(iht ziT;oePE=EcOI(+Gu?8ad0Uss*zi*Bt3xT-t5ERgaA6ETc5((+&Al3k5l%Mr?HR#MG zIVRO6wKLe8cojl;l5^*b#l6SIGm^*`m*ro#jPCks<`X+Ws-Y(p=k91>T&e%@Ua7*N zv#G_r?f6-dvL%e-Xx--R31JCkosw#y`}$X!556WRT_?a_OXXXjQ6oeFQmWDiH3o}M z(8mcTbT+SB(u$2` zPvXQ004Qx3ll+I006k8!Jo%4Nx15}icfvOlS>xQr243A?{Ik&5hNF-_IiMJ>dk$vp z9M%7@Sr>}%2qBm=Lw(-D?@oyZZ$`$JKAE(jlM{1F;_faEAS7UIZs{|0nsI8I$V(f$ z)PvtD^U6WDOE|r52{Z4vj@RdiTxfR6{Y7Wgs;v}LXDZ##)e__Hb&lq9{5c%awYyVk zx3nD{wP}5G>_#<4tBOhZ;S~)c#s^qu2>;dhS4#~i3f=?SfHk2gR&yir>G@nj>Xa%k zSNU)-MMxxu>H?>gZrV@m0q0jLy57~xb#5+?kizL<7s-ZVou0zL^l>tFHxAwH4RJg^ zYW3YmuKt}(ntlA(PS&=tP$0vH$Kue-V7lK`?qihx6#<3NV~y6;XV)v!g-?FWJ>F@t zaeSLo1#>@m*XX~y(rLTkkevh0wwaeySa9B>T$P1AljbYk!30&z8pT5>TSG{w{=E97 zOA-SS27SM_Ds_GfP5K-)E{RfvC>N?_twO&Cwgj1HV($ps)I@E1l6wG)iQEM$745AJ z+HQ|K_T7orGaq$hV@;2a4DLS-GZ<=cWoGI0SSqQ!<(%f}neu{MUQX`v6(zMbXq721 zTwdXfDTyo4fF(kC{(n$1IyEx#6L#^;VSpD7oWb+=)_J2yRtP zHp>?eO+4&ayQyp7(>QzJN%OKZ)&8qa$s04^XI}(^g0{9v65F1R{yzR&dq|3ZV#B2NYPowm@x{fEOVOLfav$BQ__Bj0r z&T6l5q58cV&=a#kB-_J^E zBraeKOE9HxB^ey3vN3F_0vNby{By1xiNO9mfaXU^5>nQ}?x&EM$0rq@ymvVeMBXr6 zx+SEOy-sucyK7q)mrPy57q_K6tC3iDA^Dag9iQnS27#okM-o~u<^ympwyQr%ne%M! z8WK$9uphmNDAm~ND#&|eXcR*G?M=_+8!W5~^$ea44&NG9hkx1pRmjk*b>~`jnX?_a z$mieeQU$0ewP@}mtoIS5G;WMv>qm0*CvN>6PDi(_sFm7c1clfJ&Psx2&;^Gd6HVG| znnDC;$EthwrcRa_w~3K)Wbvmsh2=exRry*{=S)2^;@>CY!R>yy_wJVS0h6MeZu)%f zG88u%Ef01V)SD+;9+NIsI~vH_RZMkrPi%9vn3XZc+YBbiX?yMWA(U0@ou{X18~km+ z{D67ik<|G1?{4jeo4<0Cz#noSjp1OI^Pf34@b;nI=4Q3c^V7INjYsvRhN6fPGs(5J zo(dZcJ-wrv$;2KVZ@BWR6zw_f+89>(mp{14AC&k=n`k4@bWfMlfW%EcvFqdsz5DO@ z>zQq&&#+vxJo4!n*c=|KgNP-jgfLaWJDTEBy@w;Cg`!cDQ!oc>QEv+JG9vV9+lYGU zuO!27tzyU;g>dzoLxP3FrmG7kK)w%)zx*{iv1`M9vxDG^cXE$a8S6hgD)+J<8 z&dV3Tr*M4loTQwx!BXERQPQBK5^5C{QA_z`mHM5T_}}ZTSfWy7K5avu-oU! zw@=tuj9r8-?wSD&B{;=^#u9K~dv{l%=?@gx}ulGE6 zMPv6CZU9V0m|}X76FNXqcms-asUJ^SmFhGGWWe1)pfFV0`C zcF%Ud?o>;AQLr?wJEj8)yvXoBAnXHn@)M=kc_@%IH}9kE-v)q$YTnJWUR9RT7*5}r zIEhG>**8Cm3=E!+)(||Rg=LNT zJ)U~>-5QBGe1F=2=r(Tdje)GTr_u+Smo8m;>sF$4y6^oH-|GsE>p$bRM{CZ($~;Gr zWjC5=@&YQ9lv|j`UU*Zl-vHyH;rEH>6!Pk?002)zY z)vcuxL@XOzqzxyCQs3j@f@u&~(Ozk~Pjj+Dtt_wIi?BjWYvlR?EP zLyocJ)_$^XXt+pl31_on`RR=#O8ie z|M@CgGuMlk(FeGSKP5GbeuIr|#E@()HC95_q9QrIRdpZCXKqIjZtiZToTfpL(wrP< z4ALUvi58Et}~$vr%mWQ0&XpLOHazE+UYpf&GhQoIF*Vt|;>gAJ}6O4?;!w zDu)+NEIKKFgHmU-zCYcj_MXxeNr=LgNp=hD@CU% zTt^07g6>0HAzr0()TW9i3qqMnCxia8Vn3+Q*ttK@n|S+HE%^;ur=RQ%xw`cGW`2Ix z+)HlX9(P+x994l}qkR6=FXyNpFn#tvxW}ZG-S4wPg%s%4xb^Hnajnh=CiN3d(t6+G zW0X=KvOQ5@-D-Q9l)lO`_(|k^zErtIi0zIXy2*)*4)z6={P?Tr9v~zW| zOdRYB3erJOCrW;b!|QsU2_4X&1>!>;mCGg^swhzzIZP5bi*=+Wd%!ybmBpnCP<$CZ zgb*Hn+_ZbRK1AJJu9tjcee4@AZ)XtwHSw{gN!g7rN{Ep>Q*~`^^Aco-3QGUepD`rd zxO#m*ad7OM)9~;_x=#d%aKGs)0fWpgoaFK!r-a@8$?=>wYb)Xkz2jfImLG)DXq0E` z6cn6ZnRMl)TvhlvH!*&VxnNA*G9hgJ4=zG?Q@-EjS1e;Cgc^grcMLMQ>2_Z|{c7mn zH1(7=?bQ8}CAp52;u(oTW-E46Y0@S#2iJ|oatR63!2YH7@%<+86r7meLDcHw{l8sB z47-Z=V!4aN*NVi3f@O`?E`P2)3z^B5`J_4NDUQU3(Ucz75oL}qIRn=Giyaybc4@Mc ziXa&|s%lk9lw=Kb`9~6?hJj|EKz5&!0bynZhr;_@HLMXfVqn z+lh+Yii*TTWK~q&uZ-hO`hIlQ+fL#CZa2^28H%zJ{Q3Z1ZWS@e*+)^8PO_7bM2o7en=v`x%O~{K%=rqkCdvmyL8|!&@ zyp&p9-DG63;Ef#_SN)Luz*~l7W(B!F9IJG11aGXbq!5W*{ak5bzCjvZE)ib71iDX5 z>>WQfrNXZZ`XFz@rZf$RYwk)s(wbEMeQ@#X^G+*IE_(U+{w209zDU@d?`7oVAf_KV zD))ao02-*3(tQ=(V@~F_`L5cW1w>+x^Asv>SGh9GHk)@lY?&-7F0YIeAn|f@ z{l%E^I=&MBzs87BWJh4SaBxEK%ltR)ukIjMKLlFexT{y^I@4X8%F2q$Dujeq)UL^` z$M;e`uYEVEiss>`=caQ#^O-F?CLiE`*PEV<<5S0ghnb)m8{7G^F}t{1qL7U0qk41T z1-2T47S3tE@JkoN&Qr4rxrEk?O0ZY!HICc0_+BaMrvNOr(tXG${Dp#@wLKsHM@|w` z0ZQw+=N2t~-+R+#do%cH1g{dEJqzv6bqaBjsa-H9coW%i5S`+49xHX_qg_v)p#I?Q zUyo>t`~7^#oA!2!aMI@!Wne+_($KVa9jWG%9{stbtK|VSy6v?uL{}s`2B&SS$0Dz6 zx-TOLWN`P08G__Z{rpJeH>ot8_9_|JWNAn~B*d##oHXBc)ouKioT^)GcaN*dWi#2r zBdTF*I^38|qX2Rwo9U8Uz;Q}kiZEl|I`ZWmLw~yMqwavuq+-2vex*>lRo2 z{p*Oi)tg4eqwKOwp52eX$)7IN>Eo-9sl0Z56WT@%(zoz}&*~Kqv21kWBQJ&jr-ZN3 zvlV(8_J*zfbcej*ix>vXSmItxgq4_w+;{$@_y%lT~kHb3@^JG8iOP1`Ub3 zy~1Fw7o-TxYeP$d&k1=w=YXa0jX^@0hc)Ynz^a@KLM$OglHwbix(Wok&S?2(L(5SHff2PC5 ze(Xun++e&$zOyO-g-U&=#U;y<_IH(?>e`I1(wQUp&;i?Ze>=gD2fPcJ-VD5eRIWyN z)U+YWI^PCXo^n#MTy|`%o8+uSohOlC6l68jwwQM1k3++>@-TY!+1g`=+5K zP4B^hE&*wLgDtcUtv%wMj#Cuz+M4#vm=RQIn-0P)|CDW^$ODDJ(sKO?5GbDwq@1=N z1^QV(!XEgR0cTd}=}+>@beivNZUUj-u2X5w;5sS9x@$%~QK%I?sVu~+p^9qx>)Pw$ zSsE5Kqd3q~yI&OdOnAGPG6{oZFKvyH$NiG@BBF_5^iq4|QWC`np?!@8Hs(v~ zPm+VoJu=94+9o1@1y``kHott(uOD|@~Kb4b>$&*|B!^@hx#%tdiVK^-e zGIkF<%PC7Vyw8(gSQ~Q=d&A9qE?kwQUAM#(uVa9BxoOAg&f}f2Yy`QANQ>W7+h-TO z)BH19^phN52q~dR+WQkcC&;_Y-5SzyryvR^WjC3z*?Ao_lHj>hbn2y_`}({GnOpKR znQl9GvH;@@xp1&p42C}AlTVI}j2kbn{$Ngn(s|dEdMB@c3~o90OK^qyi88qtkpm`q zl^VPB%@K<^(W5uxjVy%ic{UScVBQ`UD7!aYtYAN58N#8P_xn4)|L9F9W^H3k2O`qF zg|FoIw|@IB68+T)bpxKs>lxs4SPHojB7zmkCgeqbiiZAVL2;-9SGBK%;&uHERrTc$ zuAD^5=>|TrIM3b!ABm+Wo*mam||M>t%vme#_LnOY z?Mh_s(3$o?J^>YJ-Soj7A>wWqHoYu1X5}i#27UE?)Ofl?;ZL-^blFRB+R507CCY{D zvvp+riElpNuEn5B@uCmO&9A3T;jc*!OJDX1s3dDN-iZICp8rVa1CRdm7B-f%7TORF zm01sv8K{JoCYF5H7NwL)7N#jTCXYj625}MO-DYDK<70O1r^8m@fD-!rz|+-988DjC z)|mTUVl$EEbmr>Weo2rgzkAa!&7JN|qgeaQTRo^|aSsRfN2-8(&szi_J!#8$2+6Bg z@)N#E?t+jR6(k^FNtTPZ2_w3gV33ec2%=UzmWv%&RFT>~R)zWViJ` zi9|xvWfel_8an}dq0P9^?SH)zE_n5lC~pIR%h<-0Bnm{Ii-lsaxXK+Y^u#{5c@@r?t~_z>-fUiHUF){`lqOYjZ;BO6Gyjj?^= zju^KiJ0z8f@rI2_-=oO>^I45@r4Y_;2t#|M+E6`_i*qfIfYnAmQ04qc5Fy9kAR zJ4|U7r~wepG@Op6l4Y7pTbD}=S9Y$|ByR*Do_Fs`FsivIhlVKxE~OHXhPQLbo>H5^ z)llV!Orerf-Q>?s?P5*cGJS@+XtqNC%Mx`|;rxse#23Xn0jQQu+CJHK*=~XmsZ?1I zcFcNHuuR8G*v1`EyK@d+a;v=bm`P{hr9+zwKd)ykR@PHG3Z<*)-3x5IdsX6x3W=;A zIdjeP@W4iC*7m}y?J|GP$m*om3Q(Yegh*xVc1|&GU1Ye52js@}^g#FENhkv_heY7G z?8{;fe)t0y1~VCcAb;*M+x#u*9XhUX=Ms%IU(o;CcO@yJQB8d_wHm4cuSG-zAq3yv zh}-Dsi8v_^xDDIt@T^@A&}7%rWfV{yBD2Y#yxV@aV9;zh8O1JhQdieZ}T*nkz5pR7C6-XnwovIB`|V@OW?kc&jBZ-fu4Rcr$uaY90|Z1lm0i(}dTjkB*vKj!z+o z#XEeD_^@NDZ;tRaP=0^w;A3!EEi@ev{T0oHci6mtU7dq8b02dolv}w+dpz^haR2+$ zgUN$AftFn<|36ZbyS?sr8mD&rR(FaP{>&t zc6Ro*_H>UI4YzJgdU4Kn(jNB1cHdEzX<4&B>gk=_)1CLdh4{RHd)YfY4Sy^9SoFgA zk!9Y@-Ch4(E>)RP{~7zVovJmHA)9Sk!^1_zqi%m`mXem;9-`eD@A=Zh^=Q!@-4_1+ zWedZ-r^joJWq1KZ4}l7ckOdKQ|Hy+5CA`PWnX|{6+=RsbRfz+s6SeCb&@O&Q-KX++ zHS=g-vl$n;ld{W&AbM1O_<tL+! zaLm(?0J+c(R1AcZa`MDnM5XOMfc=cgd-uhV0Oq+C1aZKjz&VL11z@isC;1LR(*9jf z3p)@K-=pWRu_Pa#K;E@m;1VM5@{Q3a$u>gA*Ui!KLu2e1`-BW8*s&3M zdeQw%TA>wEO{?w>%A+AnDD6#xondi0~u z($QK2D8IjVq2$@l>ZIs;7J8Erp~O(Hh0D#s0obHZE^(!x7pNsiZ}tQ%;+9R}7UyXR z^wpD7GZ-O=!p6=d0&PlP01EoGT8S}vV)rbl4mmNVZcv-?XAK;x^*9?!3F?<;okpr{ zWggvR+fCXTUUJpP_)gAGoAJwoK&&rasxLKy#`MW5i}j_;mm(C#o$9DUUMdFdbAe$lFesfHH1HYrT*(JH-HT?_xr$g*ttW~xAKN>=Zib=VlGeeo!Y8@y;KiP z<0{XeZ@b`_5-O`d!AI&}5ply6Ea=?9YTFMg|z> z+g&xAoo0|3cYH|EjZ3Xk{&H|O+fivTkaB^7D(1<}$ZXkhmXACYfdrd#F&AzmjWHWE zF4Z^Qdxme#Y2Y(e&T_@E*tv|wz^CFjr|wN5*WD3meXk?jCwDyXW%R!YGr|!KGOi58 zg?qjXm6+KZhVT^ZG>&GfcL~Pf42stPPK zLoScm{=#sNzcqJ@*LQhj$J`x3gFTUHh9#je0J%KglrqVs;uUWlt5d zHTyih^Zwi3D@z734;$ft~|+n@)ZpYx^4%eKrUVa;{NvVu*Umx?EyaE|3kML((pjD z%_~Mi>50C9@Dd~C7FA?6cSUk5wbIKY?eLlAm{=+o zfsm?sLqh{z_nfM#IKw%4eVM|g`spUS2v~J;R(CQ_|NhLNLRRxg$VHakC%+Ee_d2WOdaziszY08aid?5*)`Sw_(thlxi5J)BDr1jZE`9C zF?@t=z^9O7B0fHbMIvIK56C>2MWOUnr;(QiNvfJ|>=#q&YbA9SQ-}a74Ml-3oeT!fCQt)Ewlfsi!1NS6SIE4gT-GgX z{Dptse_6Zn@0{Ua6q{?R{=IeLwO=Y^E-9Y^v2&0*n4sT$e6)hV8>S2@mcIR5Ie@jj#JPvrzm!e{VvX1*l`#3I4^cQjU98K<9FyC zI`M$qMq)wyzRNX5#W_}8Bt@c9`3rDll&-=oZqzz|FMNdE-;D2%)>4fhwT0T!igwkOaT)Ymjg8b^{{R;@funokT)6g>f zpK|#20=Rd9vy1f^a!h8{8A^>F`WSso31+FNs7s@_Jv9%!1KUwj_6CD8kljUAp2Z>VwU&sOrPn<1^H`Cp2L|NUtDDi0_%wi##P@hFi!eMbVHUZy3Xv#mbU6` zMqZaLRog!Cf`*n0`^`q^udwC}t;B`D50(|NW?{B`X)#Ms0;3FLX76gbNDrPTJQl1u zWAeS`$+3B$tu+C8UgC%iRLZ=^G+ReLS$ciBSw&hH?9A27rTPqoe0|Jic5i4ECI4`= z`W~CjSy}z&llS)PYX@y_Mz$v<^yij7N{NR%B$hR{EDQg}mm$B{#W{J)Mt(qC1QlvK z_3=@+y-PV2apc;-7FJo4XuL18n7^vilTLyRp` z@`{Q^*njRY2F{Vy{VoLsz_IMr)cc%ubl5p(TJoLe6ZJ+LnZmycAh7EsWb}oGt?9Q@ z?t!R)o0)m|oF1mhUSfT}Fb*aj_W?OdPab%wMO9+3XiA zz~JR_)_i$6h{yl+IZe!cRm}s-wDb+W8@lYIcx8EyQaVjls^=sPCc?;8Q9b|r_wUeI z-WG_KN_?o8{Eq&x8o2Q!6Slpns#4$9n18P-E+VFylv*~J8}C%L>qCRCoTOFf5ByK_ z{>gI!-SyP8E#Jv%TctAimd)Kpj}ad}YS~4nGJJV`b@ldeV3+UsDwU1qaGEqz#n(4- zWb||`H>2hcP~PP7!E_5TE{>S3$znyi39{UA~#W)X3;fu5YKn*!t?sT z$Ujq4Q$VfU?U*xs{6db&zHwc0ee$?xQaQ}>2vq;yDP!IfoXxLurlK3d>?u?8I%(bU zN?3>syC=U=>M!0*?Fh|mC)ZrO$uI?*^*=AmUha z*)r;V_|D6KZ70V-g$ng-RWe7uJ7x@9^HX4s{)9UM8s~KbAo%BDoutMXR2o_~Ivh2$ zv>0yh(1PghzP7ld1X6to#5`!57O1^9?{phvA@g5SI2j*KMoVu4TQ&{#cK#XB4*kAg z4lt{z_@qUJ;FK6@BqIuK=y)0{cone61OJWt(Bt&jaTqrk(zOns{c(^0tmx;&s)3hR z8H|tsf)A)6&D@Qip2dw`GK?2J{Q~xMe93nRGT(=uf{&W)4!zjJg4n~I*u!S%VLR+$ zOYGq`^swMb?D!pad<7j_^kB!}ZNaz;JC?23Y=-$@`>A*Y|F!x5CAx0Fhu*?8YOSX| zhjLu25DGu8tf+AQpP-MY4TOsm_#b@qKd376{rw>v)gpKK7~oMHz`8$PnLKRxpRNE0 zl^mQR?0ZPd9#Qn$h*PNwF%}dODk>^ks#oDx98_ev+&E=Zr?>4px#J5)1zUbR81`tH zQ`j?sW=r*UDS4$!yFLbQY=*0Udo>x9VTy-}j7FehMs9pc9?L#%wyHZ+=OMh`YjE$O zoPKYcQyu#ejhrzEK)WjNIVQhnZ| z?VTO}ElvNbPbbgG=!0PkQA36NTS0h{H(a5OQGAAoXY4Ui?$2N_XTjW`3-;D4>?W|D z+-bk^$Z~Nb@2yAKm4}NPrwU7#LUXw}UGJrK@IKIApRUsvffjG{=4U!e+FzIWgd?a>cf(q?)DQITP3 zf;T97xZhIN2vr`-8l5;XC&6#MSHb1;g8;7^tBR|utIrOP{T;4p<|n*ALZH9`RxM#0 z*sdS07swC%@fFSN>-Mt)L5Xp#7-64Q4ert0?ea^kGAKKq<~lO5u$ZsNJhXXV4i^0` zZ(_TNa`iH~>P}qd!A~eel)3xaq7n{(HdAYl0#+Fx#)1I86owI|dKeo{FNREcCWkR#r&a zQ}Jvmoy*vo^+gPP#0$Hdw@U1DKX-Tc9Si?$i*5~? z863}u!u|DV{{vtnp`EQTrTY6M$9)!t2h(J-`d!?FXh%v@o@XyEBdl8$bubb`x*!2BQ&ZbU07!Fmjzd zy&e|2KY0k+jml=^M&3#)gnghY2Nb5B4Lzf#wD`RmZ9wF6hpV9 z%SS2D<7Cs!-U($SV{NM5UWE_l^vAd#v{Y&g$~N(9|I)v>?RQXHR0RBoc#i#_opGSu zyz~{ufld{6{7b%YkeZ6>;63fZ`}J$=N!|WyL`UzzZtnKW0DS`w2HfjjbJT$=Pq?q@ z4he-&A{rV|zeb%BzaM0NKL%_YL_dMEz2xQYE?|KPWefi~3%@+05q8Ug7vAH; z!^3j}V}TnR;J2AUz^&$~F?qKZ6Ky2i{r~(UXvH19iSMEJF!`nYLrZKhuXJex|F3iA z{#gEBe}f|sf2Fl#M~5{S1MlDV$G=;A|7|L>*_MQph(p4^$ksUeDb{zW#Tw~hyZ;~G z0YS~|F08EQ#Oi<(Sm_;(RUehuF#%Q|sQo+s8PyE~SariGhzeRotmLo#i(co>In-P? zBEgPtV8`s(F$H!kjveD*$9mW?)@3nr!H%(d$S49k#>TX`RZ#Un`yN#f-`D4S?t8V?Uc+8{t?RnJ!}a@N z>8!R9T~7^mZ5kNvScxrDo}=}<(W!vUiJ5>rVj=?IcXq&_(w#~8m_0$Knvbilu7(-n z%nwd!Ea+gVh(}y$#WLJaZz!+6L2vnD8d9}sE3!#5s+Mpf04wu6QYp)I81Xz;JAb{L z@T}(w7Ub7HlI_|~5=!bBzO6+#JA5EHduV31a+f-xvj~OlP^h+6j*&0$TpKRgm)f3` zOI=y`ws$-u-QV>_spVh4$k9J`ZifMezDC(4Kk&Qo{s9{G3|bEZK1M%Q<%0r@Mt|)3 z7sB`X4|NT9bv;eIef4fOv-z)Fiam>;f`L2Z)%5h{J&SQ4o^_ zRM*B%_j0knGhm;z#`ApOIPAiD(kRlOzkL(Dc-ViC1O=~Ihc%=lyQ+$zU7vjV-3I(! zp1_#mY{!AxGMt459{3rmCB{7C7NnD&R#q;+fY)KH#j_c!PG@*8OEfQ; z4CqwdQH&wqZ(LWgWn`yh#Oa-%2>dkx^pt~94Ldicmc|34k{=$Uq$g-9MG@%Y5RbL9 zV^S7kO3G;(O>t&#vo@*|)M1J?4sre%vPVV$cN4?mEN#sXQUSg$$trPZ=CRO{ib`H%i+QLW@>QRWgJHKLQ{qxjQA2{_G$emC^eyD70X4Zd|yQC)YHb;+*AHa z7VFvcJM~m&QA4|YwrRU$15gp|r;h7}8=It=79W5w4RXTY=qM2`DfvKldy>O;z4Uax z=4Eb<(E{27J$GySr#!cJ8Sb@h0q&A}Y{i+5Q|8yNUr!R-<2?b_z>(=@f`Cy|czJO# zx5Lq_^mNNe6k3}Uhn5$^ssI(ZrG$1p&;kMgI05lB@g4naC`rsl!~;SaF^_`r@O$)c zCEF^-4WTrL?nnpR&fFh^gJI=${5GBNGr-{XSy@>C(Vx8!-b0$IU1YsxDu!kh;mx9_ z$;}y}`Wq$~`1Jjbu6j%V7!3R*I6qT)qz+gqz};sD@*7;t{a@{pB{^KW568<7SBN-0 zK>pO#6*S4sUWye-;_-n%@NE7Gk4%ST*tTWC2S^b4OI=x1XvdT6?;|nNhk)hUd`5y5 z)*(l+o*aa%zg*sm!!f-8ODG%R2EshIWugWF?zAMSCb+f(2-5kjTXFUzNLU|%@p%~l z)0h03pg~crfeQPQ1XG!XE*h;8qC%+&I~2NqRSEo9i7Uy4_2q#3uI7|GWAT5}OJKr% zgz1aS5nw_r`tP_4anrugg9RPJ5jkUj6Jucvg3z)aa~Z)D`#sF%P_#USczv8f7+S7l zE=Qo{2xiU^#eh_M&jgCejGN*X2BC@|xsAn5vNVB$kJ${*b$SniQQ z@w#L;_7N`>-|cy^^J9=n0@E090x#h+Jkr<^*%5;j_z0b->Q)pM-#yf!szr=Nh(*>#DC5mw^z6K!Q@l1A*H)`WYw>L}{h0q_mN`BsE)LUO#{Z znU;}=AOMuGlA;JTG~&>)CmkMVD9VqpQ~L5JuS?uaf)BM?9gfk8N)|G7egdVZSkf5O zG5OL*0{#&kTY>v={VkcEMqyYh!&6u-!7)Svtpi5-`ZhB^xpHV|$h%+TyzJcET1|p! zp+SaT2OS9e%%K^Gq%m5ZMWV(>B%$n#p1gT)=ZK~&6zzklNtuW~0npu0zi`5MkW?cb z1Rcli42LAKoDZHfZo4P(Y3n~oLcI!S6iOmz?+SbIm|A1Q_v#UuoUN-4Yte&0)mUy@ zZb}ajzgpk{?_<=orTnD`k5d59fn^Yt6ePJ#v`RMcygCTSk53c!l5EsE5;iOfw_I{$ zcH(6K@H_B_;a(g!?K=!9xL;4$d(DxE)2Lui|LQQcr4cGn&6JjG2CUgW?D=NO`Z*|3wEzwt&K+##(G9QMR9sldxtP2VZ{u~YZargg(DvmPHlqs1oKB#lIy!_5 zr6`n%+dAPv44>%WTIF&vLDG~3B9b^7>7x!oM4dORAjIzV)7QyAFVGI&<6ZXf2jfO; z5eM@56~;DrV6SY#_0O{|-rfrUdzio3E}i+SbvUMy3JD0tj+w(Ejv1=*d;$=+0Rhcx zigI3ThWjbJn_|MK#4ct#-gxV=VNhdbC;^TiuMU#&k;i zRcP4}(R}qmADF>?KVo{0oX57&Ss05{X7N@88Vge;DdJnoif62(pdw^`Y7|$9X!7V!b z37$3>LF~*k3-l%Je_aU{P!iL-%Hs#mNqYKpg}C16iMaL9f612X z!nOe|o~QAJ+Qw`#xL)ylZLQ>0E9aEKG819h|9fMQYUBWQV{sIayhCAPlzsUCOnRVvv*y z9y`!c-ZMyR|Z5LKT*5nuOs*2j_a` zTd=1b-ee$G;13g90C9C_h?k#B*Pq{X?O+6qdtH96p?73oG^7Q{0U#^DNZ36ykD#h$ z7C!>AAU%w`LmgCZC<5_h;BS=dI^2DR(lmtTzMWK37y9x2J4YqIkO_)rKwKT}Nz!roh$M`8IM!xbAVn1I1yq5sp>wH88>zgkjDNUm^dkeKQ;*2X@)-<_ z7=-Q(RU202izGj@6#2xceMFwUSpt{}6)3eoBO!uWY5Oky)bWtGfB_USpWqZFQBKV4P@A;-hRYt3@pU-L&fZQ?E1rl;k@@knClPA4M6~Dqydcx6R%ufta&u5pmgj`m;0B< zBer^%8Ls3VR7cn)i{$}SY*KIK^Sc1AFATV{`*OZK+i~@@?v>_H(=f=7DZm3E((y_` zNM&grlGFxg6uHGSEF)or9iT8tD=A{(6eLd(_3=WxgM$gc5 zEJH&+Y>_Oz{@@zC9!Ub{)zgM%5Vxq^5fy#&7edmr_2~>?Vxj0D5mFvN_?utl0pmbH zgmjb#X-^y>BFg#sk^Ti^xU`{}x26Vqtzd~p8Gp)~m6b)qDLvbSlAfA~1t?XF$&)}} z4yE6?R`%M!H21qzVZB>GNfU+$pb#o;-teFJBTS*tS$xJRjc$fn`irGewoT{9qxJPQ zn9_DIY_05*%MfqS(+wIEvTE7zoeU@I-X&gH$^8{+fC7GhTofzSeg16O0^rhj)rp0Oms2r@G zL{T|2-4a}fuWmFdDS+$zp`vR)kZ_&wJaV7^YK7;IDF9zSCJ(de7tOwFn@WUaJJ)A!RLgCj@H+9QA!xJBaAWRg=#5K7% zJFi=$*TrM2iy&Ft2hMASxIzN_}qM z$iJ~oNV@`7lqWvI91u0a26}qWz*)h4fUE98C)8HgJ6igmW;1$3B`K(4I}qpk>LJ;~ z%HOa8avk#jFo|`g(8wmbvfSuxR6RHCAk~Bqy=^(cih#dJdr;@hKzS>cr|V zj}sk$rpEN8uQcz#t0G&TTAto@x=V<^KePMrhH43z4h&S!1tn>~+ns40)?5xpVPF+c zTGN$1LyqccHl1LSh|$_*OPb%O0`Pgp1iU{$zCeoFth53kf1kz%ax3r;LMjgwpgQ(3 z4L`F++4It<7OytEueNZX1lTyN;}!sUkHFBq(JvszKHYv%_pOe74ARMXcH5F zi~(Sx&~!}E0!eD#82UZ>w91cz?t{!_M45KK!^lR&FN*C$;TiKam@u;f@-^6ZV1XU;HH2gC zILy~j)rS?An6JTJ4f_b@Yv?|Rf!^+$aBQ55}M36cM-% ze28K;Q|nX3_*S1_l)GwQA#T(s7?HKAwajIt_0?iJp3fOn0;^&YoX;66=TGoi4-sIA z=ZY6*!OOW=S5DejtYpLOLW5k*A)cJI`V{(J%?dD<;8iedvewF^=XlRClB_mVza;@@ zCAEIa^A1q`?A1RF=IhT385c1iEJK}%^s`r-Lx_<9+cDtep4FUoooGPJf{yP{NFX~C z1jgGvpm|&?HOXlQ8xsh_z>W)&j%?S{gIcqP*z#Uy0+4Vv10khm=`nK9ZK~%S<2-n~ z7Uqg!ZJUja4Ny*?vjkzh;I?hsT1mib zNyjy<4{I1BZz1Zw*Y4Y=G^M6)DRL>|0C1`x+l>N^3MCr=yxi}n*k1#B9%xG0N;&5} zdCS{yIt1r)5N>mM-2D6yNMJ|@W2(9mvaKM;^e!oq`+Iyl9=wB30-gj7)4SLj+=Lwy za|qe}<%KHZGQhXLev1a)A-Utj2mi?xg*Khz?h0{D#R#+_K?VDbVIIWFSxTh|DjLVfH$0<`Z3MVG3rTEyZp;IAZiOU_7Q@QIzI17zP%vp5{6*KXzup zsnBSyp>j%^^63kVl13tMQyIwcK(Etmfd+)2Eh% zYZqNH=>XUw;IK4p5AyoOPf8KNX(zyqpn*4gaxS&dMh?ah9ffYZ9&2~PFtv~K z8&v5vbQz%}P@`EHZ)YtW+P%<-@C6gWJ`K&8Dk@EYzZA$UaQ;c}@>&4QnVt@B7bvAp zvOf@c+D8R4BJ2AgR0d~a<<*32?CipAp1=8mVSlOXx$$ITKkp*cT2eWzF4A&jx0GQisd1V$Ra-ycEtC?ztn+OKMXd5NT_Wo0w{ zv5%Ejuf}mV0!)V3uk~y)c)nQ)Gxg#ok>JyP(e3l>#6ttqsaFe=0+@pwCt>x6cNRNt z(rJ-M<*xX7)3Fo=Ou*GPqKuK?3cJ8a$iT-cV!)yMcW_botpDu$wbE+=Mc@|nZzk_> zbQ1*cMc(1%0kj(99gYg3)lA;u1l0CEqs-L6i}qyl4pkj!naMj;MW9`oyh9ZpsMJN? zp^6vndkCxZt2)qf0fq&tO3}Vd-XWSBPQ~OMs*a#tk78LR8cArknR}L^JvU*iO;)H@ z;m*cl35NUS_h*o3#G+tKt@80U<3*OD+XMi`Q8geN?O*_UCs~Dg%v+74*RU(&jvhds z(!Xg6nUe*8?{iHE?V)u~tQ)rYb3-}7AcNx>nU70dTO?uXEt}38L?9iI6tJQCEK5bA ze2w&^os@rGLPfeiqU^2}7TG`va~8@d${G8ELbDDAP6Ikeb0|M9mYar)MzxZit>hKa%DAr~am0oQ*Z>?1RjrveDs_z_?MDSQJX&;AI0~xw|YXa=}I*Yau21d56 z`OsdIWLsbh?D`{x#)G3Y_<6J1Y>Xg`|h`EOobcTRjxoG(A_Gjc_`v^ecQ*^6yxC@h#rLE z^73tY9$#HvT+F-e`U_+n_^Y7+0KLEiOl|$A0eM&d+b1msu8lqfb7QttTod-_jCkHH z`TCX$;^T0S+XZ2-(1MDgPGp=0r-EV}w|G==a+2+9WWjW!$3W2xZANTZ{Q*8qG)u!+ zP0J{!gd|B%k5l|pkqvD^x!g9Ce0%B|sPsjyQo_$>{23$=#>BiDgcSe1-3}pw=> z@XIG4Y*Z==njn<=H~#?z^gVz@7X$C*&?qpqebSR)H%AYM#le+N#J*WX_vPGGm3;T@ zw`(s)w#ATdCk_jp*-~(WqB?)p^I*gZQ5F1f4@hG_GTx02-B?MAuDQ5bG7t#1Ji`a& zty72mF->~jhcNIi&vXlBUPZkB=#&T2UBh~6q~j7)Oq}PP6tde@JU$k7RL0Kx!5F(A zE4`H}V@7k1p57Y@Qzj>N4=sFs1rW$&V5dKQsA$DFS##iqqU-HbgtNO)?w-G^b7C|Y z{N$_I!NNIA+_TdAqaI2Jn)BTk5cc}-yY0ern216Fi|9VwBOpgizC;-S{C0o_81>j4nR-&eqIIJ;- z*|;JQj)l%d#Ip!sL^bXb`iuZ(al;x{e1e%V+7)=5s#^z;sm+XvF77vsyOD5)2>kOR zGa4^P%gdR|8)4ZVqib;CegFez_b}J*MW0!~;Dmi0y1oUBWUlH%*MRll;Oy1lT+FKs zM_jo<40e^_h(k8O+!eU+PAu3rHo&9*x9Wux+p^0Hm!0|#SHun`ve2l7Xm8Jn0Kwm` zi0%={Q{DAPWMn3b7k<%VnQm(z4L~G588tL-uy}Q=NBsZgz24{beSGXc_gEKJ5=4+5}|EHYSsLq6b8PplzfLQ3RuNGUMdTaG! zPSRc-%yyg)rUEhO*Ok=N)oZop8Zr3#KjXZv7^3_d`$p|E)}B?g+sh4x;HbVssqGIx z7(kzlt=EDJk6*>mzR)^IlQ4`|&O`ENWiX?~?;AKdMwYj^^9>2)`QRJ@1MPL>U4{-N zm>u?ZbJ}_`|CEyUzj&~7ayRelZcY+Ys<^;Y`;gr5Bxmv!0lI&#tl0Vwl?pm*z-56F zFh_ zcsDv@G8?JuF;E}Y(8Fh0!MS;Lio72v7s_ur1zBDIaA$hbfb5u(QGg^W?Rp}Ip8(B1 zVC@I|v5sXPi%+?eISDj50SW~g9a09x?k6)OK5o9e?iAr|vkc|@wjhfq&8xrM?vs-x zZ9dtHGU&?ipxJonM_Oy-pWjmh>F$F8m@C3}-H>g(n74+n792B%B9Jm?c~yp8p;Yj$ z5+n5+=G-X(O6V%GyiR#2W;1hU)S2d;VshT(?d=RLZ$Eh%nG%@X~Dq4dR|5YbH;JMT2HTpo*HDZ zVp2oV2O#Q+WkD||qKq){64w|D!IEia!0kqxF*5`1HAL=3GXoM37R?Np-C)to;IjpN zglT5zPJuwYXlAHd56g>YhM&st8K#*5H@OJ{I@8R6!xYw9%!h_kLUIMD> z0o8E_U%x>VR)QSU0F!+SZK%E{u9)Nm<0HTb{9IG)LBb?u(J|LKz=Y6l!w0Z@OYjY% z?=j)ka7q51}4Z3P*NOpn8T>2n!0_qVwL&ZESWybi^znk6Mm4b+Nv-e3~^l#V{>>}UJyVP z4o9I%pHh&D(OMst#UNWoB9Adh@0iBgb00Pefz_j4@%c9@=ndzlhm#zV#ctV@n{s22 z9_m{#*hM9obwH?wT9w)t1vCLi9v|l!WcBu4&F+U5(|t@x0bbvAxM7_f;;Fhgnow6LQ)?|W0#Yr%H-#gb3>Cc7dyS@vRV#sQm+y}H zAM78tj#mCMnlF>YERZJ-K4c<)QHTwZ{-Ct3SI}G`O8dXTA}1I{Mo<+di2)T#yv|Jz zHN1jl^2~XU|I{_-?DEjyeMjb(RhGN5@2|RfBf8pxtX6 zKpkYT5+r7R?7^Dw#el6|YJnZuHixhs;6`Y{Ps2F-1_!SbX#sM4Df9_9Crtf-wyd{X z^~23g*i`=fFVI7`bjdA9x|tXj0O80ZxmihxxF*hX3b z(Ds;AHe&9l6RuMqX9GMDw9G`3|JH{`h|nUtD0|pFwK3jJLfkz7_A4$SI$$(swZsI> z8R~@FSz8RiLu~pCF|^<~c8?|JU%LP7#UM*m?u!I;_5bFR8+5}gLsJ@psM%_TJh1&z zLB#7_Jg5)iLBNs^8hg*&4uQ>j+ae$azy<~zP{T3*^EPRW`ey<{D2E0{Do;U9fFd8< zLW=k5kXwk}jVlpE1}$)JLq;pMo0+l0L7!k6im!h#duqHPP%s01$=2oGPq}f5$=r?J zb}(fOLb_Q0OjXin<0@Qq0j!7ke;vA|7I!!eF;C{qk|E_RwBY0DSmldLK&Mc|X!>q8 zsJHEzI%u~wT5e;me}k5jnad*R^UloWK(wsNT>g%hxtPm{QrXmqD0^HZl8N(}tGB{O zHoa!94u{p+%+(*!>KE8*vE{qWaP^or;j{h)yI5q0 z8weKRFJZ1cng{f?G94yWQ$P$o_W6&-ld4kGJc;#L{v*JbtU>qYp|7tm%#FF&ZI2T} zB88%Y_X13ghQqAjip>02GWmbjlS0*0l$ZZFJ8wsuC{#qH+|(aeczqN}XiE)8Xk z6>v4rmbHvh`)(P&1)frZ4GWq;66jqoGTj7IocVM16iwf{lO4MkqE1&A{wNj=t*;Kc5?aRy z;h7mdWz$cImrE_qF#_|o)Kj(F>IQ^0TV#ufRPLZ;e9EVLn>DN{fB736!-s*oQ(@;o z(xduP@N28zUi9T#p^Ohx-t=u_S^d6%Yoq%2hx zPmdn;Jg<;RYO9OaXBU%Ok(GD=W)lbE&S0&{xHrtAk~h}PnMS&hdPGm}a(<3RTirGF z^1t*wXvJ%`uTa+JXe-omArT`<&Nr!o5iyQ#v7H2=WW0GZSF`Aoha!~AZ^qL}Fu8>= z)g|plpjdtLv{v!3~L3W@J)EpNVMXHy+tHWK(1$8gSeu zk`$zx*!g3>UtyyxZ;`vbT9IvW*_iLgp$X(;y(vU^lTD)p?2Hah}G3+9cu(L3mUUU z-}O5=-TE=(6|%d_w!P<*V;X57K(%zL*c5M2=)-tOYE95CZfiZj_#$Q&aP7;R{6^d6 z5_x=zoG0aCa+j^~;xhswi<7MXg`1~ohwI5V2lAD}vhuXE4%N$c@bMF4KH^QvG7lL^ zMqY_=Kdvg0Cwj@XObll>jvt$Vc+4eanIuVhq(^U)>c4|#V!b1Rs3JO-XX zpwZWbvR_$_(~k=s_fAPkF}N&`*5bO)y#+7KGl}U+?4MK)U}~z?KSp#6*o-}!VWYeN zYa<`psMd@)798&JpanQ#xK1LD9uBYP11*(io5zql7>6_5hBm4$e$=e>F{q%EysV9y zXrq@u@&LhK1{*r;0oF!Ov@tr7c?DJbRltgLly!~(ILB$(#mmXO_!Jc01lBo7iExh4 z0MyP9BI(?hmG}1sE4)VlHlKcJ<;u`qJCMQn=+am>yuf86~-qovaNC@LS|gkI&3? z+LqaTQCYn5)60iIXPs6LUWPOK1};{m;d$EC?9#81SF`c=hq24>>3d0lT_ahS{KAch zx2oAs8G+9GiXoW7RbTk)F_$iqK`h{GXfML+{P{;|+G7 z_&45i*a}ku>1QPI=0_MCtjxW0kR*b7rDaa5~nKiI8S+-W1p z2k#eEc3kCBWU}w`$MAk~)Sy{Gt)+6v1N&=o8)X=mn^R301CL0nH*R(9{&O|Vwe8eF z3>Wg_RK=SYTbTG|>c&+Fc>H{h=7xpIiD@MciEvXSJvFkfN=3zJojj0vufI$czi$vG zTFV#lhK4~k=}ZXnZSPatc!@FAK|%S0;Eboih)hzpA6=*V!iD_^ejD;@5}G9$)Z*Hn z_*>gQz${&$g1SFn$sPS%fG%c+GmPQ-W!W*%nZ6z&OkShDeIBqJOK~7{zH|SN|<~(+~isv%=_lgwibKdGIyXy zIOA2_bfx^_1Ahm-dirY@pV{sc=oLD=7#VO}Xroo|;@c*}3)gejZt-oa8bz=BVO8dF zm;vdm0c+#>1lU-3VsT^iI<%(}Yoid_czE&6#6`=Zjr&*|E#uLi%B(%b&_;IFMqcck zi;)5sjV?%H@EdBafZ%jU6|T0HZ}F|nWAW9(X`tIu%fZ^{gf?zvZQTFjA!wCm zRG8*HtN4@b(wl9+ss!ytf%P!4y{hRGX1u~RqF~0eYtAcf@z$K>Xl!b_3UBT~|DDJ( z{G`AnEXh&Jp=zgpf_zhu-!>KPFk)k=1l8lKDon`~!lClVb5fEe3`2}&mNq$H&7sm* zD&ItEdQ56l(LNWJG}!Mr{)Q~68RO`%HRHT*qX;8kBp^XhQ#Wn9(f52?n9vpER26L* zdiwD^cqag`iO=O~ynk-?`1-w@mRZ6Sop)ypLUucBZQsLy|7+w_x!|x&mYyIuA}_vX z`FO61_MikN$AkgH%cYB!!9tARu zIt-{?ApJ+cY<*APYrZGOi14+Fhsj>)vX^mcB;v0e;6!;p!GSkl%kzB*JB;So!^Wpc z(}4>QU7nzEqg{}}m9Al2YZ1B=ilU-gc-PLI3Xd1P^wc7g&?cHEx@EuC8zP z`TT_K@=cET$A-HzRv6fooVy^pv5?9)-rbsSER8Cj(p`*?!-p)!YJIiacnLzPcmst6 z<)W5rRVm5IMb;e&BLTZJwYwy;NbkP31bu(=u39YfkYGG#opgZFzDrVBB=R=Fmdr!@ zpCo78NDvIX0r#PXS91Roy*8oRt!uLtuO!|v<{`#38&P@9V@O%Lfk%G-_>GNkL#H6) zd(nQ%J<>o+aX+tQWQFEcLV3~t^}S`&*7w5W+gimKCL1~rItQqhWfqhS)r*$i^D8$! zb5-zKn@vgDq#0c#yhLdcF7dd2i8U(7YMCU>++XK4nV%!1RSsD3S z6L)bfnW6Zy4(oyKR*9Kg-VxVS=XU-`KPlB98&Gt6w{c2K^ET&AE7U?#!_WRvsO8x# zDEG;o81p?+1l5K|=p*t5Sampc`a)u}gi(a?<(MvgB6=34rMfhE&~tzWK$^kHe<$1F-~v>#?O1bTN3INYeO5-n~Pq=O!d%@Q#TPNF9 zblbA6gHxyM0Oi$SU^YifW`RWj59hn?64)HE4wZcSteAyg`ASr`eb8K!m|4)7E^?-= zUyGJ}RXef2^J!aYq_rNGUigN5>mc~Bv+*SZdQo6=LURN$X1dK##6kLb`}1X0>H`xq z(7?jy=3r&zKZGm6b{@bEUpOb?_=2F7{Qz91SKoL=GU~gUDzB zV*>4W;FiE;`atdHHqZR@TNJ><_*qfU8XYk|VKIt@ZGk9esK|<~2T^H7`D-zv8G4}z zdE_m#48#fFfV$|>V!ejbUk7{Usw~Fn%pDMh&2F=H#KVqN>sY7kfm8ah(u>(vREK)9 z&iDvL^LkeJ9!3?KBr7Cex(O#tjbFUf+6{2Rx2&WP_y?TuvCrZOdCs6KO=BG<5Btij zufbyu8@t~u9rZ38HTsWfW`Z@Fy(tU>g`_5RbaVh`eV0`Q;sW^xp>}MMl9MX~Df2#e z|6+zYfRc#TnMxWB?AxycSl`2S9i2`uy!epFt8yQ~xp#Om%DW>eGG?1qv}5V2)prvF zDF^8$JXVU1yYBYxR`XA|*G+yWxozVuH<$uAn@7rjC*35LPBwol%SaI1PtlneD|Np@ zh)ixQmATuRqih}q^_YtGgXFYx?3{-V9TJ#%|H?nXzR@jqpEwfOfv__=sVc@bo*8<8 zebsMWz9Ofu67XTz>wc%VgbR?OM##$}1{^nQR(^{PNO(Q^Y{wdEHJ8c-BL?-BO$=$| zOxLKz1Jcf>Q9*yIaLA!X@{Tp>zh|AQzxn??12EtE|GgO??Yt1o&3SDh=euj)qtlw2 zHXZpY4qtxPpOc|H3L5aU%nFr?v%PGtf9l5zS>NA{4%Cz7n}~0c3$==W1N^Iclj@{* zmgARx2-!FHCtoYaX3D5Hfz86VWLoUx2Wbbqd{?IC0T60>V#O%v}8Iq_PJ$a752!1G}XilPY)KqmMzPwKX^H2dd!9#B@tPX)OI+k!MQApr)b?^ zbOvDUrctY>%2FTr;C_b!CT`Pw^H>xH)11il`6iP%= zUPwHB|9F-1Sq>E*tL^4=lQ5$4rWML&4yk>+RDdy&xz1!vAVaurI}lVWQDKJd+|QJ7y()W$JxQ^TUhS4M{x4x77ePug`TS62|X*fd4Jrf7WhUyBH(T zdCLoC(GW@^L(yPFwU~zbqw&ggvgo1T^_?Rj`IKelWjJEr^$jG7_1YDS{y43G&nJHazz$mm-{cr;4*a};<941VIiF3krOLW?uQD!>cmnOk%n{Z#jSx{f%i0h^mK6+Q--2!tjuiJgr zfdye|-g9i>yYCFmkPn)|66Z6FZKGBrY;Fqy69e31efDi>ZFxV#ok>4!u-hYBegHSiHBVMz;|z((;> zWresMwle%U)j}@gubg9e(8T?ZVVqe)Tc4X4se0lt?efETqX&umE9h=qh1r7Z%8bH^ z_^q$Vw?0TR0@pzicqc*di~L=W=!^ZC1zOUX_keLb;W-z*TW!+{w^+cdJSLvakBImm z`C4~8Cnyr7hGW;q=m)a~4Jn5w0K#_FV5aj!fnn4=c1+R^G~->N1sVZx>fw8y>P64 z&|c=L;fq=a5ipyy?MeD>RFz+)!_J7-J)dT19`oQ-exqpWVXmv14;$U;kW7igs3kMJ;bDME({@!XwxHf$HB*c>o&d z;tDV_Z|BORkkeOYUWW*wg}_X{46<{ z8M2-Ob}mr!r*q}P?RY?P4yYx*mL2c!bdtvCf`a!>Wh+};2>IFmLjwg%WS>wx27a!R zm7%;I&&o6GvE2!#aQ{R#sQo>=9&tu|qWaQNZBW)#^hmK!-2!4gy#gqVD9!QQilJ~K zlcJQ26?s=J$#^muLTWs6r_DlAWMiRiTf>_--@D4B*WAd5$)m>r?%jZA2mROo{HA4F zT@BRId!@!Tj5$>9BU}J^BuMqRLZ-E5EBkGet^oso>bjOpm?KERHH@gD&C^QW&k&iW zKo+3%NSrhE1cKvUZ>HiE^EeXu!cD>-5078Sb4a&}8B92^2C&q?LsEWBHleL%Q9qY1b9g8Xa^kHEA59mE(F`_kf?)HXf5Ie<--HeOC0z(?1DLsW)~8)XBe zx5IBgeX*9evMZ^=;&jWoPp*jWCm0n{$X3l3!-j#73tE?FGb!_Z45yT#^p)1_VrSzZ z1*V02{?XzRly4hPw!W8|?}=*FR?=n5POahFzTaZM2aUq3sb` zNoA49xD>Q8l2yLj51@@(SbL^GZSV7e6{79QXya7HEy9_j z28bK>xda@G^Ux=SB{Ik-2^~V@O=BZGPVE!kUlZ4|E-UQzYGs_E8{Fv5e*yzs7`lI) zZ6HRF`;(Vo1P;5FJ;QTIeTBc|7oY7@dBoIyr%jM9-95`5yIU=m?ywcrFZdfNme3>` zM)beXE2D)Zk$e%(YJW-1(B0bX!UqBpa%Fs-@!`AG@L|9YXeAKw$*z)c;a)JW0u2g& z6?~XmtQ;M^{zMAbLs|&EH2V4dlWgKKq_!izM*I49-o4b7yHhPhgc4{KGc=>3Er^ne zA-tZMq5o+?PZ9<97{X0fq^C$M+vli7+(Sv2%_s|SEro+k5OZpqZ8 z@dfM|-1`D3f0;;0O;1{T5dMC@rh+RZj#yyA1NwEd|I*JOIt;?xLW$fE#EEF`Jb^=;?oe1IzZuaibctE|hcOh#}=hk1;x;RSJcEm$2be6g?S(d%2Pn;o&%Bq(pIm zM`LYi6(e6c8;Xi7HS>EU)eXFV6yG)C&|v_}J_d-E%eWVA&9NHG)lPDJYOrQ4_tEog*P9rJ>I%pKTbb- zxRWptP%Ej~7jz2};WHXMAQ@aiT){P|25owx-LMY$RJVLBm+5SyD(4@|$3bR}aqr+h z=!uc3lvSl4_>8Ht@v)?L_W;uh`Xla9H9!?$?0Y`(yn9CFTbAPjjK*eYMRqjg>sgBQ z1IgY78*$NZm?YAV&f**viqJ=5R#+V<1Rx)7|}0VSW&Zf8N};pR)7v%3nzZSB5HU9Agfuhl8b~s zobW75FfjWFCw%P7N&-A^!lTPrCmaTfrb=h&Fvf5gk+rOi4hVllu=1J2S2(K7@ukZV zfXgwzv3S^{qHq%X53H-Bu7r&#EC6ye=r2SZk*>i&@ZKY#aa)&wK-GJ=n(lL5-V1Zk zG#mcm$FCPG_6Qev2-KJE2Ok}q6wp|PCzs>|iLWn?-`Z#i^1(F;9-wRKrTTB~Pc|N@ zSr#DZ?+=q{z8ID7dX31S0x0Q3kXoh|^29Q7w_0NJSvFx%26|=g_M#->?B(_8NO*D@ z;*819&CZ4;eThx@Kgqz{;B`GMs8#yh6t`}ymN*@9;s7Gn zYZ78f2S0$-g|Yqe0VGdIBNKpM{JgpT6aTh7j4x!$sEOzA9HXP7v2;!qsI9@@kQEBb z{6_#CO(4+;`bRrfe?E(;3c%I+Cjgh!laq884>Yvn;OqOlM#POS_W%Y40X?DwNYR6N zTq^OVr!)(+xPk@>vw^u?|75qCNb)gE3>Q1z1}gov14>GE_erWPE_#d}8GSZkEJ5fE z6Ffg0Oh)^-jD6EKsK(F*rm^YS(?6O?$P^sU>j8yia%2=1|@=S+NicCKA-8`Oen>V!*p<_$4Q3R*@tSK`la{+i?zrAih4} z{A|M0Xt;b1l^@PC#|5VEO^}cMtpWEUe|_~mX@YR>o$?E50;vHdCj-xcohyR zbHU|f2?~aOv6kYjUgx&r1R)5gxqthz?MG@Z=i9)S!dY?TfpbKN&eLG{0r1P@i1;h#`4+9<~#TfH6hW~9XDQ^a)H zgaa{`2$tZKCcAylC{BH;;XeP&p;xsg_XUE|?bcFqF?TN-VkklDBUp(c10{*>0*GK_ z{(zK!SgM{3wpK3~eHUNpHP;t@KAXpxSL!uafGV>*xV7EgVqtzx+^tSStfc$Kp~+s4 zZCib6z^Z`pN|W;wm2oV&A$2nZV6Hrig$ZydxGl`kDp_(fv{Jfht@bej$WZ1sdL{A9 zW2U-Lwb5nSZcOO z!3xrP;Qx^5X9Zs5|M>F`OJz1g{*TjQtbCFI{tvTbtYDA)AAhQ`G-os9|Jb{cbyVd4 zcqGD#^?O0UFuTFJnn#joN0z!^wj6-N-sMX>LP9Z|V_nW^5u>WA{Y$AY1U{;Nh;=zz zAid>Uu`cH*f)QpHfhv;ja%x#3mvDPQ%P`}N1ab+;TKOj9C34AU?MvSsKCMu`Ux9A# zUE=Q+wUuz{eo27;?^N>=QcdP|77oOz{)G=iIpko|D1d}Zv8R}YpgI2Q z%=Cug{Us`f0k54wd~|6{OSuK)#>!$$IP`=u-AIkVXUsVQeu2tQkJnq9W2A|X2;MWd z*qMs+z-6y0)Y5tJ)b=bR8q8b|-`*TnXac5ONAk3}VVzA^E=FenbMm@bLY=)OlizJe z#k#~}M#Pw!M?cy~g%sIRTug)Q3o%9}$ffy>5t=t)4{%zXfC+X@Pzg`j+^jr ze(Gm?!FIz8e764vn8Rd(&B&a{1!Inz zCL)jz0U^h&pZNumcwjwR18<*XIhZ z3t0blTa)a#n}mNtzKEu|;dpm4;ITW4s=A>6MVYMtkvhGu)#B+h70p+gww>n}^zSv~ z^SF?@pwfWJ>iDo42NR%^oHe=1)*>Y_oq7PWTv#^?8!#S`lFQa2mtoA96@;{i3dlBk z9tLqmfg;=(JW^W=JTl&sh}X|43hYW~7Gp#|zxxTv&;ABCP9LinGBzP_f=H^5vF7T^ z>^F|OydTNF;cobcU03Sr@M?xztSwlBLJ!G&#i&PQBaBa~B#E8IB(?T)L7I$I{0fk8+2Q2xUlerKj&Z3JvR|O^8l^M|oO7({2?PjH4udlAR zfNu{NX>?Vy|ju{*i4Tx|q zvvELoDvbnC54i$3nIM6WzG37Tpo4T05O|1L3O(}#2+Ke&#3Ed*&&Auvep?$gtZoMP8gl1#>8;Pn^Z2wY}NCPpDGr3qIENL2R4 zz=@0AX;gKzffZw`(janES(P|0nhQ4CEN$#Zd;Yex=Q7y1VQFIoID!J@SV30i5=6s1 zRzkpOKwRfJ&x-50;9vUl085z0X`Fmdw3W1YkYF1Ct?MF`;t za2N+xLck&R>Jm#sz==R!(cjLxRvx&xOqQ_{mkZ*9p&ILiX8TS0m*GM)AaX;H-L@3D zaU3XZ)Iw1%TKuuc?bF{7;V3uz4V-=zK=FU^8{tTBJ$=9 z>7`g*4_*_)cCu<>JdZxqK)a1%>0I286&EW3S z=FsL?seF$ZVGDc)l&@d3HTHkmu~otS-2U{iMYzK@F3`LeSx!KW*!l`o#)!gzgtOvL zlJVwG_0lf2ZCLdi-6Ul9KVqd?hzuPCYh=(#0u!Q$Z@Zo*jZ{b8Uy*UfST!s_wXhL{ z)4u>e*}&$t0DE*r2GPoSd%QCH@}-w%mc6$TR7`$L`p4MI{uuSrB97 zqWyp4Qr`51S@m9j0MoTbCWGZ?LNn#FJlK_sQE=#RqD1#dm-_R_7_pFRdZFL(Q8m_dkf)hsgi*C7E+*t^+I9G|ep^G96l#XlLbjOiQ|u_?4~E}ima@2j z9_39woUx036CX~991=?bpcgpl$1EEpt`@||qr9vD5Xf#+<+GO+op^+Sa5&5g09XW- zWknn_HhAqhSP`cRMN1LRrNf9q018>!C;|3tg*}E@oL?Um>QtTQ4=G{JhX`?gR$g;}d27m_t`C@5fGBQO<3|=uew}X*P z{${TqbAc(+RS^sq|001hPptE|ClY5R8XXtc?yna{SjZ?TGW6K#&h(*IRcr2m(|0mGv@~@xqlstvPkYAsS23P@ElP4?IugJkjZeT6;ssM+IjKhto_r$7KbyzeTmSq}WG~RnI`VscML^J{_E~}uD zKL2RmDgCwCpX}3K7;UvmJ3R0_gIH_}U^OFlVFy1y?<|!{4HGp!=cNs%hmALy`Pre^ zc1kjAmpwuqc)lm%ABLE)?amisl-FkQ5Wxj^@JfqOO6Q*k8)OrYS&{bx>m*o0YQsb4 z0;=RMzPaB$a7ik%1pIouSt>COZTNP~B@@*6xH%WD8~PnEBpVeA?J!_`Yjw)lS(qwj z6G2Qx2mor;(>1n;y62i8lUY9dD>rSr=sPgu6$vd92E}s% zeiydB0*7RW^?pB2%Sf2o4d#Z<3cF}nio3Dnt%5HX9=whf#UyM3u)pcUaEiR`lC(Uq7tA28M& zY(MwJj`R4z#VO=lw&91{d(tA8+!kl^X%O>bUl3vBz5fZVpiY-EH9X;mfdTC==ErZp7^LQ_co z(yLHmuc&6_APCM{piNqA0^rUN{wU-x@c7>NO&vOCz$vQz$A-HZusJOkjQ5@iMex5_ z>!`+6@B}1w{RQ6#3VW<(wSh7p*=J)i==v&D#A);b5P-g+))di0FKnB?z4?a~#wYII z`6G{N@>Ynipi+RV7HIzR^)t&0Fj=))f3fiehpB+ka%xx&3znF|$@Htt8rID1=Z-bq zPv*|YE=s08wYSS_FS80upF1EtZdr=BhzdBpZfVD(u;b>X9S=4yuN{!KoI6jWlfm%` ze^m!QtFz;B55qdT2^1DmQNjxAnBOLFrBRh(L*`7SrRm^Qz{#(-rik3Xl4(V1dy@U{ z^Z&Up+u8JS?yU*Wq(H^5q;maSokYwtkEeEpxpT z`vsWV=*@(ZV|ysSdr;1N?ZbfiDqY9A9}^0U`Vl?xf%3kGj6$!WW9*6tCIc!xzkW_x z<>>qU`{5vi%2byw{-$#K<5xd7nUMOYY16aEgB9dvKlP5r&6_LHuXFvzY-FgCNmvQ< z<96GR2z&oL8X4YLx}m&wPQdf(A^D(_AH|L@Ot24m^c-?g=#TE4iKmFk7JK%v&vq#M zKkdDDP?X!(E@%KHNKla|AQF`*C`c9*5D^f`86-*0IkW*$kt86XBn1>`1SDrrk(?w+ zPByU#O>R=tvs%yjec#Nzb!(=kYUZEYRp%5)_uKEg_u6Yc>sjgK;fzf!!bQft6NmCP z$r?W{kPvW*5NbrE4IDy+c^`y+t|AG zX1v>&LM8h@w=wwJK3(Y3VZE{XCtrr06!(?MfW42-((Y)47sFnitPC}7u}gY7DKTi| z6JoW}UGWezdSM@f$9W@Ws92&7^Ph3>B6j2IVh%BlCE>}=BbzxG--VOo0pstyqR!1I z)g3hB*LI)N9v(0WnC$0Z0<+y$ED(pyWeAVKhgyGkkX~$*ILuWmc7+P#Gb)a)6?3S@ zcc;Tw2ku2QWsbXzXCDk9@r%BM9!oH72b2^x&%0*P7~>cJn?!cKiHhQPfTQG2E|ZDO zdR^_>*Kek{zNhNvLlZyLZ8#>%Y=t+SLpaZzHT8FxJ?Y+Av4}Vvtzq@ku5w*kPO^#2 zki?GT%VcHN@rUs^OzrQaGGi~DEu|n_pdbzvs4f#Ebf}Bitgkx==aptT`FGWXjsg%s z&DV|Mmk#G1%#A{sl0U91bulRs_iYrLN?$$6YBY$i0k#-sk_TbDgXX-0_RA24W%w%0 z{_|Qdd{JiwVtXY%si^eII-)G_KoV|Ou*qH-Q|88*G>ec5TZttrWUIRyWqL897fL1r_8 zyly+uIiTZ}KW*KZxObB`RVzE!VWPLupk0_rgP=?%u2w7wl{A9I-CPg!d*kRVZR+={ zENJUWdcohtG5ZYaB`g`&MTy_Tq_}q)V;kK^DDXYxmHBlKmun)=??&#Y9CYvtN*16l z(aPYlP-uiNfhO7%OrlE&ubpO*dd-8HxjcIlKRGNhVrlK^nkjA!HqL(Hi|bA2O$4}P(k4=r-te{i_1Z8bHwuRxC$whxUliIaTi+^R=4mPb9472f6K!&dga(WF`-x z)*J$&>zG;$RA)61;wA1Y?nUajl~mjg4Fc0=*tsu;pY9jju`oUS^|;UjZZv%L2a<(7gn54!}macW#Ud z)(lTyI-B`>DwBO~^iz?+p>l1{@^=dU-T3Dzp^AqCOs2I?s**09fvBpJ6wm*P$)mNe z#$V|L5r@k*(`zM?krT(3k_-n(^Zij3si}eJMA;<&)wSJcWwqP;IB<)7#?=i!C}M%Ff9focd<}&pawS>rpvKn1u(W1uU^v3S zSJC|?ou{?1U7RlD-SYSU1zJw^IU(Hz9+%sJa}~;U&WL@r6t{Go^{`veWOa8aGs!)* z7WHysXzdQECpbXhLgw(0kzSOo0PY96ZzdChGr7IenO6FZuGOo8ED9QgjDeytC;t@e zX!qu106M8^dPnnV5yTvQI3h6n>l@sMfAUd)QO$|CuU~0y07Xen7j5+82w5rQk(MHx zsA77Krb7YW6gq`&0xpX;`B}5!e{v)3p!@OsI$gf_nsZIo|Gd`evpr-UR4UYqbL+O6 z3H_9A{$31z^QDDMUga|_2L_hL(Hw+46h46)b4D}@DRab+*Zt)+XrSClq8>>#0!bXH zf0>4qISra@T@Hxj$Wil2kPE(!CI9~w$d?y4LZavY0OYYR%@aF5oj+ie5}%j#X%@$> z@QOMa?TeI^n|QYD=_XD1&S%dP&fOwab@kZlh7_JKDJAaXk~GKRU)(>fYpMTh*-O3V zZY+TpQVijq%e!0~DdWG?He=$aR<-x|EMBo|Lxppw^THxBwLKCSZtQb#h1yLEvIPU{ zhF@MgCa7i{{ieTb+E;*oGTuo_bf+nW!Q}Awwf?LW#1S{@uyG#%a_PJcm47SGE6fQz zaB}gk2fJj^<`?oswfpkg|GE?mI{&wF?EOOV;NpIR%`vdfTS-xH_* zH^u&hrn#zEGY~k6SLz?@Ih_S>YD%Q}d+=}IEX!65+ zK^ZBr=EJ3w<0JVJb=?n*Jo~i?=3^%Ov(PDm4+2?q=s9(4$N8=e=Eie)4H za4`Y)M3s=DgwLQjWS@5rR0fQloH%1Hd2QtBKx#&>7c8zGEG(`}h- zrqR``c;=5l0Wu7_rd!63<9<%PWj!y^aI_lzeO%UOOgwX^ey^)Wl37rqD)6Dp=F6uo zEkTpqUCjPcYbs&_ZvGZq;_``!e~tAKA)uHnjGF1b`KDCu6;g2TzmAx=Hbve*B0XBx(NiB z89x`9To&bmpH!)g=kYBHGR#6RlrUERxIuQ1RAe!As1zBrucVaZUbl&+NX2grBri1; zrUl^e68oD*>tO4G7LN;JuHty}%6|j6k{$B4GAaMyIn(wl`8U0Z*EqRsyHou;3*0Z8 z$fQlmQO&9S!}}?eho5vm<9n;9uFuu(*4JZdAG|KzTB3@ORz&6I8zFw<16Ov~uhBZP z{-q7gu6-a5D_G4&nG#{!>U_R!9O%vaOXdxGY%E8zL=*xJTUaP_jK;V|3*M3FjxZf8 zM$yskT33i{N4rNzzs7ajpg#Us@TzhRm_0p?t8|xHZ<6)s#AQ0m1U?|YF4!u&JKNh? zLEdQxu%U$ea7`(dxwX!ySOb4_;~%eqH{jHHUQDwP{B1WYE`6OA`y zx}Ze<-jQ8+)uV!S8QG0ksbsHx?ZkIe54#E>n{K#d5i*}}%}O%s>+y95R&$;~Q$0m{ zR4jYH>YuX?%HrSAVW$^`;~k52YaPaxu`(O!ndx&8pKS6O0;9eIVD?>lYrd5#D?Z+X zTkEZd`@FHW7ji8;M^G}y?kEDy6p5J4<4$(RZ|j=W%&5*Kw+aQhx3yso#y?!=|KwbY z$B%D^@;l4qkFBPPN5im#;kr3lS#*+r!)`Ku+gMOyjmlcV7qD@XyDT>P+VTnKV}FtS zXSZU)19>(TWZpPP&rxC>z5xFBD0J||Kr4v&nkB20Iap$1+({~c&sC^3V@W~R&Sz8W zR_l_cMN`}Usji&_fG$?;TWq}FQg_&bNb*Jhklh+U;76emzk5HzWVg_>4Dal$*D7SP z@q3(~)vq7F+JXTIGOb$vW{Tf@I%X||IPf1AP3}KP$FG&-f}2t#n-CMSFKOc5lRx%P zJ_hoe^dmiU#72Du24^`gj{T+btIkc+dkT--tLv=bYpcaAJ9l+-h|lF!xuNvOeu`tC z4Ox^?r7kT_@)`NhG=7DUgX!+J2>Kh-w+B>C*IyF6M1s#ZvG$JQG3L!jaBzpcRtb_W zc8C>AUK&|GJlOdnhCirKVHNtj|BdIpk6Jttw_P!tH`)qcDJWBgPW^4)L zAr@Pm>nGh-$e|3)B6jtX-8+-Z@^;4uoDW*ds19ZVyQHqgC)FL=1YyRhYV0u2O*|{F zc;g4+YZljw@GW(LW{&%t0xhZjE$_N;dyABvnX-OHKWbTfI1H41IaCC7S)%ZypNhx zZ>-OZtWX==)9zpJu+uhB;GAHo?!MUl>$*T1dQ6k1Valbsa0%;Ipt;v3D|klUuk^D> zxn@8=g8JXD=nH_a=s4qmD|^?4?{8-~yjOEOy)CERz+WIZGXBjQ-x`zUR8vfa-)^b@ z_eW%%u9T14X4V~zHpWA$M?RXoQarL4Fkl83I{QDU5e?BGC=;D3k5jFEKME2X8n$als zR-^TX#5^z6wC`id{u16DSFBtLTP_?{QZ=0o|EmIEjZesv(tlUa+%lDaaUseS`%6xv z#MI8Au5Zu<``3+z>wiDOX_iG zyv>R@SUWtfFP3Mz6(}w7N$vUevPs}pe{3vG-`@s1gnQnRBc=D*?Qjm-7_vz{E&rpL zNTsN@agQnyEz5TzExYb9;V<9vT^^^hz#V@GO$odt1^flnb zT>Wu1)ZaCkO`?0*Yamj_b+>qKQSxN(<@n6?D?pIL~?O+ z7dieytwHimfgV(pjtH~;SlntYgz_sWnJNL7Q#m?JF}?etSX%9ljQL-wlpopwwh0IFRRl#A>jncc>46WQhjN~0LGU^5sj1ItmGwMc#q#$A+dFnJ~abCH~Zjo9T+-^r9-8Zx=V| zJt?23fB7OLEG#S_5Mf?KQiWw*E$;97UPxtuLw7m#Mikw+g?r zYxdj=mB{RcY;)C|{&!JPU%w35>0Q`(m}K8~g|$2zX3E9J#{Ot*R8&xSw(7cl{Djm` zqHol1==TranaO(ZC&o`p(p~rXhr)x_RgbL?Hha|>M4Pfj)stlrCffb9{03hf;}%c% zom}Rdd|PBK9(=EoG^}p7JFLI(elq+4Po(wry2XzvEY%*$ED3>0{DjHdS_Uu`~pVz6x z89!Pf>D;Hi`d@<$`>r_0RR-)9_AWT|WRQ*=bZt4D4xEt))$Geg`W+tlKK&LI9sT`_ z{&#Wob|&G{OjwhK2@U{KCS*K$seAcN#rK zXBKk!G$|_7ylsw})&__R3m2N#hd3DiLLJkO)X>$_dssFCX$Y)m&z}9>M69i?@2Q~O z|NHj?4H{n~ZZ(pjD(`&0wn*u1`Vngt6%{Lem{~Jh;0rm#1B>DCEs{rJR*+Mf<-uO5BN?>FK$j-rXF~(0L6FtLt;#r@6DU zbYvNJoO1bhmA|V*GD~EWb`4pZzGb=Ec*mS|*CD6oerTS_bO7V?JZp@ik=_(d#8s2lA$)JoHnwp?ORoJ1F zk+xZiE8HkOVz(zj0&y51CnuMqlPk~r8|CWq^*$8tld|=6q(0q?HF8tkl92FRYR=Ro zXv{w^Dgn@KoPKx54SgiC)RmvvkgzQe7f7A@OC=KCLd0oimZO=e27@46ecmpPVvh#h z+KPR1xT7n7JKvVffz3Jb#9%11j7-e?FFrU4J zOqgdCbg;7qMH$ReK40LXFi6!FEV`ZDWv4^JNA@0wCQ_WZS?*7;C)8vbG`TwV@XOTW zGeWE97#M~K?VY9l!sxH0efjc5BVCd973XL&0Vj|cfOIIFrs^SF#TW@YlqwVWeTkQ@?OEu(#|)$x{y_riL`TbbGvhgEH&>NuB8zA z5QZyS+`4s(gjHAj&50XYS~QD!ayLL7@U@91@(;G?J2|bup?hFqVOad|v7FG7yh;Y0 zfKj>6+C?@FIH2O>^ov?>bF5>!1yB=2Ljp( zzgka)GGR5JTJup$k@AH#NJvQN>(ff?pp|Eb&PR(|&>g5~sJ;66y$a-#5@?Q0wPd%d zu}haOg)?6JJ)m5kA9eOptFWJjo*rTDcK$$OLc(CBr#;Oxdz$C+d@M( zY|&NEz(?+Vj0FWC>`z=s8`YNQ& zz|+sq&%@>sAAUQ2l2U5DnH|p4Jp%&+5IJ~~@t}Q2xLoVAxZ9vqeI>D?Dh9 zZa(~tz*ovWJpm2w9wU+PpzlYWu%Gpdz(%qJZgvw^0`}3Q#t$ZR^IpD8hD?Sh1V{vf z%IjdDQBoz@Ib>_lv{zN8dg?e>TS9^r%%pQgH39^MiN==KJeUrGlWuFob-(J(RF~bI zj)8gs;29Ecp)T6u9Y%Km($?AvLRK|!vc?4S=;(1uW(h)H z^_T7rbFKg80-WUr_yqP!Q16RbcbWnmOArJ>%E5?X-^Wg8-Iz2~q9$6# zFfcH{et1-k!50$FK2Int^auo+z@EWQ2)j()NHGq{H!fO33(ih+OtY*KtfY~tBS2#pED7({Mq zKp2h=dPYH$N5GObU_!wMAoPcXGc(E@@cx+;TLn~-KqUbb#K6PKxJ@??4i3V-el{qv zYYbz6GmNR;Q1d*brQ*}pMr`GnLfafpE-q^r(nb(Q6O(a(!|{CjZUB@4pnN^qq-z>@ z&ORKi`v!L#Vp6Gyp!yIB$QV8^>E)oM)dy@MA$8E~>m!2f1HSCvHoVdNlE#Xmrn#kM zp+6^n%n9==cSNL1%A#n9l0~W<7DPFQ3(#uUyKB6Jn-)**1R;mSSP+s(#%oBzb9bc} zS9&ZwAtnzx+?|6`#r>KI?=mu{2xMV8Pv{>oO^McoiNtsX5(W|LFlYrJxye_0N%PZA ztvftD?C7v;JinnLiEZzv@R8+TCVZDJ*^ZW9VY9^TZ5{P`pONvysjRK=yjIz90+1;i zsWA~8v4%;YV@}=`Mh=M|N1e*xM~UQ9BN?P0!5F~tU=RM$&`@9}UYtd4q@nS8%*kfl zAw0D5NSPXxjzP9GsFY$|mY|)f_5B5~U!-gxbyUbMsWdfh?fd}<&w=O;5|I9qm;XJD zfgD{y`Q~1ASsWOq)AA`$&qOv6_vX!;loXSx_$9XrnD$6VN2i;q#w>A+cw`P>Pf$gt zSc}NCTZNr`pCdfX#fxcb$rARzK7Y`a0kN(=7%>vig&P5uDiCoGkOc`F{IS~v&`oTM zW#Nzm>=YSt#H*?%D~l&9k|?dbJi#=ePEKwncZi1dJ;!uQC$L?d(tB^J?Zj zB%m3cI*Bi#N$6m0{S^d(;#IK$A4^QVdLR=+MO zo3!8NDlQ28IR%m{i5-udxFfTAe=4(Y80osF5wc!`qd<^HM~#*n;H>;)l7oyuZT~e8 z8Mq8+rMxf6Fo)$Gbq`D~{<(bzZge7}fDyiFN^%u94Hc%Jg)i1j6jfPtti@&K#&w}e+Vt|%nlYQ85yGY>`M>0#S0g+4*W za+icb((5zk6$HhJ*?!oS=(yg<4p4lfpg zUAP#0ibZM=tER4@p#h@H9(-QhE!cg6jYRfv2UYHhg8IROX7Ny{B?<49q~v7W!A94~t#6U}<+%TpoYUibVqEaSxOdvy*l&z3& zzedgU!(5>pMS8nURs~PJ%*zIXHP<67)xO=rQD#piB!?i(b3+{ zPYv2zNRO291Nz>?cjjDsY0kup)9mBWdj5#u>y2C^htcxa)b|g6b-~wI3`&#SdsuJe z;ZY1_omNB6!Z-1@MMJCC(-|MMCtLw-4>OFXSGL?eY%*@LQ2K`GemQ$)B9RL`=~^(f z2lY%A78U>q!ubuQKnCSeHd)VV#*qC01PZNtC zRk0Hi!?qV|-L*`yj5q58XoUjjRiU-vEcIz2=Vg~S1cNDDJco+7Y3WhUvQC= z79J@hf&nPzZe^4HV9f}vz+pmrmCMwz=Q7`M#3f92MOTW34nUiomH(A*irEhVD@HAU zf`XA|Y5Bur*7RwN>@rU1VL?x`YF$@!L@9OIhK_%6%&-yX1Uq9wG z`+hO&7P^Y#mcF7=E7c3I;+28>USLcC*D%BhJXWM8nkZpmn+BBzVDU832RLX!&fB@m zJ>tj-rj=_UA-jB&facD~U#{-Iy*uW;b-@Y~W67()s1{lbto-&%D z9P{MKliiIuIvSd4=xD_(=`}qONb&@U)NB3a2RHz+Q_o7mMi_5VzJKf}L0@;KNLM)w z6>_P*19jqpsO^0MnxeN(3P3OD!r?{0H~5i>!w`1?JK+iCk%Qe00+|U19V${jef*e> zeE0V4ZVPwnRmb5?kYzFp?ho?tMXs){9x^dd`e@m5Cb1)gyvzPQ^6u?VYRS;^8W>I{ zjvgU{;)fb)YHEQ0Qv%joS|@zhem7)mX39WgXdk3wxLZE1xvWvnM!2P=g>X>X---au zv9L(Ncy$M~)N=|ZosPg0vPZe@gFrUTxPf%QLJ=CGLDpP>K`tojXDyaw-gS?tjQsrBBgL=Z%fnN;f;BZk z;I_KcmF!aus(sc#^R>5?}c=BpNIo)Mt+r zSTupQSzBb&T7g3VgPaIVluo|MviF@>R&8yd8lW|7yr5~s977*9b}X`ZSJ~AS&=E*7 zV6S9RM*~Rwf;FxK$w1nW*Y=U;HCdOT2?pWhMsmOBm?6PkSjSWWdq zXS-~lTj?lEbA>;ET^uiWwHPgT1-+_CGxPQFaUfWiMk_Yp7=mZT(ikM~>tRZ87r`ym zH8QFOWeFxSq)KzuSw1DI+$az9&}YN4Jg|KLd@{MyZ*1*vq~fK3JpeBD0DjC6JF$uK zkJ*IPfJNPhiPh;79hPiHPlhAsEx9Bm{b9p4Un>Pi*`x-F%E%lNCWQmoU;WbILVEdi z8eh6(u0e_4dJ8wKXwc!oF8JN1Ac0U)ULStJ?#0f|4iuS>SGaQv3Oa*F1xzm_AUX59}cJ$60m23Mbhu^n7Z@ z!DOi?isk~$2Yz93sK{m)OY-FXnBFA31%TpGU-nrRYr~sF<*d7^))HPzS^bD?0DaW* zg@uK1>Ke+L?!CTo23UqlQNPW3@H8M-5|n8U9O$tMcN;)0>l+*OV}cigqX26I-?9uM z0vl!s#0+pOU_F2*;9915lf=c;eEwQHrd%6IPrza@T;OEN8eFMIzzFW0C6Bskq z1fdVG4bTS{2Mrd^t*m^ltH1=`q7H`AuP^KiA_v2 zJXCP=rh%!H7Z*F_-@F&j^8QQz*Q#H+7!0}%C5zvXbrc-|3Z_*y^NbzMgo2cW6Rgbf0B zc-6AbWz9zltp5nGCV=JR1&q=DIVVlQJ=i>RKUB>c!ghLx$zak5+Xx1Zido`EcGo2D zR~(NF;bmf|`t$ycU3V(*GpfdjV&2-(O}2Jo`J|r|hN;EI#T|k{1(bR~Obe41^Z=#6 z_k8cUav`|6FaYEhcs=amKo_Rk-{ADtn1shQHsxr{$gw~AeH0`U=(yzTt``Lb1vxnt z{G}x&_kqm-)qRNOH!G18f zll)S8N)UCha^jkX%XG=T305n5ev)i(i?|Ni}34@+~I8sM?x0|PQ( zIbi$l0zN1f15TuU*=xk16po22S|pav?V^IdB3&U=&0$v@i-yR^$)UF|2GPBKaaPnk z18E29I$6y|QBe^DG!VaNV%UV`n|wc;WiD_OvIj}ea#iCG_W@e&!d#Mt{3+ny?!J{L zc`M~xSbA3i^cAq25REHAg+fCRz@ckOtbqiUIQM=YPb)|Z2^*YlvU<$%d;Wo7KfI~} z;gG;`rRP_Jo~3+_f^e%Mu55Ngiz)@?oPXfCLl8wlO@R098m#ZKhHV&MW{Y7p8~;y9#dK!hZ1t0e4PM zCv;d#ovH}|)YJ%O|Ki1qkVPZ;`L*5qBitM_x@SS#cC@rGh&i1{*O|lYs8X=QiCJFw1~sX<825qL|!K}qOn%_*Ai z-ydED!&n5z=PmplT%Ka?v@zh=07tUP7-j}sr0|Nkyki3C6i6g39o?(6dF-jO);F=S zn@d9*(=9PzKoHwQHs=%wI|eha#(*~l8pvLZ#TX_OK(#8UWI|kFuN9sU@I?>+9>AVq za6n*b2q6!kZUJN_UbT&W^XA-S%=tV2#PcW*65J>M6Vcm0e>c8u#;c0`;>Fg9gc7tA zOC${4(&PJVcLP9l&x*pbhXfUi)WfTch_zRyKY@w3U%Q9SiDIJSM;`+i^BIZU9wpVW zQ|lwH^@RFhF96&SW|IOu796#?xe0C_>61zCq&p(c zf;cUIEUdCWM;8I?<|YOhNeBzv4G5^#8E1xoJuHW^!lhz)AO#80XNlx*PA>`zg`rS; zMk1%*FW+81=Q|Yo8RdOZ8jDevQFi`zmt+^XQz;g8g1j*?JAu!u_#KQ!alr069EKek zZ402JEE>arArh?Fybo9~f>pNGZ_7?UZVsFR@SEOO8f$5BkR02PG>eOkg?h$I1KnAm zIv(tkv{I)DOy9ag@)p9;N#gG9)*ApCM?b32Y0$tVPdGgf%0_Simpg%%jG}}{WGr9X z$B!SOHkn*Sr(Dta?%h-MlJ`0wh6I`f^mxrczb$O%mSiRu5A+>EtqnSevTRYlTTD=A ztTD;2pUNWDpxX)l;miZotpG?Nu1<*DL{z|O$y(()KC|{hf+G2*deiJ8%S=nIee?z-!5Cg*B7Oe+2L|8QGTf4D8subapT@OOHuj`Xtj zflvXJN{}fCL=_i%02l*_up9g`1rAT!L`Bt^B(CtQ18+NKksQ1JAL_kTUsOVvC40?^ zq4Mf%?BaC=W@AQ?$VxEkaFM>nEocWoNiTGPiwoibHeh(7Ii%NqoCU0#e@8g`ztLU+ z;GP4fFs<>s#>Qqo_{AJT+|{sgWfMXEV21!t+gMq37wgNsJ|U@eti%keF;V=kwQ;>MU z+MVA(%ilg5p2lO6U$!n>)`WU(HMH!tK`$fC5;?GVjWIl^44rim0?=zdM31eAO;KBZ zC4RTGU!*;R5%6+60s@`Q&8s*;<>+Ev(9jE}$AVK+Ayg;IuUJ$^8MBb^ZIkL~Ed?wR~JM?$pO&%haNNyvI%y4*5Pu_drp~Y3LvF9F)3xZlh`1!>#$1!m3Kvq29$B`o zdU}$@Q(HYFI0|quM70l~)z&_k^Ab7`Q)vPfoUYW1826T3Z}SYMR?wLDvNR03c#xil zR%HlRl<>}E>UJuduy0Oh;>jvrc=d_>={M?0N+x+!J9l_A$1^c>v8&K3=}Xq<^>WTB zFg~iAY~m*zpFKL|=*z5Q^oqvM!h4n3AzQv$V@{H?hu>%FJF(wTc`C@ZKaK{L_ZENX zgJ-!?d?fOd`x?){^Z;kJRW*SlaK3*jiW5(#00!uP zF*}-zKT#_`ebqLjB4X9|3k7p+Jw4%hGaCPkqaE%^Xwm)9p?i{wB`GCE1|AwPatyb6 z4?a9H62ea3q35^KUXi7KUci|_OcbDW<+14?c{+we-MD#m#9oZo z;OA0HmzA`&pVzaI$Un+FPJikI)NFKELoCYO-CaQ=VE)7NVw72NRgky_ zui;OGpHvE{eo#TcQ9%spzT`0oT=XtfW8zmlB+e-SDIxXgi$J_e+zjxcrMAf-u|p2C zu6(bo&&RZAHCX6Bxd35WqQMtPq*|s(u*X@4KH{9upTl69WnbnOCF6UBnU||g@(CBL z4%hD61&N<=V>or9G}HH2aMoFcZtJ3FaeA#7C+i~dixMR9oRF;BUayJ#-6YbY`bh zdoy0oNc(~kGT9QOjxLWdtkEv8i~|y*&aUs##La$uO^M>#5k9Wh-Ye_?(Vsw6!^v?`ywdX}u5(c_!;(B^k~l^rx`69%xsa_`GQ~8;KlPgz2>H zkxP)DW-StVCWR*yiF^mDxG4mQEaaQO!WrbvYF;}B+dWc_M7A^K6Ku$@HOZ^0O3!W- zX7IIzrV*X_cq+X!|9Bx^;$5y;!zlZ{?(|x$BeX-A81)fEBKJ~0dghNo-zhb!E=^P zwW<|%Rii1r_Pkl2Hs|Xe_xr`+5WdzsB#}*C9sQ#~rdP1-7F!U_(kuC-dL6T`cINKz z=FK`X7IH2Afr8|#jHga0Xnbe#yefU-I@_9|Dv{5juxiEd?j-iRDEiVXJxx!JEAGDZ~8`%!t7*|GZ?(heW>1ms>HHtt-<= zEyXdfb_x4D+p7>7)HM}eiR8F%9BWnl@k-szkoNS5rk;e9*gJJ2aMSBuxKCA<+Cit` zu(QM{j%(1F?XCOO0O{2_ce7660cXHmmSRXfY``WXr;ekJUWO23_~Rd(HP<<#QZIs< z_C3c2J!^uBaxSy@)zJB|9pBiu(0P)AV=<^SXYmZ0A6jo`8qnw?eRAV0h4jiT%o6RS z&GFP?NF(ywN^P-x5cDt^w&=LWXGS_AIVSqzm&1YHWnbg((l9^XnUP?5UNwKLWLC*| zmsu#vnSGG+Y|W8S-`oB*CYAOZ&7KB*t`e(XL1@op1Hd!LeH=D?{4p0noozO2Oxa!MFiAmWuDg7TO`7-Xfjni!?vv+uIJ`snkCy$+9 zDt)`>*0%1)b;mUxqzE)ip$S2;zOlv8XP0%HbIc#NTl7H6m>z#PZkt_O8z|Iy)a0=# zNhPZ~4wMan_S#-1_ZGZxOF=;ag0oGLtW@4g7jA(I*xcM)4m^0dQ7=1^st$a&Pt389x<7v?HP|;iyE8-O44mL??#l^+{yc-y7Ahv;`{t#%S z&S^$S&-wErp@>v+d@Ass=+wXea^#-q;`V%3{Wwy#_WGyZeXe#!ks6siWH?PFh)P3)a+ob3fF^lv zl!?6Tm^49qE029NEkcuQ%h8mYh{Z69iz2TgqEU3jT3mrS^H%F6G|!%+3mH|O1~zLAw~`lNnp@(SZxrzcm;OSn>P zTExnu(6rUTqb*}u7AdbGdkcv)a%bLXtWZpDLVZ9KjzuJ;Mck)p_=^Z;xFIL?md14P zAiMY6iBn*d4JJhx!yOE{yx~L2=^V;VWrS4V+;={ImM2p=orRFS+}r4T>KR;UB10om zqsjByH`|?06%oJeAVacy%1}uovqXJU^S@ixOX&F}m1vH@HUjJpcVQ(w}!wbe2d zL;R012g3~%c-tn3sg;FxKb$VO@sV`QOz6w9s5J6xwSFi|rTO@EoF6gdT@#+$kXl1& z#Ys-h^jc;Xd+>|2=4$(bfE3j!TU@)vyfKxCrJcSSu+ipuD^=y39JNRBjbCva67Uff3pcF)FJt|X ziHz4SuO!qAzl6T*qi0}fLGo^nI&07{lrFrYbZ%$L9E%7Gg9klF^lfnq7@oqhZ4X-d z)2AUb5i+fPrC6XOnN|fbUcL2#uNU|Y%e2$Xo|_45X%W=&k&Xs@v@E9A2C_un-W5#@ zH!lMJ!Yk1ibrpO}rV&AXc7pq0Q(^$=-H?baF$f9G^K_5_4VmlXFc{-cBJH$OUjhFs zmvD*UPmt!kuk8-$vy$E+zCNoF5C)_pMD{f9I?7E;fX=%9@!rHXvD{<=HlC|B4M zBi*xqa?u~=#zBn+b1m$T!Tv2-D+USU=|3cn>IYc;&RP!T9^F5>%Czq^C9mK8nOk<2 z<=j8zZqq2R_{WF%qC9fInF7%M?kDtA9C7MRCu>jqIRAkObZx#aHBsrG=O#YwIlV$V zy-Cm^@-KKo+Y@I%SLIlbUuTXKp8+G>`Os2a%NCUHX_jF1zV24y4I1PJ``auPY8o`- z6%orzq4s@iDj7*n-crkVZB7?wf?8sEMXmNyE1F~21_~+i2XgYWQ1Q1b6q!|B+X;Fn zV$mp8vkzQ9otscZQtRt1r&cdkXQZ@Qp>>)_PKn&oQ9M6A3srWN%e&5I?2pkQEz3d` z6Xzt{4uAdn_2)|YE*!1GYjeQGtBuvJ!^_Rhx8XtMsH;t!EiE}l9Mx?egBk@VTY9m(O~Uh&m@JeoEl>guRs5l8O{jt)2dPeJz#rh^qy(pdD!%g-PKaYqvnat( zL897!i9!xHdXRF$L-;II8d;FLG1l7Vi6nOD Date: Sat, 10 Apr 2021 11:40:13 +0200 Subject: [PATCH 24/93] style, minor refactor --- .pylintrc | 584 ++++++++++++++++++++++++++++++++++++ PythonRobotics/turtlebot.py | 30 +- PythonRobotics/vectors.py | 6 +- tests/test_vectors.py | 24 +- 4 files changed, 611 insertions(+), 33 deletions(-) create mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000000..c167893ce3 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,584 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + deprecated-operator-function, + deprecated-urllib-function, + xreadlines-attribute, + deprecated-sys-function, + exception-escape, + comprehension-escape, + invalid-name, + missing-function-docstring, + missing-class-docstring + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[STRING] + +# This flag controls whether the implicit-str-concat-in-sequence should +# generate a warning on implicit string concatenation in sequences defined over +# several lines. +check-str-concat-over-line-jumps=no + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[LOGGING] + +# Format style used to check logging format string. `old` means using % +# formatting, `new` is for `{}` formatting,and `fstr` is for f-strings. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index e2150b5c75..60bad5c5bc 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -7,13 +7,11 @@ """ -import numpy as np - +from collections import namedtuple from numpy import sin, cos, sign +import numpy as np import pandas as pd -from collections import namedtuple - show_animation = True # define state object @@ -26,10 +24,10 @@ class Robot: Robot kinematics simulator """ # numbers for Robotis Turtlebot3 - _v_max = 0.22 # [m/s] - _omega_max = 2.84 # [rad/s] - _accel_angular = 0.1 # [rad/s2] - _accel_linear = 0.1 # [m/s2] + v_max = 0.22 # [m/s] + omega_max = 2.84 # [rad/s] + accel_angular = 0.1 # [rad/s2] + accel_linear = 0.1 # [m/s2] _wheel_diameter = 66e-3 # [m] _wheel_distance = 160e-3 # [m] @@ -47,8 +45,8 @@ def __init__(self, def set_velocity(self, v, omega): """ set target velocities """ - self._omega_target = np.clip(omega, -self._omega_max, self._omega_max) - self._v_target = np.clip(v, 0, self._v_max) + self._omega_target = np.clip(omega, -self.omega_max, self.omega_max) + self._v_target = np.clip(v, 0, self.v_max) def step(self, dt=0.1): """ perform simulation step with timestep dt """ @@ -56,7 +54,7 @@ def step(self, dt=0.1): s = self.state # start state # update linear speed - delta_v = self._accel_linear * dt # dv is an absolute number + delta_v = self.accel_linear * dt # dv is an absolute number v_error = self._v_target - s.v if abs(v_error) > delta_v: v_new = s.v + sign(v_error) * delta_v @@ -64,7 +62,7 @@ def step(self, dt=0.1): v_new = s.v + v_error # update angular speed - delta_omega = self._accel_angular * dt + delta_omega = self.accel_angular * dt omega_error = self._omega_target - s.omega if abs(omega_error) > delta_omega: omega_new = s.omega + sign(omega_error) * delta_omega @@ -113,10 +111,10 @@ def main(): bot = Robot() # high acceleration ~ instantaneous velocity - bot._accel_angular = 1000 # high angular acceleration - bot._accel_linear = 1 # constant linear acceleration - bot._v_max = 10 # max linear velocity - bot._omega_max = 2*np.pi # max angular velocity + bot.accel_angular = 1000 # high angular acceleration + bot.accel_linear = 1 # constant linear acceleration + bot.v_max = 10 # max linear velocity + bot.omega_max = 2*np.pi # max angular velocity omega = 2*np.pi # rad/s diff --git a/PythonRobotics/vectors.py b/PythonRobotics/vectors.py index ff94a47780..3bfb77249a 100644 --- a/PythonRobotics/vectors.py +++ b/PythonRobotics/vectors.py @@ -17,9 +17,9 @@ * [vector.py](https://github.com/betados/vector_2d/blob/develop/vector_2d/vector.py) """ -import numpy as np -from math import atan2, hypot, pi, acos, sin, cos +from math import atan2, hypot, sin, cos +import numpy as np def rotation_matrix(theta): """ create 2-d rotation matrix """ @@ -33,7 +33,7 @@ def __init__(self, *args): """ create from x,y or xy """ try: self.x, self.y = float(args[0]), float(args[1]) - except: + except IndexError: self.x = float(args[0][0]) self.y = float(args[0][1]) diff --git a/tests/test_vectors.py b/tests/test_vectors.py index 17984799cc..44ec7faf50 100644 --- a/tests/test_vectors.py +++ b/tests/test_vectors.py @@ -5,10 +5,9 @@ author - Jev Kuznetsov """ - -from PythonRobotics.vectors import Vector, point_on_line import numpy as np -import math +from PythonRobotics.vectors import Vector, point_on_line + # test tuples ta = (1, 2) @@ -52,8 +51,7 @@ def test_mul(): def test_div(): - a = Vector(1, 2) - assert a / 2 == Vector(0.5, 1.) + assert Vector(1, 2) / 2 == Vector(0.5, 1.) def test_neg(): @@ -67,7 +65,6 @@ def test_length(): def test_angle(): - assert c.angle(c) == 0 assert d.angle(c) == np.pi/2 assert d.angle(e) == np.pi/4 @@ -75,7 +72,6 @@ def test_angle(): def test_from_polar(): - assert Vector.from_polar(1, 0) == Vector(1, 0) @@ -87,15 +83,15 @@ def test_subscriptable(): def test_projection(): - a = Vector(1, 1) - b = Vector(3, 3) - c = Vector(1, 3) + pa = Vector(1, 1) + pb = Vector(3, 3) + pc = Vector(1, 3) - assert point_on_line(a, b, c) == Vector(2, 2) + assert point_on_line(pa, pb, pc) == Vector(2, 2) def test_rotation(): - a = Vector(1, 0) - assert a.rotate(np.pi/2).round() == Vector(0, 1) - assert a.rotate(-np.pi/4) == Vector.from_polar(1, -np.pi/4) + v = Vector(1, 0) + assert v.rotate(np.pi/2).round() == Vector(0, 1) + assert v.rotate(-np.pi/4) == Vector.from_polar(1, -np.pi/4) From 46536791a186f498f678866e82ef40de8952f988 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 11:50:31 +0200 Subject: [PATCH 25/93] style --- PythonRobotics/vectors.py | 1 + 1 file changed, 1 insertion(+) diff --git a/PythonRobotics/vectors.py b/PythonRobotics/vectors.py index 3bfb77249a..a4cbc8ca52 100644 --- a/PythonRobotics/vectors.py +++ b/PythonRobotics/vectors.py @@ -21,6 +21,7 @@ from math import atan2, hypot, sin, cos import numpy as np + def rotation_matrix(theta): """ create 2-d rotation matrix """ return np.array([[cos(theta), sin(theta)], [-sin(theta), cos(theta)]]) From d40d6c7d02faff11661b139fc2a4eac173d79b1a Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 12:22:14 +0200 Subject: [PATCH 26/93] created PythonRobotics library, added turtlebot simulator and install file --- .gitignore | 3 +++ README.md | 7 +++++-- environment.yml | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index c971b8f9c5..92b7aa398c 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,6 @@ target/ matplotrecorder/* .vscode/settings.json + +# scratchpad files +scratch/ \ No newline at end of file diff --git a/README.md b/README.md index 804966d121..df33aba691 100644 --- a/README.md +++ b/README.md @@ -147,10 +147,13 @@ using pip : > pip install -r requirements.txt +3. Add this library to the environment (in development mode) -3. Execute python script in each directory. +> pip install -e . -4. Add star to this repo if you like it :smiley:. +4. Execute python script in each directory. + +5. Add star to this repo if you like it :smiley:. # Localization diff --git a/environment.yml b/environment.yml index 1441b79080..3eb7663906 100644 --- a/environment.yml +++ b/environment.yml @@ -1,4 +1,4 @@ -name: python_robotics +name: robotics channels: - conda-forge dependencies: From e5e08100c55ce6142a15671b387f546fac0f3d74 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 12:22:43 +0200 Subject: [PATCH 27/93] added library and setup file --- PythonRobotics/__init__.py | 0 PythonRobotics/turtlebot.py | 96 +++++++++++++++++++++++++++++++++++++ setup.py | 17 +++++++ 3 files changed, 113 insertions(+) create mode 100644 PythonRobotics/__init__.py create mode 100644 PythonRobotics/turtlebot.py create mode 100644 setup.py diff --git a/PythonRobotics/__init__.py b/PythonRobotics/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py new file mode 100644 index 0000000000..20d275ef24 --- /dev/null +++ b/PythonRobotics/turtlebot.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +""" + +Simulator of a differantial drive robot, Turtlebot3 - like + +Author - Jev Kuznetsov + +""" + +import numpy as np +from numpy import sin, cos, sign +import pandas as pd + +from collections import namedtuple + +# define state object +state_fields = ['x','y','phi','v','omega','t'] +State = namedtuple('State', state_fields, defaults = (0.,) * len(state_fields)) + + + +class Robot: + """ + Robot kinematics simulator + """ + # numbers for Robotis Turtlebot3 + _v_max = 1.0#0.22 # [m/s] + _omega_max = 2.84 # [rad/s] + _accel_angular = 0.1 # [rad/s2] + _accel_linear = 0.1 # [m/s2] + _wheel_diameter = 66e-3 # [m] + _wheel_distance = 160e-3 # [m] + + def __init__(self, + x=0., # x-position + y=0., + phi=0., + name='Turtlebot3' ): + + """ initiate robot at given location and orientation """ + self.name = name + self._states = [State(x,y,phi)] + + self._omega_target = 0. + self._v_target = 0. + + def set_velocity(self,v,omega): + """ set target velocities """ + self._omega_target = np.clip(omega, -self._omega_max, self._omega_max) + self._v_target = np.clip(v, 0, self._v_max) + + def step(self, dt = 0.1): + """ perform simulation step with timestep dt """ + + s = self.state # start state + + # update linear speed + dv = self._accel_linear * dt # dv is an absolute number + v_error = self._v_target - s.v + if abs(v_error) > dv: + v_new = s.v + sign(v_error) * dv + else: + v_new = s.v + v_error + + + s_new = State(x = s.x + s.v*cos(s.phi)*dt, + y = s.y + s.v*sin(s.phi)*dt, + phi = s.phi + s.omega*dt, + t = s.t + dt, + v = v_new) + + self._states.append(s_new) + + @property + def states(self) -> pd.DataFrame: + """ get history of all states as pandas DataFrame """ + + cols = state_fields[:-1] + data = {} + + for var_name in cols: + data[var_name] = [getattr(s,var_name) for s in self._states] + + t = pd.Index(name='time',data=[s.t for s in self._states]) + + df = pd.DataFrame(data, index=t) + + return df + + @property + def state(self): + """ last known state """ + return self._states[-1] + + def __repr__(self): + return f'{self.name} {self.state})' diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000..202cb63f59 --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + + +from setuptools import setup, find_packages + + +setup( + name='PythonRobotics', + version = '1.0.1', + description="Python implementation of robotics algoritms", + author= "Atsushi Sakai et al.", + packages=find_packages(include=['PythonRobotics']), + url = "https://github.com/AtsushiSakai/PythonRobotics", + install_requires=[], + license='MIT' + ) \ No newline at end of file From 31496072368772deefbab2bed4c812235ac1a62a Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 12:24:40 +0200 Subject: [PATCH 28/93] fixed speed calculations --- PythonRobotics/turtlebot.py | 78 ++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index 20d275ef24..3fe19fe02a 100644 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -14,9 +14,8 @@ from collections import namedtuple # define state object -state_fields = ['x','y','phi','v','omega','t'] -State = namedtuple('State', state_fields, defaults = (0.,) * len(state_fields)) - +state_fields = ['x', 'y', 'phi', 'v', 'omega', 't'] +State = namedtuple('State', state_fields, defaults=(0.,) * len(state_fields)) class Robot: @@ -24,67 +23,76 @@ class Robot: Robot kinematics simulator """ # numbers for Robotis Turtlebot3 - _v_max = 1.0#0.22 # [m/s] - _omega_max = 2.84 # [rad/s] - _accel_angular = 0.1 # [rad/s2] - _accel_linear = 0.1 # [m/s2] - _wheel_diameter = 66e-3 # [m] - _wheel_distance = 160e-3 # [m] - - def __init__(self, - x=0., # x-position - y=0., - phi=0., - name='Turtlebot3' ): - + _v_max = 0.22 # [m/s] + _omega_max = 2.84 # [rad/s] + _accel_angular = 0.1 # [rad/s2] + _accel_linear = 0.1 # [m/s2] + _wheel_diameter = 66e-3 # [m] + _wheel_distance = 160e-3 # [m] + + def __init__(self, + x=0., # x-position + y=0., + phi=0., + name='Turtlebot3'): """ initiate robot at given location and orientation """ self.name = name - self._states = [State(x,y,phi)] + self._states = [State(x, y, phi)] self._omega_target = 0. self._v_target = 0. - def set_velocity(self,v,omega): + def set_velocity(self, v, omega): """ set target velocities """ self._omega_target = np.clip(omega, -self._omega_max, self._omega_max) self._v_target = np.clip(v, 0, self._v_max) - def step(self, dt = 0.1): + def step(self, dt=0.1): """ perform simulation step with timestep dt """ - - s = self.state # start state + + s = self.state # start state # update linear speed - dv = self._accel_linear * dt # dv is an absolute number + delta_v = self._accel_linear * dt # dv is an absolute number v_error = self._v_target - s.v - if abs(v_error) > dv: - v_new = s.v + sign(v_error) * dv + if abs(v_error) > delta_v: + v_new = s.v + sign(v_error) * delta_v else: v_new = s.v + v_error + # update angular speed + delta_omega = self._accel_angular * dt + omega_error = self._omega_target - s.omega + if abs(omega_error) > delta_omega: + omega_new = s.omega + sign(omega_error) * delta_omega + else: + omega_new = s.omega + omega_error + - s_new = State(x = s.x + s.v*cos(s.phi)*dt, - y = s.y + s.v*sin(s.phi)*dt, - phi = s.phi + s.omega*dt, - t = s.t + dt, - v = v_new) + # create new state + s_new = State(x=s.x + s.v*cos(s.phi)*dt, + y=s.y + s.v*sin(s.phi)*dt, + phi=s.phi + s.omega*dt, + t=s.t + dt, + v=v_new, + omega=omega_new) self._states.append(s_new) @property def states(self) -> pd.DataFrame: """ get history of all states as pandas DataFrame """ - + cols = state_fields[:-1] - data = {} - + data = {} + for var_name in cols: - data[var_name] = [getattr(s,var_name) for s in self._states] + data[var_name] = [getattr(s, var_name) for s in self._states] - t = pd.Index(name='time',data=[s.t for s in self._states]) + t = pd.Index(name='time', data=[s.t for s in self._states]) df = pd.DataFrame(data, index=t) - + return df @property From c5776b0ee12359611f1364c2fb94b915c50052d0 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 12:25:03 +0200 Subject: [PATCH 29/93] minor tidying up --- .gitignore | 5 ++++- PythonRobotics/turtlebot.py | 5 ++--- setup.py | 18 +++++++++--------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 92b7aa398c..bc5469c3e8 100644 --- a/.gitignore +++ b/.gitignore @@ -73,4 +73,7 @@ matplotrecorder/* .vscode/settings.json # scratchpad files -scratch/ \ No newline at end of file +scratch/ + +# IDE files +.vscode/ diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index 3fe19fe02a..a4b0d5e866 100644 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -23,7 +23,7 @@ class Robot: Robot kinematics simulator """ # numbers for Robotis Turtlebot3 - _v_max = 0.22 # [m/s] + _v_max = 0.22 # [m/s] _omega_max = 2.84 # [rad/s] _accel_angular = 0.1 # [rad/s2] _accel_linear = 0.1 # [m/s2] @@ -61,14 +61,13 @@ def step(self, dt=0.1): v_new = s.v + v_error # update angular speed - delta_omega = self._accel_angular * dt + delta_omega = self._accel_angular * dt omega_error = self._omega_target - s.omega if abs(omega_error) > delta_omega: omega_new = s.omega + sign(omega_error) * delta_omega else: omega_new = s.omega + omega_error - # create new state s_new = State(x=s.x + s.v*cos(s.phi)*dt, y=s.y + s.v*sin(s.phi)*dt, diff --git a/setup.py b/setup.py index 202cb63f59..cfd77c1cfd 100644 --- a/setup.py +++ b/setup.py @@ -6,12 +6,12 @@ setup( - name='PythonRobotics', - version = '1.0.1', - description="Python implementation of robotics algoritms", - author= "Atsushi Sakai et al.", - packages=find_packages(include=['PythonRobotics']), - url = "https://github.com/AtsushiSakai/PythonRobotics", - install_requires=[], - license='MIT' - ) \ No newline at end of file + name='PythonRobotics', + version='1.0.1', + description="Python implementation of robotics algoritms", + author="Atsushi Sakai et al.", + packages=find_packages(include=['PythonRobotics']), + url="https://github.com/AtsushiSakai/PythonRobotics", + install_requires=[], + license='MIT' +) From 681e8ee73c8e1ef69285fdaef3a3358de463a796 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 12:29:56 +0200 Subject: [PATCH 30/93] removed init file as it should NOT be in tests folder. See https://stackoverflow.com/a/41752043 --- tests/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/__init__.py diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 From 63ee685bdd097864341613236efbc97f198aeda5 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 12:30:05 +0200 Subject: [PATCH 31/93] removed init file as it should NOT be in tests folder. See https://stackoverflow.com/a/41752043 From 24a8718d11167718836e2be10de87ade5581e0ff Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 12:30:50 +0200 Subject: [PATCH 32/93] added main (demo) function to turtlebot simulator --- PythonRobotics/turtlebot.py | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index a4b0d5e866..1ce8c8bea9 100644 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -8,11 +8,14 @@ """ import numpy as np +import matplotlib.pyplot as plt from numpy import sin, cos, sign import pandas as pd from collections import namedtuple +show_animation = True + # define state object state_fields = ['x', 'y', 'phi', 'v', 'omega', 't'] State = namedtuple('State', state_fields, defaults=(0.,) * len(state_fields)) @@ -101,3 +104,65 @@ def state(self): def __repr__(self): return f'{self.name} {self.state})' + + +def main(): + """ demonstrate functionality """ + + bot = Robot() + + # high acceleration ~ instantaneous velocity + bot._accel_angular = 1000 # high angular acceleration + bot._accel_linear = 1 # constant linear acceleration + bot._v_max = 10 # max linear velocity + bot._omega_max = 2*np.pi # max angular velocity + + omega = 2*np.pi # rad/s + + # calculate path + t_sim = 1.0 # simulation time [sec] + dt = 0.01 # timestep + n_steps = int(t_sim / dt) + + bot.set_velocity(1.0, omega) + + for _ in range(n_steps): + bot.step(dt) + + print('Simulation result:\n', bot.states) + + # plot data + if show_animation: + plt.style.use('seaborn-whitegrid') + + plt.cla() + plt.tight_layout() + + plt.subplot(2, 2, 1) + bot.states.v.plot() + plt.title('v (linear velocity)') + + plt.subplot(2, 2, 2) + plt.plot(bot.states.x, bot.states.y, 'x-',) + plt.xlabel('x') + plt.ylabel('y') + plt.title('position') + + plt.subplot(2, 2, 3) + bot.states.omega.plot() + plt.title('omega (angular velocity)') + plt.ylabel('[rad/s]') + + plt.subplot(2, 2, 4) + bot.states.phi.plot() + plt.title('phi') + plt.ylabel('phi [rad]') + + plt.subplots_adjust(hspace=0.4) + + plt.show() + + +if __name__ == '__main__': + print('Turtlebot simulation demo starting...') + main() From 7b182ee4f02e1a6deb28d00410ecc78ab7f044bf Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 12:31:07 +0200 Subject: [PATCH 33/93] restored original name --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index 3eb7663906..1441b79080 100644 --- a/environment.yml +++ b/environment.yml @@ -1,4 +1,4 @@ -name: robotics +name: python_robotics channels: - conda-forge dependencies: From 9795a9fc96fcd2f5fd19778e6334f8deddd0e9a3 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 12:31:20 +0200 Subject: [PATCH 34/93] restored original name From e33b00c66f0e9822aaa2c85efe338c1eb31c64de Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 12:31:36 +0200 Subject: [PATCH 35/93] made executable --- PythonRobotics/turtlebot.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 PythonRobotics/turtlebot.py diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py old mode 100644 new mode 100755 From d6e283da28798ab99ef749a4bc08464824c0e80c Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 12:31:43 +0200 Subject: [PATCH 36/93] moved plt import --- PythonRobotics/turtlebot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index 1ce8c8bea9..bad155d2c1 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -8,7 +8,7 @@ """ import numpy as np -import matplotlib.pyplot as plt + from numpy import sin, cos, sign import pandas as pd @@ -108,6 +108,7 @@ def __repr__(self): def main(): """ demonstrate functionality """ + import matplotlib.pyplot as plt bot = Robot() From ed0f00e6c481baee246ff073d79f77ad99a48605 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 12:31:46 +0200 Subject: [PATCH 37/93] added test --- tests/test_turtlebot.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/test_turtlebot.py diff --git a/tests/test_turtlebot.py b/tests/test_turtlebot.py new file mode 100644 index 0000000000..88ece2c2e8 --- /dev/null +++ b/tests/test_turtlebot.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +""" +Test turtlebot simulator + +to run this test first install the PythonRobotics as development version. +do this by running +>pip install -e . +from the main PythonRobotics directory. +This removes the need for the `conftest` hack ;-). + +author - Jev Kuznetsov +""" + +from PythonRobotics.PythonRobotics import turtlebot as m + +def test_1(): + m.show_animation = False + m.main() + + From 5fcef70fcd94ece0d86737d26cc94111d568c677 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 12:31:50 +0200 Subject: [PATCH 38/93] deleted trailing spaces --- PythonRobotics/turtlebot.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index bad155d2c1..b862ac2ada 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """ - -Simulator of a differantial drive robot, Turtlebot3 - like + +Simulator of a differantial drive robot, Turtlebot3 - like Author - Jev Kuznetsov @@ -22,8 +22,8 @@ class Robot: - """ - Robot kinematics simulator + """ + Robot kinematics simulator """ # numbers for Robotis Turtlebot3 _v_max = 0.22 # [m/s] From ca5a01db98bff60364fe83dceeae3271827ec1eb Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 12:31:54 +0200 Subject: [PATCH 39/93] removed spaces --- tests/test_turtlebot.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_turtlebot.py b/tests/test_turtlebot.py index 88ece2c2e8..45dbfb2ded 100644 --- a/tests/test_turtlebot.py +++ b/tests/test_turtlebot.py @@ -3,7 +3,7 @@ Test turtlebot simulator to run this test first install the PythonRobotics as development version. -do this by running +do this by running >pip install -e . from the main PythonRobotics directory. This removes the need for the `conftest` hack ;-). @@ -13,8 +13,7 @@ from PythonRobotics.PythonRobotics import turtlebot as m + def test_1(): m.show_animation = False - m.main() - - + m.main() \ No newline at end of file From 0a9ab322196290f53566398df9763c1543df7df3 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 12:31:59 +0200 Subject: [PATCH 40/93] update style --- tests/test_turtlebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_turtlebot.py b/tests/test_turtlebot.py index 45dbfb2ded..df4ff5cb77 100644 --- a/tests/test_turtlebot.py +++ b/tests/test_turtlebot.py @@ -16,4 +16,4 @@ def test_1(): m.show_animation = False - m.main() \ No newline at end of file + m.main() From 6e3a67b370331603447c13fac44ac63bb1aa7272 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 12:32:03 +0200 Subject: [PATCH 41/93] made test compatible with CI again --- tests/test_turtlebot.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/test_turtlebot.py b/tests/test_turtlebot.py index df4ff5cb77..6023f96549 100644 --- a/tests/test_turtlebot.py +++ b/tests/test_turtlebot.py @@ -2,18 +2,16 @@ """ Test turtlebot simulator -to run this test first install the PythonRobotics as development version. -do this by running ->pip install -e . -from the main PythonRobotics directory. -This removes the need for the `conftest` hack ;-). - author - Jev Kuznetsov """ +import conftest from PythonRobotics.PythonRobotics import turtlebot as m def test_1(): m.show_animation = False m.main() + +if __name__ == '__main__': + conftest.run_this_test(__file__) \ No newline at end of file From a7ba9948287aa5acfa7c52bb2caaad7c47810e78 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 12:32:06 +0200 Subject: [PATCH 42/93] code style --- tests/test_turtlebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_turtlebot.py b/tests/test_turtlebot.py index 6023f96549..0ecca7c1e1 100644 --- a/tests/test_turtlebot.py +++ b/tests/test_turtlebot.py @@ -14,4 +14,4 @@ def test_1(): m.main() if __name__ == '__main__': - conftest.run_this_test(__file__) \ No newline at end of file + conftest.run_this_test(__file__) From ec95e46fee909cae42144978066659ffbdabc23d Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 12:32:09 +0200 Subject: [PATCH 43/93] style fix --- tests/test_turtlebot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_turtlebot.py b/tests/test_turtlebot.py index 0ecca7c1e1..0f3e960d83 100644 --- a/tests/test_turtlebot.py +++ b/tests/test_turtlebot.py @@ -13,5 +13,6 @@ def test_1(): m.show_animation = False m.main() + if __name__ == '__main__': conftest.run_this_test(__file__) From 49ef01a66bb11af0ee6957d0482da84ec5c9410f Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 12:32:13 +0200 Subject: [PATCH 44/93] removed annotation --- PythonRobotics/turtlebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index b862ac2ada..e2150b5c75 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -82,7 +82,7 @@ def step(self, dt=0.1): self._states.append(s_new) @property - def states(self) -> pd.DataFrame: + def states(self): """ get history of all states as pandas DataFrame """ cols = state_fields[:-1] From 770ee40374ebc061bb47aee1cafe94436824c764 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 13:02:32 +0200 Subject: [PATCH 45/93] changed import line --- tests/test_turtlebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_turtlebot.py b/tests/test_turtlebot.py index 0f3e960d83..97b54ea580 100644 --- a/tests/test_turtlebot.py +++ b/tests/test_turtlebot.py @@ -6,7 +6,7 @@ """ import conftest -from PythonRobotics.PythonRobotics import turtlebot as m +from PythonRobotics import turtlebot as m def test_1(): From 7a2053c04cfeea8f6ba24f59875a5a2ff2ca9490 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 14:52:31 +0200 Subject: [PATCH 46/93] added pylint file --- .pylintrc | 584 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 584 insertions(+) create mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000000..c167893ce3 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,584 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + deprecated-operator-function, + deprecated-urllib-function, + xreadlines-attribute, + deprecated-sys-function, + exception-escape, + comprehension-escape, + invalid-name, + missing-function-docstring, + missing-class-docstring + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[STRING] + +# This flag controls whether the implicit-str-concat-in-sequence should +# generate a warning on implicit string concatenation in sequences defined over +# several lines. +check-str-concat-over-line-jumps=no + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[LOGGING] + +# Format style used to check logging format string. `old` means using % +# formatting, `new` is for `{}` formatting,and `fstr` is for f-strings. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception From db345b640ce8fbc79b638d8200f4e61dab80dd4e Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 14:53:26 +0200 Subject: [PATCH 47/93] refactor and code style --- PythonRobotics/turtlebot.py | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index e2150b5c75..e61b6ce717 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -7,13 +7,11 @@ """ +from collections import namedtuple +from math import sin, cos import numpy as np - -from numpy import sin, cos, sign import pandas as pd -from collections import namedtuple - show_animation = True # define state object @@ -26,10 +24,10 @@ class Robot: Robot kinematics simulator """ # numbers for Robotis Turtlebot3 - _v_max = 0.22 # [m/s] - _omega_max = 2.84 # [rad/s] - _accel_angular = 0.1 # [rad/s2] - _accel_linear = 0.1 # [m/s2] + v_max = 0.22 # [m/s] + omega_max = 2.84 # [rad/s] + accel_angular = 0.1 # [rad/s2] + accel_linear = 0.1 # [m/s2] _wheel_diameter = 66e-3 # [m] _wheel_distance = 160e-3 # [m] @@ -47,8 +45,8 @@ def __init__(self, def set_velocity(self, v, omega): """ set target velocities """ - self._omega_target = np.clip(omega, -self._omega_max, self._omega_max) - self._v_target = np.clip(v, 0, self._v_max) + self._omega_target = np.clip(omega, -self.omega_max, self.omega_max) + self._v_target = np.clip(v, 0, self.v_max) def step(self, dt=0.1): """ perform simulation step with timestep dt """ @@ -56,18 +54,18 @@ def step(self, dt=0.1): s = self.state # start state # update linear speed - delta_v = self._accel_linear * dt # dv is an absolute number + delta_v = self.accel_linear * dt # dv is an absolute number v_error = self._v_target - s.v if abs(v_error) > delta_v: - v_new = s.v + sign(v_error) * delta_v + v_new = s.v + np.sign(v_error) * delta_v else: v_new = s.v + v_error # update angular speed - delta_omega = self._accel_angular * dt + delta_omega = self.accel_angular * dt omega_error = self._omega_target - s.omega if abs(omega_error) > delta_omega: - omega_new = s.omega + sign(omega_error) * delta_omega + omega_new = s.omega + np.sign(omega_error) * delta_omega else: omega_new = s.omega + omega_error @@ -113,10 +111,10 @@ def main(): bot = Robot() # high acceleration ~ instantaneous velocity - bot._accel_angular = 1000 # high angular acceleration - bot._accel_linear = 1 # constant linear acceleration - bot._v_max = 10 # max linear velocity - bot._omega_max = 2*np.pi # max angular velocity + bot.accel_angular = 1000 # high angular acceleration + bot.accel_linear = 1 # constant linear acceleration + bot.v_max = 10 # max linear velocity + bot.omega_max = 2*np.pi # max angular velocity omega = 2*np.pi # rad/s From 61acf714a7bfe87a4c15b521dbbf37e6d7a88fef Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Mon, 12 Apr 2021 11:47:24 +0200 Subject: [PATCH 48/93] gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bc5469c3e8..cae29a2fdd 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,4 @@ scratch/ # IDE files .vscode/ +workspace.code-workspace From 974320a49ecbf62b49479b7670a0c5319f7c2956 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Mon, 12 Apr 2021 11:47:24 +0200 Subject: [PATCH 49/93] gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bc5469c3e8..cae29a2fdd 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,4 @@ scratch/ # IDE files .vscode/ +workspace.code-workspace From 262f8f08d3d782dff18d4c5b418d4472e22793b5 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 14:52:31 +0200 Subject: [PATCH 50/93] added pylint file --- .pylintrc | 584 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 584 insertions(+) create mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000000..c167893ce3 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,584 @@ +[MASTER] + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. +confidence= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + deprecated-operator-function, + deprecated-urllib-function, + xreadlines-attribute, + deprecated-sys-function, + exception-escape, + comprehension-escape, + invalid-name, + missing-function-docstring, + missing-class-docstring + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable=c-extension-no-member + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'error', 'warning', 'refactor', and 'convention' +# which contain the number of messages in each category, as well as 'statement' +# which is the total number of statements analyzed. This score is used by the +# global evaluation report (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +#msg-template= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis). It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[STRING] + +# This flag controls whether the implicit-str-concat-in-sequence should +# generate a warning on implicit string concatenation in sequences defined over +# several lines. +check-str-concat-over-line-jumps=no + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. +#class-attribute-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. +#variable-rgx= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io + + +[LOGGING] + +# Format style used to check logging format string. `old` means using % +# formatting, `new` is for `{}` formatting,and `fstr` is for f-strings. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. Available dictionaries: none. To make it work, +# install the python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma, + dict-separator + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[SIMILARITIES] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules=optparse,tkinter.tix + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled). +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled). +import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=cls + + +[DESIGN] + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "BaseException, Exception". +overgeneral-exceptions=BaseException, + Exception From 80e1d16c78aa49f72b544155391f8cd5ef2ba6a7 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Sat, 10 Apr 2021 13:02:32 +0200 Subject: [PATCH 51/93] changed import line --- tests/test_turtlebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_turtlebot.py b/tests/test_turtlebot.py index 0f3e960d83..97b54ea580 100644 --- a/tests/test_turtlebot.py +++ b/tests/test_turtlebot.py @@ -6,7 +6,7 @@ """ import conftest -from PythonRobotics.PythonRobotics import turtlebot as m +from PythonRobotics import turtlebot as m def test_1(): From 90eef74169398fc8a492650b7e749f64c752087f Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Mon, 12 Apr 2021 12:17:08 +0200 Subject: [PATCH 52/93] changed imports --- tests/test_vectors.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_vectors.py b/tests/test_vectors.py index 17984799cc..ba133e22e7 100644 --- a/tests/test_vectors.py +++ b/tests/test_vectors.py @@ -4,12 +4,13 @@ author - Jev Kuznetsov """ - - -from PythonRobotics.vectors import Vector, point_on_line import numpy as np import math +import conftest +from PythonRobotics.vectors import Vector, point_on_line + + # test tuples ta = (1, 2) tb = (3, 4) From b616992c4a8a0a01c2a7f5b6f67f2556276050b7 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Mon, 12 Apr 2021 14:05:32 +0200 Subject: [PATCH 53/93] Revert "style fix" This reverts commit d713053c407179e0261ab09986acbf1a79848f7d. --- tests/test_turtlebot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_turtlebot.py b/tests/test_turtlebot.py index 97b54ea580..072ce709f3 100644 --- a/tests/test_turtlebot.py +++ b/tests/test_turtlebot.py @@ -13,6 +13,5 @@ def test_1(): m.show_animation = False m.main() - if __name__ == '__main__': conftest.run_this_test(__file__) From acccd8262a021b93c5baa288fe98c7e79298b03f Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Mon, 12 Apr 2021 17:55:05 +0200 Subject: [PATCH 54/93] added distance --- PythonRobotics/vectors.py | 3 +++ tests/test_vectors.py | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/PythonRobotics/vectors.py b/PythonRobotics/vectors.py index ec008a4615..1f1d46c23d 100644 --- a/PythonRobotics/vectors.py +++ b/PythonRobotics/vectors.py @@ -86,6 +86,9 @@ def __neg__(self): def __abs__(self): return hypot(self.x, self.y) + + def distance(self, other): + return hypot(self.x-other.x, self.y-other.y) @classmethod def from_polar(cls, r, theta): diff --git a/tests/test_vectors.py b/tests/test_vectors.py index c9a0aae515..7d56f3c91a 100644 --- a/tests/test_vectors.py +++ b/tests/test_vectors.py @@ -95,3 +95,13 @@ def test_rotation(): v = Vector(1, 0) assert v.rotate(np.pi/2).round() == Vector(0, 1) assert v.rotate(-np.pi/4) == Vector.from_polar(1, -np.pi/4) + + +def test_distance(): + + assert Vector(1, 0).distance(Vector(0, 0)) == 1 + assert Vector(2, 2).distance(Vector(1, 1)) == np.sqrt(2) + + +if __name__ == '__main__': + conftest.run_this_test(__file__) From 858fdfead9506fbc11b31b9e29852e44f778f446 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Mon, 12 Apr 2021 22:22:32 +0200 Subject: [PATCH 55/93] changed state to function --- PythonRobotics/turtlebot.py | 39 ++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index e61b6ce717..d03f35a433 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -8,7 +8,7 @@ """ from collections import namedtuple -from math import sin, cos +from math import fabs, sin, cos import numpy as np import pandas as pd @@ -79,21 +79,23 @@ def step(self, dt=0.1): self._states.append(s_new) - @property - def states(self): - """ get history of all states as pandas DataFrame """ - - cols = state_fields[:-1] - data = {} - - for var_name in cols: - data[var_name] = [getattr(s, var_name) for s in self._states] + def states(self, raw=False): + """ get history of all states as pandas DataFrame + or as a list if raw=True + """ + if raw: + return self._states + else: + cols = state_fields[:-1] + data = {} - t = pd.Index(name='time', data=[s.t for s in self._states]) + for var_name in cols: + data[var_name] = [getattr(s, var_name) for s in self._states] - df = pd.DataFrame(data, index=t) + t = pd.Index(name='time', data=[s.t for s in self._states]) + df = pd.DataFrame(data, index=t) - return df + return df @property def state(self): @@ -128,7 +130,8 @@ def main(): for _ in range(n_steps): bot.step(dt) - print('Simulation result:\n', bot.states) + states = bot.states() + print('Simulation result:\n', states) # plot data if show_animation: @@ -138,22 +141,22 @@ def main(): plt.tight_layout() plt.subplot(2, 2, 1) - bot.states.v.plot() + states.v.plot() plt.title('v (linear velocity)') plt.subplot(2, 2, 2) - plt.plot(bot.states.x, bot.states.y, 'x-',) + plt.plot(states.x, states.y, 'x-',) plt.xlabel('x') plt.ylabel('y') plt.title('position') plt.subplot(2, 2, 3) - bot.states.omega.plot() + states.omega.plot() plt.title('omega (angular velocity)') plt.ylabel('[rad/s]') plt.subplot(2, 2, 4) - bot.states.phi.plot() + states.phi.plot() plt.title('phi') plt.ylabel('phi [rad]') From 6347f1caafdb8df7b09da1d6a3d56078bb50b3df Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Mon, 12 Apr 2021 22:45:43 +0200 Subject: [PATCH 56/93] changed states interface --- PythonRobotics/turtlebot.py | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index d03f35a433..dbb6cf4b75 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -8,7 +8,7 @@ """ from collections import namedtuple -from math import fabs, sin, cos +from math import sin, cos import numpy as np import pandas as pd @@ -27,7 +27,7 @@ class Robot: v_max = 0.22 # [m/s] omega_max = 2.84 # [rad/s] accel_angular = 0.1 # [rad/s2] - accel_linear = 0.1 # [m/s2] + accel_linear = 1 # [m/s2] _wheel_diameter = 66e-3 # [m] _wheel_distance = 160e-3 # [m] @@ -38,7 +38,7 @@ def __init__(self, name='Turtlebot3'): """ initiate robot at given location and orientation """ self.name = name - self._states = [State(x, y, phi)] + self.states = [State(x, y, phi)] self._omega_target = 0. self._v_target = 0. @@ -77,30 +77,27 @@ def step(self, dt=0.1): v=v_new, omega=omega_new) - self._states.append(s_new) + self.states.append(s_new) - def states(self, raw=False): - """ get history of all states as pandas DataFrame - or as a list if raw=True - """ - if raw: - return self._states - else: - cols = state_fields[:-1] - data = {} + + + def states_df(self): + """ states as DataFrame """ + cols = state_fields[:-1] + data = {} - for var_name in cols: - data[var_name] = [getattr(s, var_name) for s in self._states] + for var_name in cols: + data[var_name] = [getattr(s, var_name) for s in self.states] - t = pd.Index(name='time', data=[s.t for s in self._states]) - df = pd.DataFrame(data, index=t) + t = pd.Index(name='time', data=[s.t for s in self.states]) + df = pd.DataFrame(data, index=t) - return df + return df @property def state(self): """ last known state """ - return self._states[-1] + return self.states[-1] def __repr__(self): return f'{self.name} {self.state})' @@ -130,7 +127,7 @@ def main(): for _ in range(n_steps): bot.step(dt) - states = bot.states() + states = bot.states_df() print('Simulation result:\n', states) # plot data From 98f53f0a4ea52c7b44f80dcae01f450d3bafef7c Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Mon, 12 Apr 2021 22:49:44 +0200 Subject: [PATCH 57/93] added plots --- PythonRobotics/plots.py | 42 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 PythonRobotics/plots.py diff --git a/PythonRobotics/plots.py b/PythonRobotics/plots.py new file mode 100644 index 0000000000..aecd95e46f --- /dev/null +++ b/PythonRobotics/plots.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python +""" +plotting functions + +the functions are kept in a separate file to increase the +readability of main algorithm. +""" + +import math +#import numpy as np +import matplotlib.pyplot as plt + + +def plot_arrow(x, y, yaw, length=0.1, width=0.5, fc="r", ec="k"): + """ + Plot arrow + """ + + if not isinstance(x, float): + for ix, iy, iyaw in zip(x, y, yaw): + plot_arrow(ix, iy, iyaw) + else: + plt.arrow(x, y, length * math.cos(yaw), length * math.sin(yaw), + fc=fc, ec=ec, head_width=width, head_length=width) + plt.plot(x, y) + + +def plot_point(xy, txt=None, style='o'): + plt.plot(xy[0], xy[1], style) + if txt is not None: + plt.text(xy[0]+0.1, xy[1], txt) + + +def plot_path(points, style='o-'): + x = [pt[0] for pt in points] + y = [pt[1] for pt in points] + plt.plot(x, y, style) + + +def plot_vector(p1, p2): + plt.quiver(p1.x, p1.y, (p2-p1).x, (p2-p1).y, + angles='xy', scale_units='xy', scale=1) From 12bb074ea7ea13be7f14461b98192bf680d33079 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Mon, 12 Apr 2021 22:50:25 +0200 Subject: [PATCH 58/93] style --- PythonRobotics/vectors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PythonRobotics/vectors.py b/PythonRobotics/vectors.py index 1f1d46c23d..7886a106f1 100644 --- a/PythonRobotics/vectors.py +++ b/PythonRobotics/vectors.py @@ -86,7 +86,7 @@ def __neg__(self): def __abs__(self): return hypot(self.x, self.y) - + def distance(self, other): return hypot(self.x-other.x, self.y-other.y) From ed1bd844c3d9c03127c9f2d98d9d4f398c8bb12a Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Mon, 12 Apr 2021 22:50:51 +0200 Subject: [PATCH 59/93] added file --- PathTracking/pure_pursuit/pure_pursuit_2.py | 112 ++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 PathTracking/pure_pursuit/pure_pursuit_2.py diff --git a/PathTracking/pure_pursuit/pure_pursuit_2.py b/PathTracking/pure_pursuit/pure_pursuit_2.py new file mode 100644 index 0000000000..2338f89533 --- /dev/null +++ b/PathTracking/pure_pursuit/pure_pursuit_2.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +""" + Pure pursuit controller V2 + + Copyright (c) 2021 AIGRO B.V. - Jev Kuznetsov +""" +import math +import numpy as np +import matplotlib.pyplot as plt +from PythonRobotics.vectors import Vector +from PythonRobotics.turtlebot import Robot +from PythonRobotics.plots import plot_arrow, plot_path + +show_animation = True + + +class Waypoints: + """ navigation waypoints, collection of xy Vectors """ + + def __init__(self, points: 'list[Vector]'): + """ create waypoints. """ + self.points = [Vector(x, y) for x, y in points] + self.next_idx = None + + def find_next_idx(self, xy: 'Vector') -> 'int': + """ get next waypoint """ + + if self.next_idx is None: # first time search + # distance between points + dst = [pt.distance(xy) for pt in self.points] + idx = np.argmin(dst) + self.next_idx = idx + 1 + else: + # calculate distances to n and n+1 waypoints + idx = self.next_idx + dst_a = xy.distance(self.points[idx-1]) + dst_b = xy.distance(self.points[idx]) + if dst_a > dst_b: # next point + self.next_idx += 1 + + return self.next_idx + + @property + def target_reached(self): + return not self.next_idx < len(self.points) + + +def point_on_line(a, b, p): + """ project point p to a line defined by points a and b """ + ap = p - a + ab = b - a + result = a + ap.dot(ab) / ab.dot(ab) * ab + return result + + +def plot_state(states, waypoints): + """plot robot state, can be used in animaton + + Args: + states (namedtuple): state history, containing ['x', 'y', 'phi', 'v', 'omega', 't'] fields + """ + + plt.cla() + # for stopping simulation with the esc key. + plt.gcf().canvas.mpl_connect( + 'key_release_event', + lambda event: [exit(0) if event.key == 'escape' else None]) + + state = states[-1] + plot_arrow(state.x, state.y, state.phi, width=0.1, length=0.01) + + # plot trajectory + x = [state.x for state in states] + y = [state.y for state in states] + + plt.plot(x, y, "-b", label="trajectory") + plot_path(waypoints.points) + + plt.axis("equal") + plt.grid(True) + plt.title("Speed[m/s]: %.3f" % state.v) + plt.pause(0.001) + + +def main(): + + # sim parameters + t_sim = 10.0 # simulation time [sec] + dt = 0.1 # timestep + n_steps = int(t_sim / dt) + + # create waypoints + coord = [(0, 0), (0, 2), (2, 2), (2, 0), (2, -2)] + wp = Waypoints(coord) + print('Waypoints: ', wp.points) + + # create robot + bot = Robot(phi=math.pi/4) + bot.set_velocity(3., math.pi/2) + + # simulate + for _ in range(n_steps): + bot.step(dt) + + if show_animation: # pragma: no cover + plot_state(bot.states, wp) + + print('Simulation result:\n', bot.states_df()) + + +if __name__ == "__main__": + main() From b4781b7274e613e0c649cc316f721a74093c612f Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Mon, 12 Apr 2021 22:51:16 +0200 Subject: [PATCH 60/93] style --- PythonRobotics/turtlebot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index dbb6cf4b75..67c9d23db1 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -10,6 +10,7 @@ from collections import namedtuple from math import sin, cos import numpy as np +import matplotlib.pyplot as plt import pandas as pd show_animation = True @@ -79,7 +80,7 @@ def step(self, dt=0.1): self.states.append(s_new) - + def states_df(self): """ states as DataFrame """ @@ -105,8 +106,7 @@ def __repr__(self): def main(): """ demonstrate functionality """ - import matplotlib.pyplot as plt - + bot = Robot() # high acceleration ~ instantaneous velocity From 8c2fc91a06965355b5c6a4ea83219417f40da568 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Mon, 12 Apr 2021 22:51:37 +0200 Subject: [PATCH 61/93] style --- PathTracking/pure_pursuit/pure_pursuit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PathTracking/pure_pursuit/pure_pursuit.py b/PathTracking/pure_pursuit/pure_pursuit.py index ff995033a9..d54e776084 100644 --- a/PathTracking/pure_pursuit/pure_pursuit.py +++ b/PathTracking/pure_pursuit/pure_pursuit.py @@ -6,8 +6,8 @@ Guillaume Jacquenot (@Gjacquenot) """ -import numpy as np import math +import numpy as np import matplotlib.pyplot as plt # Parameters From 8cfdf7297886cbd926eac0a8a21c67bb58408ae4 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Mon, 12 Apr 2021 22:56:01 +0200 Subject: [PATCH 62/93] updated notebook --- PathTracking/pure_pursuit/pure_pursuit.ipynb | 91 +++++++++++++------- 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/PathTracking/pure_pursuit/pure_pursuit.ipynb b/PathTracking/pure_pursuit/pure_pursuit.ipynb index 6770adc026..e60b86c227 100644 --- a/PathTracking/pure_pursuit/pure_pursuit.ipynb +++ b/PathTracking/pure_pursuit/pure_pursuit.ipynb @@ -100,64 +100,89 @@ "metadata": {}, "outputs": [], "source": [ + "# Now let's create a example path, find \n", + "\n", "\n", " \n", "class Waypoints:\n", - " \"\"\" navigation waypoints \"\"\"\n", + " \"\"\" navigation waypoints, collection of xy Vectors \"\"\"\n", " \n", - " def __init__(self, x, y):\n", + " def __init__(self, points: '(x,y) tuples'):\n", " \"\"\" create waypoints. \"\"\"\n", - " self.xy = np.column_stack((x,y))\n", + " self.points = [Vector(x,y) for x,y in points]\n", + " self.next_idx = None \n", " \n", - " def find_nearest(self,pt):\n", - " \"\"\" get index of nearest waypoint \"\"\"\n", - "\n", - " dst = np.linalg.norm(self.xy - pt, axis=1) # distance between points\n", - " idx = np.argmin(dst)\n", + " def find_next_idx(self,xy: 'Vector') -> 'idx':\n", + " \"\"\" get next waypoint \"\"\"\n", " \n", - " return idx\n", + " if self.next_idx is None: # first time search\n", + " dst = [pt.distance(xy) for pt in self.points] # distance between points\n", + " idx = np.argmin(dst)\n", + " self.next_idx = idx + 1\n", + " else:\n", + " # calculate distances to n and n+1 waypoints\n", + " idx = self.next_idx\n", + " dst_a = xy.distance(self.points[idx-1])\n", + " dst_b = xy.distance(self.points[idx])\n", + " if dst_a > dst_b: # next point\n", + " self.next_idx += 1\n", + " \n", + " return self.next_idx\n", "\n", " @property\n", - " def x(self):\n", - " return self.xy[:,0]\n", - " \n", - " @property\n", - " def y(self):\n", - " return self.xy[:,1]\n", - " \n", - " \n", - "\n", + " def target_reached(self):\n", + " return False if self.next_idx < len(self.points) else True\n", " \n", "def point_on_line(a, b, p):\n", " \"\"\" project point p to a line defined by points a and b \"\"\"\n", " ap = p - a\n", " ab = b - a\n", - " result = a + np.dot(ap, ab) / np.dot(ab, ab) * ab\n", + " result = a + ap.dot(ab) / ab.dot(ab) * ab\n", " return result\n", " \n", "# create waypoints \n", - "cx = np.arange(0, 50, 5)\n", - "cy = [np.sin(ix / 5.0) * ix / 2.0 for ix in cx]\n", + "coord = [(0,0),(0,10),(10,10),(10,0),(10,-10)]\n", "\n", - "wp = Waypoints(cx,cy)\n", - "plot_path(wp.xy)\n", + "wp = Waypoints(coord)\n", + "plot_path(wp.points)\n", "\n", - "# test nearst point search\n", - "pt = np.array((29,20))\n", - "plot_point(pt)\n", + "#plot projection\n", + "#px = point_on_line(wp.points[idx],wp.points[idx+1],pt)\n", "\n", - "idx = wp.find_nearest(pt)\n", - "# plot nearest segment\n", - "plot_path(wp.xy[idx:idx+2,:], style='ko-') \n", + "# simulate moving along path\n", + "positions = [Vector(xy) for xy in [(-5,-5), (0,0), (0,5), (0,9),(5,10),(6,11),(10,-5),(10,-9)]]\n", "\n", - "#plot projection\n", - "px = point_on_line(wp.xy[idx],wp.xy[idx+1],pt)\n", - "plot_point(px)\n", + "for idx, xy in enumerate(positions):\n", + " \n", + " wp_idx = wp.find_next_idx(xy)\n", + " print('idx:', wp_idx)\n", + " if wp.target_reached:\n", + " print('Target reached')\n", + " break\n", + " next_wp = wp.points[wp_idx]\n", + " plot_path((xy,next_wp))\n", + " plot_point(xy,'xy_%i' % idx)\n", + " \n", "\n", - "plot_path((pt,px),style='--')\n", "\n", "plt.axis('equal')" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# complex path\n", + " \n", + "# create waypoints \n", + "cx = np.arange(0, 50, 5)\n", + "cy = [np.sin(ix / 5.0) * ix / 2.0 for ix in cx]\n", + "\n", + "wp = Waypoints(zip(cx,cy))\n", + "plot_path(wp.points)" + ] } ], "metadata": { From 236ac68ea87656bdc8a839f8e6cb50236f564457 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Mon, 12 Apr 2021 22:22:32 +0200 Subject: [PATCH 63/93] changed state to function --- PythonRobotics/turtlebot.py | 39 ++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index e61b6ce717..d03f35a433 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -8,7 +8,7 @@ """ from collections import namedtuple -from math import sin, cos +from math import fabs, sin, cos import numpy as np import pandas as pd @@ -79,21 +79,23 @@ def step(self, dt=0.1): self._states.append(s_new) - @property - def states(self): - """ get history of all states as pandas DataFrame """ - - cols = state_fields[:-1] - data = {} - - for var_name in cols: - data[var_name] = [getattr(s, var_name) for s in self._states] + def states(self, raw=False): + """ get history of all states as pandas DataFrame + or as a list if raw=True + """ + if raw: + return self._states + else: + cols = state_fields[:-1] + data = {} - t = pd.Index(name='time', data=[s.t for s in self._states]) + for var_name in cols: + data[var_name] = [getattr(s, var_name) for s in self._states] - df = pd.DataFrame(data, index=t) + t = pd.Index(name='time', data=[s.t for s in self._states]) + df = pd.DataFrame(data, index=t) - return df + return df @property def state(self): @@ -128,7 +130,8 @@ def main(): for _ in range(n_steps): bot.step(dt) - print('Simulation result:\n', bot.states) + states = bot.states() + print('Simulation result:\n', states) # plot data if show_animation: @@ -138,22 +141,22 @@ def main(): plt.tight_layout() plt.subplot(2, 2, 1) - bot.states.v.plot() + states.v.plot() plt.title('v (linear velocity)') plt.subplot(2, 2, 2) - plt.plot(bot.states.x, bot.states.y, 'x-',) + plt.plot(states.x, states.y, 'x-',) plt.xlabel('x') plt.ylabel('y') plt.title('position') plt.subplot(2, 2, 3) - bot.states.omega.plot() + states.omega.plot() plt.title('omega (angular velocity)') plt.ylabel('[rad/s]') plt.subplot(2, 2, 4) - bot.states.phi.plot() + states.phi.plot() plt.title('phi') plt.ylabel('phi [rad]') From 60b92dc9f722bbbcfdadf23d2bae5222b2b44b00 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Mon, 12 Apr 2021 22:45:43 +0200 Subject: [PATCH 64/93] changed states interface --- PythonRobotics/turtlebot.py | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index d03f35a433..dbb6cf4b75 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -8,7 +8,7 @@ """ from collections import namedtuple -from math import fabs, sin, cos +from math import sin, cos import numpy as np import pandas as pd @@ -27,7 +27,7 @@ class Robot: v_max = 0.22 # [m/s] omega_max = 2.84 # [rad/s] accel_angular = 0.1 # [rad/s2] - accel_linear = 0.1 # [m/s2] + accel_linear = 1 # [m/s2] _wheel_diameter = 66e-3 # [m] _wheel_distance = 160e-3 # [m] @@ -38,7 +38,7 @@ def __init__(self, name='Turtlebot3'): """ initiate robot at given location and orientation """ self.name = name - self._states = [State(x, y, phi)] + self.states = [State(x, y, phi)] self._omega_target = 0. self._v_target = 0. @@ -77,30 +77,27 @@ def step(self, dt=0.1): v=v_new, omega=omega_new) - self._states.append(s_new) + self.states.append(s_new) - def states(self, raw=False): - """ get history of all states as pandas DataFrame - or as a list if raw=True - """ - if raw: - return self._states - else: - cols = state_fields[:-1] - data = {} + + + def states_df(self): + """ states as DataFrame """ + cols = state_fields[:-1] + data = {} - for var_name in cols: - data[var_name] = [getattr(s, var_name) for s in self._states] + for var_name in cols: + data[var_name] = [getattr(s, var_name) for s in self.states] - t = pd.Index(name='time', data=[s.t for s in self._states]) - df = pd.DataFrame(data, index=t) + t = pd.Index(name='time', data=[s.t for s in self.states]) + df = pd.DataFrame(data, index=t) - return df + return df @property def state(self): """ last known state """ - return self._states[-1] + return self.states[-1] def __repr__(self): return f'{self.name} {self.state})' @@ -130,7 +127,7 @@ def main(): for _ in range(n_steps): bot.step(dt) - states = bot.states() + states = bot.states_df() print('Simulation result:\n', states) # plot data From ca5ff1d1364065e3aa7e57bf8f35664af80a4f4e Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Mon, 12 Apr 2021 22:51:16 +0200 Subject: [PATCH 65/93] style --- PythonRobotics/turtlebot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index dbb6cf4b75..67c9d23db1 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -10,6 +10,7 @@ from collections import namedtuple from math import sin, cos import numpy as np +import matplotlib.pyplot as plt import pandas as pd show_animation = True @@ -79,7 +80,7 @@ def step(self, dt=0.1): self.states.append(s_new) - + def states_df(self): """ states as DataFrame """ @@ -105,8 +106,7 @@ def __repr__(self): def main(): """ demonstrate functionality """ - import matplotlib.pyplot as plt - + bot = Robot() # high acceleration ~ instantaneous velocity From 89cb4d71c143748a9cfcd3f068d512bdf6c81dcd Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Tue, 13 Apr 2021 11:45:34 +0200 Subject: [PATCH 66/93] added turtle graphics --- PythonRobotics/turtlebot.py | 41 +++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index 67c9d23db1..e72d25288a 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -8,12 +8,16 @@ """ from collections import namedtuple -from math import sin, cos +from math import sin, cos, degrees +import turtle import numpy as np import matplotlib.pyplot as plt import pandas as pd + show_animation = True +plot_result = False +plot_scale = 1000 # define state object state_fields = ['x', 'y', 'phi', 'v', 'omega', 't'] @@ -80,8 +84,6 @@ def step(self, dt=0.1): self.states.append(s_new) - - def states_df(self): """ states as DataFrame """ cols = state_fields[:-1] @@ -106,14 +108,14 @@ def __repr__(self): def main(): """ demonstrate functionality """ - - bot = Robot() + + sim = Robot() # high acceleration ~ instantaneous velocity - bot.accel_angular = 1000 # high angular acceleration - bot.accel_linear = 1 # constant linear acceleration - bot.v_max = 10 # max linear velocity - bot.omega_max = 2*np.pi # max angular velocity + sim.accel_angular = 1000 # high angular acceleration + sim.accel_linear = 1 # constant linear acceleration + sim.v_max = 10 # max linear velocity + sim.omega_max = 2*np.pi # max angular velocity omega = 2*np.pi # rad/s @@ -122,16 +124,29 @@ def main(): dt = 0.01 # timestep n_steps = int(t_sim / dt) - bot.set_velocity(1.0, omega) + sim.set_velocity(1.0, omega) + + if show_animation: + screen = turtle.Screen() + screen.setup(640, 480) + bot = turtle.Turtle() for _ in range(n_steps): - bot.step(dt) - states = bot.states_df() + sim.step(dt) + + if show_animation: + bot.setpos(sim.state.x*plot_scale, sim.state.y*plot_scale) + bot.setheading(degrees(sim.state.phi)) + + #print('close plot window to continue....') + # turtle.done() + + states = sim.states_df() print('Simulation result:\n', states) # plot data - if show_animation: + if plot_result: plt.style.use('seaborn-whitegrid') plt.cla() From 648249d3df25ddda95fe1b8abdf921c2a43fb091 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Tue, 13 Apr 2021 12:14:11 +0200 Subject: [PATCH 67/93] moved animation inside the simulation --- PythonRobotics/turtlebot.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index e72d25288a..5234782592 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -40,14 +40,19 @@ def __init__(self, x=0., # x-position y=0., phi=0., - name='Turtlebot3'): + show_animation=False): """ initiate robot at given location and orientation """ - self.name = name self.states = [State(x, y, phi)] - self._omega_target = 0. self._v_target = 0. + if show_animation: + self.screen = turtle.Screen() + self.screen.setup(640, 480) + self.turtle = turtle.Turtle() + else: + self.turtle = None + def set_velocity(self, v, omega): """ set target velocities """ self._omega_target = np.clip(omega, -self.omega_max, self.omega_max) @@ -84,6 +89,11 @@ def step(self, dt=0.1): self.states.append(s_new) + # animate if required + if self.turtle is not None: + self.turtle.setpos(s_new.x*plot_scale, s_new.y * plot_scale) + self.turtle.setheading(degrees(s_new.phi)) + def states_df(self): """ states as DataFrame """ cols = state_fields[:-1] @@ -103,13 +113,13 @@ def state(self): return self.states[-1] def __repr__(self): - return f'{self.name} {self.state})' + return f'Turtlebot {self.state})' def main(): """ demonstrate functionality """ - sim = Robot() + sim = Robot(show_animation=show_animation) # high acceleration ~ instantaneous velocity sim.accel_angular = 1000 # high angular acceleration @@ -126,22 +136,9 @@ def main(): sim.set_velocity(1.0, omega) - if show_animation: - screen = turtle.Screen() - screen.setup(640, 480) - bot = turtle.Turtle() - for _ in range(n_steps): - sim.step(dt) - if show_animation: - bot.setpos(sim.state.x*plot_scale, sim.state.y*plot_scale) - bot.setheading(degrees(sim.state.phi)) - - #print('close plot window to continue....') - # turtle.done() - states = sim.states_df() print('Simulation result:\n', states) @@ -176,6 +173,10 @@ def main(): plt.show() + if show_animation: + print('close plot window to continue....') + turtle.done() + if __name__ == '__main__': print('Turtlebot simulation demo starting...') From 6a720376fed65aab691284c9e55f4c2f483ef1f4 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Tue, 13 Apr 2021 12:15:38 +0200 Subject: [PATCH 68/93] renamed parameter --- PythonRobotics/turtlebot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index 5234782592..5f76b4b2f1 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -40,13 +40,13 @@ def __init__(self, x=0., # x-position y=0., phi=0., - show_animation=False): + animate=False): """ initiate robot at given location and orientation """ self.states = [State(x, y, phi)] self._omega_target = 0. self._v_target = 0. - if show_animation: + if animate: self.screen = turtle.Screen() self.screen.setup(640, 480) self.turtle = turtle.Turtle() @@ -119,7 +119,7 @@ def __repr__(self): def main(): """ demonstrate functionality """ - sim = Robot(show_animation=show_animation) + sim = Robot(animate=show_animation) # high acceleration ~ instantaneous velocity sim.accel_angular = 1000 # high angular acceleration From 958c18c8feb05a9a84fe1fc6b1d7db5a3180c999 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Tue, 13 Apr 2021 12:19:01 +0200 Subject: [PATCH 69/93] Squashed commit of the following: commit 6a720376fed65aab691284c9e55f4c2f483ef1f4 Author: Jev Kuznetsov Date: Tue Apr 13 12:15:38 2021 +0200 renamed parameter commit 648249d3df25ddda95fe1b8abdf921c2a43fb091 Author: Jev Kuznetsov Date: Tue Apr 13 12:14:11 2021 +0200 moved animation inside the simulation commit 89cb4d71c143748a9cfcd3f068d512bdf6c81dcd Author: Jev Kuznetsov Date: Tue Apr 13 11:45:34 2021 +0200 added turtle graphics commit ca5ff1d1364065e3aa7e57bf8f35664af80a4f4e Author: Jev Kuznetsov Date: Mon Apr 12 22:51:16 2021 +0200 style commit 60b92dc9f722bbbcfdadf23d2bae5222b2b44b00 Author: Jev Kuznetsov Date: Mon Apr 12 22:45:43 2021 +0200 changed states interface commit 236ac68ea87656bdc8a839f8e6cb50236f564457 Author: Jev Kuznetsov Date: Mon Apr 12 22:22:32 2021 +0200 changed state to function --- PythonRobotics/turtlebot.py | 48 ++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index 67c9d23db1..5f76b4b2f1 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -8,12 +8,16 @@ """ from collections import namedtuple -from math import sin, cos +from math import sin, cos, degrees +import turtle import numpy as np import matplotlib.pyplot as plt import pandas as pd + show_animation = True +plot_result = False +plot_scale = 1000 # define state object state_fields = ['x', 'y', 'phi', 'v', 'omega', 't'] @@ -36,14 +40,19 @@ def __init__(self, x=0., # x-position y=0., phi=0., - name='Turtlebot3'): + animate=False): """ initiate robot at given location and orientation """ - self.name = name self.states = [State(x, y, phi)] - self._omega_target = 0. self._v_target = 0. + if animate: + self.screen = turtle.Screen() + self.screen.setup(640, 480) + self.turtle = turtle.Turtle() + else: + self.turtle = None + def set_velocity(self, v, omega): """ set target velocities """ self._omega_target = np.clip(omega, -self.omega_max, self.omega_max) @@ -80,7 +89,10 @@ def step(self, dt=0.1): self.states.append(s_new) - + # animate if required + if self.turtle is not None: + self.turtle.setpos(s_new.x*plot_scale, s_new.y * plot_scale) + self.turtle.setheading(degrees(s_new.phi)) def states_df(self): """ states as DataFrame """ @@ -101,19 +113,19 @@ def state(self): return self.states[-1] def __repr__(self): - return f'{self.name} {self.state})' + return f'Turtlebot {self.state})' def main(): """ demonstrate functionality """ - - bot = Robot() + + sim = Robot(animate=show_animation) # high acceleration ~ instantaneous velocity - bot.accel_angular = 1000 # high angular acceleration - bot.accel_linear = 1 # constant linear acceleration - bot.v_max = 10 # max linear velocity - bot.omega_max = 2*np.pi # max angular velocity + sim.accel_angular = 1000 # high angular acceleration + sim.accel_linear = 1 # constant linear acceleration + sim.v_max = 10 # max linear velocity + sim.omega_max = 2*np.pi # max angular velocity omega = 2*np.pi # rad/s @@ -122,16 +134,16 @@ def main(): dt = 0.01 # timestep n_steps = int(t_sim / dt) - bot.set_velocity(1.0, omega) + sim.set_velocity(1.0, omega) for _ in range(n_steps): - bot.step(dt) + sim.step(dt) - states = bot.states_df() + states = sim.states_df() print('Simulation result:\n', states) # plot data - if show_animation: + if plot_result: plt.style.use('seaborn-whitegrid') plt.cla() @@ -161,6 +173,10 @@ def main(): plt.show() + if show_animation: + print('close plot window to continue....') + turtle.done() + if __name__ == '__main__': print('Turtlebot simulation demo starting...') From 2d25b0b5b30ac2ae6792b091b770079fe5f77104 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Tue, 13 Apr 2021 12:32:54 +0200 Subject: [PATCH 70/93] made plot scale configurable --- PythonRobotics/turtlebot.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index 5f76b4b2f1..d283e54eb3 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -17,7 +17,7 @@ show_animation = True plot_result = False -plot_scale = 1000 + # define state object state_fields = ['x', 'y', 'phi', 'v', 'omega', 't'] @@ -36,6 +36,9 @@ class Robot: _wheel_diameter = 66e-3 # [m] _wheel_distance = 160e-3 # [m] + # plot settings + plot_scale = 1000 + def __init__(self, x=0., # x-position y=0., @@ -91,7 +94,7 @@ def step(self, dt=0.1): # animate if required if self.turtle is not None: - self.turtle.setpos(s_new.x*plot_scale, s_new.y * plot_scale) + self.turtle.setpos(s_new.x*self.plot_scale, s_new.y * self.plot_scale) self.turtle.setheading(degrees(s_new.phi)) def states_df(self): From 5bd84e2c913e9f6fe1809d3ee2a0fb4e5a03fa63 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Tue, 13 Apr 2021 13:21:34 +0200 Subject: [PATCH 71/93] moved settings to profile dict --- PythonRobotics/turtlebot.py | 66 +++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index d283e54eb3..473c602816 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -24,27 +24,50 @@ State = namedtuple('State', state_fields, defaults=(0.,) * len(state_fields)) +# robot settings are defined with profiles +profile_simple = {'v_max': 1., + 'omega_max': 2.84, + 'accel_angular': 1., + 'accel_linear': 1., + 'wheel_diameter': 100e-3, + 'wheel_distance': 200e-3, + 'plot_scale': 100} + + +profile_turtlebot3 = {'v_max': 0.22, + 'omega_max': 2.84, + 'accel_angular': 0.1, + 'accel_linear': 1., + 'wheel_diameter': 66e-3, + 'wheel_distance': 160e-3, + 'plot_scale': 1000} + +profile_car = {'v_max': 200 / 3.6, # m/s + 'omega_max': 2.84, # rad/s + 'accel_angular': 0.5, # rad/s2 + 'accel_linear': 3., # m/s2 + 'wheel_diameter': 0.63, # m + 'wheel_distance': 2.6, + 'plot_scale': 1000} + + class Robot: """ Robot kinematics simulator """ - # numbers for Robotis Turtlebot3 - v_max = 0.22 # [m/s] - omega_max = 2.84 # [rad/s] - accel_angular = 0.1 # [rad/s2] - accel_linear = 1 # [m/s2] - _wheel_diameter = 66e-3 # [m] - _wheel_distance = 160e-3 # [m] - - # plot settings - plot_scale = 1000 def __init__(self, x=0., # x-position y=0., phi=0., + profile=None, animate=False): """ initiate robot at given location and orientation """ + if profile is not None: + self.profile = profile + else: + self.profile = profile_simple + self.states = [State(x, y, phi)] self._omega_target = 0. self._v_target = 0. @@ -56,6 +79,13 @@ def __init__(self, else: self.turtle = None + def __getattr__(self, attr): + """ get parameter from profile """ + if attr in self.profile: + return self.profile[attr] + else: + raise AttributeError(f"no attribute '{attr}' in self.profile") + def set_velocity(self, v, omega): """ set target velocities """ self._omega_target = np.clip(omega, -self.omega_max, self.omega_max) @@ -94,7 +124,8 @@ def step(self, dt=0.1): # animate if required if self.turtle is not None: - self.turtle.setpos(s_new.x*self.plot_scale, s_new.y * self.plot_scale) + self.turtle.setpos(s_new.x*self.plot_scale, + s_new.y * self.plot_scale) self.turtle.setheading(degrees(s_new.phi)) def states_df(self): @@ -124,19 +155,12 @@ def main(): sim = Robot(animate=show_animation) - # high acceleration ~ instantaneous velocity - sim.accel_angular = 1000 # high angular acceleration - sim.accel_linear = 1 # constant linear acceleration - sim.v_max = 10 # max linear velocity - sim.omega_max = 2*np.pi # max angular velocity - - omega = 2*np.pi # rad/s - # calculate path - t_sim = 1.0 # simulation time [sec] - dt = 0.01 # timestep + t_sim = 10 # simulation time [sec] + dt = 0.1 # timestep n_steps = int(t_sim / dt) + omega = 2*np.pi # rad/s sim.set_velocity(1.0, omega) for _ in range(n_steps): From e06f6d0f90bc1fab6287b30893a65713aca67ace Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Tue, 13 Apr 2021 13:22:18 +0200 Subject: [PATCH 72/93] changed visualisation --- PathTracking/pure_pursuit/pure_pursuit_2.py | 39 ++++----------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/PathTracking/pure_pursuit/pure_pursuit_2.py b/PathTracking/pure_pursuit/pure_pursuit_2.py index 2338f89533..9613809f86 100644 --- a/PathTracking/pure_pursuit/pure_pursuit_2.py +++ b/PathTracking/pure_pursuit/pure_pursuit_2.py @@ -5,6 +5,7 @@ Copyright (c) 2021 AIGRO B.V. - Jev Kuznetsov """ import math +import turtle import numpy as np import matplotlib.pyplot as plt from PythonRobotics.vectors import Vector @@ -53,35 +54,6 @@ def point_on_line(a, b, p): return result -def plot_state(states, waypoints): - """plot robot state, can be used in animaton - - Args: - states (namedtuple): state history, containing ['x', 'y', 'phi', 'v', 'omega', 't'] fields - """ - - plt.cla() - # for stopping simulation with the esc key. - plt.gcf().canvas.mpl_connect( - 'key_release_event', - lambda event: [exit(0) if event.key == 'escape' else None]) - - state = states[-1] - plot_arrow(state.x, state.y, state.phi, width=0.1, length=0.01) - - # plot trajectory - x = [state.x for state in states] - y = [state.y for state in states] - - plt.plot(x, y, "-b", label="trajectory") - plot_path(waypoints.points) - - plt.axis("equal") - plt.grid(True) - plt.title("Speed[m/s]: %.3f" % state.v) - plt.pause(0.001) - - def main(): # sim parameters @@ -95,18 +67,21 @@ def main(): print('Waypoints: ', wp.points) # create robot - bot = Robot(phi=math.pi/4) + bot = Robot(phi=math.pi/4, animate= True) + bot.plot_scale = 100 bot.set_velocity(3., math.pi/2) # simulate for _ in range(n_steps): bot.step(dt) - if show_animation: # pragma: no cover - plot_state(bot.states, wp) + + print('Simulation result:\n', bot.states_df()) + print('close simulation window to exit...') + turtle.done() if __name__ == "__main__": main() From d918ecaf5c6565c9b219969cc062a74982a257d1 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Tue, 13 Apr 2021 13:21:34 +0200 Subject: [PATCH 73/93] moved settings to profile dict --- PythonRobotics/turtlebot.py | 63 ++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index 5f76b4b2f1..1794e57838 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -24,24 +24,50 @@ State = namedtuple('State', state_fields, defaults=(0.,) * len(state_fields)) +# robot settings are defined with profiles +profile_simple = {'v_max': 1., + 'omega_max': 2.84, + 'accel_angular': 1., + 'accel_linear': 1., + 'wheel_diameter': 100e-3, + 'wheel_distance': 200e-3, + 'plot_scale': 100} + + +profile_turtlebot3 = {'v_max': 0.22, + 'omega_max': 2.84, + 'accel_angular': 0.1, + 'accel_linear': 1., + 'wheel_diameter': 66e-3, + 'wheel_distance': 160e-3, + 'plot_scale': 1000} + +profile_car = {'v_max': 200 / 3.6, # m/s + 'omega_max': 2.84, # rad/s + 'accel_angular': 0.5, # rad/s2 + 'accel_linear': 3., # m/s2 + 'wheel_diameter': 0.63, # m + 'wheel_distance': 2.6, + 'plot_scale': 1000} + + class Robot: """ Robot kinematics simulator """ - # numbers for Robotis Turtlebot3 - v_max = 0.22 # [m/s] - omega_max = 2.84 # [rad/s] - accel_angular = 0.1 # [rad/s2] - accel_linear = 1 # [m/s2] - _wheel_diameter = 66e-3 # [m] - _wheel_distance = 160e-3 # [m] def __init__(self, x=0., # x-position y=0., phi=0., + profile=None, animate=False): """ initiate robot at given location and orientation """ + if profile is not None: + self.profile = profile + else: + self.profile = profile_simple + self.states = [State(x, y, phi)] self._omega_target = 0. self._v_target = 0. @@ -53,6 +79,13 @@ def __init__(self, else: self.turtle = None + def __getattr__(self, attr): + """ get parameter from profile """ + if attr in self.profile: + return self.profile[attr] + else: + raise AttributeError(f"no attribute '{attr}' in self.profile") + def set_velocity(self, v, omega): """ set target velocities """ self._omega_target = np.clip(omega, -self.omega_max, self.omega_max) @@ -91,7 +124,8 @@ def step(self, dt=0.1): # animate if required if self.turtle is not None: - self.turtle.setpos(s_new.x*plot_scale, s_new.y * plot_scale) + self.turtle.setpos(s_new.x*self.plot_scale, + s_new.y * self.plot_scale) self.turtle.setheading(degrees(s_new.phi)) def states_df(self): @@ -121,19 +155,12 @@ def main(): sim = Robot(animate=show_animation) - # high acceleration ~ instantaneous velocity - sim.accel_angular = 1000 # high angular acceleration - sim.accel_linear = 1 # constant linear acceleration - sim.v_max = 10 # max linear velocity - sim.omega_max = 2*np.pi # max angular velocity - - omega = 2*np.pi # rad/s - # calculate path - t_sim = 1.0 # simulation time [sec] - dt = 0.01 # timestep + t_sim = 10 # simulation time [sec] + dt = 0.1 # timestep n_steps = int(t_sim / dt) + omega = 2*np.pi # rad/s sim.set_velocity(1.0, omega) for _ in range(n_steps): From 360a07a4ad5ab93a57c77715875565c04b3613d8 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Tue, 13 Apr 2021 17:09:25 +0200 Subject: [PATCH 74/93] added graphics module --- PythonRobotics/graphics.py | 59 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 PythonRobotics/graphics.py diff --git a/PythonRobotics/graphics.py b/PythonRobotics/graphics.py new file mode 100644 index 0000000000..c27147a3df --- /dev/null +++ b/PythonRobotics/graphics.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +""" + +Graphics for simulations, based on turtle graphics + +Author - Jev Kuznetsov + +""" + +import turtle + + +class World: + """ wrapper class for visualisations """ + + def __init__(self, coord=(-10, -10, 10, 10)): + """ create world with worldcoordinates as coord """ + + self.screen = turtle.Screen() + self.screen.setworldcoordinates(*coord) + self.screen.tracer(0, 0) + + def update(self): + """ update screen """ + self.screen.update() + + + def plot_path(self, points, color='red',dotsize=10): + """ plot conntected points """ + + t = turtle.Turtle() + t.color(color) + + t.hideturtle() + for x, y in points: + t.setpos(x, y) + t.dot(dotsize) + self.update() + + + + +def main(): + """ demo function """ + + w = World() + + # plot path + points = [(-5, -5), (0, 0), (0, 2), + (2, 2), (2, 0), (2, -2), (5, 5)] + + w.plot_path(points) + print('Please close graphics window...') + turtle.done() + + + +if __name__ == "__main__": + main() From f70e3a93bc5129e0cb2adff027aefe3ed89ed18c Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Tue, 13 Apr 2021 17:49:04 +0200 Subject: [PATCH 75/93] removed animation --- PythonRobotics/turtlebot.py | 35 ++++++----------------------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index fdb8d5b642..89b8b749c9 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -9,14 +9,12 @@ from collections import namedtuple from math import sin, cos, degrees -import turtle import numpy as np import matplotlib.pyplot as plt import pandas as pd show_animation = True -plot_result = False # define state object state_fields = ['x', 'y', 'phi', 'v', 'omega', 't'] @@ -29,8 +27,7 @@ 'accel_angular': 1., 'accel_linear': 1., 'wheel_diameter': 100e-3, - 'wheel_distance': 200e-3, - 'plot_scale': 100} + 'wheel_distance': 200e-3} profile_turtlebot3 = {'v_max': 0.22, @@ -38,16 +35,14 @@ 'accel_angular': 0.1, 'accel_linear': 1., 'wheel_diameter': 66e-3, - 'wheel_distance': 160e-3, - 'plot_scale': 1000} + 'wheel_distance': 160e-3} profile_car = {'v_max': 200 / 3.6, # m/s 'omega_max': 2.84, # rad/s 'accel_angular': 0.5, # rad/s2 'accel_linear': 3., # m/s2 'wheel_diameter': 0.63, # m - 'wheel_distance': 2.6, - 'plot_scale': 1000} + 'wheel_distance': 2.6} class Robot: @@ -59,8 +54,7 @@ def __init__(self, x=0., # x-position y=0., phi=0., - profile=None, - animate=False): + profile=None): """ initiate robot at given location and orientation """ if profile is not None: self.profile = profile @@ -71,13 +65,6 @@ def __init__(self, self._omega_target = 0. self._v_target = 0. - if animate: - self.screen = turtle.Screen() - self.screen.setup(640, 480) - self.turtle = turtle.Turtle() - else: - self.turtle = None - def __getattr__(self, attr): """ get parameter from profile """ if attr in self.profile: @@ -121,12 +108,6 @@ def step(self, dt=0.1): self.states.append(s_new) - # animate if required - if self.turtle is not None: - self.turtle.setpos(s_new.x*self.plot_scale, - s_new.y * self.plot_scale) - self.turtle.setheading(degrees(s_new.phi)) - def states_df(self): """ states as DataFrame """ cols = state_fields[:-1] @@ -152,7 +133,7 @@ def __repr__(self): def main(): """ demonstrate functionality """ - sim = Robot(animate=show_animation) + sim = Robot() # calculate path t_sim = 10 # simulation time [sec] @@ -169,7 +150,7 @@ def main(): print('Simulation result:\n', states) # plot data - if plot_result: + if show_animation: plt.style.use('seaborn-whitegrid') plt.cla() @@ -199,10 +180,6 @@ def main(): plt.show() - if show_animation: - print('close plot window to continue....') - turtle.done() - if __name__ == '__main__': print('Turtlebot simulation demo starting...') From c36a6908ba93368d7a3f021d51c4ee914ff4b493 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Tue, 13 Apr 2021 18:01:57 +0200 Subject: [PATCH 76/93] refactored graphics --- PathTracking/pure_pursuit/pure_pursuit_2.py | 17 +++++++++++------ PythonRobotics/graphics.py | 20 +++++++++++++------- PythonRobotics/turtlebot.py | 2 +- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/PathTracking/pure_pursuit/pure_pursuit_2.py b/PathTracking/pure_pursuit/pure_pursuit_2.py index 9613809f86..de830d415a 100644 --- a/PathTracking/pure_pursuit/pure_pursuit_2.py +++ b/PathTracking/pure_pursuit/pure_pursuit_2.py @@ -4,13 +4,14 @@ Copyright (c) 2021 AIGRO B.V. - Jev Kuznetsov """ + +import time import math import turtle import numpy as np -import matplotlib.pyplot as plt from PythonRobotics.vectors import Vector from PythonRobotics.turtlebot import Robot -from PythonRobotics.plots import plot_arrow, plot_path +from PythonRobotics.graphics import World show_animation = True @@ -56,32 +57,36 @@ def point_on_line(a, b, p): def main(): + world = World((-1, -1, 3, 3)) + # sim parameters t_sim = 10.0 # simulation time [sec] dt = 0.1 # timestep n_steps = int(t_sim / dt) # create waypoints - coord = [(0, 0), (0, 2), (2, 2), (2, 0), (2, -2)] + coord = [(0, 0), (0, 2), (2, 2), (2, 0), (2, -5)] + world.plot_path(coord) wp = Waypoints(coord) print('Waypoints: ', wp.points) # create robot - bot = Robot(phi=math.pi/4, animate= True) + bot = Robot(phi=math.pi/4) bot.plot_scale = 100 bot.set_velocity(3., math.pi/2) # simulate for _ in range(n_steps): bot.step(dt) + world.robot_step(bot.state) - - + time.sleep(0.01) print('Simulation result:\n', bot.states_df()) print('close simulation window to exit...') turtle.done() + if __name__ == "__main__": main() diff --git a/PythonRobotics/graphics.py b/PythonRobotics/graphics.py index c27147a3df..077c0ec4dd 100644 --- a/PythonRobotics/graphics.py +++ b/PythonRobotics/graphics.py @@ -6,7 +6,7 @@ Author - Jev Kuznetsov """ - +from math import degrees import turtle @@ -20,24 +20,30 @@ def __init__(self, coord=(-10, -10, 10, 10)): self.screen.setworldcoordinates(*coord) self.screen.tracer(0, 0) - def update(self): - """ update screen """ - self.screen.update() + self.turtle = turtle.Turtle() + self.turtle.color('blue') + + + def robot_step(self, state): + """ plot robot state """ + self.turtle.setpos(state.x, state.y) + self.turtle.setheading(degrees(state.phi)) + self.screen.update() def plot_path(self, points, color='red',dotsize=10): """ plot conntected points """ t = turtle.Turtle() t.color(color) - + t.hideturtle() for x, y in points: t.setpos(x, y) t.dot(dotsize) - self.update() + self.screen.update() + - def main(): diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index 89b8b749c9..8720aec4a2 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -8,7 +8,7 @@ """ from collections import namedtuple -from math import sin, cos, degrees +from math import sin, cos import numpy as np import matplotlib.pyplot as plt import pandas as pd From 88374a1f6c15007c509cd7cbfdedb88b0d7d0943 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Tue, 13 Apr 2021 19:26:27 +0200 Subject: [PATCH 77/93] added marker --- PythonRobotics/graphics.py | 43 ++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/PythonRobotics/graphics.py b/PythonRobotics/graphics.py index 077c0ec4dd..db5f437b47 100644 --- a/PythonRobotics/graphics.py +++ b/PythonRobotics/graphics.py @@ -7,31 +7,59 @@ """ from math import degrees +from time import sleep import turtle class World: """ wrapper class for visualisations """ - def __init__(self, coord=(-10, -10, 10, 10)): + def __init__(self, coord=(-10, -10, 10, 10), window_size=(800, 800)): """ create world with worldcoordinates as coord """ self.screen = turtle.Screen() + self.screen.setup(width=window_size[0], height=window_size[1]) self.screen.setworldcoordinates(*coord) self.screen.tracer(0, 0) self.turtle = turtle.Turtle() self.turtle.color('blue') + self._markers = {} # marker objects + + def add_marker(self, name, + color='green', + shape="circle", + size=0.5, + trace=False): + """ add marker (markers are Turtle objects) """ + m = turtle.Turtle() + m.color(color) + m.shape(shape) + m.shapesize(size,size) + if not trace: + m.penup() + self._markers[name] = m + + def move_marker(self, name, xy): + """move marker to a position + + Args: + name (str): marker name + xy (Vector): (x,y) vector + """ + + self._markers[name].setpos(xy) + self.screen.update() - def robot_step(self, state): + def move_robot(self, state): """ plot robot state """ self.turtle.setpos(state.x, state.y) self.turtle.setheading(degrees(state.phi)) self.screen.update() - def plot_path(self, points, color='red',dotsize=10): + def plot_path(self, points, color='red', dotsize=10): """ plot conntected points """ t = turtle.Turtle() @@ -44,8 +72,6 @@ def plot_path(self, points, color='red',dotsize=10): self.screen.update() - - def main(): """ demo function """ @@ -56,10 +82,15 @@ def main(): (2, 2), (2, 0), (2, -2), (5, 5)] w.plot_path(points) + + w.add_marker('m') + for x in range(10): + w.move_marker('m', (x/10, 0)) + sleep(0.1) + print('Please close graphics window...') turtle.done() - if __name__ == "__main__": main() From 1aba5f08b27d6d153912e98559c20a83c147c335 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Tue, 13 Apr 2021 20:18:37 +0200 Subject: [PATCH 78/93] removed unnecessary __init__.py --- __init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 __init__.py diff --git a/__init__.py b/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 From c1f6e9bbcb67c37d6ee4c50147ab98740be488eb Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Tue, 13 Apr 2021 20:29:10 +0200 Subject: [PATCH 79/93] added xy property --- PythonRobotics/turtlebot.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index 8720aec4a2..143d6abbff 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -12,7 +12,7 @@ import numpy as np import matplotlib.pyplot as plt import pandas as pd - +from PythonRobotics.vectors import Vector show_animation = True @@ -108,7 +108,7 @@ def step(self, dt=0.1): self.states.append(s_new) - def states_df(self): + def states_df(self) -> 'pd.DataFrame': """ states as DataFrame """ cols = state_fields[:-1] data = {} @@ -121,6 +121,11 @@ def states_df(self): return df + @property + def xy(self) -> 'Vector': + """ xy position as Vector """ + return Vector(self.state.x,self.state.y) + @property def state(self): """ last known state """ From ddad22abc481aa7ac1b8fbdf36aa97ef4f119e5e Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Tue, 13 Apr 2021 20:30:05 +0200 Subject: [PATCH 80/93] style --- PythonRobotics/graphics.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/PythonRobotics/graphics.py b/PythonRobotics/graphics.py index db5f437b47..87ec0ffe9c 100644 --- a/PythonRobotics/graphics.py +++ b/PythonRobotics/graphics.py @@ -27,7 +27,8 @@ def __init__(self, coord=(-10, -10, 10, 10), window_size=(800, 800)): self._markers = {} # marker objects - def add_marker(self, name, + def add_marker(self, + name, color='green', shape="circle", size=0.5, @@ -36,7 +37,7 @@ def add_marker(self, name, m = turtle.Turtle() m.color(color) m.shape(shape) - m.shapesize(size,size) + m.shapesize(size, size) if not trace: m.penup() self._markers[name] = m From 474638c5bd46c0ad7f688e8035279c7d98f8489e Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Tue, 13 Apr 2021 20:30:39 +0200 Subject: [PATCH 81/93] added target position calculation --- PathTracking/pure_pursuit/pure_pursuit_2.py | 49 ++++++++++++++------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/PathTracking/pure_pursuit/pure_pursuit_2.py b/PathTracking/pure_pursuit/pure_pursuit_2.py index de830d415a..c3174df8da 100644 --- a/PathTracking/pure_pursuit/pure_pursuit_2.py +++ b/PathTracking/pure_pursuit/pure_pursuit_2.py @@ -5,6 +5,7 @@ Copyright (c) 2021 AIGRO B.V. - Jev Kuznetsov """ +from collections import UserList import time import math import turtle @@ -16,12 +17,13 @@ show_animation = True -class Waypoints: +class Waypoints(UserList): """ navigation waypoints, collection of xy Vectors """ - def __init__(self, points: 'list[Vector]'): + def __init__(self, *args, **kwargs): """ create waypoints. """ - self.points = [Vector(x, y) for x, y in points] + super().__init__(*args, **kwargs) + self.next_idx = None def find_next_idx(self, xy: 'Vector') -> 'int': @@ -29,14 +31,14 @@ def find_next_idx(self, xy: 'Vector') -> 'int': if self.next_idx is None: # first time search # distance between points - dst = [pt.distance(xy) for pt in self.points] + dst = [pt.distance(xy) for pt in self.data] idx = np.argmin(dst) self.next_idx = idx + 1 else: # calculate distances to n and n+1 waypoints idx = self.next_idx - dst_a = xy.distance(self.points[idx-1]) - dst_b = xy.distance(self.points[idx]) + dst_a = xy.distance(self.data[idx-1]) + dst_b = xy.distance(self.data[idx]) if dst_a > dst_b: # next point self.next_idx += 1 @@ -44,7 +46,7 @@ def find_next_idx(self, xy: 'Vector') -> 'int': @property def target_reached(self): - return not self.next_idx < len(self.points) + return not self.next_idx < len(self.data) def point_on_line(a, b, p): @@ -55,32 +57,49 @@ def point_on_line(a, b, p): return result +def target_position(state, wp_a, wp_b, look_ahead=1) -> 'Vector': + """ calculate robot target position """ + + # future position + d = Vector(state.x, state.y) + Vector.from_polar(look_ahead, state.phi) + + # project to line + e = point_on_line(wp_a, wp_b, d) + + return e + + def main(): world = World((-1, -1, 3, 3)) + world.add_marker('target', trace=True) # sim parameters t_sim = 10.0 # simulation time [sec] dt = 0.1 # timestep n_steps = int(t_sim / dt) - # create waypoints - coord = [(0, 0), (0, 2), (2, 2), (2, 0), (2, -5)] + coord = [Vector(xy) for xy in [(0, 0), (0, 2), (2, 2), (2, 0), (2, -5)]] world.plot_path(coord) - wp = Waypoints(coord) - print('Waypoints: ', wp.points) + waypoints = Waypoints(coord) + print('Waypoints: ', waypoints) # create robot bot = Robot(phi=math.pi/4) - bot.plot_scale = 100 bot.set_velocity(3., math.pi/2) # simulate - for _ in range(n_steps): + for i_step in range(n_steps): bot.step(dt) - world.robot_step(bot.state) - time.sleep(0.01) + idx = waypoints.find_next_idx(bot.xy) + target = target_position(bot.state, waypoints[idx-1], waypoints[idx]) + + world.move_robot(bot.state) + + world.move_marker('target', target) + print(f"step[{i_step}] next_wp:{waypoints.next_idx}") + time.sleep(0.1) print('Simulation result:\n', bot.states_df()) From a9e3808a11a496bbb50f3bb11f9314ab0d77619d Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Tue, 13 Apr 2021 20:34:22 +0200 Subject: [PATCH 82/93] nice printout --- PythonRobotics/vectors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PythonRobotics/vectors.py b/PythonRobotics/vectors.py index 7886a106f1..3bd3c128bf 100644 --- a/PythonRobotics/vectors.py +++ b/PythonRobotics/vectors.py @@ -39,7 +39,7 @@ def __init__(self, *args): self.y = float(args[0][1]) def __repr__(self): - return f"{self.__class__.__name__}(x={self.x}, y={self.y})" + return f"(x={self.x:.3f}, y={self.y:.3f})" def __add__(self, other): return Vector(self.x + other.x, self.y + other.y) From 32cd77db6985a4c84d65ca6023f1781fe4ea4c96 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Tue, 13 Apr 2021 23:53:52 +0200 Subject: [PATCH 83/93] pure pursuit works! --- PathTracking/pure_pursuit/pure_pursuit_2.py | 122 ++++++++++++++++---- PythonRobotics/graphics.py | 7 +- 2 files changed, 106 insertions(+), 23 deletions(-) diff --git a/PathTracking/pure_pursuit/pure_pursuit_2.py b/PathTracking/pure_pursuit/pure_pursuit_2.py index c3174df8da..6b438ee72a 100644 --- a/PathTracking/pure_pursuit/pure_pursuit_2.py +++ b/PathTracking/pure_pursuit/pure_pursuit_2.py @@ -9,6 +9,7 @@ import time import math import turtle +from typing import Tuple import numpy as np from PythonRobotics.vectors import Vector from PythonRobotics.turtlebot import Robot @@ -16,6 +17,15 @@ show_animation = True +# sim parameters +WAYPOINTS_TYPE = 'sine' +TARGET_SPEED = .2 # [m/s] +LOOK_AHEAD_1 = 0.1 # [m] lenth of velocity vector +LOOK_AHEAD_2 = 0.2 # [m] step forward along the path segment +Kp = 1.0 # controller gain +t_sim = 50.0 # simulation time [sec] +dt = 0.1 # timestep +SIM_DELAY = 0.01 class Waypoints(UserList): """ navigation waypoints, collection of xy Vectors """ @@ -23,6 +33,7 @@ class Waypoints(UserList): def __init__(self, *args, **kwargs): """ create waypoints. """ super().__init__(*args, **kwargs) + self.data = [Vector(d) for d in self.data] self.next_idx = None @@ -49,7 +60,7 @@ def target_reached(self): return not self.next_idx < len(self.data) -def point_on_line(a, b, p): +def project_on_line(a: Vector, b: Vector, p: Vector) -> Vector: """ project point p to a line defined by points a and b """ ap = p - a ab = b - a @@ -57,49 +68,116 @@ def point_on_line(a, b, p): return result -def target_position(state, wp_a, wp_b, look_ahead=1) -> 'Vector': - """ calculate robot target position """ - +def target_position(xy: Vector, + phi: float, + a: Vector, b: Vector, + velocity_vector= LOOK_AHEAD_1, + look_ahead=LOOK_AHEAD_2 + ) -> Tuple[Vector, Vector]: + """calculate look-ahead and target positions + + Args: + xy (Vector): robot position + phi (float): robot pose [rad] + a (Vector): first waypoint + b (Vector): second waypoint + velocity_vector (float, optional): magnitude of velocity vector . Defaults to 1.0. + look_ahead (float, optional): forward distance from projecton point to path. + Defaults to 1.0. + + Returns: + Tuple[Vector, Vector, float]: target & look-ahead positions, and angle error + """ + # point names consistent with https://www.geogebra.org/calculator/vh5d7jvy # future position - d = Vector(state.x, state.y) + Vector.from_polar(look_ahead, state.phi) + v = Vector.from_polar(velocity_vector, phi) + d = xy + v # project to line - e = point_on_line(wp_a, wp_b, d) + e = project_on_line(a, b, d) + f = look_ahead*(b - a)/abs(b - a) + e + + # calculate angele error + alpha = v.angle(f-xy) + + return f, d, alpha + + +def proportional_control(target: float, current: float) -> float: + a = Kp * (target - current) + + return a + +def generate_waypoints(key: str) -> Waypoints: + """generate different types of waypoints + + Args: + key (str): name of waypoint generator - return e + Returns: + Waypoints: waypoints to follow + """ + if key == 'simple': + coord = [Vector(xy) for xy in [(0, 0), (0, 2), (2, 2), (2, 0), (2, -3)]] + waypoints = Waypoints(coord) + elif key == 'sine': + cx = np.arange(0, 5, 0.02) + cy = [np.sin(ix / 0.1) * ix / 2.0 for ix in cx] + waypoints = Waypoints(zip(cx,cy)) + else: + raise KeyError(f"Don't know how to generate waypoints for {key}") + return waypoints def main(): - world = World((-1, -1, 3, 3)) - world.add_marker('target', trace=True) + # create visualisation + world = World((0, -3, 5, 3)) + world.add_marker('target') + world.add_marker('future_xy', color="magenta") - # sim parameters - t_sim = 10.0 # simulation time [sec] - dt = 0.1 # timestep - n_steps = int(t_sim / dt) # create waypoints - coord = [Vector(xy) for xy in [(0, 0), (0, 2), (2, 2), (2, 0), (2, -5)]] - world.plot_path(coord) - waypoints = Waypoints(coord) + waypoints = generate_waypoints(WAYPOINTS_TYPE) print('Waypoints: ', waypoints) + world.plot_path(waypoints) # create robot bot = Robot(phi=math.pi/4) - bot.set_velocity(3., math.pi/2) + i_step = 0 # simulate - for i_step in range(n_steps): + while True: + + # advance simulation bot.step(dt) + # calculate target point idx = waypoints.find_next_idx(bot.xy) - target = target_position(bot.state, waypoints[idx-1], waypoints[idx]) + if waypoints.target_reached: + print('Target reached') + break - world.move_robot(bot.state) + target, future_xy, alpha = target_position( + bot.xy, bot.state.phi, waypoints[idx-1], waypoints[idx]) + # control robot + omega = proportional_control(0, alpha) + bot.set_velocity(TARGET_SPEED, omega) + + # visualise + world.move_robot(bot.state) + world.move_marker('future_xy', future_xy) world.move_marker('target', target) - print(f"step[{i_step}] next_wp:{waypoints.next_idx}") - time.sleep(0.1) + print(f"step[{i_step}] next_wp:{waypoints.next_idx} target:{target} omega:{omega:.2f}") + time.sleep(SIM_DELAY) + + i_step += 1 + + # check for exit + if world.click_xy: + print('Aborted by click') + break + print('Simulation result:\n', bot.states_df()) diff --git a/PythonRobotics/graphics.py b/PythonRobotics/graphics.py index 87ec0ffe9c..df0e887172 100644 --- a/PythonRobotics/graphics.py +++ b/PythonRobotics/graphics.py @@ -21,11 +21,16 @@ def __init__(self, coord=(-10, -10, 10, 10), window_size=(800, 800)): self.screen.setup(width=window_size[0], height=window_size[1]) self.screen.setworldcoordinates(*coord) self.screen.tracer(0, 0) + self.screen.onclick(self.click_callback) self.turtle = turtle.Turtle() self.turtle.color('blue') self._markers = {} # marker objects + self.click_xy = None # last clicked position + + def click_callback(self,x,y): + self.click_xy = (x,y) def add_marker(self, name, @@ -60,7 +65,7 @@ def move_robot(self, state): self.turtle.setheading(degrees(state.phi)) self.screen.update() - def plot_path(self, points, color='red', dotsize=10): + def plot_path(self, points, color='red', dotsize=3): """ plot conntected points """ t = turtle.Turtle() From f290becd0b19c4b866c91bc35600c5d2e0ab0fe8 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Wed, 14 Apr 2021 00:00:34 +0200 Subject: [PATCH 84/93] added notebook --- PathTracking/pure_pursuit/pure_pursuit.ipynb | 136 ++++++++----------- 1 file changed, 54 insertions(+), 82 deletions(-) diff --git a/PathTracking/pure_pursuit/pure_pursuit.ipynb b/PathTracking/pure_pursuit/pure_pursuit.ipynb index e60b86c227..496f264fd9 100644 --- a/PathTracking/pure_pursuit/pure_pursuit.ipynb +++ b/PathTracking/pure_pursuit/pure_pursuit.ipynb @@ -6,10 +6,9 @@ "source": [ "# Pure pursuit controller\n", "\n", + "This notebook shows basic geometrical calculations needed for a pure pursuit controller.\n", "\n", - "References:\n", - "\n", - "* [TheNatureOfCode](https://natureofcode.com/book/chapter-6-autonomous-agents/#chapter06_section8)\n", + "An excellent explanation can be found on [TheNatureOfCode](https://natureofcode.com/book/chapter-6-autonomous-agents/#chapter06_section8)\n", "\n", "\"Drawing\"\n", "\n", @@ -21,7 +20,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -58,9 +57,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Heading error = 1.315 rad\n", + "Point E: (x=2.000, y=1.000)\n" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "

" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "# reproduce geogebra setup\n", "\n", @@ -96,92 +114,46 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "# Now let's create a example path, find \n", - "\n", + "# complex path generation\n", + " \n", "\n", - " \n", - "class Waypoints:\n", - " \"\"\" navigation waypoints, collection of xy Vectors \"\"\"\n", - " \n", - " def __init__(self, points: '(x,y) tuples'):\n", - " \"\"\" create waypoints. \"\"\"\n", - " self.points = [Vector(x,y) for x,y in points]\n", - " self.next_idx = None \n", - " \n", - " def find_next_idx(self,xy: 'Vector') -> 'idx':\n", - " \"\"\" get next waypoint \"\"\"\n", - " \n", - " if self.next_idx is None: # first time search\n", - " dst = [pt.distance(xy) for pt in self.points] # distance between points\n", - " idx = np.argmin(dst)\n", - " self.next_idx = idx + 1\n", - " else:\n", - " # calculate distances to n and n+1 waypoints\n", - " idx = self.next_idx\n", - " dst_a = xy.distance(self.points[idx-1])\n", - " dst_b = xy.distance(self.points[idx])\n", - " if dst_a > dst_b: # next point\n", - " self.next_idx += 1\n", - " \n", - " return self.next_idx\n", - "\n", - " @property\n", - " def target_reached(self):\n", - " return False if self.next_idx < len(self.points) else True\n", - " \n", - "def point_on_line(a, b, p):\n", - " \"\"\" project point p to a line defined by points a and b \"\"\"\n", - " ap = p - a\n", - " ab = b - a\n", - " result = a + ap.dot(ab) / ab.dot(ab) * ab\n", - " return result\n", - " \n", "# create waypoints \n", - "coord = [(0,0),(0,10),(10,10),(10,0),(10,-10)]\n", - "\n", - "wp = Waypoints(coord)\n", - "plot_path(wp.points)\n", + "cx = np.arange(0, 5, 0.1)\n", + "cy = [np.sin(ix / 0.1) * ix / 2.0 for ix in cx]\n", "\n", - "#plot projection\n", - "#px = point_on_line(wp.points[idx],wp.points[idx+1],pt)\n", - "\n", - "# simulate moving along path\n", - "positions = [Vector(xy) for xy in [(-5,-5), (0,0), (0,5), (0,9),(5,10),(6,11),(10,-5),(10,-9)]]\n", - "\n", - "for idx, xy in enumerate(positions):\n", - " \n", - " wp_idx = wp.find_next_idx(xy)\n", - " print('idx:', wp_idx)\n", - " if wp.target_reached:\n", - " print('Target reached')\n", - " break\n", - " next_wp = wp.points[wp_idx]\n", - " plot_path((xy,next_wp))\n", - " plot_point(xy,'xy_%i' % idx)\n", - " \n", - "\n", - "\n", - "plt.axis('equal')" + "plt.plot(cx,cy,'o-')" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "# complex path\n", - " \n", - "# create waypoints \n", - "cx = np.arange(0, 50, 5)\n", - "cy = [np.sin(ix / 5.0) * ix / 2.0 for ix in cx]\n", - "\n", - "wp = Waypoints(zip(cx,cy))\n", - "plot_path(wp.points)" + "Now let's continue to in to the `pure_pursuit_2.py` for full iplementation of the controller!" ] } ], From d99cf438e86ee4f7a8955f33ed18c0883a2d4037 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Wed, 14 Apr 2021 10:30:26 +0200 Subject: [PATCH 85/93] minor refactor, added testcase for graphics --- PathTracking/pure_pursuit/pure_pursuit_2.py | 3 ++- PythonRobotics/graphics.py | 18 +++++++++++------- PythonRobotics/turtlebot.py | 14 +++++++++----- tests/test_graphics.py | 17 +++++++++++++++++ tests/test_turtlebot.py | 2 +- 5 files changed, 40 insertions(+), 14 deletions(-) create mode 100644 tests/test_graphics.py diff --git a/PathTracking/pure_pursuit/pure_pursuit_2.py b/PathTracking/pure_pursuit/pure_pursuit_2.py index 6b438ee72a..4d97e77220 100644 --- a/PathTracking/pure_pursuit/pure_pursuit_2.py +++ b/PathTracking/pure_pursuit/pure_pursuit_2.py @@ -133,6 +133,7 @@ def main(): # create visualisation world = World((0, -3, 5, 3)) + world.screen.title('Pure pursuit 2') world.add_marker('target') world.add_marker('future_xy', color="magenta") @@ -165,7 +166,7 @@ def main(): bot.set_velocity(TARGET_SPEED, omega) # visualise - world.move_robot(bot.state) + world.move_robot(bot.xy, bot.phi) world.move_marker('future_xy', future_xy) world.move_marker('target', target) print(f"step[{i_step}] next_wp:{waypoints.next_idx} target:{target} omega:{omega:.2f}") diff --git a/PythonRobotics/graphics.py b/PythonRobotics/graphics.py index df0e887172..9b07a58179 100644 --- a/PythonRobotics/graphics.py +++ b/PythonRobotics/graphics.py @@ -6,10 +6,11 @@ Author - Jev Kuznetsov """ -from math import degrees +import math from time import sleep import turtle +TESTING = True class World: """ wrapper class for visualisations """ @@ -58,11 +59,11 @@ def move_marker(self, name, xy): self._markers[name].setpos(xy) self.screen.update() - def move_robot(self, state): + def move_robot(self, xy, phi): """ plot robot state """ - self.turtle.setpos(state.x, state.y) - self.turtle.setheading(degrees(state.phi)) + self.turtle.setpos(xy) + self.turtle.setheading(math.degrees(phi)) self.screen.update() def plot_path(self, points, color='red', dotsize=3): @@ -92,10 +93,13 @@ def main(): w.add_marker('m') for x in range(10): w.move_marker('m', (x/10, 0)) - sleep(0.1) + w.move_robot((0, x), math.pi/2) + if not TESTING: + sleep(0.1) - print('Please close graphics window...') - turtle.done() + if not TESTING: + print('Please close graphics window...') + turtle.done() if __name__ == "__main__": diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index 143d6abbff..ab5dc82bdd 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -14,7 +14,7 @@ import pandas as pd from PythonRobotics.vectors import Vector -show_animation = True +PLOT_RESULT = True # define state object state_fields = ['x', 'y', 'phi', 'v', 'omega', 't'] @@ -108,7 +108,7 @@ def step(self, dt=0.1): self.states.append(s_new) - def states_df(self) -> 'pd.DataFrame': + def states_df(self) -> pd.DataFrame: """ states as DataFrame """ cols = state_fields[:-1] data = {} @@ -122,12 +122,16 @@ def states_df(self) -> 'pd.DataFrame': return df @property - def xy(self) -> 'Vector': + def xy(self) -> Vector: """ xy position as Vector """ return Vector(self.state.x,self.state.y) @property - def state(self): + def phi(self) -> float: + return self.state.phi + + @property + def state(self) -> State: """ last known state """ return self.states[-1] @@ -155,7 +159,7 @@ def main(): print('Simulation result:\n', states) # plot data - if show_animation: + if PLOT_RESULT: plt.style.use('seaborn-whitegrid') plt.cla() diff --git a/tests/test_graphics.py b/tests/test_graphics.py new file mode 100644 index 0000000000..907da73e01 --- /dev/null +++ b/tests/test_graphics.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +""" +Test turtle graphics + +author - Jev Kuznetsov +""" + +import conftest +from PythonRobotics import graphics as m + + +def test_1(): + m.TESTING = True + m.main() + +if __name__ == '__main__': + conftest.run_this_test(__file__) diff --git a/tests/test_turtlebot.py b/tests/test_turtlebot.py index 072ce709f3..3a1aeac1b7 100644 --- a/tests/test_turtlebot.py +++ b/tests/test_turtlebot.py @@ -10,7 +10,7 @@ def test_1(): - m.show_animation = False + m.PLOT_RESULT = False m.main() if __name__ == '__main__': From 739c8788504fc282a15f6afb03465896002eff32 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Wed, 14 Apr 2021 11:13:36 +0200 Subject: [PATCH 86/93] cleaned up, added new waypoint course --- .../pure_pursuit/{ => img}/pure_pursuit.png | Bin PathTracking/pure_pursuit/pure_pursuit.ipynb | 98 ++++-------------- PathTracking/pure_pursuit/pure_pursuit_2.py | 17 ++- PythonRobotics/turtlebot.py | 8 +- 4 files changed, 41 insertions(+), 82 deletions(-) rename PathTracking/pure_pursuit/{ => img}/pure_pursuit.png (100%) diff --git a/PathTracking/pure_pursuit/pure_pursuit.png b/PathTracking/pure_pursuit/img/pure_pursuit.png similarity index 100% rename from PathTracking/pure_pursuit/pure_pursuit.png rename to PathTracking/pure_pursuit/img/pure_pursuit.png diff --git a/PathTracking/pure_pursuit/pure_pursuit.ipynb b/PathTracking/pure_pursuit/pure_pursuit.ipynb index 496f264fd9..c5fffd922c 100644 --- a/PathTracking/pure_pursuit/pure_pursuit.ipynb +++ b/PathTracking/pure_pursuit/pure_pursuit.ipynb @@ -8,13 +8,14 @@ "\n", "This notebook shows basic geometrical calculations needed for a pure pursuit controller.\n", "\n", + "\n", "An excellent explanation can be found on [TheNatureOfCode](https://natureofcode.com/book/chapter-6-autonomous-agents/#chapter06_section8)\n", "\n", "\"Drawing\"\n", "\n", "An interactive geometric model below is avaliable on [GeoGebra](https://www.geogebra.org/calculator/vh5d7jvy)\n", "\n", - "\n", + "\n", "\n" ] }, @@ -22,43 +23,6 @@ "cell_type": "code", "execution_count": 1, "metadata": {}, - "outputs": [], - "source": [ - "\n", - "from PythonRobotics.vectors import Vector\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "plt.style.use('ggplot')\n", - "plt.rcParams['figure.figsize'] = (8,8)\n", - "\n", - "# support functions\n", - "\n", - "def plot_point(xy, txt = None, style = 'o'):\n", - " plt.plot(xy[0], xy[1], style) \n", - " if txt is not None:\n", - " plt.text(xy[0]+0.1,xy[1], txt)\n", - " \n", - "def plot_path(points, style = 'o-'):\n", - " x = [pt[0] for pt in points]\n", - " y = [pt[1] for pt in points]\n", - " plt.plot(x,y, style)\n", - " \n", - "def plot_vector(p1,p2):\n", - " plt.quiver(p1.x,p1.y,(p2-p1).x,(p2-p1).y,angles='xy', scale_units='xy', scale=1)\n", - " \n", - "def point_on_line(a: Vector,b: Vector,p: Vector):\n", - " \"\"\"\n", - " project point p to line defined by a and b\n", - " \"\"\"\n", - " ap = p - a\n", - " ab = b - a\n", - " return a + ap.dot(ab) / ab.dot(ab) * ab" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, "outputs": [ { "name": "stdout", @@ -80,6 +44,23 @@ } ], "source": [ + "\n", + "from PythonRobotics.vectors import Vector \n", + "from PythonRobotics.plots import plot_point, plot_path, plot_vector\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "plt.style.use('ggplot')\n", + "plt.rcParams['figure.figsize'] = (8,8)\n", + "\n", + " \n", + "def point_on_line(a: Vector,b: Vector,p: Vector):\n", + " \"\"\"\n", + " project point p to line defined by a and b\n", + " \"\"\"\n", + " ap = p - a\n", + " ab = b - a\n", + " return a + ap.dot(ab) / ab.dot(ab) * ab\n", + "\n", "# reproduce geogebra setup\n", "\n", "look_ahead = 0.5 # [m]\n", @@ -112,48 +93,13 @@ "plt.axis('equal');" ] }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# complex path generation\n", - " \n", - "\n", - "# create waypoints \n", - "cx = np.arange(0, 5, 0.1)\n", - "cy = [np.sin(ix / 0.1) * ix / 2.0 for ix in cx]\n", - "\n", - "plt.plot(cx,cy,'o-')" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Now let's continue to in to the `pure_pursuit_2.py` for full iplementation of the controller!" + "Now let's continue to in to the `pure_pursuit_2.py` for full iplementation of the controller!\n", + "\n", + "" ] } ], diff --git a/PathTracking/pure_pursuit/pure_pursuit_2.py b/PathTracking/pure_pursuit/pure_pursuit_2.py index 4d97e77220..adb2077be4 100644 --- a/PathTracking/pure_pursuit/pure_pursuit_2.py +++ b/PathTracking/pure_pursuit/pure_pursuit_2.py @@ -18,8 +18,8 @@ show_animation = True # sim parameters -WAYPOINTS_TYPE = 'sine' -TARGET_SPEED = .2 # [m/s] +WAYPOINTS_TYPE = 'square' +TARGET_SPEED = .1 # [m/s] LOOK_AHEAD_1 = 0.1 # [m] lenth of velocity vector LOOK_AHEAD_2 = 0.2 # [m] step forward along the path segment Kp = 1.0 # controller gain @@ -124,6 +124,19 @@ def generate_waypoints(key: str) -> Waypoints: cx = np.arange(0, 5, 0.02) cy = [np.sin(ix / 0.1) * ix / 2.0 for ix in cx] waypoints = Waypoints(zip(cx,cy)) + elif key == 'square': + w_tops = np.linspace(0.1, 1, 5) # turn widhts + waypoints = Waypoints([Vector(0,0)]) + + v_up = Vector(0,1) + waypoints.append(waypoints[-1] + 0.5*v_up) + for w in reversed(w_tops): + waypoints.append(waypoints[-1] + Vector(w,0)) + waypoints.append(waypoints[-1] - v_up) + waypoints.append(waypoints[-1] + Vector(0.4,0)) + waypoints.append(waypoints[-1] + v_up) + + else: raise KeyError(f"Don't know how to generate waypoints for {key}") diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index ab5dc82bdd..f5f47b926f 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -8,7 +8,7 @@ """ from collections import namedtuple -from math import sin, cos +import math import numpy as np import matplotlib.pyplot as plt import pandas as pd @@ -23,7 +23,7 @@ # robot settings are defined with profiles profile_simple = {'v_max': 1., - 'omega_max': 2.84, + 'omega_max': math.pi*2, 'accel_angular': 1., 'accel_linear': 1., 'wheel_diameter': 100e-3, @@ -99,8 +99,8 @@ def step(self, dt=0.1): omega_new = s.omega + omega_error # create new state - s_new = State(x=s.x + s.v*cos(s.phi)*dt, - y=s.y + s.v*sin(s.phi)*dt, + s_new = State(x=s.x + s.v*math.cos(s.phi)*dt, + y=s.y + s.v*math.sin(s.phi)*dt, phi=s.phi + s.omega*dt, t=s.t + dt, v=v_new, From 508906a5586524b0076fc8792fe4eb72310d71a8 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Wed, 14 Apr 2021 11:21:03 +0200 Subject: [PATCH 87/93] added gif --- .../pure_pursuit/img/pure_pursuit_2.gif | Bin 0 -> 51069 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 PathTracking/pure_pursuit/img/pure_pursuit_2.gif diff --git a/PathTracking/pure_pursuit/img/pure_pursuit_2.gif b/PathTracking/pure_pursuit/img/pure_pursuit_2.gif new file mode 100644 index 0000000000000000000000000000000000000000..c96ead6a1a1fbd0699fce9b80c82bef2c6ab30c0 GIT binary patch literal 51069 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 Date: Wed, 14 Apr 2021 11:26:34 +0200 Subject: [PATCH 88/93] Create readme.md --- PathTracking/pure_pursuit/readme.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 PathTracking/pure_pursuit/readme.md diff --git a/PathTracking/pure_pursuit/readme.md b/PathTracking/pure_pursuit/readme.md new file mode 100644 index 0000000000..5bef32f664 --- /dev/null +++ b/PathTracking/pure_pursuit/readme.md @@ -0,0 +1,5 @@ +# Pure pusuit controller + +![animation](img/pure_pursuit_2.gif) + +See `pure_pursuit.ipynb` and `pure_pursuit_2.py` for explanation and a prototype From f3c850c68555f58827cf64b19debd7d193731cba Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Wed, 14 Apr 2021 13:16:11 +0200 Subject: [PATCH 89/93] style --- PathTracking/pure_pursuit/pure_pursuit_2.py | 44 ++++++++++++--------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/PathTracking/pure_pursuit/pure_pursuit_2.py b/PathTracking/pure_pursuit/pure_pursuit_2.py index adb2077be4..6d952f6af8 100644 --- a/PathTracking/pure_pursuit/pure_pursuit_2.py +++ b/PathTracking/pure_pursuit/pure_pursuit_2.py @@ -19,14 +19,15 @@ # sim parameters WAYPOINTS_TYPE = 'square' -TARGET_SPEED = .1 # [m/s] -LOOK_AHEAD_1 = 0.1 # [m] lenth of velocity vector -LOOK_AHEAD_2 = 0.2 # [m] step forward along the path segment -Kp = 1.0 # controller gain +TARGET_SPEED = .1 # [m/s] +LOOK_AHEAD_1 = 0.1 # [m] lenth of velocity vector +LOOK_AHEAD_2 = 0.2 # [m] step forward along the path segment +Kp = 1.0 # controller gain t_sim = 50.0 # simulation time [sec] dt = 0.1 # timestep SIM_DELAY = 0.01 + class Waypoints(UserList): """ navigation waypoints, collection of xy Vectors """ @@ -71,7 +72,7 @@ def project_on_line(a: Vector, b: Vector, p: Vector) -> Vector: def target_position(xy: Vector, phi: float, a: Vector, b: Vector, - velocity_vector= LOOK_AHEAD_1, + velocity_vector=LOOK_AHEAD_1, look_ahead=LOOK_AHEAD_2 ) -> Tuple[Vector, Vector]: """calculate look-ahead and target positions @@ -81,12 +82,14 @@ def target_position(xy: Vector, phi (float): robot pose [rad] a (Vector): first waypoint b (Vector): second waypoint - velocity_vector (float, optional): magnitude of velocity vector . Defaults to 1.0. - look_ahead (float, optional): forward distance from projecton point to path. - Defaults to 1.0. + velocity_vector (float, optional): magnitude of velocity vector . + Defaults to 1.0. + look_ahead (float, optional): forward distance from projecton + point to path. Defaults to 1.0. Returns: - Tuple[Vector, Vector, float]: target & look-ahead positions, and angle error + Tuple[Vector, Vector, float]: target & look-ahead positions, + and angle error """ # point names consistent with https://www.geogebra.org/calculator/vh5d7jvy # future position @@ -108,6 +111,7 @@ def proportional_control(target: float, current: float) -> float: return a + def generate_waypoints(key: str) -> Waypoints: """generate different types of waypoints @@ -118,30 +122,31 @@ def generate_waypoints(key: str) -> Waypoints: Waypoints: waypoints to follow """ if key == 'simple': - coord = [Vector(xy) for xy in [(0, 0), (0, 2), (2, 2), (2, 0), (2, -3)]] + coord = [Vector(xy) + for xy in [(0, 0), (0, 2), (2, 2), (2, 0), (2, -3)]] waypoints = Waypoints(coord) elif key == 'sine': cx = np.arange(0, 5, 0.02) cy = [np.sin(ix / 0.1) * ix / 2.0 for ix in cx] - waypoints = Waypoints(zip(cx,cy)) + waypoints = Waypoints(zip(cx, cy)) elif key == 'square': - w_tops = np.linspace(0.1, 1, 5) # turn widhts - waypoints = Waypoints([Vector(0,0)]) + w_tops = np.linspace(0.1, 1, 5) # turn widhts + waypoints = Waypoints([Vector(0, 0)]) - v_up = Vector(0,1) + v_up = Vector(0, 1) waypoints.append(waypoints[-1] + 0.5*v_up) for w in reversed(w_tops): - waypoints.append(waypoints[-1] + Vector(w,0)) + waypoints.append(waypoints[-1] + Vector(w, 0)) waypoints.append(waypoints[-1] - v_up) - waypoints.append(waypoints[-1] + Vector(0.4,0)) + waypoints.append(waypoints[-1] + Vector(0.4, 0)) waypoints.append(waypoints[-1] + v_up) - else: raise KeyError(f"Don't know how to generate waypoints for {key}") return waypoints + def main(): # create visualisation @@ -182,7 +187,9 @@ def main(): world.move_robot(bot.xy, bot.phi) world.move_marker('future_xy', future_xy) world.move_marker('target', target) - print(f"step[{i_step}] next_wp:{waypoints.next_idx} target:{target} omega:{omega:.2f}") + print( + f"step[{i_step}] next_wp:{waypoints.next_idx} \ + target:{target} omega:{omega:.2f}") time.sleep(SIM_DELAY) i_step += 1 @@ -192,7 +199,6 @@ def main(): print('Aborted by click') break - print('Simulation result:\n', bot.states_df()) print('close simulation window to exit...') From e83c4313a934d962bdc2a16917c7b6aec2cff6d9 Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Wed, 14 Apr 2021 13:16:52 +0200 Subject: [PATCH 90/93] style --- PythonRobotics/graphics.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/PythonRobotics/graphics.py b/PythonRobotics/graphics.py index 9b07a58179..7f141bcaad 100644 --- a/PythonRobotics/graphics.py +++ b/PythonRobotics/graphics.py @@ -12,6 +12,7 @@ TESTING = True + class World: """ wrapper class for visualisations """ @@ -28,10 +29,10 @@ def __init__(self, coord=(-10, -10, 10, 10), window_size=(800, 800)): self.turtle.color('blue') self._markers = {} # marker objects - self.click_xy = None # last clicked position + self.click_xy = None # last clicked position - def click_callback(self,x,y): - self.click_xy = (x,y) + def click_callback(self, x, y): + self.click_xy = (x, y) def add_marker(self, name, From ea49edbeedef8358937fb8e5a88293e5d0568b7d Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Wed, 14 Apr 2021 13:20:08 +0200 Subject: [PATCH 91/93] style --- tests/test_graphics.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_graphics.py b/tests/test_graphics.py index 907da73e01..976b007141 100644 --- a/tests/test_graphics.py +++ b/tests/test_graphics.py @@ -13,5 +13,6 @@ def test_1(): m.TESTING = True m.main() + if __name__ == '__main__': conftest.run_this_test(__file__) From 611795f90c84c6fa78e601c4a5b46bdad4e1897f Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Wed, 14 Apr 2021 13:20:53 +0200 Subject: [PATCH 92/93] style --- tests/test_turtlebot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_turtlebot.py b/tests/test_turtlebot.py index 3a1aeac1b7..d148203498 100644 --- a/tests/test_turtlebot.py +++ b/tests/test_turtlebot.py @@ -13,5 +13,6 @@ def test_1(): m.PLOT_RESULT = False m.main() + if __name__ == '__main__': conftest.run_this_test(__file__) From 34083b42da7b92f1f2cc7ab58c4b15877841705f Mon Sep 17 00:00:00 2001 From: Jev Kuznetsov Date: Wed, 14 Apr 2021 13:32:26 +0200 Subject: [PATCH 93/93] style --- PythonRobotics/plots.py | 1 - PythonRobotics/turtlebot.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/PythonRobotics/plots.py b/PythonRobotics/plots.py index aecd95e46f..ebd09f3a3f 100644 --- a/PythonRobotics/plots.py +++ b/PythonRobotics/plots.py @@ -7,7 +7,6 @@ """ import math -#import numpy as np import matplotlib.pyplot as plt diff --git a/PythonRobotics/turtlebot.py b/PythonRobotics/turtlebot.py index f5f47b926f..d6bc046436 100755 --- a/PythonRobotics/turtlebot.py +++ b/PythonRobotics/turtlebot.py @@ -124,7 +124,7 @@ def states_df(self) -> pd.DataFrame: @property def xy(self) -> Vector: """ xy position as Vector """ - return Vector(self.state.x,self.state.y) + return Vector(self.state.x, self.state.y) @property def phi(self) -> float: